def add_view_frustum(): position = np.array(lm_camera_params['eye']) center = np.array(lm_camera_params['center']) up = np.array(lm_camera_params['up']) aspect = lm_camera_params['aspect'] fov = math.radians(lm_camera_params['vfov']) M = lookat_matrix(position, center, up) z = 5 half_fov = fov * .5 y = math.tan(half_fov) * z x = aspect * y p = list(position) p1 = list(position + np.dot(M, [-x, -y, -z])) p2 = list(position + np.dot(M, [x, -y, -z])) p3 = list(position + np.dot(M, [x, y, -z])) p4 = list(position + np.dot(M, [-x, y, -z])) # Add mesh geom = three.Geometry( vertices=[p, p1, p2, p, p2, p3, p, p3, p4, p, p4, p1]) mat = three.MeshBasicMaterial(color='#00ff00', wireframe=True, side='DoubleSide') mesh = three.Line(geometry=geom, material=mat) scene.add(mesh)
def draw_mesh(mesh, color=None): vertices, faces = mesh.to_vertices_and_faces() hexcolor = rgb_to_hex(color[:3]) if color else '#cccccc' vertexcolors = [hexcolor] * len(vertices) faces = [f + [None, [vertexcolors[i] for i in f], None] for f in faces] geo = p3js.Geometry(vertices=vertices, faces=faces) geo.exec_three_obj_method('computeFaceNormals') return p3js.Mesh(geometry=geo, material=p3js.MeshLambertMaterial(vertexColors='VertexColors'), position=[0, 0, 0])
def display_path(th_scene, vs, **kwargs): """Display path.""" geom = three.Geometry(vertices=vs) mat_line = three.LineBasicMaterial(**kwargs) line = three.Line(geometry=geom, material=mat_line) th_scene.add(line) mat_points = three.PointsMaterial(**kwargs) points = three.Points(geometry=geom, material=mat_points) th_scene.add(points)
def lines_children(origins, targets, color="blue"): material = p3js.LineBasicMaterial(color=color, linewidth=4) scene_children = [] # For each 24 joint for origin, target in zip(origins, targets): geometry = p3js.Geometry(vertices=np.array([origin, target]).tolist()) line = p3js.Line(geometry, material) scene_children.append(line) return scene_children
def get_polylines_pythreejs(polylines): lines = [] for x in polylines: line_geometry = pythreejs.Geometry(vertices=x["vertices"]) line = pythreejs.Line( geometry=line_geometry, material=pythreejs.LineBasicMaterial(color=x["color"]), type='LinePieces') lines.append(line) return lines
def draw_mesh(mesh, hexcolor): mesh_quads_to_triangles(mesh) vertices, faces = mesh.to_vertices_and_faces() vertexcolors = [hexcolor] * len(vertices) faces = [f + [None, [vertexcolors[i] for i in f], None] for f in faces] geo = p3js.Geometry(vertices=vertices, faces=faces) geo.exec_three_obj_method('computeFaceNormals') return p3js.Mesh( geometry=geo, material=p3js.MeshLambertMaterial(vertexColors='VertexColors'), position=[0, 0, 0])
def joint_children(joints3D, color="blue", links=None): material = p3js.LineBasicMaterial(color=color, linewidth=4) scene_children = [] # For each 24 joint if links is None: links = [ (0, 1, 2, 3, 4), (0, 5, 6, 7, 8), (0, 9, 10, 11, 12), (0, 13, 14, 15, 16), (0, 17, 18, 19, 20), ] for link in links: for j1, j2 in zip(link[0:-1], link[1:]): geometry = p3js.Geometry(vertices=joints3D[(j1, j2), :].tolist()) line = p3js.Line(geometry, material) scene_children.append(line) return scene_children
def ray2mesh(ray): rays = py3js.Group() w = ray.wavelength rc, gc, bc = wavelength2RGB(w) rc = int(255 * rc) gc = int(255 * gc) bc = int(255 * bc) material = py3js.LineBasicMaterial( color="#{:02X}{:02X}{:02X}".format(rc, gc, bc)) rl = ray2list(ray) for r in rl: geometry = py3js.Geometry() geometry.vertices = r line = py3js.Line(geometry, material) rays.add(line) return rays
def ray2mesh(ray): rays = py3js.Group() if ray.draw_color is None: color = wavelength2RGB(ray.wavelength) else: color = colors.to_rgb(ray.draw_color) int_colors = [int(255 * c) for c in color] material = py3js.LineBasicMaterial(color="#{:02X}{:02X}{:02X}".format( *int_colors)) rl = ray2list(ray) for r in rl: geometry = py3js.Geometry() geometry.vertices = r line = py3js.Line(geometry, material) rays.add(line) return rays
def axes(max_dist): """ Generate X, Y, Z axes of length max_width in the form of a pythreejs Line object. Parameters ---------- max_dist : float maximum extent of grid from origin in each dimension Returns ------- axes : pythreejs.Line a pythreejs Line object representing the xyz axes. """ axes_geom = p3j.Geometry( vertices=[[0, 0, 0], [max_dist, 0, 0], [0, 0, 0], [0, max_dist, 0], [0, 0, 0], [0, 0, max_dist]], colors=['white', 'white', 'white', 'white', 'white', 'white']) return p3j.Line(geometry=axes_geom, material=p3j.LineBasicMaterial( linewidth=1, vertexColors='VertexColors'))
def create_js_scene_view(gcollect, add_objects=True, add_labels=False, gobject_jsmap=None, jslink=False): """create PyThreeJS Scene for GeometricCollection and one-way link all GeometricObject attributes and creation/deletion Properties ---------- gcollect : GeometricCollection add_objects: bool add objects to scene add_labels : bool add object labels to scene gobject_jsmap : None or dict if None use default gobject->jsobject mapping jslink : bool if True, where possible, create client side links http://ipywidgets.readthedocs.io/en/latest/examples/Widget%20Events.html#The-difference-between-linking-in-the-kernel-and-linking-in-the-client Returns ------- scene : pythreejs.Scene scene.children = [gobjcontainer,light] Examples -------- >>> from pandas3js.models import GeometricCollection, Sphere >>> collection = GeometricCollection() >>> scene = create_js_scene_view(collection,add_objects=True,add_labels=True) >>> container = scene.children[0] >>> [type(child) for child in container.children] [] >>> sphere = Sphere(id=1) >>> collection.add_object(sphere) >>> [type(child) for child in container.children] [<class 'traitlets.traitlets.Sprite'>, <class 'traitlets.traitlets.Mesh'>] >>> mesh = container.children[1] >>> mesh.position [0.0, 0.0, 0.0] >>> sphere.position = (1,0,0) >>> mesh.position [1.0, 0.0, 0.0] >>> sphere = collection.pop(1) >>> [type(child) for child in container.children] [] """ assert isinstance( gcollect, GeometricCollection), 'gcollect must be a GeometricCollection' meshes = [] for gobject in gcollect.idobjects: if add_labels: lmesh = create_jslabelmesh_view(gobject, gobject_jsmap, jslink=jslink) meshes.append(lmesh) if add_objects: gmesh = create_jsmesh_view(gobject, gobject_jsmap, jslink=jslink) meshes.append(gmesh) # create dummy parent mesh to house all meshes, so we can use single mouse picker # NB: it would be better to use groups https://threejs.org/docs/#api/objects/Group # but this is not implemented in pythreejs gcontainer = js.Mesh(geometry=js.Geometry(), material=js.BasicMaterial(), position=[0, 0, 0], children=meshes) scenelight = js.AmbientLight(color='#777777') scene = js.Scene(children=[gcontainer, scenelight]) def gobjects_changed(change): old = set(change.old) new = set(change.new) removed_objects = old.difference(new) added_objects = new.difference(old) if removed_objects: removed_ids = [o.id for o in removed_objects] original_children = [] for child in gcontainer.children: if child.gobject_id not in removed_ids: original_children.append(child) else: original_children = gcontainer.children new_meshes = [] for gobject in added_objects: if add_labels: new_meshes.append( create_jslabelmesh_view(gobject, gobject_jsmap, jslink=jslink)) if add_objects: new_meshes.append( create_jsmesh_view(gobject, gobject_jsmap, jslink=jslink)) gcontainer.children = new_meshes + original_children gcollect.observe(gobjects_changed, names='idobjects') return scene
def _get_grids(self, obj_vertices): extents = self._get_extents(obj_vertices) grid_verts = [] deltas = [extent[1] - extent[0] for extent in extents] max_extent = max(deltas) space1 = 10.0**pymath.floor( pymath.log(max_extent) / pymath.log(10.0) - 0.5) space2 = 2 * 10.0**pymath.floor( pymath.log(max_extent / 2.0) / pymath.log(10.0) - 0.5) space = space2 if max_extent / space2 < 5: space = space1 N = int(pymath.floor(max_extent / space + 2.0)) grid_cols = [] axis_cols = ['#ff3333', '#33ff33', '#3333ff'] ends = [] for axis1 in range(3): start = pymath.floor(extents[axis1][0] / space) * space ends.append(start + space * N) for axis2 in range(3): axis3 = [x for x in [0, 1, 2] if x not in [axis1, axis2]][0] if axis1 == axis2: continue delta = extents[axis1][1] - extents[axis1][0] start2 = pymath.floor(extents[axis2][0] / space) * space end2 = start2 + (N - 1) * space verts = self._get_grid_lines(axis1, start, space, N, axis2, start2, end2) grid_verts.extend(verts) grid_cols.extend([axis_cols[axis3] for vert in verts]) # now draw the X,Y,Z labels: char_width = max_extent * 0.05 char_lines = [] # X: char_lines_x = [] char_lines_x.append([[0.0, 0.0], [1.0, 1.0]]) char_lines_x.append([[0.0, 1.0], [1.0, 0.0]]) char_lines.append(char_lines_x) # Y: char_lines_y = [] char_lines_y.append([[0.5, 0.0], [0.5, 0.5]]) char_lines_y.append([[0.5, 0.5], [0.0, 1.0]]) char_lines_y.append([[0.5, 0.5], [1.0, 1.0]]) char_lines.append(char_lines_y) # Z: char_lines_z = [] char_lines_z.append([[1.0, 1.0], [0.0, 1.0]]) char_lines_z.append([[0.0, 1.0], [1.0, 0.0]]) char_lines_z.append([[1.0, 0.0], [0.0, 0.0]]) char_lines.append(char_lines_z) for iaxis in range(3): ax1 = [0, 1, 2][iaxis] ax2 = [2, 2, 1][iaxis] char_lns = char_lines[iaxis] segs = [[[0, 0], [ends[iaxis] + char_width, 0]], [[ends[iaxis] + char_width, 0], [ends[iaxis] + 0.5 * char_width, 0.5 * char_width]], [[ends[iaxis] + char_width, 0], [ends[iaxis] + 0.5 * char_width, -0.5 * char_width]]] for seg in segs: for pt in seg: pt3 = [0, 0, 0] pt3[ax1] += pt[0] pt3[ax2] += pt[1] grid_verts.append(pt3) grid_cols.append('#000000') for seg in char_lns: for pt in seg: pt3 = [0, 0, 0] pt3[iaxis] += ends[iaxis] + 2 * char_width pt3[ax1] += pt[0] * char_width pt3[ax2] += 1.2 * (pt[1] - 0.5) * char_width grid_verts.append(pt3) grid_cols.append('#000000') lines_geom = pjs.Geometry(vertices=grid_verts, colors=grid_cols) lines = pjs.LineSegments(geometry=lines_geom, material=pjs.LineBasicMaterial( linewidth=self.grid_lines_width, transparent=True, opacity=0.5, dashSize=10, gapSize=10, vertexColors='VertexColors'), type='LinePieces') return lines, space
def _render_stl(self, stl_file): vertices, faces = self._conv_stl(stl_file) # Map the vertex colors into the 'color' slot of the faces faces = [f + [None, [OBJ_COLOR for i in f], None] for f in faces] # Create the geometry: obj_geometry = pjs.Geometry(vertices=vertices, faces=faces, colors=[OBJ_COLOR] * len(vertices)) # Calculate normals per face, for nice crisp edges: obj_geometry.exec_three_obj_method('computeFaceNormals') # Create a mesh. Note that the material need to be told to use the vertex colors. my_object_mesh = pjs.Mesh( geometry=obj_geometry, material=pjs.MeshLambertMaterial(vertexColors='VertexColors'), position=[0, 0, 0], # Center the cube ) n_vert = len(vertices) center = [ sum([vertex[i] for vertex in vertices]) / float(n_vert) for i in range(3) ] extents = self._get_extents(vertices) max_delta = max([extent[1] - extent[0] for extent in extents]) camPos = [center[i] + 4 * max_delta for i in range(3)] light_pos = [center[i] + (i + 3) * max_delta for i in range(3)] # Set up a scene and render it: camera = pjs.PerspectiveCamera(position=camPos, fov=20, children=[ pjs.DirectionalLight( color='#ffffff', position=light_pos, intensity=0.5) ]) camera.up = (0, 0, 1) scene_things = [ my_object_mesh, camera, pjs.AmbientLight(color='#888888') ] if self.draw_grids: grids, space = self._get_grids(vertices) scene_things.append(grids) scene = pjs.Scene(children=scene_things, background=BACKGROUND_COLOR) renderer_obj = pjs.Renderer( camera=camera, background='#cccc88', background_opacity=0, scene=scene, controls=[pjs.OrbitControls(controlling=camera)], width=self.width, height=self.height) display_things = [renderer_obj] if self.draw_grids: s = """ <svg width="{}" height="30"> <rect width="20" height="20" x="{}" y="0" style="fill:none;stroke-width:1;stroke:rgb(0,255,0)" /> <text x="{}" y="15">={:.1f}</text> Sorry, your browser does not support inline SVG. </svg>""".format(self.width, self.width // 2, self.width // 2 + 25, space) display_things.append(HTML(s)) display(*display_things)
def draw_mesh(self, mesh, color=None, id=None): v, f = mesh.to_vertices_and_faces() return p3js.Geometry(vertices=v, faces=f)
def hand_obj_children( obj_verts=None, obj_faces=None, gt_obj_verts=None, gt_obj_faces=None, hand_verts=None, mano_faces_left=None, display_wireframe=False, inside_face_colors=True, hand_opacity=1, obj_opacity=0.2, ): """Args: obj_verts(numpy.ndarray): vertices of object hand_verts(numpy.ndarray): vertices of handect *_faces(numpy.ndarray): faces """ scene_children = [] if obj_verts is not None: geo_obj = p3js.Geometry(vertices=obj_verts.tolist(), faces=obj_faces.tolist()) geo_obj.exec_three_obj_method("computeFaceNormals") mat = p3js.MeshLambertMaterial(color="red", side="FrontSide", transparent=True) mat.opacity = obj_opacity # obj_opacity surf_obj = p3js.Mesh(geometry=geo_obj, material=mat) if inside_face_colors: back_color = "#a91818" else: back_color = "red" mat_bak = p3js.MeshLambertMaterial(color=back_color, side="BackSide", transparent=True) mat_bak.opacity = obj_opacity surf_obj_back = p3js.Mesh(geometry=geo_obj, material=mat_bak) scene_children.append(surf_obj) scene_children.append(surf_obj_back) if display_wireframe: obj_edges = p3js.Mesh( geometry=geo_obj, material=p3js.MeshBasicMaterial(color="black", wireframe=True), ) scene_children.append(obj_edges) if gt_obj_verts is not None: geo_obj = p3js.Geometry(vertices=gt_obj_verts.tolist(), faces=gt_obj_faces.tolist()) geo_obj.exec_three_obj_method("computeFaceNormals") mat = p3js.MeshLambertMaterial(color="orange", side="FrontSide", transparent=True) mat.opacity = obj_opacity surf_obj = p3js.Mesh(geometry=geo_obj, material=mat) mat_back = p3js.MeshLambertMaterial(color="#a91818", side="BackSide", transparent=True) mat_back.opacity = obj_opacity surf_obj_back = p3js.Mesh(geometry=geo_obj, material=mat_bak) scene_children.append(surf_obj) scene_children.append(surf_obj_back) if display_wireframe: obj_edges = p3js.Mesh( geometry=geo_obj, material=p3js.MeshBasicMaterial(color="black", wireframe=True), ) scene_children.append(obj_edges) if hand_verts is not None: geo_hand = p3js.Geometry(vertices=hand_verts.tolist(), faces=mano_faces_left.tolist()) geo_hand.exec_three_obj_method("computeFaceNormals") mat = p3js.MeshLambertMaterial(color="blue", side="FrontSide", transparent=True) mat.opacity = hand_opacity surf_hand = p3js.Mesh(geometry=geo_hand, material=mat) bak_mat = p3js.MeshLambertMaterial(color="blue", side="BackSide", transparent=True) bak_mat.opacity = hand_opacity surf_hand_bak = p3js.Mesh(geometry=geo_hand, material=bak_mat) scene_children.append(surf_hand) scene_children.append(surf_hand_bak) if display_wireframe: hand_edges = p3js.Mesh( geometry=geo_hand, material=p3js.MeshBasicMaterial(color="black", wireframe=True), ) scene_children.append(hand_edges) return scene_children
def surf2mesh(S, P=(0, 0, 0), D=(0, 0, 0), wire=False): color = "#ffff00" points, polylist = S.polylist() #Conversion para quethreejs la entienda polylist = list(polylist) lpoly = [] lpoints = [] for l in points: lpoints.append(list(l)) for l in polylist: lpoly.append(list(map(int, l))) vertices = lpoints faces = lpoly # Map the vertex colors into the 'color' slot of the faces # Map the normals nfaces = [] for f in faces: p0 = points[f[0]] p1 = points[f[1]] p2 = points[f[2]] v0 = array(p1) - array(p0) v1 = array(p2) - array(p0) v3 = cross(v0, v1) v3 = tuple(v3 / sqrt(v3[0]**2 + v3[1]**2 + v3[2]**2)) nfaces.append(f + [v3, color, None]) # Create the geometry: surfaceGeometry = py3js.Geometry( vertices=vertices, faces=nfaces, #colors=vertexcolors ) #surfaceGeometry = py3js.SphereGeometry(radius=300, widthSegments=32, heightSegments=24) if wire: surfaceGeometry = py3js.WireframeGeometry(surfaceGeometry) # Calculate normals per face, for nice crisp edges: surfaceGeometry.exec_three_obj_method('computeFaceNormals') surfaceMaterial = py3js.MeshPhongMaterial(color=color, ambient="#050505", specular="#ffffff", shininess=15, emissive="#000000", side='DoubleSide', transparent=True, opacity=.8) #surfaceMaterial = py3js.MeshLambertMaterial(color='red',side='DoubleSide') # Create a mesh. Note that the material need to be told to use the vertex colors. surfaceMesh = py3js.Mesh( geometry=surfaceGeometry, material=surfaceMaterial, ) surfaceMesh.rotation = *D, "ZYX" surfaceMesh.position = tuple(P) return surfaceMesh
def generate_3js_render( element_groups, canvas_size, zoom, camera_fov=30, background_color="white", background_opacity=1.0, reuse_objects=False, use_atom_arrays=False, use_label_arrays=False, ): """Create a pythreejs scene of the elements. Regarding initialisation performance, see: https://github.com/jupyter-widgets/pythreejs/issues/154 """ import pythreejs as pjs key_elements = {} group_elements = pjs.Group() key_elements["group_elements"] = group_elements unique_atom_sets = {} for el in element_groups["atoms"]: element_hash = ( ("radius", el.sradius), ("color", el.color), ("fill_opacity", el.fill_opacity), ("stroke_color", el.get("stroke_color", "black")), ("ghost", el.ghost), ) unique_atom_sets.setdefault(element_hash, []).append(el) group_atoms = pjs.Group() group_ghosts = pjs.Group() atom_geometries = {} atom_materials = {} outline_materials = {} for el_hash, els in unique_atom_sets.items(): el = els[0] data = dict(el_hash) if reuse_objects: atom_geometry = atom_geometries.setdefault( el.sradius, pjs.SphereBufferGeometry(radius=el.sradius, widthSegments=30, heightSegments=30), ) else: atom_geometry = pjs.SphereBufferGeometry(radius=el.sradius, widthSegments=30, heightSegments=30) if reuse_objects: atom_material = atom_materials.setdefault( (el.color, el.fill_opacity), pjs.MeshLambertMaterial(color=el.color, transparent=True, opacity=el.fill_opacity), ) else: atom_material = pjs.MeshLambertMaterial(color=el.color, transparent=True, opacity=el.fill_opacity) if use_atom_arrays: atom_mesh = pjs.Mesh(geometry=atom_geometry, material=atom_material) atom_array = pjs.CloneArray( original=atom_mesh, positions=[e.position.tolist() for e in els], merge=False, ) else: atom_array = [ pjs.Mesh( geometry=atom_geometry, material=atom_material, position=e.position.tolist(), name=e.info_string, ) for e in els ] data["geometry"] = atom_geometry data["material_body"] = atom_material if el.ghost: key_elements["group_ghosts"] = group_ghosts group_ghosts.add(atom_array) else: key_elements["group_atoms"] = group_atoms group_atoms.add(atom_array) if el.get("stroke_width", 1) > 0: if reuse_objects: outline_material = outline_materials.setdefault( el.get("stroke_color", "black"), pjs.MeshBasicMaterial( color=el.get("stroke_color", "black"), side="BackSide", transparent=True, opacity=el.get("stroke_opacity", 1.0), ), ) else: outline_material = pjs.MeshBasicMaterial( color=el.get("stroke_color", "black"), side="BackSide", transparent=True, opacity=el.get("stroke_opacity", 1.0), ) # TODO use stroke width to dictate scale if use_atom_arrays: outline_mesh = pjs.Mesh( geometry=atom_geometry, material=outline_material, scale=(1.05, 1.05, 1.05), ) outline_array = pjs.CloneArray( original=outline_mesh, positions=[e.position.tolist() for e in els], merge=False, ) else: outline_array = [ pjs.Mesh( geometry=atom_geometry, material=outline_material, position=e.position.tolist(), scale=(1.05, 1.05, 1.05), ) for e in els ] data["material_outline"] = outline_material if el.ghost: group_ghosts.add(outline_array) else: group_atoms.add(outline_array) key_elements.setdefault("atom_arrays", []).append(data) group_elements.add(group_atoms) group_elements.add(group_ghosts) group_labels = add_labels(element_groups, key_elements, use_label_arrays) group_elements.add(group_labels) if len(element_groups["cell_lines"]) > 0: cell_line_mat = pjs.LineMaterial( linewidth=1, color=element_groups["cell_lines"].group_properties["color"]) cell_line_geo = pjs.LineSegmentsGeometry(positions=[ el.position.tolist() for el in element_groups["cell_lines"] ]) cell_lines = pjs.LineSegments2(geometry=cell_line_geo, material=cell_line_mat) key_elements["cell_lines"] = cell_lines group_elements.add(cell_lines) if len(element_groups["bond_lines"]) > 0: bond_line_mat = pjs.LineMaterial( linewidth=element_groups["bond_lines"]. group_properties["stroke_width"], vertexColors="VertexColors", ) bond_line_geo = pjs.LineSegmentsGeometry( positions=[ el.position.tolist() for el in element_groups["bond_lines"] ], colors=[[Color(c).rgb for c in el.color] for el in element_groups["bond_lines"]], ) bond_lines = pjs.LineSegments2(geometry=bond_line_geo, material=bond_line_mat) key_elements["bond_lines"] = bond_lines group_elements.add(bond_lines) group_millers = pjs.Group() if len(element_groups["miller_lines"]) or len( element_groups["miller_planes"]): key_elements["group_millers"] = group_millers if len(element_groups["miller_lines"]) > 0: miller_line_mat = pjs.LineMaterial( linewidth=3, vertexColors="VertexColors" # TODO use stroke_width ) miller_line_geo = pjs.LineSegmentsGeometry( positions=[ el.position.tolist() for el in element_groups["miller_lines"] ], colors=[[Color(el.stroke_color).rgb] * 2 for el in element_groups["miller_lines"]], ) miller_lines = pjs.LineSegments2(geometry=miller_line_geo, material=miller_line_mat) group_millers.add(miller_lines) for el in element_groups["miller_planes"]: vertices = el.position.tolist() faces = [( 0, 1, 2, triangle_normal(vertices[0], vertices[1], vertices[2]), "black", 0, )] if len(vertices) == 4: faces.append(( 2, 3, 0, triangle_normal(vertices[2], vertices[3], vertices[0]), "black", 0, )) elif len(vertices) != 3: raise NotImplementedError("polygons with more than 4 points") plane_geom = pjs.Geometry(vertices=vertices, faces=faces) plane_mat = pjs.MeshBasicMaterial( color=el.fill_color, transparent=True, opacity=el.fill_opacity, side="DoubleSide", ) plane_mesh = pjs.Mesh(geometry=plane_geom, material=plane_mat) group_millers.add(plane_mesh) group_elements.add(group_millers) scene = pjs.Scene(background=None) scene.add([group_elements]) view_width, view_height = canvas_size minp, maxp = element_groups.get_position_range() # compute a minimum camera distance, that is guaranteed to encapsulate all elements camera_dist = maxp[2] + sqrt(maxp[0]**2 + maxp[1]**2) / tan( radians(camera_fov / 2)) camera = pjs.PerspectiveCamera( fov=camera_fov, position=[0, 0, camera_dist], aspect=view_width / view_height, zoom=zoom, ) scene.add([camera]) ambient_light = pjs.AmbientLight(color="lightgray") key_elements["ambient_light"] = ambient_light direct_light = pjs.DirectionalLight(position=(maxp * 2).tolist()) key_elements["direct_light"] = direct_light scene.add([camera, ambient_light, direct_light]) camera_control = pjs.OrbitControls(controlling=camera, screenSpacePanning=True) atom_picker = pjs.Picker(controlling=group_atoms, event="dblclick") key_elements["atom_picker"] = atom_picker material = pjs.SpriteMaterial( map=create_arrow_texture(right=False), transparent=True, depthWrite=False, depthTest=False, ) atom_pointer = pjs.Sprite(material=material, scale=(4, 3, 1), visible=False) scene.add(atom_pointer) key_elements["atom_pointer"] = atom_pointer renderer = pjs.Renderer( camera=camera, scene=scene, controls=[camera_control, atom_picker], width=view_width, height=view_height, alpha=True, clearOpacity=background_opacity, clearColor=background_color, ) return renderer, key_elements