def virtualHelixMesh(pos1, pos2, radius: float = 1.0, color: str = 'red') -> Mesh: pos1 = np.array(pos1, dtype=float) pos2 = np.array(pos2, dtype=float) delta = pos2 - pos1 dist = np.linalg.norm(delta) c_geometry = CylinderBufferGeometry(radiusTop=radius, radiusBottom=radius, height=dist, radiusSegments=16) # print(c_geometry.height) v2 = normalize(delta) # NOTE default direction in Three.js is the Y direction for geometry v1 = np.array((0, 1, 0)) # Calculate angle between Vectors angle = math.atan2(np.dot(np.cross(v1, v2), v1), np.dot(v1, v2)) normal_vec = normalize(np.cross(v1, v2)).tolist() mid_point = (pos2 + pos1) / 2 xAxis = [mid_point[0], 0, 0] yAxis = [0, mid_point[1], 0] zAxis = [0, 0, mid_point[2]] mesh = Mesh(geometry=c_geometry, material=MeshLambertMaterial(color=color)) rotm = rotationMatrix(v2) mesh.setRotationFromMatrix(threeMatrix(rotm)) mesh.position = mid_point.tolist() return mesh, mid_point
def __init__(self, color='pink', transparent=False): Mesh.__init__(self, geometry=PlaneGeometry(), material=MeshBasicMaterial(color=color)) self.material.side = 'DoubleSide' if transparent: self.material.transparent = transparent self.material.opacity = 0.5
def __init__(self, p1, p2, p3, color='yellow'): geometry = BufferGeometry( attributes={ 'position': BufferAttribute(np.vstack(( p1, p2, p3)).reshape(3, 3).astype(np.float32), normalized=False) }) material = MeshBasicMaterial(color=color) material.side = 'DoubleSide' Mesh.__init__(self, geometry=geometry, material=material)
def create_face_geom( coord_vals: SoCoordValsListType, face_indices: SoIndicesListType, face_color: SoVectorType, transparency: float, translation: SoVectorType = None, quaternion: SoQuaternionType = None ) -> ThreeJSSceneGraphObjectListType: """ Returns a pythreejs `Mesh` object that consists of the faces given by face_indices and the coord_vals. Additionally the attributes `Mesh.default_material` and `Mesh.geometry.default_color` will be set before returning the Mesh. Those attributes contain references to the default colors and materials that are set in this function to restore later changes. """ vertices = np.asarray(coord_vals, dtype='float32') faces = np.asarray(face_indices, dtype='uint16') normals = compute_normals(faces, vertices) faces = faces.ravel() vertexcolors = np.asarray([face_color] * len(coord_vals), dtype='float32') face_geometry = BufferGeometry( attributes=dict(position=BufferAttribute(vertices, normalized=False), index=BufferAttribute(faces, normalized=False), normal=BufferAttribute(normals, normalized=False), color=BufferAttribute(vertexcolors, normalized=False))) # this is used for returning to original state after highlighting face_geometry.default_color = vertexcolors # BUG: This is a bug in pythreejs and currently does not work #faceGeometry.exec_three_obj_method('computeFaceNormals') #faceGeometry.exec_three_obj_method('computeVertexNormals') col = so_col_to_hex(face_color) material = MeshPhongMaterial(color=col, transparency=transparency, depthTest=True, depthWrite=True, metalness=0) object_mesh = Mesh( geometry=face_geometry, material=material, position=[0, 0, 0] # Center the cube ) object_mesh.default_material = material if quaternion: object_mesh.quaternion = quaternion if translation: object_mesh.position = translation return [object_mesh]
def convert_object_to_pythreejs(object): """ Cases for the conversion :return: """ obs = [] if object['type'] == 'spheres': for ipos in object['positions']: obj3d = Mesh(geometry=SphereBufferGeometry(radius=object['radius'], widthSegments=32, heightSegments=16), material=MeshLambertMaterial(color=object["color"]), position=ipos) obs.append(obj3d) elif object['type'] == 'cylinders': for ipos in object['positionPairs']: obj3d = _get_cylinder_from_vec(ipos[0], ipos[1], color=object['color']) obs.append(obj3d) elif object['type'] == 'lines': for ipos, jpos in zip(object['positions'][::2], object['positions'][1::2]): obj3d = _get_line_from_vec(ipos, jpos) obs.append(obj3d) return obs
def convert_object_to_pythreejs(object): """ Cases for the conversion :return: """ obs = [] if object["type"] == "spheres": for ipos in object["positions"]: obj3d = Mesh( geometry=SphereBufferGeometry(radius=object["radius"], widthSegments=32, heightSegments=16), material=MeshLambertMaterial(color=object["color"]), position=ipos, ) obs.append(obj3d) elif object["type"] == "cylinders": for ipos in object["positionPairs"]: obj3d = _get_cylinder_from_vec(ipos[0], ipos[1], color=object["color"]) obs.append(obj3d) elif object["type"] == "lines": for ipos, jpos in zip(object["positions"][::2], object["positions"][1::2]): obj3d = _get_line_from_vec(ipos, jpos) obs.append(obj3d) else: warnings.warn( f"Primitive type {object['type']} has not been implemented for this renderer." ) return obs
def faces_to_mesh(name, vertices, faces, colors, opacity=None): """ :param name: :param vertices: :param faces: :param colors: :param opacity: :return: """ geometry = BufferGeometry(attributes=dict( position=BufferAttribute(vertices, normalized=False), index=BufferAttribute(faces, normalized=False), color=BufferAttribute(colors), )) mat_atts = dict(vertexColors="VertexColors", side="DoubleSide") if opacity is not None: mat_atts["opacity"] = opacity mat_atts["transparent"] = True material = MeshBasicMaterial(**mat_atts) mesh = Mesh( name=name, geometry=geometry, material=material, ) return mesh
def animate_mesh(mesh, norm_displ_verts, displ_time, displ_magn): from pythreejs import ( AnimationAction, AnimationClip, AnimationMixer, NumberKeyframeTrack, ) mesh.morphAttributes = { "position": [ BufferAttribute(norm_displ_verts), ] } morphed_mesh = Mesh( mesh, MeshPhongMaterial(color="#ff3333", shininess=150, morphTargets=True)) pill_track = NumberKeyframeTrack(name=".morphTargetInfluences[0]", times=displ_time, values=displ_magn) pill_clip = AnimationClip(tracks=[pill_track]) pill_action = AnimationAction(AnimationMixer(morphed_mesh), pill_clip, morphed_mesh) return pill_action
def pseudomaterial_render(atoms): c = cm.get_cmap("plasma") scale_axis_vertices = [[-1, -1, -1], [9, -1, -1]] scale_line_geom = Geometry(vertices=scale_axis_vertices, colors=['black'] * len(scale_axis_vertices)) scale_lines = Line( geometry=scale_line_geom, material=LineBasicMaterial(linewidth=50, vertexColors='VertexColors'), type='LinePieces', ) a = atoms.a[1] cube_vertices = [[0, 0, 0], [a, 0, 0], [a, 0, a], [a, 0, 0], [a, a, 0], [a, a, a], [a, a, 0], [0, a, 0], [0, a, a], [0, a, 0], [0, 0, 0], [0, 0, a], [a, 0, a], [a, a, a], [0, a, a], [0, 0, a]] linesgeom = Geometry(vertices=cube_vertices, colors=['black'] * len(cube_vertices)) lines = Line( geometry=linesgeom, material=LineBasicMaterial(linewidth=5, vertexColors='VertexColors'), type='LinePieces', ) balls = [] for p in atoms.itertuples(): positions = (p.x * p.a, p.y * p.a, p.z * p.a) new_ball = Mesh( geometry=SphereGeometry(radius=p.sigma, widthSegments=32, heightSegments=24), material=MeshLambertMaterial(color=rgb2hex(c(p.epsilon_norm))), position=positions) balls.append(new_ball) # [scale*2*a,scale*2*a,scale*2*a] camera = PerspectiveCamera(position=[25, a, a], up=[0, 1, 0], children=[ DirectionalLight(color='white', position=[3, 5, 1], intensity=0.5) ]) scene = Scene(children=[ scale_lines, lines, *balls, camera, AmbientLight(color='#777') ]) renderer = Renderer( camera=camera, scene=scene, controls=[OrbitControls(controlling=camera, target=[a, a, a])]) return renderer
def __init__( self, image: Union[str, np.ndarray], center_pos: Coord3 = (0, 0, 0), rotation: Coord3 = (0, 0, 0), width: float = 10, height: float = 10, *args, **kwargs ): """ Plot an image as a plane. Arguments: image (Union[str, np.ndarray]): The image to plot. This can be a URL, a blob, or a numpy array. If it is a numpy array, it must be either 2D (greyscale), 3D (RGB), or 4D (RGBA). center_pos: Center pos rotation: Rotation of the img plane, in radians width (float: 10): The width of the final rendered plane height (float: 10): The height of the final rendered plane """ super().__init__(*args, **kwargs) if isinstance(image, str): tex = ImageTexture(imageUri=image) elif isinstance(image, (list, np.ndarray)): if len(image.shape) == 2: # handle 2D (grayscale). # broadcast to rgba channels: image = np.array([image, image, image, np.ones(image.shape)]).T elif image.shape[-1] == 3: # handle RGB: image = np.dstack([image, np.ones(image.shape)]) tex = DataTexture(data=image.astype(np.float32), type="FloatType") else: raise ValueError(f"Expected string or array, but got {type(image)}") plane = PlaneGeometry(width=width, height=height) mat = MeshBasicMaterial(map=tex) mesh = Mesh(geometry=plane, material=mat, position=center_pos) mesh.rotation = rotation if len(rotation) == 4 else [*rotation, "XYZ"] self.center = center_pos self.size = (width,height) self._objects.append(mesh)
def _get_cylinder_from_vec(v0, v1, radius=0.15, color="#FFFFFF"): v0 = np.array(v0) v1 = np.array(v1) vec = v1 - v0 mid_point = (v0 + v1) / 2. rot_vec = np.cross([0, 1, 0], vec) rot_vec_len = np.linalg.norm(rot_vec) rot_vec = rot_vec / rot_vec_len rot_arg = np.arccos(np.dot([0, 1, 0], vec) / np.linalg.norm(vec)) new_bond = Mesh(geometry=CylinderBufferGeometry(radiusTop=radius, radiusBottom=radius, height=np.linalg.norm(v1 - v0), radialSegments=12, heightSegments=10), material=MeshLambertMaterial(color=color), position=tuple(mid_point)) rot = R.from_rotvec(rot_arg * rot_vec) new_bond.quaternion = tuple(rot.as_quat()) return new_bond
def virtualHelixMesh( pos1, pos2, radius: float = 1.0, color: str = 'red') -> Mesh: pos1 = np.array(pos1, dtype=float) pos2 = np.array(pos2, dtype=float) delta = pos2 - pos1 dist = np.linalg.norm(delta) c_geometry = CylinderBufferGeometry( radiusTop=radius, radiusBottom=radius, height=dist, radiusSegments=16 ) # print(c_geometry.height) v2 = normalize(delta) # NOTE default direction in Three.js is the Y direction for geometry v1 = np.array((0, 1, 0)) # Calculate angle between Vectors angle = math.atan2( np.dot(np.cross(v1, v2), v1), np.dot(v1, v2) ) normal_vec = normalize(np.cross(v1, v2)).tolist() mid_point = (pos2 + pos1) / 2 xAxis = [mid_point[0], 0, 0] yAxis = [0, mid_point[1], 0] zAxis = [0, 0, mid_point[2]] mesh = Mesh(geometry=c_geometry, material=MeshLambertMaterial(color=color)) rotm = rotationMatrix(v2) mesh.setRotationFromMatrix(threeMatrix(rotm)) mesh.position = mid_point.tolist() return mesh, mid_point
def _get_cylinder_from_vec(v0, v1, d_args=None): """Draw the cylinder given the two endpoints. Args: v0 (list): one endpoint of line v1 (list): other endpoint of line d_args (dict): properties of the line (line_width and color) Returns: Mesh: Pythreejs object that displays the cylinders """ obj_args = update_object_args(d_args, "Cylinders", ['radius', 'color']) v0 = np.array(v0) v1 = np.array(v1) vec = v1 - v0 mid_point = (v0 + v1) / 2.0 rot_vec = np.cross([0, 1, 0], vec) rot_vec_len = np.linalg.norm(rot_vec) rot_vec = rot_vec / rot_vec_len rot_arg = np.arccos(np.dot([0, 1, 0], vec) / np.linalg.norm(vec)) new_bond = Mesh( geometry=CylinderBufferGeometry( radiusTop=obj_args['radius'], radiusBottom=obj_args['radius'], height=np.linalg.norm(v1 - v0), radialSegments=12, heightSegments=10, ), material=MeshLambertMaterial(color=obj_args['color']), position=tuple(mid_point), ) rot = R.from_rotvec(rot_arg * rot_vec) quat = tuple(rot.as_quat()) if any(isnan(itr_q) for itr_q in quat): new_bond.quaternion = (0, 0, 0, 0) else: new_bond.quaternion = quat return new_bond
def _load_mesh(self, dae, scale): materials = self._load_material(dae) for geometry in dae.geometries: for primitive in geometry.primitives: vertices = primitive.vertex[primitive.vertex_index] * scale normals = primitive.normal[primitive.normal_index] buffer_geometry = BufferGeometry( attributes={ 'position': BufferAttribute(array=vertices), 'normal': BufferAttribute(array=normals) }) material = materials[primitive.material] mesh = Mesh(geometry=buffer_geometry, material=material) self.add(mesh)
def occ_shape_to_threejs(self, shp: TopoDS_Shape, shape_color, edge_color, transparency, opacity): # first, compute the tesselation from .renderer_occ import occ_shape_to_faces from .threejs_utils import create_material np_vertices, np_faces, np_normals, edges = occ_shape_to_faces( shp, self.quality, self.render_edges, self.parallel) # set geometry properties buffer_geometry_properties = { "position": BufferAttribute(np_vertices), "index": BufferAttribute(np_faces), } if self.compute_normals_mode == NORMAL.SERVER_SIDE: if np_normals.shape != np_vertices.shape: raise AssertionError("Wrong number of normals/shapes") buffer_geometry_properties["normal"] = BufferAttribute(np_normals) # build a BufferGeometry instance shape_geometry = BufferGeometry(attributes=buffer_geometry_properties) # if the client has to render normals, add the related js instructions if self.compute_normals_mode == NORMAL.CLIENT_SIDE: shape_geometry.exec_three_obj_method("computeVertexNormals") # then a default material shp_material = create_material(shape_color, transparent=transparency, opacity=opacity) # and to the dict of shapes, to have a mapping between meshes and shapes mesh_id = "%s" % uuid.uuid4().hex self.mesh_id = mesh_id # finally create the mesh shape_mesh = Mesh(geometry=shape_geometry, material=shp_material, name=mesh_id) # edge rendering, if set to True if self.render_edges: edge_list = flatten(list(map(explode, edges))) lines = LineSegmentsGeometry(positions=edge_list) mat = LineMaterial(linewidth=1, color=edge_color) edge_lines = LineSegments2(lines, mat, name=mesh_id) else: edge_lines = None return shape_mesh, edge_lines
def _get_spheres(ctk_scene, d_args=None): if ctk_scene.phiEnd and ctk_scene.phiStart: phi_length = ctk_scene.phiEnd - ctk_scene.phiStart else: phi_length = np.pi * 2 return [ Mesh( geometry=SphereBufferGeometry( radius=ctk_scene.radius, phiStart=ctk_scene.phiStart or 0, phiLength=phi_length, widthSegments=32, heightSegments=32, ), material=MeshLambertMaterial(color=ctk_scene.color), position=tuple(ipos), ) for ipos in ctk_scene.positions ]
def _get_surface_from_positions(positions, d_args, draw_edges=False): # get defaults obj_args = update_object_args(d_args, "Surfaces", ["color", "opacity"]) num_triangle = len(positions) / 3.0 assert num_triangle.is_integer() # make decision on transparency if obj_args["opacity"] > 0.99: transparent = False else: transparent = True num_triangle = int(num_triangle) index_list = [[itr * 3, itr * 3 + 1, itr * 3 + 2] for itr in range(num_triangle)] # Vertex ositions as a list of lists surf_vertices = BufferAttribute(array=positions, normalized=False) # Indices surf_indices = BufferAttribute(array=np.array(index_list, dtype=np.uint16).ravel(), normalized=False) geometry = BufferGeometry(attributes={ "position": surf_vertices, "index": surf_indices }) new_surface = Mesh( geometry=geometry, material=MeshLambertMaterial( color=obj_args["color"], side="DoubleSide", transparent=transparent, opacity=obj_args["opacity"], ), ) if draw_edges == True: edges = EdgesGeometry(geometry) edges_lines = LineSegments(edges, LineBasicMaterial(color=obj_args["color"])) return new_surface, edges_lines else: return new_surface, None
def __init__(self, color='red', radius=0.025): Mesh.__init__(self, geometry=SphereGeometry(radius=radius), material=MeshLambertMaterial(color=color))
def AddShapeToScene( self, shp, shape_color=None, # the default render_edges=False, edge_color=None, vertex_color=None, quality=1.0, transparency=False, opacity=1.0, ): # first, compute the tesselation tess = ShapeTesselator(shp) tess.Compute(compute_edges=render_edges, mesh_quality=quality, parallel=True) # get vertices and normals vertices_position = tess.GetVerticesPositionAsTuple() number_of_triangles = tess.ObjGetTriangleCount() number_of_vertices = len(vertices_position) # number of vertices should be a multiple of 3 if number_of_vertices % 3 != 0: raise AssertionError("Wrong number of vertices") if number_of_triangles * 9 != number_of_vertices: raise AssertionError("Wrong number of triangles") # then we build the vertex and faces collections as numpy ndarrays np_vertices = np.array(vertices_position, dtype="float32").reshape( int(number_of_vertices / 3), 3) # Note: np_faces is just [0, 1, 2, 3, 4, 5, ...], thus arange is used np_faces = np.arange(np_vertices.shape[0], dtype="uint32") # set geometry properties buffer_geometry_properties = { "position": BufferAttribute(np_vertices), "index": BufferAttribute(np_faces), } if self._compute_normals_mode == NORMAL.SERVER_SIDE: # get the normal list, converts to a numpy ndarray. This should not raise # any issue, since normals have been computed by the server, and are available # as a list of floats np_normals = np.array(tess.GetNormalsAsTuple(), dtype="float32").reshape(-1, 3) # quick check if np_normals.shape != np_vertices.shape: raise AssertionError("Wrong number of normals/shapes") buffer_geometry_properties["normal"] = BufferAttribute(np_normals) # build a BufferGeometry instance shape_geometry = BufferGeometry(attributes=buffer_geometry_properties) # if the client has to render normals, add the related js instructions if self._compute_normals_mode == NORMAL.CLIENT_SIDE: shape_geometry.exec_three_obj_method("computeVertexNormals") # then a default material shp_material = self._material(shape_color, transparent=transparency, opacity=opacity) # and to the dict of shapes, to have a mapping between meshes and shapes mesh_id = "%s" % uuid.uuid4().hex self._shapes[mesh_id] = shp # finally create the mesh shape_mesh = Mesh(geometry=shape_geometry, material=shp_material, name=mesh_id) # edge rendering, if set to True if render_edges: edges = list( map( lambda i_edge: [ tess.GetEdgeVertex(i_edge, i_vert) for i_vert in range(tess.ObjEdgeGetVertexCount(i_edge)) ], range(tess.ObjGetEdgeCount()), )) edge_list = _flatten(list(map(_explode, edges))) lines = LineSegmentsGeometry(positions=edge_list) mat = LineMaterial(linewidth=1, color=edge_color) edge_lines = LineSegments2(lines, mat, name=mesh_id) self._displayed_non_pickable_objects.add(edge_lines) return shape_mesh
""" Link up the StructureMoleculeComponent objects to pythreejs Also includes some helper functions for draw addition objects using pythreejs """ from pythreejs import MeshLambertMaterial, Mesh, SphereBufferGeometry, CylinderBufferGeometry, Object3D, LineSegments2, LineSegmentsGeometry, LineMaterial, Scene, AmbientLight, Renderer, OrbitControls, OrthographicCamera, DirectionalLight from crystal_toolkit.components.structure import StructureMoleculeComponent from IPython.display import display from scipy.spatial.transform import Rotation as R import numpy as np ball = Mesh(geometry=SphereBufferGeometry(radius=1, widthSegments=32, heightSegments=16), material=MeshLambertMaterial(color='red'), position=[0, 1, 0]) def traverse_scene_object(scene_data, parent=None): """ Recursivesly populate a scene object with tree of children :param scene_data: :param parent: :return: """ for sub_object in scene_data["contents"]: if "type" in sub_object.keys(): parent.add(convert_object_to_pythreejs(sub_object)) else: new_parent = Object3D(name=sub_object["name"]) if parent is None: parent = new_parent
def visualize(self): """Start the visualization and initialize widgets""" from pythreejs import PlainGeometry, Mesh, LambertMaterial, \ PhongMaterial, DirectionalLight, \ PerspectiveCamera, Scene, AmbientLight, \ Renderer, OrbitControls, Line, \ LineBasicMaterial, BoxGeometry, make_text from matplotlib import colors from matplotlib import cm from IPython.display import display import numpy as np import ipywidgets as widgets bbox = self._compute_bounding_box() diam = bbox[1, :] - bbox[0, :] center = .5 * _np.sum(bbox, axis=0) xmin, ymin, zmin = bbox[0, :] xmax, ymax, zmax = bbox[1, :] position = (center + 2.5 * diam).tolist() # Generate coordinate system base box = PlainGeometry( vertices=_np.array([[xmin, ymin, zmin], [xmax, ymin, zmin], [xmin, ymin, zmin], [xmin, ymax, zmin], [xmin, ymin, zmin], [xmin, ymin, zmax]]), colors=['red', 'red', 'green', 'green', 'blue', 'blue']) self._coord_box = Line(geometry=box, material=LineBasicMaterial( linewidth=10, vertexColors='VertexColors'), type='LinePieces') if self.data is not None: vis_data = self.data if not self.is_real: vis_data = _np.real(self.data) if not self.is_scalar: vis_data = _np.sum(_np.abs(self.data)**2, axis=2) self._vis_data = vis_data self._cmap = cm.jet self._vmin = vis_data.min() self._vmax = vis_data.max() cnorm = colors.Normalize(vmin=self._vmin, vmax=self._vmax) facecolors = self._convert_data_to_colors(self._cmap, cnorm, vis_data) else: facecolors = self.elements.shape[0] * [ 3 * [_np.array([1., 1., 1.])] ] self._geom = PlainGeometry(vertices=self.vertices, faces=self.elements, faceColors=facecolors) self._mesh = Mesh( geometry=self._geom, material=LambertMaterial(vertexColors='VertexColors')) self._wireframe = Mesh(geometry=self._geom, material=PhongMaterial(wireframe=True, color='black')) light = DirectionalLight(color='white', position=position, intensity=0.5) camera = PerspectiveCamera(position=position, fov=20) self._scene = Scene(children=[ self._coord_box, self._mesh, self._wireframe, AmbientLight(color='white') ]) self._renderer = Renderer(camera=camera, background='white', background_opacity=1, scene=self._scene, controls=[OrbitControls(controlling=camera)]) self._axes_info = widgets.Label(value="x: red; y: green; z: blue") coord_system_toggle = widgets.Checkbox( value=self._coord_box.visible, description='Show coordinate axes', disabled=False) coord_system_toggle.observe(self.on_toggle_coord_system, names='value') if self.data is not None: # Enable/Disable wireframe wireframe_toggle = widgets.Checkbox(value=self._wireframe.visible, description='Enable wireframe', disabled=False) wireframe_toggle.observe(self.on_toggle_wireframe, names='value') # Change vmin/vmax vmin_box = widgets.FloatText(value=self._vmin, description='vmin:', disabled=False) vmin_box.observe(self.on_change_vmin, names='value') vmax_box = widgets.FloatText(value=self._vmax, description='vmax:', disabled=False) vmax_box.observe(self.on_change_vmax, names='value') vmin_info = widgets.Label( value='Lower bound: {0}'.format(self._vmin)) vmax_info = widgets.Label( value='Upper bound: {0}'.format(self._vmax)) range_info_box = widgets.VBox([vmin_info, vmax_info]) range_change = widgets.HBox([vmin_box, vmax_box]) toggles = widgets.HBox([wireframe_toggle, coord_system_toggle]) vbox = widgets.VBox( [self._axes_info, range_info_box, range_change, toggles]) display(self._renderer, vbox) else: display(self._renderer, self._axes_info, coord_system_toggle)
def _render_shape(self, shape_index, shape=None, edges=None, vertices=None, mesh_color=None, edge_color=None, vertex_color=None, render_edges=False, edge_width=1, vertex_width=5, deflection=0.05, transparent=False, opacity=1.0): edge_list = None edge_lines = None points = None shape_mesh = None if shape is not None: if mesh_color is None: mesh_color = self.default_mesh_color if edge_color is None: edge_color = self.default_edge_color if vertex_color is None: vertex_color = self.default_edge_color # same as edge_color # BEGIN copy # The next lines are copied with light modifications from # https://github.com/tpaviot/pythonocc-core/blob/master/src/Display/WebGl/jupyter_renderer.py # first, compute the tesselation tess = Tesselator(shape) tess.Compute(uv_coords=False, compute_edges=render_edges, mesh_quality=self.quality, parallel=True) # get vertices and normals vertices_position = tess.GetVerticesPositionAsTuple() number_of_triangles = tess.ObjGetTriangleCount() number_of_vertices = len(vertices_position) # number of vertices should be a multiple of 3 if number_of_vertices % 3 != 0: raise AssertionError("Wrong number of vertices") if number_of_triangles * 9 != number_of_vertices: raise AssertionError("Wrong number of triangles") # then we build the vertex and faces collections as numpy ndarrays np_vertices = np.array(vertices_position, dtype='float32')\ .reshape(int(number_of_vertices / 3), 3) # Note: np_faces is just [0, 1, 2, 3, 4, 5, ...], thus arange is used np_faces = np.arange(np_vertices.shape[0], dtype='uint32') # compute normals np_normals = np.array(tess.GetNormalsAsTuple(), dtype='float32').reshape(-1, 3) if np_normals.shape != np_vertices.shape: raise AssertionError("Wrong number of normals/shapes") # build a BufferGeometry instance shape_geometry = BufferGeometry( attributes={ 'position': BufferAttribute(np_vertices), 'index': BufferAttribute(np_faces), 'normal': BufferAttribute(np_normals) }) shp_material = self._material(mesh_color, transparent=True, opacity=opacity) shape_mesh = Mesh(geometry=shape_geometry, material=shp_material, name="mesh_%d" % shape_index) if render_edges: edge_list = list( map( lambda i_edge: [ tess.GetEdgeVertex(i_edge, i_vert) for i_vert in range( tess.ObjEdgeGetVertexCount(i_edge)) ], range(tess.ObjGetEdgeCount()))) # END copy if vertices is not None: vertices_list = [] for vertex in vertices: p = BRep_Tool.Pnt(vertex) vertices_list.append((p.X(), p.Y(), p.Z())) vertices_list = np.array(vertices_list, dtype=np.float32) attributes = { "position": BufferAttribute(vertices_list, normalized=False) } mat = PointsMaterial(color=vertex_color, sizeAttenuation=False, size=vertex_width) geom = BufferGeometry(attributes=attributes) points = Points(geometry=geom, material=mat) if edges is not None: edge_list = [discretize_edge(edge, deflection) for edge in edges] if edge_list is not None: edge_list = _flatten(list(map(_explode, edge_list))) lines = LineSegmentsGeometry(positions=edge_list) mat = LineMaterial(linewidth=edge_width, color=edge_color) edge_lines = LineSegments2(lines, mat, name="edges_%d" % shape_index) if shape_mesh is not None or edge_lines is not None or points is not None: index_mapping = {"mesh": None, "edges": None, "shape": shape_index} if shape_mesh is not None: ind = len(self.pickable_objects.children) self.pickable_objects.add(shape_mesh) index_mapping["mesh"] = ind if edge_lines is not None: ind = len(self.pickable_objects.children) self.pickable_objects.add(edge_lines) index_mapping["edges"] = ind if points is not None: ind = len(self.pickable_objects.children) self.pickable_objects.add(points) index_mapping["mesh"] = ind self.pick_mapping.append(index_mapping)
def __init__(self, mesh: trimesh.Trimesh = None, obj: str = None, normalize: bool =False, color: Union[str,ColorRGB] ="#00bbee", alpha: float=1., transform: Union[Callable, None] = None, *args, **kwargs ): """ Add a mesh to the scene. Arguments: mesh: Mesh object, with attributes verticies, faces obj: object filename normalize : Normalize the coordinates of the vertices to be between -1 and 1 color: Color for the mesh alpha: transparency of the mesh transform: a function to transform the vertices """ if mesh is not None and obj is not None: raise ValueError('Received both mesh and obj') if isinstance(mesh, str): # perhaps this is a filename? try: mesh = trimesh.load(args[0]) except Exception as e: raise ValueError( "Did not understand arguments to method Figure#mesh" ) from e if isinstance(obj, np.ndarray): obj_data = obj elif isinstance(obj, list): obj_data = np.asarray(obj) # Do something with this obj_data? elif isinstance(obj, str): if "\n" in obj: # this is the file contents. raise NotImplementedError() else: try: # open the mesh file mesh = trimesh.load(obj) except Exception as e: raise ValueError("Could not read file as OBJ") from e assert hasattr(mesh, "vertices") and hasattr(mesh, "faces"), "Invalid mesh object" if mesh is None: raise ValueError("Could not understand how to parse mesh.") if transform is None: transform = lambda x: x verts = transform(mesh.vertices) faces = mesh.faces if normalize: # Normalize the vertex indices to be between -1,1 # Shifting these does change the coordinate system, # so visualizing multiple meshes won't work verts[:, 0] = _normalize_shift(verts[:, 0]) verts[:, 1] = _normalize_shift(verts[:, 1]) verts[:, 2] = _normalize_shift(verts[:, 2]) geo = BufferGeometry( attributes={ "position": BufferAttribute( array=verts.astype("float32"), normalized=False, # dtype=np.float64 ), "index": BufferAttribute( array=faces.astype("uint64").ravel(), normalized=False, # dtype=np.float64, ), } ) self._coords = verts transparent = alpha != 1. mat = MeshLambertMaterial(color=color, opacity=alpha, transparent=transparent) mesh = Mesh(geometry=geo, material=mat) geo.exec_three_obj_method("computeVertexNormals") super().__init__(*args, **kwargs) self._objects.append(mesh)
def DisplayMesh(self, mesh, color=default_mesh_color): """ Display a MEFISTO2 triangle mesh """ if not HAVE_SMESH: print("SMESH not installed, DisplayMesh method unavailable.") return if not isinstance(mesh, SMESH_Mesh): raise AssertionError("You mush provide an SMESH_Mesh instance") mesh_ds = mesh.GetMeshDS() # the mesh data source face_iter = mesh_ds.facesIterator() # vertices positions are stored to a liste vertices_position = [] for _ in range(mesh_ds.NbFaces()-1): face = face_iter.next() #print('Face %i, type %i' % (i, face.GetType())) #print(dir(face)) # if face.GetType == 3 : triangle mesh, then 3 nodes for j in range(3): node = face.GetNode(j) #print('Coordinates of node %i:(%f,%f,%f)'%(i, node.X(), node.Y(), node.Z())) vertices_position.append(node.X()) vertices_position.append(node.Y()) vertices_position.append(node.Z()) number_of_vertices = len(vertices_position) # then we build the vertex and faces collections as numpy ndarrays np_vertices = np.array(vertices_position, dtype='float32').reshape(int(number_of_vertices / 3), 3) # Note: np_faces is just [0, 1, 2, 3, 4, 5, ...], thus arange is used np_faces = np.arange(np_vertices.shape[0], dtype='uint32') # set geometry properties buffer_geometry_properties = {'position': BufferAttribute(np_vertices), 'index' : BufferAttribute(np_faces)} # build a BufferGeometry instance mesh_geometry = BufferGeometry(attributes=buffer_geometry_properties) mesh_geometry.exec_three_obj_method('computeVertexNormals') # then a default material mesh_material = MeshPhongMaterial(color=color, polygonOffset=True, polygonOffsetFactor=1, polygonOffsetUnits=1, shininess=0.5, wireframe=False, side='DoubleSide') edges_material = MeshPhongMaterial(color='black', polygonOffset=True, polygonOffsetFactor=1, polygonOffsetUnits=1, shininess=0.5, wireframe=True) # create a mesh unique id mesh_id = uuid.uuid4().hex # finally create the mash shape_mesh = Mesh(geometry=mesh_geometry, material=mesh_material, name=mesh_id) edges_mesh = Mesh(geometry=mesh_geometry, material=edges_material, name=mesh_id) # a special display for the mesh camera_target = [0., 0., 0.] # the point to look at camera_position = [0, 0., 100.] # the camera initial position camera = PerspectiveCamera(position=camera_position, lookAt=camera_target, up=[0, 0, 1], fov=50, children=[DirectionalLight(color='#ffffff', position=[50, 50, 50], intensity=0.9)]) scene_shp = Scene(children=[shape_mesh, edges_mesh, camera, AmbientLight(color='#101010')]) renderer = Renderer(camera=camera, background=self._background, background_opacity=self._background_opacity, scene=scene_shp, controls=[OrbitControls(controlling=camera, target=camera_target)], width=self._size[0], height=self._size[1], antialias=True) display(renderer)
def _render_shape( self, shape_index, shape=None, edges=None, vertices=None, mesh_color=None, edge_color=None, vertex_color=None, render_edges=False, edge_width=1, vertex_width=5, transparent=False, opacity=1.0, ): edge_list = None edge_lines = None points = None shape_mesh = None start_render_time = self._start_timer() if shape is not None: if mesh_color is None: mesh_color = self.default_mesh_color if edge_color is None: edge_color = self.default_edge_color if vertex_color is None: vertex_color = self.default_edge_color # same as edge_color # Compute the tesselation start_tesselation_time = self._start_timer() np_vertices, np_triangles, np_normals = tessellate( shape, self.quality, self.angular_tolerance ) if np_normals.shape != np_vertices.shape: raise AssertionError("Wrong number of normals/shapes") self._stop_timer("tesselation time", start_tesselation_time) # build a BufferGeometry instance shape_geometry = BufferGeometry( attributes={ "position": BufferAttribute(np_vertices), "index": BufferAttribute(np_triangles.ravel()), "normal": BufferAttribute(np_normals), } ) shp_material = self._material(mesh_color, transparent=True, opacity=opacity) shape_mesh = Mesh( geometry=shape_geometry, material=shp_material, name="mesh_%d" % shape_index ) if render_edges: edges = get_edges(shape) if vertices is not None: vertices_list = [] for vertex in vertices: vertices_list.append(get_point(vertex)) vertices_list = np.array(vertices_list, dtype=np.float32) attributes = {"position": BufferAttribute(vertices_list, normalized=False)} mat = PointsMaterial(color=vertex_color, sizeAttenuation=False, size=vertex_width) geom = BufferGeometry(attributes=attributes) points = Points(geometry=geom, material=mat) if edges is not None: start_discretize_time = self._start_timer() edge_list = [discretize_edge(edge, self.edge_accuracy) for edge in edges] self._stop_timer("discretize time", start_discretize_time) if edge_list is not None: edge_list = flatten(list(map(explode, edge_list))) lines = LineSegmentsGeometry(positions=edge_list) mat = LineMaterial(linewidth=edge_width, color=edge_color) edge_lines = LineSegments2(lines, mat, name="edges_%d" % shape_index) if shape_mesh is not None or edge_lines is not None or points is not None: index_mapping = {"mesh": None, "edges": None, "shape": shape_index} if shape_mesh is not None: ind = len(self.pickable_objects.children) self.pickable_objects.add(shape_mesh) index_mapping["mesh"] = ind if edge_lines is not None: ind = len(self.pickable_objects.children) self.pickable_objects.add(edge_lines) index_mapping["edges"] = ind if points is not None: ind = len(self.pickable_objects.children) self.pickable_objects.add(points) index_mapping["mesh"] = ind self.pick_mapping.append(index_mapping) self._stop_timer("shape render time", start_render_time)
def AddShapeToScene(self, shp, # the TopoDS_Shape to be displayed shape_color=default_shape_color, # the default render_edges=False, edge_color=default_edge_color, compute_uv_coords=False, quality=1.0, transparency=False, opacity=1.): # first, compute the tesselation tess = Tesselator(shp) tess.Compute(uv_coords=compute_uv_coords, compute_edges=render_edges, mesh_quality=quality, parallel=self._parallel) # get vertices and normals vertices_position = tess.GetVerticesPositionAsTuple() number_of_triangles = tess.ObjGetTriangleCount() number_of_vertices = len(vertices_position) # number of vertices should be a multiple of 3 if number_of_vertices % 3 != 0: raise AssertionError("Wrong number of vertices") if number_of_triangles * 9 != number_of_vertices: raise AssertionError("Wrong number of triangles") # then we build the vertex and faces collections as numpy ndarrays np_vertices = np.array(vertices_position, dtype='float32').reshape(int(number_of_vertices / 3), 3) # Note: np_faces is just [0, 1, 2, 3, 4, 5, ...], thus arange is used np_faces = np.arange(np_vertices.shape[0], dtype='uint32') # set geometry properties buffer_geometry_properties = {'position': BufferAttribute(np_vertices), 'index' : BufferAttribute(np_faces)} if self._compute_normals_mode == NORMAL.SERVER_SIDE: # get the normal list, converts to a numpy ndarray. This should not raise # any issue, since normals have been computed by the server, and are available # as a list of floats np_normals = np.array(tess.GetNormalsAsTuple(), dtype='float32').reshape(-1, 3) # quick check if np_normals.shape != np_vertices.shape: raise AssertionError("Wrong number of normals/shapes") buffer_geometry_properties['normal'] = BufferAttribute(np_normals) # build a BufferGeometry instance shape_geometry = BufferGeometry(attributes=buffer_geometry_properties) # if the client has to render normals, add the related js instructions if self._compute_normals_mode == NORMAL.CLIENT_SIDE: shape_geometry.exec_three_obj_method('computeVertexNormals') # then a default material shp_material = self._material(shape_color, transparent=transparency, opacity=opacity) # create a mesh unique id mesh_id = uuid.uuid4().hex # finally create the mash shape_mesh = Mesh(geometry=shape_geometry, material=shp_material, name=mesh_id) # and to the dict of shapes, to have a mapping between meshes and shapes self._shapes[mesh_id] = shp # edge rendering, if set to True edge_lines = None if render_edges: edges = list(map(lambda i_edge: [tess.GetEdgeVertex(i_edge, i_vert) for i_vert in range(tess.ObjEdgeGetVertexCount(i_edge))], range(tess.ObjGetEdgeCount()))) edges = list(filter(lambda edge: len(edge) == 2, edges)) np_edge_vertices = np.array(edges, dtype=np.float32).reshape(-1, 3) np_edge_indices = np.arange(np_edge_vertices.shape[0], dtype=np.uint32) edge_geometry = BufferGeometry(attributes={ 'position': BufferAttribute(np_edge_vertices), 'index' : BufferAttribute(np_edge_indices) }) edge_material = LineBasicMaterial(color=edge_color, linewidth=1) edge_lines = LineSegments(geometry=edge_geometry, material=edge_material) # Add geometries to pickable or non pickable objects self._displayed_pickable_objects.add(shape_mesh) if render_edges: self._displayed_non_pickable_objects.add(edge_lines)