def test_trace_with_geometric_objects_1(self): """ Only one of the geometric objects has a transformation applied. """ root = Node(name="Root", geometry=Sphere(radius=10.0)) a = Node(name="A", parent=root, geometry=Sphere(radius=1.0)) b = Node(name="B", parent=root, geometry=Sphere(radius=1.0)) b.translate((5.0, 0.0, 0.0)) scene = Scene(root) tracer = PhotonTracer(scene) position = (-3.0, 0.0, 0.0) direction = (1.0, 0.0, 0.0) initial_ray = Ray( position=position, direction=direction, wavelength=555.0, is_alive=True ) expected_history = [ initial_ray, # Starting ray replace(initial_ray, position=(-1.0, 0.0, 0.0)), # Moved to intersection replace(initial_ray, position=(1.0, 0.0, 0.0)), # Moved to intersection replace(initial_ray, position=(4.0, 0.0, 0.0)), # Moved to intersection replace(initial_ray, position=(6.0, 0.0, 0.0)), # Moved to intersection replace(initial_ray, position=(10.0, 0.0, 0.0), is_alive=False), # Exit ray ] history = tracer.follow(initial_ray) for pair in zip(history, expected_history): a, b = pair print("Testing {} {}".format(a.position, b.position)) assert np.allclose(a.position, b.position)
def test_is_entering_false(self): a = Node(name="A", parent=None) b = Node(name="B", parent=a) b.geometry = Sphere(radius=1.0) surface_point = (-1.0, 0.0, 0.0) entering_direction = (-1.0, 0.0, 0.0) assert b.geometry.is_entering(surface_point, entering_direction) == False
def test_trace_with_material_object(self): """ Root node and test object has a material attached. """ np.random.seed(1) # No reflections # np.random.seed(2) # Reflection at last inteface root = Node(name="Root", geometry=Sphere(radius=10.0, material=Dielectric.make_constant((400, 800), 1.0))) b = Node(name="B", parent=root, geometry=Sphere(radius=1.0, material=Dielectric.make_constant((400, 800), 1.5))) b.translate((5.0, 0.0, 0.0)) scene = Scene(root) tracer = PhotonTracer(scene) position = (-3.0, 0.0, 0.0) direction = (1.0, 0.0, 0.0) initial_ray = Ray( position=position, direction=direction, wavelength=555.0, is_alive=True ) expected_history = [ initial_ray, # Starting ray replace(initial_ray, position=(4.0, 0.0, 0.0)), # Moved to intersection replace(initial_ray, position=(4.0, 0.0, 0.0)), # Refracted into A replace(initial_ray, position=(6.0, 0.0, 0.0)), # Moved to intersection replace(initial_ray, position=(6.0, 0.0, 0.0)), # Refracted out of A replace(initial_ray, position=(10.0, 0.0, 0.0), is_alive=False), # Exit ray ] history = tracer.follow(initial_ray) for pair in zip(history, expected_history): a, b = pair print("Testing {} {}".format(a.position, b.position)) assert np.allclose(a.position, b.position)
def test_intersections(self): a = Node(name="A", parent=None) b = Node(name="B", parent=a) b.geometry = Sphere(radius=1.0) loc = (-1.0, 0.0, 0.0) vec = (1.0, 0.0, 0.0) intersections = b.intersections(loc, vec) points = np.array([x.point for x in intersections]) assert np.allclose(points, ((-1.0, 0.0, 0.0), (1.0, 0.0, 0.0)))
def test_basic_scene(self): a = Node(name="A", parent=None) b = Node(name="B", parent=a) b.geometry = Sphere(radius=1.0) b.translate((2.0, 0.0, 0.0)) s = Scene(root=a) r = MeshcatRenderer(zmq_url='tcp://127.0.0.1:6000') r.render(s) time.sleep(0.5) r.remove(s)
def test_intersection_when_on_surface(self): """ Make sure we return intersection points even with zero distance from ray. """ a = Node(name="A", parent=None) a.geometry = Sphere(radius=1.0) loc = (-1.0, 0.0, 0.0) vec = (1.0, 0.0, 0.0) intersections = a.intersections(loc, vec) points = np.array([x.point for x in intersections]) expected = np.array([(-1.0, 0.0, 0.0), (1.0, 0.0, 0.0)]) assert np.allclose(points, expected)
def node_tree(): b_pos = (1.0, 0.0, 0.0) b_axis = (0.0, 1.0, 0.0) b_rads = 3.14159265 / 2 c_pos = (1.0, 0.0, 0.0) a = Node(name="a", parent=None) b = Node(name="b", parent=a) b.location = b_pos b.rotate(b_rads, b_axis) c = b.add_child_node(name="c") c.location = c_pos return a, b, c
def test_no_interaction(self): np.random.seed(0) mat = Dielectric.make_constant((400, 800), 1.0) root = Node(name="Root", parent=None) root.geometry = Sphere(radius=10.0, material=mat) a = Node(name="A", parent=root) a.geometry = Sphere(radius=1.0, material=mat) ray = Ray(position=(-1.0, 0.0, 0.0), direction=(1.0, 0.0, 0.0), wavelength=555.0, is_alive=True) volume = Volume(a, 2.0) new_ray = volume.trace(ray) expected = replace(ray, position=(1.0, 0.0, 0.0)) assert new_ray == expected
def test_intersection(self): root = Node(name='Root') a = Node(name="A", parent=root) b = Node(name="B", parent=a) b.geometry = Sphere(radius=1.0) a.translate((1.0, 0.0, 0.0)) loc = (-2.0, 0.0, 0.0) vec = (1.0, 0.0, 0.0) scene = Scene(root=root) intersections = scene.intersections(loc, vec) points = tuple([x.point for x in intersections]) # In frame of a everything is shifed 1 along x assert points == ((0.0, 0.0, 0.0), (2.0, 0.0, 0.0))
def test_intersection_with_rotation_around_z(self): root = Node(name='Root') a = Node(name="A", parent=root) b = Node(name="B", parent=a) b.geometry = Sphere(radius=1.0) a.translate((1.0, 0.0, 0.0)) # This rotation make an z translation in b becomes a -y translation in a a.rotate(np.pi / 2, axis=(0.0, 0.0, 1.0)) loc = (-2.0, 0.0, 0.0) vec = (1.0, 0.0, 0.0) scene = Scene(root=root) intersections = scene.intersections(loc, vec) points = tuple([x.point for x in intersections]) assert np.allclose(points, ((0.0, 0.0, 0.0), (2.0, 0.0, 0.0)))
def test_intersection_coordinate_system(self): root = Node(name="Root", geometry=Sphere(radius=10.0)) a = Node(name="A", parent=root, geometry=Sphere(radius=1.0)) a.translate((1.0, 0.0, 0.0)) scene = Scene(root) initial_ray = Ray( position=(-2.0, 0.0, 0.0), direction=(1.0, 0.0, 0.0), wavelength=None, is_alive=True, ) scene_intersections = scene.intersections(initial_ray.position, initial_ray.direction) a_intersections = tuple(map(lambda x: x.to(root), scene_intersections)) assert scene_intersections == a_intersections
def test_zero_reflection(self): np.random.seed(0) mat1 = Dielectric.make_constant((400, 800), 1.0) mat2 = Dielectric.make_constant((400, 800), 1.0) root = Node(name="Root", parent=None) root.geometry = Sphere(radius=10.0, material=mat1) a = Node(name="A", parent=root) a.geometry = Sphere(radius=1.0, material=mat2) ray = Ray(position=(-1.0, 0.0, 0.0), direction=(0.0, 0.0, 1.0), wavelength=555.0, is_alive=True) from_node = root to_node = a interface = DielectricInterface(from_node, to_node) new_ray = interface.trace(ray) expected = ray # unchanged assert new_ray == expected
def test_init(self): node = Node(name='A') inter = Intersection(coordsys=Node, hit=Node, point=(0.0, 0.0, 0.0), distance=0.0) assert type(inter) == Intersection
def test_intersection_coordinate_system(self): root = Node(name="Root", geometry=Sphere(radius=10.0)) a = Node(name="A", parent=root, geometry=Sphere(radius=1.0)) a.translate((1.0, 0.0, 0.0)) scene = Scene(root) initial_ray = Ray( position=(-2.0, 0.0, 0.0), direction=(1.0, 0.0, 0.0), wavelength=None, is_alive=True, ) scene_intersections = scene.intersections( initial_ray.position, initial_ray.direction ) a_intersections = tuple(map(lambda x: x.to(root), scene_intersections)) assert scene_intersections == a_intersections
def test_intersection_with_rotation_around_x(self): root = Node(name='Root') a = Node(name="A", parent=root) b = Node(name="B", parent=a) b.geometry = Sphere(radius=1.0) b.translate((1.0, 0.0, 0.0)) # Rotation around x therefore no displace in x b.rotate(np.pi / 2, (1.0, 0.0, 0.0)) loc = (-2.0, 0.0, 0.0) vec = (1.0, 0.0, 0.0) scene = Scene(root=root) intersections = scene.intersections(loc, vec) points = tuple([x.point for x in intersections]) assert points == ((0.0, 0.0, 0.0), (2.0, 0.0, 0.0))
def representation(self, from_node: Node, to_node: Node) -> Ray: """ Representation of the ray in another coordinate system. Parameters ---------- from_node : Node The node which represents the ray's current coordinate system to_node : Node The node in which the new ray should be represented. Notes ----- Use this method to express the ray location and direction as viewed in the `to_node` coordinate system. """ new_position = from_node.point_to_node(self.position, to_node) new_direction = from_node.vector_to_node(self.direction, to_node) new_ray = replace(self, position=new_position, direction=new_direction) return new_ray
def make_touching_scene(n1=1.5, n2=1.5, n3=1.5): world = Node( name="world (air)", geometry=Sphere( radius=10.0, material=Dielectric.air() ) ) box1 = Node( name="box one (glass)", geometry=Box( (1.0, 1.0, 1.0), material=Dielectric.make_constant( x_range=(300.0, 4000.0), refractive_index=n1 ) ), parent=world ) box2 = Node( name="box two (glass)", geometry=Box( (1.0, 1.0, 1.0), material=Dielectric.make_constant( x_range=(300.0, 4000.0), refractive_index=n2 ) ), parent=world ) box2.translate((0.0, 0.0, 1.0)) box3 = Node( name="box three (glass)", geometry=Box( (1.0, 1.0, 1.0), material=Dielectric.make_constant( x_range=(300.0, 4000.0), refractive_index=n3 ) ), parent=world ) box3.translate((0.0, 0.0, 2.0)) scene = Scene(world) return scene, world, box1, box2, box3
def make_embedded_scene(n1=1.5): world = Node( name="world (air)", geometry=Sphere( radius=10.0, material=Dielectric.air() ) ) box = Node( name="box (glass)", geometry=Box( (1.0, 1.0, 1.0), material=Dielectric.make_constant( x_range=(300.0, 4000.0), refractive_index=n1 ) ), parent=world ) scene = Scene(world) return scene, world, box
def test_equality(self): node = Node(name='A') inter1 = Intersection(coordsys=Node, hit=Node, point=(0.0, 0.0, 0.0), distance=0.0) inter2 = Intersection(coordsys=Node, hit=Node, point=(0.0, 0.0, 0.0), distance=0.0) assert inter1 == inter2
def test_ray_reflecting_interaction(self): # low > very high refractive index np.random.seed(2) mat1 = Dielectric.make_constant((400, 800), 1.0) mat2 = Dielectric.make_constant((400, 800), 6.0) root = Node(name="Root", parent=None) root.geometry = Sphere(radius=10.0, material=mat1) a = Node(name="A", parent=root) a.geometry = Sphere(radius=1.0, material=mat2) ray = Ray(position=(-1.0, 0.0, 0.0), direction=norm((-0.2, 0.2, 1.0)), wavelength=555.0, is_alive=True) from_node = root to_node = a interface = DielectricInterface(from_node, to_node) new_ray = interface.trace(ray) assert np.sign(ray.direction[0]) != np.sign(new_ray.direction[0]) # Reflected # Direction should be different after refracting interface assert all([not np.allclose(new_ray.direction, ray.direction), np.allclose(new_ray.position, ray.position), new_ray.wavelength == ray.wavelength, new_ray.is_alive == ray.is_alive])
def test_intersection_with_translation(self): a = Node(name="A", parent=None) b = Node(name="B", parent=a) b.geometry = Sphere(radius=1.0) b.translate((1.0, 0.0, 0.0)) aloc = (-2.0, 0.0, 0.0) avec = (1.0, 0.0, 0.0) bloc = b.point_to_node(aloc, b) bvec = b.vector_to_node(avec, b) intersections = b.intersections(bloc, bvec) points = tuple(x.point for x in intersections) assert np.allclose(points, ((-1.0, 0.0, 0.0), (1.0, 0.0, 0.0))) # In local frame of b sphere is at origin intersections = a.intersections(aloc, avec) points = np.array(tuple(x.to(a).point for x in intersections)) expected = np.array(((0.0, 0.0, 0.0), (2.0, 0.0, 0.0))) # In frame of a everything is shifed 1 along x assert np.allclose(points, expected)
def test_trace_with_translated_geometric_object(self): """ Single translated geometric objects. """ root = Node(name="Root", geometry=Sphere(radius=10.0)) a = Node(name="A", parent=root, geometry=Sphere(radius=1.0)) a.translate((5.0, 0.0, 0.0)) scene = Scene(root) tracer = PhotonTracer(scene) position = (-2.0, 0.0, 0.0) direction = (1.0, 0.0, 0.0) initial_ray = Ray( position=position, direction=direction, wavelength=555.0, is_alive=True ) expected_history = [ initial_ray, # Starting ray replace(initial_ray, position=(4.0, 0.0, 0.0)), # First intersection replace(initial_ray, position=(6.0, 0.0, 0.0)), # Second intersection replace(initial_ray, position=(10.0, 0.0, 0.0), is_alive=False), # Exit ray ] history = tracer.follow(initial_ray) for pair in zip(history, expected_history): assert pair[0] == pair[1]
def make_embedded_lumophore_scene(n1=1.5): world = Node( name="world (air)", geometry=Sphere( radius=10.0, material=Dielectric.air() ) ) box = Node( name="box (lumophore)", geometry=Box( (1.0, 1.0, 1.0), material=Lumophore.make_lumogen_f_red( x=np.linspace(300.0, 4000.0), absorption_coefficient=10.0, quantum_yield=1.0 ) ), parent=world ) scene = Scene(world) return scene, world, box
def test_trace_raises_trace_error(self): """ Tests interface between objects in which only one of them has a material. This should raise a RuntimeError because it cannot be traced in a physically correct way. """ np.random.seed(2) root = Node(name="Root", geometry=Sphere(radius=10.0)) b = Node(name="B", parent=root, geometry=Sphere(radius=1.0, material=Dielectric.make_constant((400, 800), 1.5))) b.translate((5.0, 0.0, 0.0)) scene = Scene(root) tracer = PhotonTracer(scene) position = (-3.0, 0.0, 0.0) direction = (1.0, 0.0, 0.0) initial_ray = Ray( position=position, direction=direction, wavelength=555.0, is_alive=True ) did_raise = False try: tracer.follow(initial_ray) except TraceError as err: did_raise = True assert did_raise
def test_basic_scene(self): a = Node(name="A", parent=None) b = Node(name="B", parent=a) b.geometry = Sphere(radius=1.0) b.translate((2.0, 0.0, 0.0)) s = Scene(root=a) r = MeshcatRenderer() r.render(s) time.sleep(0.5) r.remove(s)
def test_intersection_with_rotation_around_z(self): root = Node(name='Root') a = Node(name="A", parent=root) b = Node(name="B", parent=a) b.geometry = Sphere(radius=1.0) a.translate((1.0, 0.0, 0.0)) # This rotation make an z translation in b becomes a -y translation in a a.rotate(np.pi/2, axis=(0.0, 0.0, 1.0)) loc = (-2.0, 0.0, 0.0) vec = (1.0, 0.0, 0.0) scene = Scene(root=root) intersections = scene.intersections(loc, vec) points = tuple([x.point for x in intersections]) assert np.allclose(points, ((0.0, 0.0, 0.0), (2.0, 0.0, 0.0)))
def test_intersection_with_rotation_around_x(self): root = Node(name='Root') a = Node(name="A", parent=root) b = Node(name="B", parent=a) b.geometry = Sphere(radius=1.0) b.translate((1.0, 0.0, 0.0)) # Rotation around x therefore no displace in x b.rotate(np.pi/2, (1.0, 0.0, 0.0)) loc = (-2.0, 0.0, 0.0) vec = (1.0, 0.0, 0.0) scene = Scene(root=root) intersections = scene.intersections(loc, vec) points = tuple([x.point for x in intersections]) assert points == ((0.0, 0.0, 0.0), (2.0, 0.0, 0.0))
from pvtrace.scene.renderer import MeshcatRenderer from pvtrace.geometry.utils import magnitude import logging # We want to see pvtrace logging here #logging.getLogger('pvtrace').setLevel(logging.CRITICAL) logging.getLogger('trimesh').setLevel(logging.CRITICAL) logging.getLogger('matplotlib').setLevel(logging.CRITICAL) wavelength_range = (200, 800) wavelength = np.linspace(*wavelength_range, 1000) lumogen = Lumophore.make_lumogen_f_red(wavelength, 1000, 1.0) linear_background = Lumophore.make_linear_background(wavelength, 1.0) # Make a world coordinate system world_node = Node(name='world') world_node.geometry = Sphere( radius=10.0, material=Dielectric.make_constant((300, 1000.0), 1.0) ) refractive_index = np.column_stack( (wavelength, np.ones(wavelength.shape) * 1.5) ) # Add LSC size = (1.0, 1.0, 0.02) lsc = Node(name="LSC", parent=world_node) lsc.geometry = Box( size, material=Host( refractive_index, # LSC refractive index
def test_init(self): assert type(Node()) == Node
def test_name(self): assert Node(name="a").name == 'a'
material = Host( np.column_stack( # refractive index spectrum (wavelength, np.ones(wavelength.size) * 1.5) ), [lumophore], # list of lumophores, reuse the one we already have. ) # Make the cylinder node with length 5cm and radius 0.02cm and give the material we # just made. cylinder = Node( name="cylinder (glass)", geometry=Cylinder( length=5.0, radius=0.02, material=material ), parent=world ) # Make a light source. This is a laser emitting light along the whole length of the # cylinder. We need to translate and rotate the light source to get it to fire along # the axis. We use the position delegate to generate photons along the same length # as the cylinder. light = Node( name="light (555nm laser)", light=Light(position_delegate=lambda : (np.random.uniform(-2.5, 2.5), 0.0, 0.0)), parent=world )
def test_parent(self): a = Node() b = Node(parent=a) assert a.parent == None assert b.parent == a
""" from pvtrace.geometry.cylinder import Cylinder from pvtrace.geometry.sphere import Sphere from pvtrace.scene.renderer import MeshcatRenderer from pvtrace.scene.scene import Scene from pvtrace.scene.node import Node from pvtrace.light.light import Light from pvtrace.algorithm import photon_tracer from pvtrace.material.dielectric import Dielectric import numpy as np import functools import sys import time # World node contains the simulation; large sphere filled with air world = Node(name="world (air)", geometry=Sphere(radius=10.0, material=Dielectric.air())) # A small cylinder shape made from glass cylinder = Node(name="cylinder (glass)", geometry=Cylinder(length=1.0, radius=1.0, material=Dielectric.glass()), parent=world) # A light source with 60-deg divergence light = Node(name="light (555nm laser)", light=Light(divergence_delegate=functools.partial( Light.cone_divergence, np.radians(60))), parent=world) light.translate((0.0, 0.0, -1.0))
def test_coodinate_system_conversions(self): a = Node(name='a') b = Node(name='b', parent=a) c = Node(name='c', parent=b) d = Node(name='d', parent=a) b.translate((1, 1, 1)) c.translate((0, 1, 1)) d.translate((-1, -1, -1)) theta = 0.5 * np.pi b.rotate(theta, (0, 0, 1)) c.rotate(theta, (1, 0, 0)) d.rotate(theta, (0, 1, 0)) # Points in node d to a require just travelling up the nodes. assert np.allclose(d.point_to_node((0, 0, 0), a), (-1, -1, -1)) assert np.allclose(d.point_to_node((1, 1, 1), a), (0, 0, -2)) # Directions in node d to a require just travelling up the nodes. assert np.allclose(d.vector_to_node((1, 0, 0), a), (0, 0, -1)) assert np.allclose(d.vector_to_node((0, 1, 0), a), (0, 1, 0)) assert np.allclose(d.vector_to_node((0, 0, 1), a), (1, 0, 0)) # Points in node d to c requires going up and down nodes assert np.allclose(c.point_to_node((0, 0, 0), d), (-3, 2, 1)) assert np.allclose(c.point_to_node((1, 1, 1), d), (-4, 3, 2)) # Directions in node d to c require going up and down nodes assert np.allclose(c.vector_to_node((1, 0, 0), d), (0, 1, 0)) assert np.allclose(c.vector_to_node((0, 1, 0), d), (-1, 0, 0)) assert np.allclose(c.vector_to_node((0, 0, 1), d), (0, 0, 1))
radius=10.0, material=Dielectric.air() ) ) cylinder = Node( name="cylinder (glass)", geometry=Cylinder( length=1.0, radius=1.0, material=Dielectric.glass() ), parent=world ) light = Node( name="light (555nm laser)", light=Light(divergence_delegate=functools.partial(Light.cone_divergence, np.radians(60))), parent=world ) light.translate((0.0, 0.0, -1.0)) rend = MeshcatRenderer() scene = Scene(world) tracer = PhotonTracer(scene) rend.render(scene) for ray in light.emit(10): path = tracer.follow(ray) print(path) rend.add_ray_path(path) while True: try: time.sleep(.3)
from pvtrace.scene.node import Node from pvtrace.scene.scene import Scene from pvtrace.scene.renderer import MeshcatRenderer from pvtrace.geometry.sphere import Sphere from pvtrace.material.dielectric import Dielectric from pvtrace.light.light import Light from pvtrace.algorithm import photon_tracer import time import functools import numpy as np # Add nodes to the scene graph world = Node(name="world (air)", geometry=Sphere(radius=10.0, material=Dielectric.air())) sphere = Node(name="sphere (glass)", geometry=Sphere(radius=1.0, material=Dielectric.glass()), parent=world) sphere.translate((0, 0, 2)) # Add source of photons light = Node(name="Light (555nm)", light=Light(divergence_delegate=functools.partial( Light.cone_divergence, np.radians(20)))) # Use meshcat to render the scene (optional) viewer = MeshcatRenderer(open_browser=True) scene = Scene(world) for ray in light.emit(100): # Do something with the photon trace information... info = photon_tracer.follow(ray, scene) rays, events = zip(*info)
def test_coodinate_system_conversions(self): a = Node(name='a') b = Node(name='b', parent=a) c = Node(name='c', parent=b) d = Node(name='d', parent=a) b.translate((1,1,1)) c.translate((0,1,1)) d.translate((-1, -1, -1)) theta = 0.5 * np.pi b.rotate(theta, (0, 0, 1)) c.rotate(theta, (1, 0, 0)) d.rotate(theta, (0, 1, 0)) # Points in node d to a require just travelling up the nodes. assert np.allclose(d.point_to_node((0, 0, 0), a), (-1, -1, -1)) assert np.allclose(d.point_to_node((1, 1, 1), a), (0, 0, -2)) # Directions in node d to a require just travelling up the nodes. assert np.allclose(d.vector_to_node((1, 0, 0), a), (0, 0, -1)) assert np.allclose(d.vector_to_node((0, 1, 0), a), (0, 1, 0)) assert np.allclose(d.vector_to_node((0, 0, 1), a), (1, 0, 0)) # Points in node d to c requires going up and down nodes assert np.allclose(c.point_to_node((0, 0, 0), d), (-3, 2, 1)) assert np.allclose(c.point_to_node((1, 1, 1), d), (-4, 3, 2)) # Directions in node d to c require going up and down nodes assert np.allclose(c.vector_to_node((1, 0, 0), d), (0, 1, 0)) assert np.allclose(c.vector_to_node((0, 1, 0), d), (-1, 0, 0)) assert np.allclose(c.vector_to_node((0, 0, 1), d), (0, 0, 1))
def _make_scene(self): """ Creates the scene based on configuration values. """ # Make world node (l, w, d) = self.size world = Node( name="World", geometry=Box((l * 100, w * 100, d * 100), material=Material(refractive_index=self.n0)), ) # Create components (Absorbers, Luminophores and Scatteres) if len(self._user_components) == 0: self._user_components = self._make_default_components() components = [] for component_data in self._user_components: cls = component_data.pop("cls") coefficient = component_data.pop("coefficient") component = cls(coefficient, **component_data) components.append(component) # Create LSC node lsc = Node( name="LSC", geometry=Box( (l, w, d), material=Material( refractive_index=self.n1, components=components, surface=Surface(delegate=OptionalMirrorAndSolarCell(self)), ), ), parent=world, ) if self._air_gap_mirror_info["want_air_gap_mirror"]: sheet_thickness = 0.25 * d # make it appear thinner than the LSC air_gap_mirror = Node( name="Air Gap Mirror", geometry=Box( (l, w, sheet_thickness), # same surface air but very thin material=Material( refractive_index=self.n0, components=[], surface=Surface(delegate=AirGapMirror(self)), ), ), parent=world, ) # Move adjacent to bottom surface with a small air gap air_gap_mirror.translate((0.0, 0.0, -(0.5 * d + sheet_thickness))) # Use user light if any have been given, otherwise use default values. if len(self._user_lights) == 0: self._user_lights = self._make_default_lights() # Create light nodes for light_data in self._user_lights: name = light_data["name"] light = Light( name=name, direction=light_data["direction"], wavelength=light_data["wavelength"], position=light_data["position"], ) light_node = Node(name=name, light=light, parent=world) light_node.location = light_data["location"] if light_data["rotation"]: light_node.rotate(*light_data["rotation"]) self._scene = Scene(world)