def poloidal_trajectory_plot(self, field_tracer, world, equilibrium, num_of_fieldlines=5, max_tracing_length=15): if not (isinstance(num_of_fieldlines, int) and num_of_fieldlines > 0): raise TypeError( "The number of fieldlines to trace must be an integer > 0.") point_a = Point3D(self._point_a.x, 0, self._point_a.y) point_b = Point3D(self._point_b.x, 0, self._point_b.y) interface_vector = point_a.vector_to(point_b) plot_equilibrium(equilibrium, detail=False) for i in range(num_of_fieldlines): sample_point = point_a + interface_vector * i / num_of_fieldlines _, _, trajectory = field_tracer.trace( world, sample_point, max_length=max_tracing_length, save_trajectory=True) rz_trajectory = np.zeros((trajectory.shape[0], 2)) for i in range(trajectory.shape[0]): r = sqrt(trajectory[i, 0]**2 + trajectory[i, 1]**2) z = trajectory[i, 2] rz_trajectory[i, :] = r, z plt.plot(rz_trajectory[:, 0], rz_trajectory[:, 1], 'g')
def __init__(self, slit_id, centre_point, basis_x, dx, basis_y, dy, dz=0.001, parent=None, csg_aperture=False, curvature_radius=0): # perform validation of input parameters if not isinstance(dx, (float, int)): raise TypeError("dx argument for BolometerSlit must be of type float/int.") if not dx > 0: raise ValueError("dx argument for BolometerSlit must be greater than zero.") if not isinstance(dy, (float, int)): raise TypeError("dy argument for BolometerSlit must be of type float/int.") if not dy > 0: raise ValueError("dy argument for BolometerSlit must be greater than zero.") if not isinstance(centre_point, Point3D): raise TypeError("centre_point argument for BolometerSlit must be of type Point3D.") if not isinstance(curvature_radius, (float, int)): raise TypeError("curvature_radius argument for BolometerSlit " "must be of type float/int.") if curvature_radius < 0: raise ValueError("curvature_radius argument for BolometerSlit " "must not be negative.") if not isinstance(basis_x, Vector3D): raise TypeError("The basis vectors of BolometerSlit must be of type Vector3D.") if not isinstance(basis_y, Vector3D): raise TypeError("The basis vectors of BolometerSlit must be of type Vector3D.") self._centre_point = centre_point self._basis_x = basis_x.normalise() self.dx = dx self._basis_y = basis_y.normalise() self.dy = dy self.dz = dz self._curvature_radius = curvature_radius # NOTE - target primitive and aperture surface cannot be co-incident otherwise numerics will cause Raysect # to be blind to one of the two surfaces. slit_normal = basis_x.cross(basis_y) transform = translate(centre_point.x, centre_point.y, centre_point.z) * rotate_basis(slit_normal, basis_y) super().__init__(parent=parent, transform=transform, name=slit_id) self.target = Box(lower=Point3D(-dx/2*1.01, -dy/2*1.01, -dz/2), upper=Point3D(dx/2*1.01, dy/2*1.01, dz/2), transform=None, material=NullMaterial(), parent=self, name=slit_id+' - target') self._csg_aperture = None self.csg_aperture = csg_aperture # round off the detector corners, if applicable if self._curvature_radius > 0: mask_corners(self)
def generate_annulus_mesh_segments(lower_corner, upper_corner, number_segments, world, material=None): """ Generates an annulus from many smaller mesh segments. Used for calculating sensitivity matrices for poloidal inversion grids. :param Point2D lower_corner: the lower corner of the poloidal 2D cell. :param Point2D upper_corner: the upper corner of the poloidal 2D cell. :param int number_segments: The number of angular mesh segments used to build the annulus. :param World world: The scene-graph to which the annulus will be attached. :return: Node holding all the annulus segment primitives. :rtype: Node """ material = material or UnityVolumeEmitter() annulus_node = Node(parent=world) theta_adjusted = 360 / number_segments # Set of points in x-z plane p1a = Point3D(lower_corner.x, 0, lower_corner.y) # corresponds to lower corner is x-z plane p2a = Point3D(lower_corner.x, 0, upper_corner.y) p3a = Point3D(upper_corner.x, 0, upper_corner.y) # corresponds to upper corner in x-z plane p4a = Point3D(upper_corner.x, 0, lower_corner.y) # Set of points rotated away from x-z plane p1b = p1a.transform(rotate_z(theta_adjusted)) p2b = p2a.transform(rotate_z(theta_adjusted)) p3b = p3a.transform(rotate_z(theta_adjusted)) p4b = p4a.transform(rotate_z(theta_adjusted)) vertices = [[p1a.x, p1a.y, p1a.z], [p2a.x, p2a.y, p2a.z], [p3a.x, p3a.y, p3a.z], [p4a.x, p4a.y, p4a.z], [p1b.x, p1b.y, p1b.z], [p2b.x, p2b.y, p2b.z], [p3b.x, p3b.y, p3b.z], [p4b.x, p4b.y, p4b.z]] triangles = [[1, 0, 3], [1, 3, 2], # front face (x-z) [7, 4, 5], [7, 5, 6], # rear face (rotated out of x-z plane) [5, 1, 2], [5, 2, 6], # top face (x-y plane) [3, 0, 4], [3, 4, 7], # bottom face (x-y plane) [4, 0, 5], [1, 5, 0], # inner face (y-z plane) [2, 3, 7], [2, 7, 6]] # outer face (y-z plane) base_segment = Mesh(vertices=vertices, triangles=triangles, smoothing=False) # Construct annulus by duplicating and rotating base segment. for i in range(number_segments): theta_rotation = theta_adjusted * i segment = Mesh(instance=base_segment, transform=rotate_z(theta_rotation), material=material, parent=annulus_node) return annulus_node
def test_invalid_types(self): voxel_coords = {'v1': (2, 0), 'v2': (2, 1), 'v3': (3, 1)} with self.assertRaises(TypeError, msg="Calling with dict with string keys didn't error"): AxisymmetricVoxel(voxel_coords) voxel_coords = {1: (2, 0), 2: (2, 1), 3: (3, 1)} with self.assertRaises(TypeError, msg="Calling with dict with int keys didn't error"): AxisymmetricVoxel(voxel_coords) voxel_coords = [Point3D(2, 0, 0), Point3D(2, 0, 1), Point3D(3, 0, 1)] with self.assertRaises(TypeError, msg="Calling with list of Point3D didn't error"): AxisymmetricVoxel(voxel_coords) voxel_coords = np.asarray([[2, 0, 0], [2, 0, 1], [3, 0, 1]]) with self.assertRaises(TypeError, msg="Calling with Nx3 array didn't error"): AxisymmetricVoxel(voxel_coords)
def _sample_along_beam_axis(function, beam_axis, beam_to_world, debug=False): if debug: samples = [] for i, z in enumerate(beam_axis): p = Point3D(0, 0, z).transform(beam_to_world) samples.append(function(p.x, p.y, p.z)) else: samples = [] for z in beam_axis: p = Point3D(0, 0, z).transform(beam_to_world) samples.append(function(p.x, p.y, p.z)) return samples
def csg_aperture(self, value): if value is True: width = max(self.dx, self.dy) face = Box(Point3D(-width, -width, -self.dz/2), Point3D(width, width, self.dz/2)) slit = Box(lower=Point3D(-self.dx/2, -self.dy/2, -self.dz/2 - self.dz*0.1), upper=Point3D(self.dx/2, self.dy/2, self.dz/2 + self.dz*0.1)) self._csg_aperture = Subtract(face, slit, parent=self, material=AbsorbingSurface(), name=self.name+' - CSG Aperture') else: if isinstance(self._csg_aperture, Primitive): self._csg_aperture.parent = None self._csg_aperture = None
def _generate_rays(self, template, ray_count): origin_vectors = self._sphere_sampler(ray_count) directions = self._vector_sampler(ray_count) rays = [] for n in range(ray_count): # calculate surface point origin = origin_vectors[n].copy() origin.length = self._radius origin = Point3D(*origin) # calculate surface normal normal = -origin_vectors[n] # transform sampling direction from surface space direction = directions[n].transform( rotate_basis(normal, normal.orthogonal())) # USE WITH HEMISPHERECOSINESAMPLER # cosine weighted distribution, projected area weight is # implicit in distribution, so set weight appropriately rays.append((template.copy(origin, direction), 0.5)) return rays
def plot_brdf(light_angle): light_position = Point3D(np.sin(np.deg2rad(light_angle)), 0, np.cos(np.deg2rad(light_angle))) light_direction = origin.vector_to(light_position).normalise() phis = np.linspace(0, 360, 200) num_phis = len(phis) thetas = np.linspace(0, 90, 100) num_thetas = len(thetas) values = np.zeros((num_thetas, num_phis)) for i in range(num_thetas): for j in range(num_phis): theta = np.deg2rad(thetas[i]) phi = np.deg2rad(phis[j]) outgoing = Vector3D( np.cos(phi) * np.sin(theta), np.sin(phi) * np.sin(theta), np.cos(theta)) values[i, j] = aluminium.bsdf(light_direction, outgoing, 500.0) fig, ax = plt.subplots(subplot_kw=dict(projection='polar')) cs = ax.contourf(np.deg2rad(phis), thetas, values, extend="both") cs.cmap.set_under('k') plt.title("Light angle: {} degrees".format(light_angle))
def csg_to_mesh(csg_primitive): vertices, triangles = to_mesh(csg_primitive.primitive_a) mesh_a = Mesh(vertices, triangles) vertices, triangles = to_mesh(csg_primitive.primitive_b) mesh_b = Mesh(vertices, triangles) if csg_primitive.__class__ == Intersect: operator = IntersectOperator() elif csg_primitive.__class__ == Union: operator = UnionOperator() elif csg_primitive.__class__ == Subtract: operator = SubtractOperator() else: raise ValueError("Unidentified CSG primitive '{}'.".format(csg_primitive.__class__)) mesh = perform_mesh_csg(mesh_a, mesh_b, operator=operator) vertices = mesh.data.vertices.copy() triangles = mesh.data.triangles.copy() if csg_primitive.parent: to_world = csg_primitive.to_root() else: to_world = csg_primitive.transform # Convert vertices to positions in world coordinates for i in range(vertices.shape[0]): p = Point3D(vertices[i, 0], vertices[i, 1], vertices[i, 2]).transform(to_world) vertices[i, 0] = p.x vertices[i, 1] = p.y vertices[i, 2] = p.z return vertices, triangles
def __call__(self, x, y, z): direction = Vector3D(1, 0, 0) spectrum = Spectrum(400, 750, 1) observed = self.model.emission(Point3D(x, y, z), direction, spectrum) return observed.total()
def __init__(self, point, direction, parent=None, name=""): if not isinstance(point, Point3D): raise TypeError( "point argument for SpectroscopicSightLine must be of type Point3D." ) if not isinstance(direction, Vector3D): raise TypeError( "direction argument for SpectroscopicSightLine must be of type Vector3D." ) self._point = Point3D(0, 0, 0) self._direction = Vector3D(1, 0, 0) self._transform = AffineMatrix3D() self._spectral_pipeline = SpectralRadiancePipeline0D(accumulate=False) # TODO - carry over wavelength range and resolution settings self._observer = SightLine(pipelines=[self._spectral_pipeline], parent=parent, name=name) self.name = name self.point = point self.direction = direction
def voxel_matches_polygon(self, coordinate_list): for voxel_coords in coordinate_list: voxel_coords = np.asarray(voxel_coords) rmax = voxel_coords[:, 0].max() rmin = voxel_coords[:, 0].min() zmax = voxel_coords[:, 1].max() zmin = voxel_coords[:, 1].min() router = 1.5 * rmax rinner = 0.5 * rmin zupper = 1.5 * zmax if zmax > 0 else 0.5 * zmax zlower = 0.5 * zmin if zmin > 0 else 1.5 * zmin test_rs = np.linspace(rinner, router, int(100 * (router - rinner))) test_zs = np.linspace(zlower, zupper, int(100 * (zupper - zlower))) voxel = AxisymmetricVoxel(voxel_coords, primitive_type='csg') polygon = Polygon(voxel_coords, closed=True) test_verts = list(itertools.product(test_rs, test_zs)) try: inside_poly = polygon.contains_points(test_verts) except AttributeError: # Polygon.contains_points was only introduced in Matplotlib 2.2. # Before that we need to convert to Path inside_poly = Path(polygon.get_verts()).contains_points(test_verts) inside_csg = [any(child.contains(Point3D(r, 0, z)) for child in voxel.children) for (r, z) in test_verts] self.assertSequenceEqual(inside_csg, inside_poly.tolist())
def _check_barrel_surface(self, lens, azimuths, barrel_z): """ Checks barrel surface of a lens by calculating ray-lens intersection. Hit point position and angle of incidence are compared to predicted ones. :param lens: Spherical lens object to test plane surface of. :param azimuths: Azimuth angles to test lens surface at. :param barrel_z: """ lens_radius = lens.diameter / 2 for z in barrel_z: for ta in azimuths: # get x-y coordinates of the surface point from azimuth x = lens_radius * cos(ta) y = lens_radius * sin(ta) # get origin by surface point offset and calculate ray direction surface_point = Point3D(x, y, z) direction = Vector3D(-x, -y, 0) origin = Point3D(1.1 * x, 1.1 * y, z) # calculate ray-lens intersection intersection = lens.hit(CoreRay(origin, direction)) hit_point = intersection.hit_point.transform( intersection.primitive_to_world) # distance of expected surface point and the ray hit point distance = hit_point.vector_to(surface_point).length self.assertAlmostEqual( distance, 0, self.tolerance_distance, msg= "Ray-curved surface hit point and predicted surface point difference" " is larger than tolerance.") # angle of incidence on the sphere surface should be perpendicular cos_angle_incidence = intersection.normal.dot( intersection.ray.direction.normalise()) self.assertAlmostEqual( fabs(cos_angle_incidence), 1, self.tolerance_angle, msg="Angle of incidence differs from perpendicular.")
def generate_annulus_mesh_segments(lower_corner, upper_corner, theta_width, theta_offset, world): material = UnityVolumeEmitter() # Set of points in x-z plane p1a = Point3D(lower_corner.x, 0, lower_corner.y) # corresponds to lower corner is x-z plane p2a = Point3D(lower_corner.x, 0, upper_corner.y) p3a = Point3D(upper_corner.x, 0, upper_corner.y) # corresponds to upper corner in x-z plane p4a = Point3D(upper_corner.x, 0, lower_corner.y) # Set of points rotated away from x-z plane p1b = p1a.transform(rotate_z(theta_width)) p2b = p2a.transform(rotate_z(theta_width)) p3b = p3a.transform(rotate_z(theta_width)) p4b = p4a.transform(rotate_z(theta_width)) vertices = [[p1a.x, p1a.y, p1a.z], [p2a.x, p2a.y, p2a.z], [p3a.x, p3a.y, p3a.z], [p4a.x, p4a.y, p4a.z], [p1b.x, p1b.y, p1b.z], [p2b.x, p2b.y, p2b.z], [p3b.x, p3b.y, p3b.z], [p4b.x, p4b.y, p4b.z]] triangles = [ [1, 0, 3], [1, 3, 2], # front face (x-z) [7, 4, 5], [7, 5, 6], # rear face (rotated out of x-z plane) [5, 1, 2], [5, 2, 6], # top face (x-y plane) [3, 0, 4], [3, 4, 7], # bottom face (x-y plane) [4, 0, 5], [1, 5, 0], # inner face (y-z plane) [2, 3, 7], [2, 7, 6] ] # outer face (y-z plane) return Mesh(vertices=vertices, triangles=triangles, smoothing=False, transform=rotate_z(theta_offset), material=material, parent=world)
def test_planoconcave(self): """ Test planoconcave lens for a range of curvatures and center thickness combinations. """ # combinations of lens parameters to test diameter = 75 curvatures = np.array([0.5, 0.75, 1, 1.5, 5]) * diameter threshold_ratios = np.array([1, 1.05, 2, 3, 5, 10]) radius = diameter / 2 radius2 = radius**2 for curvature in curvatures: for threshold_ratio in threshold_ratios: # center thickness calculated from the minimum threshold front_thickness = curvature - sqrt(curvature**2 - radius2) center_thickness = threshold_ratio * front_thickness lens = PlanoConcave(diameter, center_thickness, curvature) # calculate coordinates of center of curvature of the lens front surface center_front = Point3D(0, 0, center_thickness + curvature) # check lens front and back surface by inside and outside envelopes azimuths = np.linspace(0, 2 * pi, self.n_testpoints, endpoint=False) min_altitude = self.pad_constant max_altitude = asin(radius / curvature) - self.pad_constant altitude = np.linspace(max_altitude, min_altitude, self.n_testpoints, endpoint=True) radii = curvature * np.sin(altitude) self._check_spherical_surface(curvature, lens, center_front, True, False, azimuths, radii) radii = np.linspace(radius - self.pad_constant, self.pad_constant, self.n_testpoints) self._check_plane_surface(lens, azimuths, radii) # check lens barrel surface by inside and outside envelope min_z = 0 max_z = center_thickness + front_thickness if max_z - min_z <= 3 * self.pad_constant: barrel_z = np.array(((min_z + max_z) / 2), ndmin=1) else: barrel_z = np.linspace(min_z + self.pad_constant, max_z - self.pad_constant, self.n_testpoints, endpoint=True) self._check_barrel_surface(lens, azimuths, barrel_z)
def _mayavi_source_from_raysect_object(self): lower = self._raysect_object.lower upper = self._raysect_object.upper # more negative face in x-z plane p1a = lower # lower corner in x-z plane p2a = Point3D(lower.x, lower.y, upper.z) p3a = Point3D(upper.x, lower.y, upper.z) # upper corner in x-z plane p4a = Point3D(upper.x, lower.y, lower.z) # more positive face in x-z plane p1b = Point3D(lower.x, upper.y, lower.z) p2b = Point3D(lower.x, upper.y, upper.z) p3b = upper p4b = Point3D(upper.x, upper.y, lower.z) vertices = [[p1a.x, p1a.y, p1a.z], [p2a.x, p2a.y, p2a.z], [p3a.x, p3a.y, p3a.z], [p4a.x, p4a.y, p4a.z], [p1b.x, p1b.y, p1b.z], [p2b.x, p2b.y, p2b.z], [p3b.x, p3b.y, p3b.z], [p4b.x, p4b.y, p4b.z]] triangles = [[1, 0, 3], [1, 3, 2], # front face (x-z) [7, 4, 5], [7, 5, 6], # rear face (x-z) [5, 1, 2], [5, 2, 6], # top face (x-y) [3, 0, 4], [3, 4, 7], # bottom face (x-y) [4, 0, 5], [1, 5, 0], # left face (y-z) [2, 3, 7], [2, 7, 6]] # right face (y-z) mesh = Mesh(vertices, triangles) self._raysect_mesh = subdivide(mesh)
def load_dms_output(config, world, plasma, spec, fibgeom): from raysect.optical.observer import FibreOptic, PowerPipeline0D, SpectralRadiancePipeline0D from raysect.optical import translate, rotate, rotate_basis from raysect.core import Vector3D, Point3D power_arr = np.zeros(fibgeom.numfibres) spectra_arr = np.zeros((spec.pixels, fibgeom.numfibres)) for i, f in enumerate(power_arr): print("Analysing fibre: ", int(i + 1)) fibgeom.set_fibre(number=int(i + 1)) start_point = Point3D(fibgeom.origin[0], fibgeom.origin[1], fibgeom.origin[2]) forward_vector = Vector3D(fibgeom.xhat(), fibgeom.yhat(), fibgeom.zhat()).normalise() up_vector = Vector3D(0, 0, 1.0) if config['dms']['power_pipeline']: power = PowerPipeline0D() fibre = FibreOptic([power], acceptance_angle=1, radius=0.001, spectral_bins=spec.pixels, spectral_rays=1, pixel_samples=5, transform=translate(*start_point) * rotate_basis(forward_vector, up_vector), parent=world) fibre.min_wavelength = spec.wlower fibre.max_wavelength = spec.wupper fibre.observe() power_arr[i] = power.value.mean else: power_arr[i] = None if config['dms']['radiance_pipeline']: spectra = SpectralRadiancePipeline0D(display_progress=False) fibre = FibreOptic([spectra], acceptance_angle=1, radius=0.001, spectral_bins=spec.pixels, spectral_rays=1, pixel_samples=5, transform=translate(*start_point) * rotate_basis(forward_vector, up_vector), parent=world) fibre.min_wavelength = spec.wlower fibre.max_wavelength = spec.wupper fibre.observe() spectra_arr[:, i] = spectra.samples.mean else: spectra_arr[:, i] = None return power_arr, spectra_arr
def visualise_scenegraph(camera, focal_distance=1, zoom=1): if not isinstance(camera, Observer): raise TypeError("The vtk visualisation function takes a Raysect Observer object as its argument.") world = camera.root if not isinstance(world, World): raise TypeError("The vtk visualisation function requires the Raysect Observer object to be connected to a valid scene-graph.") # Add the actors to the renderer renderer = vtk.vtkRenderer() renderer.SetBackground(0, 0, 0) for child in world.children: vtk_element = map_raysect_element_to_vtk(child) if isinstance(vtk_element, VTKAssembly): renderer.AddActor(vtk_element.assembly) else: renderer.AddActor(vtk_element.actor) axes = vtk.vtkAxesActor() renderer.AddActor(axes) renWin = vtk.vtkRenderWindow() renWin.AddRenderer(renderer) renWin.SetSize(512, 512) iren = vtk.vtkRenderWindowInteractor() iren.SetRenderWindow(renWin) iren.SetInteractorStyle(vtk.vtkInteractorStyleTrackballCamera()) iren.Initialize() camera_origin = Point3D(0, 0, 0).transform(camera.transform) camera_direction = Vector3D(0, 0, 1).transform(camera.transform) up_direction = Vector3D(0, 1, 0).transform(camera.transform) focal_point = camera_origin + camera_direction * focal_distance vtk_camera = vtk.vtkCamera() vtk_camera.SetPosition(camera_origin.x, camera_origin.y, camera_origin.z) vtk_camera.SetFocalPoint(focal_point.x, focal_point.y, focal_point.z) vtk_camera.SetViewUp(up_direction.x, up_direction.y, up_direction.z) vtk_camera.ComputeViewPlaneNormal() vtk_camera.SetDistance(focal_distance) vtk_camera.Zoom(zoom) renderer.SetActiveCamera(vtk_camera) renderer.SetBackground(1.0, 0.9688, 0.8594) renderer.SetBackground(vtk_colors.GetColor3d("SlateGray")) # Start the event loop. iren.Start()
def __init__(self, slit_id, centre_point, basis_x, dx, basis_y, dy, dz=0.001, parent=None, csg_aperture=False): self._centre_point = centre_point self._basis_x = basis_x.normalise() self.dx = dx self._basis_y = basis_y.normalise() self.dy = dy self.dz = dz # NOTE - target primitive and aperture surface cannot be co-incident otherwise numerics will cause Raysect # to be blind to one of the two surfaces. slit_normal = basis_x.cross(basis_y) transform = translate(centre_point.x, centre_point.y, centre_point.z) * rotate_basis( slit_normal, basis_y) super().__init__(parent=parent, transform=transform, name=slit_id) self.target = Box(lower=Point3D(-dx / 2 * 1.01, -dy / 2 * 1.01, -dz / 2), upper=Point3D(dx / 2 * 1.01, dy / 2 * 1.01, dz / 2), transform=None, material=NullMaterial(), parent=self, name=slit_id + ' - target') self._csg_aperture = None self.csg_aperture = csg_aperture
def fibre_distance_world(self, world): from cherab.tools.observers.intersections import find_wall_intersection from raysect.core import Vector3D, Point3D start_point = Point3D(self.origin[0] + 1.0 * self.xhat(), self.origin[1] + 1.0 * self.yhat(), self.origin[2] + 1.0 * self.zhat()) forward_vector = Vector3D(self.xhat(), self.yhat(), self.zhat()) hit_point, primitive = find_wall_intersection(world, start_point, forward_vector, delta=1E-3) return abs((self.origin[0] - hit_point[0]) / self.xhat())
def box_to_mesh(box): if not isinstance(box, Box): raise TypeError("The _box_to_mesh() function takes a Raysect Box primitive as an argument, " "wrong type '{}' given.".format(type(box))) lower = box.lower upper = box.upper # more negative face in x-z plane p1a = lower # lower corner in x-z plane p2a = Point3D(lower.x, lower.y, upper.z) p3a = Point3D(upper.x, lower.y, upper.z) # upper corner in x-z plane p4a = Point3D(upper.x, lower.y, lower.z) # more positive face in x-z plane p1b = Point3D(lower.x, upper.y, lower.z) p2b = Point3D(lower.x, upper.y, upper.z) p3b = upper p4b = Point3D(upper.x, upper.y, lower.z) vertices = [[p1a.x, p1a.y, p1a.z], [p2a.x, p2a.y, p2a.z], [p3a.x, p3a.y, p3a.z], [p4a.x, p4a.y, p4a.z], [p1b.x, p1b.y, p1b.z], [p2b.x, p2b.y, p2b.z], [p3b.x, p3b.y, p3b.z], [p4b.x, p4b.y, p4b.z]] triangles = [[1, 0, 3], [1, 3, 2], # front face (x-z) [7, 4, 5], [7, 5, 6], # rear face (x-z) [5, 1, 2], [5, 2, 6], # top face (x-y) [3, 0, 4], [3, 4, 7], # bottom face (x-y) [4, 0, 5], [1, 5, 0], # left face (y-z) [2, 3, 7], [2, 7, 6]] # right face (y-z) mesh = Mesh(vertices, triangles) mesh = subdivide(mesh) vertices = np.array(mesh.data.vertices) triangles = np.array(mesh.data.triangles) if box.parent: to_world = box.to_root() else: to_world = box.transform # Convert vertices to positions in world coordinates for i in range(vertices.shape[0]): p = Point3D(vertices[i, 0], vertices[i, 1], vertices[i, 2]).transform(to_world) vertices[i, 0] = p.x vertices[i, 1] = p.y vertices[i, 2] = p.z return vertices, triangles
def mesh_to_mesh(mesh): vertices = mesh.data.vertices.copy() triangles = mesh.data.triangles.copy()[:, 0:3] if mesh.parent: to_world = mesh.to_root() else: to_world = mesh.transform # Convert vertices to positions in world coordinates for i in range(vertices.shape[0]): p = Point3D(vertices[i, 0], vertices[i, 1], vertices[i, 2]).transform(to_world) vertices[i, 0] = p.x vertices[i, 1] = p.y vertices[i, 2] = p.z return vertices, triangles
def get_pini_alignment(pulse, oct8_pini): import idlbridge as idl global _idl_was_setup if not _idl_was_setup: _setup_idl() _idl_was_setup = True # Note: array index starts at zero, so actual pini index equals pini number - 1/. oct8_pini -= 1 idl.execute("ret = get_cherab_pinialignment(pulse={})".format(pulse)) ret = idl.get("ret") # Pull out the origin points from the IDL structure, convert to Point3D origin = Point3D(ret['origin'][oct8_pini][0] / 1000, ret['origin'][oct8_pini][1] / 1000, ret['origin'][oct8_pini][2] / 1000) # Pull out the direction vector from the IDL structure, convert to Vector3D direction = Vector3D(ret['vector'][oct8_pini][0], ret['vector'][oct8_pini][1], ret['vector'][oct8_pini][2]) # TODO - note divergence numbers are different between Carine and Corentin. div_u = ret['divu'][oct8_pini] / (2 * PI) * 360 div_v = ret['divv'][oct8_pini] / (2 * PI) * 360 divergence = (div_u, div_v) # Minimal 1/e width (at the source) of the beam (scalar in meters) initial_width = 0.001 # Approximate with 1mm as an effective point source. pini_length = PINI_LENGTHS[oct8_pini] pini_geometry = (origin, direction, divergence, initial_width, pini_length) return pini_geometry
def voxel_matches_polygon(self, coordinate_list): for voxel_coords in coordinate_list: voxel_coords = np.asarray(voxel_coords) rmax = voxel_coords[:, 0].max() rmin = voxel_coords[:, 0].min() zmax = voxel_coords[:, 1].max() zmin = voxel_coords[:, 1].min() router = 1.5 * rmax rinner = 0.5 * rmin zupper = 1.5 * zmax if zmax > 0 else 0.5 * zmax zlower = 0.5 * zmin if zmin > 0 else 1.5 * zmin test_rs = np.linspace(rinner, router, int(50 * (router - rinner))) test_zs = np.linspace(zlower, zupper, int(50 * (zupper - zlower))) # Test for 0 area: not supported by mesh representation x, y = voxel_coords.T area = 0.5 * np.abs(np.dot(x, np.roll(y, 1)) - np.dot(y, np.roll(x, 1))) if area == 0: continue voxel = AxisymmetricVoxel(voxel_coords, primitive_type='mesh') polygon = Polygon(voxel_coords, closed=True).get_path() test_verts = list(itertools.product(test_rs, test_zs)) inside_poly = polygon.contains_points(test_verts) inside_voxel = [any(child.contains(Point3D(r, 0, z)) for child in voxel.children) for (r, z) in test_verts] # Due to numerical precision, some points may be inside the # Matplotlib polygon but not the Mesh. Check in this case that the # "failing" points are just very close to the edge of the polygon fails = np.nonzero(np.not_equal(inside_voxel, inside_poly))[0] for fail in fails: if inside_voxel[fail] and not inside_poly[fail]: # Polygon should be made slightly bigger inside_poly[fail] = polygon.contains_point(test_verts[fail], radius=-0.01) elif inside_poly[fail] and not inside_voxel[fail]: # Polygon should be made slightly smaller inside_poly[fail] = polygon.contains_point(test_verts[fail], radius=0.01) self.assertSequenceEqual(inside_voxel, inside_poly.tolist(), "Failed for vertices {}".format(voxel_coords))
def box_to_mesh(box): if not isinstance(box, Box): raise TypeError( "The _box_to_mesh() function takes a Raysect Box primitive as an argument, " "wrong type '{}' given.".format(type(box))) lower = box.lower upper = box.upper # more negative face in x-z plane p1a = lower # lower corner in x-z plane p2a = Point3D(lower.x, lower.y, upper.z) p3a = Point3D(upper.x, lower.y, upper.z) # upper corner in x-z plane p4a = Point3D(upper.x, lower.y, lower.z) # more positive face in x-z plane p1b = Point3D(lower.x, upper.y, lower.z) p2b = Point3D(lower.x, upper.y, upper.z) p3b = upper p4b = Point3D(upper.x, upper.y, lower.z) vertices = [[p1a.x, p1a.y, p1a.z], [p2a.x, p2a.y, p2a.z], [p3a.x, p3a.y, p3a.z], [p4a.x, p4a.y, p4a.z], [p1b.x, p1b.y, p1b.z], [p2b.x, p2b.y, p2b.z], [p3b.x, p3b.y, p3b.z], [p4b.x, p4b.y, p4b.z]] triangles = [ [1, 0, 3], [1, 3, 2], # front face (x-z) [7, 4, 5], [7, 5, 6], # rear face (x-z) [5, 1, 2], [5, 2, 6], # top face (x-y) [3, 0, 4], [3, 4, 7], # bottom face (x-y) [4, 0, 5], [1, 5, 0], # left face (y-z) [2, 3, 7], [2, 7, 6] ] # right face (y-z) return Mesh(vertices=vertices, triangles=triangles, smoothing=False, transform=box.transform, material=box.material, name=box.name)
import numpy as np import matplotlib.pyplot as plt from mayavi import mlab from raysect.core import World, Point3D, Vector3D from cherab.core.math import ConstantVector3D from vita.modules.cherab import FieldlineTracer, Euler # the world scene-graph world = World() b_field = ConstantVector3D(Vector3D(0, 1.5, 0)) field_tracer = FieldlineTracer(b_field, method=Euler(step_size=0.001)) start_point = Point3D(0, 0, 0) end_point, trajectory = field_tracer.trace(world, start_point, save_trajectory=True, max_steps=1000) num_segments = len(trajectory) x = np.zeros(num_segments) y = np.zeros(num_segments) z = np.zeros(num_segments) for ith_position, position in enumerate(trajectory): x[ith_position] = position.x y[ith_position] = position.y z[ith_position] = position.z mlab.plot3d(x, y, z, tube_radius=0.0005, color=(1, 0, 0))
def sphere_to_mesh(sphere, subdivision_count=2): if not isinstance(sphere, Sphere): raise TypeError("The _sphere_to_mesh() function takes a Raysect Box primitive as an argument, " "wrong type '{}' given.".format(type(sphere))) # Calculate vertices and faces using the icosohedren method # We compute a regular icosohedren with 12 vertices and 20 faces. # Vertices given by all perturbations of: # (0, ±1, ±ϕ), (±1, ±ϕ, 0), (±ϕ, 0, ±1), where ϕ = golden ratio golden_ratio = 1.61803398875 radius = sphere.radius v1 = Vector3D(-1.0, golden_ratio, 0.0).normalise() * radius v2 = Vector3D(1.0, golden_ratio, 0.0).normalise() * radius v3 = Vector3D(-1.0, -golden_ratio, 0.0).normalise() * radius v4 = Vector3D(1.0, -golden_ratio, 0.0).normalise() * radius v5 = Vector3D(0.0, -1.0, golden_ratio).normalise() * radius v6 = Vector3D(0.0, 1.0, golden_ratio).normalise() * radius v7 = Vector3D(0.0, -1.0, -golden_ratio).normalise() * radius v8 = Vector3D(0.0, 1.0, -golden_ratio).normalise() * radius v9 = Vector3D(golden_ratio, 0.0, -1.0).normalise() * radius v10 = Vector3D(golden_ratio, 0.0, 1.0).normalise() * radius v11 = Vector3D(-golden_ratio, 0.0, -1.0).normalise() * radius v12 = Vector3D(-golden_ratio, 0.0, 1.0).normalise() * radius vertices = [ [v1.x, v1.y, v1.z], [v2.x, v2.y, v2.z], [v3.x, v3.y, v3.z], [v4.x, v4.y, v4.z], [v5.x, v5.y, v5.z], [v6.x, v6.y, v6.z], [v7.x, v7.y, v7.z], [v8.x, v8.y, v8.z], [v9.x, v9.y, v9.z], [v10.x, v10.y, v10.z], [v11.x, v11.y, v11.z], [v12.x, v12.y, v12.z], ] triangles = [ [0, 11, 5], [0, 5, 1], [0, 1, 7], [0, 7, 10], [0, 10, 11], [1, 5, 9], [5, 11, 4], [11, 10, 2], [10, 7, 6], [7, 1, 8], [3, 9, 4], [3, 4, 2], [3, 2, 6], [3, 6, 8], [3, 8, 9], [4, 9, 5], [2, 4, 11], [6, 2, 10], [8, 6, 7], [9, 8, 1] ] # Optional - subdivision of icosohedren to increase resolution num_vertices = 12 num_triangles = 20 for i in range(subdivision_count): for j in range(num_triangles): triangle = triangles[j] # extract current triangle vertices v0_id = triangle[0] v1_id = triangle[1] v2_id = triangle[2] v0 = Vector3D(vertices[v0_id][0], vertices[v0_id][1], vertices[v0_id][2]) v1 = Vector3D(vertices[v1_id][0], vertices[v1_id][1], vertices[v1_id][2]) v2 = Vector3D(vertices[v2_id][0], vertices[v2_id][1], vertices[v2_id][2]) # subdivide with three new vertices v3 = (v0 + v1).normalise() * radius v3_id = num_vertices v4 = (v1 + v2).normalise() * radius v4_id = num_vertices + 1 v5 = (v2 + v0).normalise() * radius v5_id = num_vertices + 2 vertices.append([v3.x, v3.y, v3.z]) vertices.append([v4.x, v4.y, v4.z]) vertices.append([v5.x, v5.y, v5.z]) # ... and three new faces triangles[j] = [v0_id, v3_id, v5_id] # replace the first face triangles.append([v3_id, v1_id, v4_id]) triangles.append([v4_id, v2_id, v5_id]) triangles.append([v3_id, v4_id, v5_id]) num_vertices += 3 num_triangles += 3 vertices = np.array(vertices) triangles = np.array(triangles) if sphere.parent: to_world = sphere.to_root() else: to_world = sphere.transform # Convert vertices to positions in world coordinates for i in range(vertices.shape[0]): p = Point3D(vertices[i, 0], vertices[i, 1], vertices[i, 2]).transform(to_world) vertices[i, 0] = p.x vertices[i, 1] = p.y vertices[i, 2] = p.z return vertices, triangles
def cone_to_mesh(cone, vertical_divisions=10, cylindrical_divisions=36, base_radial_divisions=5): if not isinstance(cone, Cone): raise TypeError("The _cone_to_mesh() function takes a Raysect Cone primitive as an argument, " "wrong type '{}' given.".format(type(cone))) radius = cone.radius height = cone.height # first make the cone body working_cylindrical_divisions = cylindrical_divisions cone_body_vertices = [] for i in range(vertical_divisions): working_radius = radius * (1 - (i / vertical_divisions)) working_height = height * (i / vertical_divisions) theta_step = 360 / working_cylindrical_divisions for j in range(working_cylindrical_divisions): theta_rad = np.deg2rad(j * theta_step) cone_body_vertices.append([working_radius * np.cos(theta_rad), working_radius * np.sin(theta_rad), working_height]) working_cylindrical_divisions -= int(cylindrical_divisions / (vertical_divisions + 1)) if working_cylindrical_divisions < 5: working_cylindrical_divisions = 5 # Finally, add centre point cone_body_vertices.append([0, 0, height]) # Triangulate the vertices vertices_2d = np.array(cone_body_vertices)[:, 0:2] cone_body_triangles = Delaunay(vertices_2d).simplices.tolist() # Now make the cone base working_cylindrical_divisions = cylindrical_divisions cone_base_vertices = [] for i in range(base_radial_divisions): working_radius = radius * (1 - (i / base_radial_divisions)) theta_step = 360 / working_cylindrical_divisions for j in range(working_cylindrical_divisions): theta_rad = np.deg2rad(j * theta_step) cone_base_vertices.append([working_radius * np.cos(theta_rad), working_radius * np.sin(theta_rad), 0]) working_cylindrical_divisions -= int(cylindrical_divisions / (base_radial_divisions + 1)) if working_cylindrical_divisions < 5: working_cylindrical_divisions = 5 # Finally, add centre point cone_base_vertices.append([0, 0, 0]) # Triangulate the vertices vertices_2d = np.array(cone_base_vertices)[:, 0:2] cone_base_triangles = np.flip(Delaunay(vertices_2d).simplices + len(cone_body_vertices), 1).tolist() # Combine the resulting triangles together vertices = cone_body_vertices + cone_base_vertices triangles = cone_body_triangles + cone_base_triangles vertices = np.array(vertices) triangles = np.array(triangles) if cone.parent: to_world = cone.to_root() else: to_world = cone.transform # Convert vertices to positions in world coordinates for i in range(vertices.shape[0]): p = Point3D(vertices[i, 0], vertices[i, 1], vertices[i, 2]).transform(to_world) vertices[i, 0] = p.x vertices[i, 1] = p.y vertices[i, 2] = p.z return vertices, triangles
def cylinder_to_mesh(cylinder, vertical_divisions=10, cylindrical_divisions=36, radial_divisions=5): if not isinstance(cylinder, Cylinder): raise TypeError("The _cylinder_to_mesh() function takes a Raysect Cylinder primitive as an argument, " "wrong type '{}' given.".format(type(cylinder))) radius = cylinder.radius height = cylinder.height # first make the main cylinder theta_step = 360 / cylindrical_divisions vertices = [] for i in range(vertical_divisions): z = (i / (vertical_divisions - 1)) * height for j in range(cylindrical_divisions): theta_rad = np.deg2rad(j * theta_step) vertices.append([radius * np.cos(theta_rad), radius * np.sin(theta_rad), z]) triangles = [] for i in range(vertical_divisions - 1): row_start = cylindrical_divisions * i next_row_start = cylindrical_divisions * (i + 1) for j in range(cylindrical_divisions): v1 = row_start + j if j != cylindrical_divisions - 1: v2 = row_start + j + 1 v3 = next_row_start + j + 1 else: v2 = row_start v3 = next_row_start v4 = next_row_start + j triangles.append([v1, v2, v3]) triangles.append([v3, v4, v1]) def _make_cap_triangles(n_cylindrical_segments, n_radial_segments, radius, z_height): working_cylindrical_segments = n_cylindrical_segments cap_vertices = [] for i in range(n_radial_segments): working_radius = radius * (1 - (i / n_radial_segments)) theta_step = 360 / working_cylindrical_segments for j in range(working_cylindrical_segments): theta_rad = np.deg2rad(j * theta_step) cap_vertices.append([working_radius * np.cos(theta_rad), working_radius * np.sin(theta_rad), z_height]) working_cylindrical_segments -= int(n_cylindrical_segments/(n_radial_segments+1)) if working_cylindrical_segments < 5: working_cylindrical_segments = 5 # Finally, add centre point cap_vertices.append([0, 0, z_height]) vertices_2d = np.array(cap_vertices)[:, 0:2] triangles = Delaunay(vertices_2d).simplices return cap_vertices, triangles # Make the upper and lower end caps lower_cap_vertices, lower_cap_triangles = _make_cap_triangles(cylindrical_divisions, radial_divisions, radius, 0) lower_cap_triangles = np.flip(lower_cap_triangles, 1) lower_cap_triangles += len(vertices) lower_cap_triangles = lower_cap_triangles.tolist() vertices += lower_cap_vertices triangles += lower_cap_triangles upper_cap_vertices, upper_cap_triangles = _make_cap_triangles(cylindrical_divisions, radial_divisions, radius, height) upper_cap_triangles += len(vertices) upper_cap_triangles = upper_cap_triangles.tolist() vertices += upper_cap_vertices triangles += upper_cap_triangles vertices = np.array(vertices) triangles = np.array(triangles) if cylinder.parent: to_world = cylinder.to_root() else: to_world = cylinder.transform # Convert vertices to positions in world coordinates for i in range(vertices.shape[0]): p = Point3D(vertices[i, 0], vertices[i, 1], vertices[i, 2]).transform(to_world) vertices[i, 0] = p.x vertices[i, 1] = p.y vertices[i, 2] = p.z return vertices, triangles
########################## # add machine components # config_file = get_resource("ST40-IVC1", "configuration", 'st40_ivc1_config') load_wall_configuration(config_file, world) eq002 = get_resource("ST40-IVC1", "equilibrium", "eq_006_2T_export") fiesta = Fiesta(eq002) b_field = fiesta.b_field lcfs = fiesta.get_midplane_lcfs()[1] seed_points = [ Point3D(0.75, 0, 0), Point3D(lcfs + 0.001, 0, 0), Point3D(lcfs + 0.01, 0, 0), Point3D(lcfs + 0.02, 0, -0.01) ] field_tracer = FieldlineTracer(b_field, method=RK2(step_size=0.0001)) end_point, _, trajectory1 = field_tracer.trace(world, seed_points[0], save_trajectory=True, max_length=15) end_point, _, trajectory2 = field_tracer.trace(world, seed_points[1], save_trajectory=True, max_length=15) end_point, _, trajectory3 = field_tracer.trace(world, seed_points[2], save_trajectory=True, max_length=15) end_point, _, trajectory4 = field_tracer.trace(world, seed_points[3], save_trajectory=True, max_length=15) # mlab.plot3d([0, 0.005, 0.001], [0, 0, 0], [0, 0, 0], tube_radius=0.0005, color=(1, 0, 0)) # mlab.plot3d([0, 0, 0], [0, 0.005, 0.001], [0, 0, 0], tube_radius=0.0005, color=(0, 1, 0))