def _renderer_default(self): center = tuple(self.ds.domain_center.in_units("code_length").d) right = tuple((self.ds.domain_right_edge + (self.ds.domain_right_edge - self.ds.domain_center) * 2.0).in_units("code_length").d) camera = pythreejs.PerspectiveCamera( position=right, fov=20, children=[pythreejs.AmbientLight()]) scene = pythreejs.Scene( children=[camera, pythreejs.AmbientLight(color="#dddddd")] + self.grid_views) orbit_control = pythreejs.OrbitControls(controlling=camera) renderer = pythreejs.Renderer( scene=scene, camera=camera, controls=[orbit_control], width=400, height=400, background="black", background_opacity=1, antialias=True, ) camera.lookAt(center) orbit_control.target = center renderer.layout.border = "1px solid darkgrey" return renderer
def view_3js(sheet, coords=['x', 'y', 'z'], **draw_specs): """ Creates a javascript renderer of the edge lines to be displayed in Jupyter Notebooks Returns ------- renderer: a :class:`pythreejs.pythreejs.Renderer` instance lines: a :class:`pythreejs.pythreejs.Line` object Example ------- >>> from IPython import display >>> renderer, lines = view_3js(eptm) >>> display(renderer) """ lines = edge_lines(sheet, coords, **draw_specs) scene = py3js.Scene(children=[ lines, py3js.DirectionalLight(color='#ccaabb', position=[0, 5, 0]), py3js.AmbientLight(color='#cccccc') ]) c = py3js.PerspectiveCamera(position=[0, 5, 5]) renderer = py3js.Renderer(camera=c, scene=scene, controls=[py3js.OrbitControls(controlling=c)]) return renderer, lines
def plot_voxelgrid_with_pythreejs(voxel_centers, voxel_colors, width, height, **kwargs): if pythreejs is None: raise ImportError( "pythreejs is needed for plotting with pythreejs backend.") if display is None: raise ImportError( "IPython is needed for plotting with pythreejs backend.") centroid, camera_position = get_centroid_and_camera_position(voxel_centers) camera = pythreejs.PerspectiveCamera(fov=90, aspect=width / height, position=camera_position, up=[0, 0, 1]) mesh = get_voxelgrid_pythreejs(voxel_centers, voxel_colors) scene = pythreejs.Scene(children=[camera, mesh], background=None) controls = pythreejs.OrbitControls(controlling=camera, target=tuple(centroid)) camera.lookAt(tuple(centroid)) renderer = pythreejs.Renderer(scene=scene, camera=camera, controls=[controls], width=width, height=height) display(renderer)
def _ipython_display_(self): # This needs to actually display, which is not the same as returning a display. cam = pythreejs.PerspectiveCamera( position=[25, 35, 100], fov=20, children=[pythreejs.AmbientLight()], ) children = [cam, pythreejs.AmbientLight(color="#dddddd")] material = pythreejs.MeshBasicMaterial(color="#ff0000", vertexColors="VertexColors", side="DoubleSide") for model in self.components: mesh = pythreejs.Mesh(geometry=model.geometry, material=material, position=[0, 0, 0]) children.append(mesh) scene = pythreejs.Scene(children=children) rendererCube = pythreejs.Renderer( camera=cam, background="white", background_opacity=1, scene=scene, controls=[pythreejs.OrbitControls(controlling=cam)], width=800, height=800, ) return rendererCube
def __init__(self, obj, width=512, height=512, textureMap=None, scalarField=None, vectorField=None): # Note: subclass's constructor should define # self.MeshConstructor and self.isLineMesh, which will # determine how the geometry is interpreted. if (self.isLineMesh is None): self.isLineMesh = False if (self.MeshConstructor is None): self.MeshConstructor = pythreejs.Mesh light = pythreejs.PointLight(color='white', position=[0, 0, 5]) light.intensity = 0.6 self.cam = pythreejs.PerspectiveCamera(position = [0, 0, 5], up = [0, 1, 0], aspect=width / height, children=[light]) self.avoidRedrawFlicker = False self.objects = pythreejs.Group() self.meshes = pythreejs.Group() self.ghostMeshes = pythreejs.Group() # Translucent meshes kept around by preserveExisting self.materialLibrary = MaterialLibrary(self.isLineMesh) # Sometimes we do not use a particular attribute buffer, e.g. the index buffer when displaying # per-face scalar fields. But to avoid reallocating these buffers when # switching away from these cases, we need to preserve the buffers # that may have previously been allocated. This is done with the bufferAttributeStash. # A buffer attribute, if it exists, must always be attached to the # current BufferGeometry or in this stash (but not both!). self.bufferAttributeStash = {} self.currMesh = None # The main mesh being viewed self.wireframeMesh = None # Wireframe for the main visualization mesh self.pointsMesh = None # Points for the main visualization mesh self.vectorFieldMesh = None self.cachedWireframeMaterial = None self.cachedPointsMaterial = None self.objects.add([self.meshes, self.ghostMeshes]) self.shouldShowWireframe = False self.scalarField = None self.vectorField = None self.arrowMaterial = None # Will hold this viewer's instance of the special vector field shader self._arrowSize = 60 # Camera needs to be part of the scene because the scene light is its child # (so that it follows the camera). self.scene = pythreejs.Scene(children=[self.objects, self.cam, pythreejs.AmbientLight(intensity=0.5)]) # Sane trackball controls. self.controls = pythreejs.TrackballControls(controlling=self.cam) self.controls.staticMoving = True self.controls.rotateSpeed = 2.0 self.controls.zoomSpeed = 2.0 self.controls.panSpeed = 1.0 self.renderer = pythreejs.Renderer(camera=self.cam, scene=self.scene, controls=[self.controls], width=width, height=height) self.update(True, obj, updateModelMatrix=True, textureMap=textureMap, scalarField=scalarField, vectorField=vectorField)
def _default_camera(self): # see https://github.com/maartenbreddels/ipyvolume/pull/40 for an explanation z = 2 * np.tan(45.0 / 2.0 * np.pi / 180) / np.tan( self.camera_fov / 2.0 * np.pi / 180) return pythreejs.PerspectiveCamera(fov=self.camera_fov, position=(0, 0, z), width=400, height=500)
def __initialize_camera(self, width, height): camera_target = np.mean([drawable.center for drawable in self.drawables], axis=0) camera_position = tuple(camera_target + [0, 0, np.mean([drawable.scale for drawable in self.drawables])]) directional_light = three.DirectionalLight(color = '#ffffff', position = [0, 10, 0], intensity = 1) camera = three.PerspectiveCamera( position=camera_position, aspect=width/height, lookAt=camera_target, fov=50, near=.1, far=20000, children=[directional_light] ) return camera
def __init__(self, width=600, height=400): light = p3js.DirectionalLight(color='#ffffff', position=[0, 0, 1], intensity=0.5) self.camera = p3js.PerspectiveCamera(position=[2.0, 5.0, 2.0], fov=50, children=[light], aspect=width/float(height)) self.camera.up = (0.0, 0.0, 1.0) self.width = 600 self.height = 400 self.geometry = [] self.draw_axes(size=1)
def get_camera_pythreejs(centroid, xyz, width, height): camera = pythreejs.PerspectiveCamera( fov=90, aspect=width / height, position=tuple(centroid + [0, abs(xyz.max(0)[1]), abs(xyz.max(0)[2]) * 1.5]), up=[0, 0, 1]) camera.lookAt(tuple(centroid)) return camera
def display_jupyter(self, window_size=(800, 600), axes_arrow_length=None): """Returns a PyThreeJS Renderer and AnimationAction for displaying and animating the scene inside a Jupyter notebook. Parameters ========== window_size : 2-tuple of integers 2-tuple containing the width and height of the renderer window in pixels. axes_arrow_length : float If a positive value is supplied a red (x), green (y), and blue (z) arrows of the supplied length will be displayed as arrows for the global axes. Returns ======= vbox : widgets.VBox A vertical box containing the action (pythreejs.AnimationAction) and renderer (pythreejs.Renderer). """ if p3js is None: raise ImportError('pythreejs needs to be installed.') self._generate_meshes_tracks() view_width = window_size[0] view_height = window_size[1] camera = p3js.PerspectiveCamera(position=[1, 1, 1], aspect=view_width / view_height) key_light = p3js.DirectionalLight() ambient_light = p3js.AmbientLight() children = self._meshes + [camera, key_light, ambient_light] if axes_arrow_length is not None: children += [p3js.AxesHelper(size=abs(axes_arrow_length))] scene = p3js.Scene(children=children) controller = p3js.OrbitControls(controlling=camera) renderer = p3js.Renderer(camera=camera, scene=scene, controls=[controller], width=view_width, height=view_height) clip = p3js.AnimationClip(tracks=self._tracks, duration=self.times[-1]) action = p3js.AnimationAction(p3js.AnimationMixer(scene), clip, scene) return widgets.VBox([action, renderer])
def __init__(self, eye_pos, obj_pos, obj, up=(0, 1, 0)): self.camera = three.PerspectiveCamera( position=tuple(eye_pos), lookAt=tuple(obj_pos), fov=30, ) self.camera.up = up self.light = three.PointLight( color="white", position=(-eye_pos[0], eye_pos[1], eye_pos[2]), ) self.obj = obj self.scene = three.Scene(children=[self.obj, self.light, self.camera])
def __init__(self, settings): self.__update_settings(settings) self._light = p3s.DirectionalLight(color='white', position=[0, 0, 1], intensity=0.6) self._light2 = p3s.AmbientLight(intensity=0.5) self._cam = p3s.PerspectiveCamera(position=[0, 0, 1], lookAt=[0, 0, 0], fov=self.__s["fov"], aspect=self.__s["width"]/self.__s["height"], children=[self._light]) self._orbit = p3s.OrbitControls(controlling=self._cam) self._scene = p3s.Scene(children=[self._cam, self._light2], background=self.__s["background"])#"#4c4c80" self._renderer = p3s.Renderer(camera=self._cam, scene = self._scene, controls=[self._orbit], width=self.__s["width"], height=self.__s["height"], antialias=self.__s["antialias"]) self.__objects = {} self.__cnt = 0
def pvcamera_to_threejs_camera(pv_camera, lights, aspect): """Return an ipygany camera dict from a ``pyvista.Plotter`` object.""" # scene will be centered at focal_point, so adjust the position position = np.array(pv_camera.position) - np.array(pv_camera.focal_point) far = np.linalg.norm(position) * 100000 return tjs.PerspectiveCamera( up=pv_camera.up, children=lights, position=position.tolist(), fov=pv_camera.view_angle, aspect=aspect, far=far, near=0.01, )
def __init__(self): # TODO: arguments for width/height self._width = 600 self._height = 400 self._ball = _three.Mesh( geometry=_three.SphereGeometry( radius=1, widthSegments=30, heightSegments=20, ), material=_three.MeshLambertMaterial(color='lightgray'), ) self._axes = _three.AxesHelper(size=1.2) self._ambient_light = _three.AmbientLight( intensity=0.5, ) self._directional_light1 = _three.DirectionalLight( position=[0, 0, 1], intensity=0.6, ) self._directional_light2 = _three.DirectionalLight( position=[0, 0, -1], intensity=0.6, ) self._scene = _three.Scene( children=[ self._ball, self._axes, self._ambient_light, self._directional_light1, self._directional_light2, ], ) self._camera = _three.PerspectiveCamera( position=[0, 0, 2.4], up=[0, 0, 1], aspect=self._width/self._height, ) self._controls = _three.OrbitControls(controlling=self._camera) self._renderer = _three.Renderer( camera=self._camera, scene=self._scene, controls=[self._controls], width=self._width, height=self._height, #alpha=True, #clearOpacity=0.5, )
def viewer_cloth(cloth): view_width = 800 view_height = 600 camera = THREE.PerspectiveCamera(position=[20, 5, 30], aspect=view_width / view_height) key_light = THREE.DirectionalLight(position=[10, 10, 10]) ambient_light = THREE.AmbientLight() axes_helper = THREE.AxesHelper(0.5) scene = THREE.Scene() controller = THREE.OrbitControls(controlling=camera) renderer = THREE.Renderer(camera=camera, scene=scene, controls=[controller], width=view_width, height=view_height) scene.children = [cloth, axes_helper, camera, key_light, ambient_light] return renderer
def getView(self, regenerateView=False, viewWidth=600, viewHeight=400): if regenerateView or self.view is None: center = self.model.get_center() camera = three.PerspectiveCamera( position=(center + np.array([0, 200, 0])).tolist(), aspect=viewWidth / viewHeight) key_light = three.DirectionalLight(position=[0, 10, 10]) ambient_light = three.AmbientLight() scene = three.Scene( children=[self.selectable, camera, key_light, ambient_light]) three.Picker(controlling=scene, event='mousemove') controller = three.OrbitControls(controlling=camera, screenSpacePanning=True, target=center.tolist()) camera.lookAt(center.tolist()) self.coord_label = HTML('Select beads by double-clicking') selIncrButton = Button(description='Increase selection') selIncrButton.on_click(self.increaseSelection) selDecrButton = Button(description='Decrease selection') selDecrButton.on_click(self.decreaseSelection) click_picker = three.Picker(controlling=self.selectable, event='dblclick') click_picker.observe(self.selectBead, names=['point']) renderer = three.Renderer(camera=camera, scene=scene, controls=[controller, click_picker], width=viewWidth, height=viewHeight, antialias=True) self.view = VBox([ self.coord_label, renderer, HBox([selDecrButton, selIncrButton]) ]) return self.view
def _setup_scene(self, bounding_box, render_size): self.min_width = render_size[0] self.min_height = render_size[1] fov_angle = 60 if len(bounding_box[0]) == 2: lower = np.array([bounding_box[0][0], bounding_box[0][1], 0]) upper = np.array([bounding_box[1][0], bounding_box[1][1], 0]) bounding_box = (lower, upper) combined_bounds = np.hstack(bounding_box) absx = np.abs(combined_bounds[0] - combined_bounds[3]) not_mathematical_distance_scaling = 1.2 self.camera_distance = np.sin( (90 - fov_angle / 2) * np.pi / 180) * 0.5 * absx / np.sin( fov_angle / 2 * np.pi / 180) self.camera_distance *= not_mathematical_distance_scaling xhalf = (combined_bounds[0] + combined_bounds[3]) / 2 yhalf = (combined_bounds[1] + combined_bounds[4]) / 2 zhalf = (combined_bounds[2] + combined_bounds[5]) / 2 self.mesh_center = (xhalf, yhalf, zhalf) self.cam = p3js.PerspectiveCamera( aspect=render_size[0] / render_size[1], position=[0, 0, 0 + self.camera_distance]) self.light = p3js.AmbientLight(color='white', intensity=1.0) self.scene = p3js.Scene(children=[self.cam, self.light], background='white') self.controller = p3js.OrbitControls( controlling=self.cam, position=[0, 0, 0 + self.camera_distance], target=[0, 0, 0]) self.freeze_camera(True) self.renderer = p3js.Renderer(camera=self.cam, scene=self.scene, controls=[self.controller], webgl_version=1, width=render_size[0], height=render_size[1])
def display_scene(lm_scene): """Display Lightmetrica scene.""" # Scene scene = three.Scene() # Camera # Get lm camera information lm_main_camera = lm_scene.camera() lm_camera_params = lm_main_camera.underlying_value() camera = three.PerspectiveCamera(fov=lm_camera_params['vfov'], aspect=lm_camera_params['aspect'], near=0.1, far=10000) camera.position = lm_camera_params['eye'] camera.up = lm_camera_params['up'] scene.add(camera) # Mesh def add_lm_scene_mesh(): # Default material mat_default = three.MeshBasicMaterial(color='#000000', wireframe=True, transparent=True, opacity=0.2, depthTest=False) # Convert lm mesh def traverse_func(node, trans): # Underlying mesh mesh = node.primitive.mesh if mesh is None: return # Iterate through all triangles vs = [] def process_triangle(face_index, tri): vs.append(list(tri.p1.p)) vs.append(list(tri.p2.p)) vs.append(list(tri.p3.p)) mesh.foreach_triangle(process_triangle) # Create geometry ps_attr = three.BufferAttribute(array=vs, normalized=False) geom = three.BufferGeometry(attributes={'position': ps_attr}) # Create mesh mesh = three.Mesh(geometry=geom, material=mat_default) mesh.matrixAutoUpdate = False mesh.matrix = trans.T.flatten().tolist() scene.add(mesh) lm_scene.traverse_primitive_nodes(traverse_func) add_lm_scene_mesh() # View frustum 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) add_view_frustum() # Axis axes = three.AxesHelper(size=1) scene.add(axes) # Renderer controls = three.OrbitControls(controlling=camera) # Rendered image size w = 1000 h = w / lm_camera_params['aspect'] # We need to set both target and lookAt in this order. # Otherwise the initial target position becomes wrong. # cf. https://github.com/jupyter-widgets/pythreejs/issues/200 controls.target = lm_camera_params['center'] camera.lookAt(lm_camera_params['center']) renderer = three.Renderer(camera=camera, scene=scene, width=w, height=h, controls=[controls]) # Button to reset camera configuration # Note that we need to press the button twice to reset the control # to the correct target possibly due to the bug of pythreejs. reset_camera_button = widgets.Button(description="Reset Camera") @reset_camera_button.on_click def reset_camera_button_on_click(b): controls.reset() controls.target = lm_camera_params['center'] camera.lookAt(lm_camera_params['center']) # Display all display(reset_camera_button) display(renderer) return scene, camera, renderer
def create_jsrenderer(scene, height=400, width=400, background='gray', orthographic=False, camera_position=(0, 0, -10), view=(10, -10, -10, 10), fov=50): """ Properties ---------- orthographic : bool use orthographic camera (True) or perspective (False) camera_position : tuple position of camera in scene view : tuple view extents: (top, bottom, left, right) (orthographic only) fov : float camera field of view (perspective only) Returns ------- camera : pythreejs.Camera renderer : pythreejs.Renderer Examples -------- >>> import pythreejs as js >>> scene = js.Scene(children=[js.AmbientLight(color='#777777')]) >>> camera, renderer = create_jsrenderer(scene,200,200, 'gray', (1,-1,-1,1)) >>> type(renderer) <class 'pythreejs.pythreejs.Renderer'> >>> type(camera) <class 'pythreejs.pythreejs.OrthographicCamera'> """ if orthographic: top, bottom, left, right = view camera = js.OrthographicCamera(position=camera_position, up=[0, 0, 1], top=top, bottom=bottom, left=left, right=right, near=.1, far=2000) else: camera = js.PerspectiveCamera(position=camera_position, up=[0, 0, 1], far=2000, near=.1, fov=fov, aspect=width / float(height)) camera.children = [ js.DirectionalLight(color='white', position=[3, 5, 1], intensity=0.5) ] control = js.OrbitControls(controlling=camera) renderer = js.Renderer(camera=camera, background=background, background_opacity=.1, height=str(height), width=str(width), scene=scene, controls=[control]) return camera, renderer
def cqdisplay(result, color='#708090', scale=1.0): 'display CQ object in a ThreeJS Webgl context' # Open stream output = StringIO.StringIO() # cadquery will stream a ThreeJS JSON (using old v3 schema, which is deprecated) exporters.exportShape(result.shape.findSolid().scale(scale), 'TJS', output) # store stream to a variable contents = output.getvalue() # Close stream output.close() # Overwrite the JSON color portion with user defined color. Disallows NAMED colors col = list(matplotlib.colors.hex2color(color)) old_col_str = '"colorDiffuse" : [0.6400000190734865, 0.10179081114814892, 0.126246120426746]' new_col_str = '"colorDiffuse" : ' + str(col) new_contents = contents.replace(old_col_str, new_col_str) # Take the string and create a proper json object contents = json.loads(contents) # Vertices and Faces are both flat lists, but the pythreejs module requires list of lists old_v = contents['vertices'] old_f = contents['faces'] # Splits the list up in 3s, to produce a list of lists representing the vertices vertices = [old_v[i:i+3] for i in range(0, len(old_v), 3)] # JSON Schema has first position in the face's list reserved to indicate type. # Cadquery returns Triangle mesh, so we know that we must split list into lists of length 4 # 1st entry to indicate triangle, next 3 to specify vertices three_faces = [old_f[i:i+4] for i in range(0, len(old_f), 4)] faces = [] # Drop the first entry in the face list for entry in three_faces: entry.pop(0) faces.append(entry) # Cadquery does not supply face normals in the JSON, # and we cannot use THREE.JS built in 'computefaceNormals' # (at least, not easily) # Instead, we just calculate the face normals ourselves. # It is just the cross product of 2 vectors in the triangle. # TODO: see if there is a better way to achieve this result face_normals = [] for entry in faces: v_a = np.asarray(vertices[entry[0]]) v_b = np.asarray(vertices[entry[1]]) v_c = np.asarray(vertices[entry[2]]) vec_a = v_b - v_a vec_b = v_c - v_a cross = np.cross(vec_a, vec_b) face_normals.append([cross[0], cross[1], cross[2]]) # set up geometry geom = pythreejs.PlainGeometry(vertices=vertices, faces=faces, faceNormals=face_normals) mtl = pythreejs.LambertMaterial(color=color, shading='FlatShading') obj = pythreejs.Mesh(geometry=geom, material=mtl) # set up scene and camera cam_dist = 50 fov = 35 cam = pythreejs.PerspectiveCamera( position=[cam_dist, cam_dist, cam_dist], fov=fov, children=[pythreejs.DirectionalLight(color='#ffffff', position=[-3, 5, 1], intensity=0.45)]) scn_chld = [ obj, pythreejs.AmbientLight(color='#dddddd') ] scn = pythreejs.Scene(children=scn_chld) render = pythreejs.Renderer( width='830'.decode('utf-8'), height='553'.decode('utf-8'), camera=cam, scene=scn, controls=[pythreejs.OrbitControls(controlling=cam)] ) return render
def __init__(self, scipp_obj_dict=None, positions=None, axes=None, masks=None, cmap=None, log=None, vmin=None, vmax=None, color=None, aspect=None, background=None, nan_color=None, pixel_size=None, tick_size=None, show_outline=True): super().__init__(scipp_obj_dict=scipp_obj_dict, positions=positions, axes=axes, masks=masks, cmap=cmap, log=log, vmin=vmin, vmax=vmax, color=color, aspect=aspect, button_options=['X', 'Y', 'Z']) self.vslice = None self.current_cut_surface_value = None self.cut_slider_steps = 10. self.cbar_image = widgets.Image() self.cut_options = { "Xplane": 0, "Yplane": 1, "Zplane": 2, "Xcylinder": 3, "Ycylinder": 4, "Zcylinder": 5, "Sphere": 6, "Value": 7 } # Prepare colormaps self.cmap = copy(cm.get_cmap(self.params["values"][self.name]["cmap"])) self.cmap.set_bad(color=nan_color) self.scalar_map = cm.ScalarMappable( norm=self.params["values"][self.name]["norm"], cmap=self.cmap) self.masks_scalar_map = None if self.params["masks"][self.name]["show"]: self.masks_cmap = copy( cm.get_cmap(self.params["masks"][self.name]["cmap"])) self.masks_cmap.set_bad(color=nan_color) self.masks_scalar_map = cm.ScalarMappable( norm=self.params["values"][self.name]["norm"], cmap=self.masks_cmap) # Generate the colorbar image self.create_colorbar() # Useful variables self.permutations = {"x": ["y", "z"], "y": ["x", "z"], "z": ["x", "y"]} self.remaining_inds = [0, 1] # Search the coordinates to see if one contains vectors. If so, it will # be used as position vectors. self.axlabels = {"x": "", "y": "", "z": ""} self.positions = None self.pixel_size = pixel_size self.tick_size = tick_size if positions is not None: coord = self.data_array.coords[positions] self.positions = np.array(coord.values, dtype=np.float32) self.axlabels.update({ "x": name_with_unit(coord, name="X"), "y": name_with_unit(coord, name="Y"), "z": name_with_unit(coord, name="Z") }) else: # If no positions are supplied, create a meshgrid from coordinate # axes. coords = [] labels = [] for dim, val in self.slider.items(): if val.disabled: arr = self.slider_coord[self.name][dim] if self.histograms[self.name][dim][dim]: arr = to_bin_centers(arr, dim) coords.append(arr.values) labels.append( name_with_unit(self.slider_coord[self.name][dim])) z, y, x = np.meshgrid(*coords, indexing='ij') self.positions = np.array( [x.ravel(), y.ravel(), z.ravel()], dtype=np.float32).T if self.pixel_size is None: self.pixel_size = coords[0][1] - coords[0][0] self.axlabels.update({ "z": labels[0], "y": labels[1], "x": labels[2] }) # Find spatial and value limits self.xminmax, self.center_of_mass = self.get_spatial_extents() self.vminmax = [ sc.min(self.data_array.data).value, sc.max(self.data_array.data).value ] # Create the point cloud with pythreejs self.points_geometry, self.points_material, self.points = \ self.create_points_geometry() # Create outline around point positions self.outline, self.axticks = self.create_outline() # Save the size of the outline box for later self.box_size = np.diff(list(self.xminmax.values()), axis=1).ravel() # Define camera: look at the centre of mass of the points camera_lookat = self.center_of_mass camera_pos = np.array(self.center_of_mass) + 1.2 * self.box_size self.camera = p3.PerspectiveCamera(position=list(camera_pos), aspect=config.plot.width / config.plot.height) # Add red/green/blue axes helper self.axes_3d = p3.AxesHelper(10.0 * np.linalg.norm(camera_pos)) # Create the pythreejs scene self.scene = p3.Scene(children=[ self.camera, self.axes_3d, self.points, self.outline, self.axticks ], background=background) # Add camera controller self.controller = p3.OrbitControls(controlling=self.camera, target=camera_lookat) self.camera.lookAt(camera_lookat) # Render the scene into a widget self.renderer = p3.Renderer(camera=self.camera, scene=self.scene, controls=[self.controller], width=config.plot.width, height=config.plot.height) # Update visibility of outline according to keyword arg self.outline.visible = show_outline self.axticks.visible = show_outline # Opacity slider: top value controls opacity if no cut surface is # active. If a cut curface is present, the upper slider is the opacity # of the slice, while the lower slider value is the opacity of the # data not in the cut surface. self.opacity_slider = widgets.FloatRangeSlider( min=0.0, max=1.0, value=[0.1, 1], step=0.01, description="Opacity slider: When no cut surface is active, the " "max value of the range slider controls the overall opacity, " "and the lower value has no effect. When a cut surface is " "present, the max value is the opacity of the slice, while the " "min value is the opacity of the background.", continuous_update=True, style={'description_width': '60px'}) self.opacity_slider.observe(self.update_opacity, names="value") self.opacity_checkbox = widgets.Checkbox( value=self.opacity_slider.continuous_update, description="Continuous update", indent=False, layout={"width": "20px"}) self.opacity_checkbox_link = widgets.jslink( (self.opacity_checkbox, 'value'), (self.opacity_slider, 'continuous_update')) self.toggle_outline_button = widgets.ToggleButton(value=show_outline, description='', button_style='') self.toggle_outline_button.observe(self.toggle_outline, names="value") # Run a trigger to update button text self.toggle_outline({"new": show_outline}) # Add buttons to provide a choice of different cut surfaces: # - Cartesian X, Y, Z # - Cylindrical X, Y, Z (cylinder major axis) # - Sperical R # - Value-based iso-surface # Note additional spaces required in cylindrical names because # options must be unique. self.cut_surface_buttons = widgets.ToggleButtons( options=[('X ', self.cut_options["Xplane"]), ('Y ', self.cut_options["Yplane"]), ('Z ', self.cut_options["Zplane"]), ('R ', self.cut_options["Sphere"]), (' X ', self.cut_options["Xcylinder"]), (' Y ', self.cut_options["Ycylinder"]), (' Z ', self.cut_options["Zcylinder"]), ('', self.cut_options["Value"])], value=None, description='Cut surface:', button_style='', tooltips=[ 'X-plane', 'Y-plane', 'Z-plane', 'Sphere', 'Cylinder-X', 'Cylinder-Y', 'Cylinder-Z', 'Value' ], icons=(['cube'] * 3) + ['circle-o'] + (['toggle-on'] * 3) + ['magic'], style={"button_width": "55px"}, layout={'width': '350px'}) self.cut_surface_buttons.observe(self.update_cut_surface_buttons, names="value") # Add a capture for a click event: if the active button is clicked, # this resets the togglebuttons value to None and deletes the cut # surface. self.cut_surface_buttons.on_msg(self.check_if_reset_needed) # Add slider to control position of cut surface self.cut_slider = widgets.FloatSlider(min=0, max=1, description="Position:", disabled=True, value=0.5, layout={"width": "350px"}) self.cut_checkbox = widgets.Checkbox(value=True, description="Continuous update", indent=False, layout={"width": "20px"}, disabled=True) self.cut_checkbox_link = widgets.jslink( (self.cut_checkbox, 'value'), (self.cut_slider, 'continuous_update')) self.cut_slider.observe(self.update_cut_surface, names="value") # Allow to change the thickness of the cut surface self.cut_surface_thickness = widgets.BoundedFloatText( value=0.05 * self.box_size.max(), min=0, layout={"width": "150px"}, disabled=True, description="Thickness:", style={'description_width': 'initial'}) self.cut_surface_thickness.observe(self.update_cut_surface, names="value") self.cut_thickness_link = widgets.jslink( (self.cut_slider, 'step'), (self.cut_surface_thickness, 'value')) self.cut_slider.observe(self.update_cut_surface, names="value") # Put widgets into boxes self.cut_surface_controls = widgets.HBox([ self.cut_surface_buttons, widgets.VBox([ widgets.HBox([self.cut_slider, self.cut_checkbox]), self.cut_surface_thickness ]) ]) self.box = widgets.VBox([ widgets.HBox([self.renderer, self.cbar_image]), widgets.VBox(self.vbox), widgets.HBox([ self.opacity_slider, self.opacity_checkbox, self.toggle_outline_button ]), self.cut_surface_controls ]) # Update list of members to be returned in the SciPlot object self.members.update({ "camera": self.camera, "scene": self.scene, "renderer": self.renderer }) return
def _default_camera(self): # return pythreejs.CombinedCamera(fov=46, position=(0, 0, 2), width=400, height=500) return pythreejs.PerspectiveCamera(fov=46, position=(0, 0, 2), width=400, height=500)
def visualise(mesh, geometric_field, number_of_dimensions, xi_interpolation, dependent_field=None, variable=None, mechanics_animation=False, colour_map_dependent_component_number=None, cmap='gist_rainbow', resolution=1, node_labels=False): if number_of_dimensions != 3: print( 'Warning: Only visualisation of 3D meshes is currently supported.') return if xi_interpolation != [1, 1, 1]: print( 'Warning: Only visualisation of 3D elements with linear Lagrange \ interpolation along all coordinate directions is currently \ supported.') return view_width = 600 view_height = 600 debug = False if debug: vertices = [[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 1], [1, 0, 0], [1, 0, 1], [1, 1, 0], [1, 1, 1]] faces = [[0, 1, 3], [0, 3, 2], [0, 2, 4], [2, 6, 4], [0, 4, 1], [1, 4, 5], [2, 3, 6], [3, 7, 6], [1, 5, 3], [3, 5, 7], [4, 6, 5], [5, 6, 7]] vertexcolors = [ '#000000', '#0000ff', '#00ff00', '#ff0000', '#00ffff', '#ff00ff', '#ffff00', '#ffffff' ] else: # Get mesh topology information. num_nodes = mesh_tools.num_nodes_get(mesh, mesh_component=1) node_nums = list(range(1, num_nodes + 1)) num_elements, element_nums = mesh_tools.num_element_get( mesh, mesh_component=1) # Convert geometric field to a morphic mesh and export to json mesh = mesh_tools.OpenCMISS_to_morphic(mesh, geometric_field, element_nums, node_nums, dimension=3, interpolation='linear') vertices, faces, _, xi_element_nums, xis = get_faces( mesh, res=resolution, exterior_only=True, include_xi=True) vertices = vertices.tolist() faces = faces.tolist() centroid = np.mean(vertices, axis=0) max_positions = np.max(vertices, axis=0) min_positions = np.min(vertices, axis=0) range_positions = max_positions - min_positions if (dependent_field is not None) and (colour_map_dependent_component_number is not None): solution = np.zeros(xis.shape[0]) for idx, (xi, xi_element_num) in enumerate(zip(xis, xi_element_nums)): solution[idx] = mesh_tools.interpolate_opencmiss_field_xi( dependent_field, xi, element_ids=[xi_element_num], dimension=3, deriv=1)[colour_map_dependent_component_number - 1] minima = min(solution) maxima = max(solution) import matplotlib norm = matplotlib.colors.Normalize(vmin=minima, vmax=maxima, clip=True) mapper = cm.ScalarMappable(norm=norm, cmap=cm.get_cmap(name=cmap)) vertex_colors = np.zeros((len(vertices), 3), dtype='float32') for idx, v in enumerate(solution): vertex_colors[idx, :] = mapper.to_rgba(v, alpha=None)[:3] # else: # raise ValueError('Visualisation not supported.') else: vertex_colors = np.tile(np.array([0.5, 0.5, 0.5], dtype='float32'), (len(vertices), 1)) geometry = pjs.BufferGeometry(attributes=dict( position=pjs.BufferAttribute(vertices, normalized=False), index=pjs.BufferAttribute( np.array(faces).astype(dtype='uint16').ravel(), normalized=False), color=pjs.BufferAttribute(vertex_colors), )) if mechanics_animation: deformed_vertices = np.zeros((xis.shape[0], 3), dtype='float32') for idx, (xi, xi_element_num) in enumerate(zip(xis, xi_element_nums)): deformed_vertices[idx, :] = \ mesh_tools.interpolate_opencmiss_field_xi( dependent_field, xi, element_ids=[xi_element_num], dimension=3, deriv=1)[0][:3] geometry.morphAttributes = { 'position': [ pjs.BufferAttribute(deformed_vertices), ] } geometry.exec_three_obj_method('computeFaceNormals') geometry.exec_three_obj_method('computeVertexNormals') surf1 = pjs.Mesh(geometry, pjs.MeshPhongMaterial(color='#ff3333', shininess=150, morphTargets=True, side='FrontSide'), name='A') surf2 = pjs.Mesh(geometry, pjs.MeshPhongMaterial(color='#ff3333', shininess=150, morphTargets=True, side='BackSide'), name='B') surf = pjs.Group(children=[surf1, surf2]) # camera = pjs.PerspectiveCamera( # fov=20, position=[range_positions[0] * 10, # range_positions[1] * 10, # range_positions[2] * 10], # width=view_width, # height=view_height, near=1, # far=max(range_positions) * 10) camera = pjs.PerspectiveCamera(position=[ range_positions[0] * 3, range_positions[1] * 3, range_positions[2] * 3 ], aspect=view_width / view_height) camera.up = [0, 0, 1] camera.lookAt(centroid.tolist()) scene3 = pjs.Scene(children=[ surf1, surf2, camera, pjs.DirectionalLight(position=[3, 5, 1], intensity=0.6), pjs.AmbientLight(intensity=0.5) ]) axes = pjs.AxesHelper(size=range_positions[0] * 2) scene3.add(axes) A_track = pjs.NumberKeyframeTrack( name='scene/A.morphTargetInfluences[0]', times=[0, 3], values=[0, 1]) B_track = pjs.NumberKeyframeTrack( name='scene/B.morphTargetInfluences[0]', times=[0, 3], values=[0, 1]) pill_clip = pjs.AnimationClip(tracks=[A_track, B_track]) pill_action = pjs.AnimationAction(pjs.AnimationMixer(scene3), pill_clip, scene3) renderer3 = pjs.Renderer( camera=camera, scene=scene3, controls=[pjs.OrbitControls(controlling=camera)], width=view_width, height=view_height) display(renderer3, pill_action) else: geometry.exec_three_obj_method('computeFaceNormals') geometry.exec_three_obj_method('computeVertexNormals') surf1 = pjs.Mesh(geometry=geometry, material=pjs.MeshLambertMaterial( vertexColors='VertexColors', side='FrontSide')) # Center the cube. surf2 = pjs.Mesh(geometry=geometry, material=pjs.MeshLambertMaterial( vertexColors='VertexColors', side='BackSide')) # Center the cube. surf = pjs.Group(children=[surf1, surf2]) camera = pjs.PerspectiveCamera(position=[ range_positions[0] * 3, range_positions[1] * 3, range_positions[2] * 3 ], aspect=view_width / view_height) camera.up = [0, 0, 1] camera.lookAt(centroid.tolist()) # if perspective: # camera.mode = 'perspective' # else: # camera.mode = 'orthographic' lights = [ pjs.DirectionalLight(position=[ range_positions[0] * 16, range_positions[1] * 12, range_positions[2] * 17 ], intensity=0.5), pjs.AmbientLight(intensity=0.8), ] orbit = pjs.OrbitControls(controlling=camera, screenSpacePanning=True, target=centroid.tolist()) scene = pjs.Scene() axes = pjs.AxesHelper(size=max(range_positions) * 2) scene.add(axes) scene.add(surf1) scene.add(surf2) scene.add(lights) if node_labels: # Add text labels for each mesh node. v, ids = mesh.get_node_ids(group='_default') for idx, v in enumerate(v): text = make_text(str(ids[idx]), position=(v[0], v[1], v[2])) scene.add(text) # Add text for axes labels. x_axis_label = make_text('x', position=(max(range_positions) * 2, 0, 0)) y_axis_label = make_text('y', position=(0, max(range_positions) * 2, 0)) z_axis_label = make_text('z', position=(0, 0, max(range_positions) * 2)) scene.add(x_axis_label) scene.add(y_axis_label) scene.add(z_axis_label) renderer = pjs.Renderer(scene=scene, camera=camera, controls=[orbit], width=view_width, height=view_height) camera.zoom = 1 display(renderer) return vertices, faces
def _render_obj(self, rendered_obj, **kw): obj_geometry = pjs.BufferGeometry(attributes=dict( position=pjs.BufferAttribute(rendered_obj.plot_verts), color=pjs.BufferAttribute(rendered_obj.base_cols), normal=pjs.BufferAttribute( rendered_obj.face_normals.astype('float32')))) vertices = rendered_obj.vertices # 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], ) line_material = pjs.LineBasicMaterial(color='#ffffff', transparent=True, opacity=0.3, linewidth=1.0) my_object_wireframe_mesh = pjs.LineSegments( geometry=obj_geometry, material=line_material, position=[0, 0, 0], ) n_vert = vertices.shape[0] center = vertices.mean(axis=0) extents = self._get_extents(vertices) max_delta = np.max(extents[:, 1] - extents[:, 0]) 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) v = [0.0, 0.0, 0.0] if n_vert > 0: v = vertices[0].tolist() select_point_geom = pjs.SphereGeometry(radius=1.0) select_point_mesh = pjs.Mesh( select_point_geom, material=pjs.MeshBasicMaterial(color=SELECTED_VERTEX_COLOR), position=v, scale=(0.0, 0.0, 0.0)) #select_edge_mesh = pjs.ArrowHelper(dir=pjs.Vector3(1.0, 0.0, 0.0), origin=pjs.Vector3(0.0, 0.0, 0.0), length=1.0, # hex=SELECTED_EDGE_COLOR_INT, headLength=0.1, headWidth=0.05) arrow_cyl_mesh = pjs.Mesh(geometry=pjs.SphereGeometry(radius=0.01), material=pjs.MeshLambertMaterial()) arrow_head_mesh = pjs.Mesh(geometry=pjs.SphereGeometry(radius=0.001), material=pjs.MeshLambertMaterial()) scene_things = [ my_object_mesh, my_object_wireframe_mesh, select_point_mesh, arrow_cyl_mesh, arrow_head_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) click_picker = pjs.Picker(controlling=my_object_mesh, event='dblclick') out = Output() top_msg = HTML() def on_dblclick(change): if change['name'] == 'point': try: point = np.array(change['new']) face = click_picker.faceIndex face_points = rendered_obj.face_verts[face] face_vecs = face_points - np.roll(face_points, 1, axis=0) edge_lens = np.sqrt((face_vecs**2).sum(axis=1)) point_vecs = face_points - point[np.newaxis, :] point_dists = (point_vecs**2).sum(axis=1) min_point = np.argmin(point_dists) v1s = point_vecs.copy() v2s = np.roll(v1s, -1, axis=0) edge_mids = 0.5 * (v2s + v1s) edge_mid_dists = (edge_mids**2).sum(axis=1) min_edge_point = np.argmin(edge_mid_dists) edge_start = min_edge_point edge = face * 3 + edge_start close_vert = rendered_obj.face_verts[face, min_point] edge_start_vert = rendered_obj.face_verts[face, edge_start] edge_end_vert = rendered_obj.face_verts[face, (edge_start + 1) % 3] vertex = face * 3 + min_point radius = min( [edge_lens.max() * 0.02, 0.1 * edge_lens.min()]) edge_head_length = radius * 4 edge_head_width = radius * 2 select_point_mesh.scale = (radius, radius, radius) top_msg.value = '<font color="{}">selected face: {}</font>, <font color="{}">edge: {}</font>, <font color="{}"> vertex: {}</font>'.format( SELECTED_FACE_COLOR, face, SELECTED_EDGE_COLOR, edge, SELECTED_VERTEX_COLOR, vertex) newcols = rendered_obj.base_cols.copy() newcols[face * 3:(face + 1) * 3] = np.array( SELECTED_FACE_RGB, dtype='float32') select_point_mesh.position = close_vert.tolist() obj_geometry.attributes['color'].array = newcols with out: make_arrow(arrow_cyl_mesh, arrow_head_mesh, edge_start_vert, edge_end_vert, radius / 2, radius, radius * 3, SELECTED_EDGE_COLOR) except: with out: print(traceback.format_exc()) click_picker.observe(on_dblclick, names=['point']) renderer_obj = pjs.Renderer( camera=camera, background='#cccc88', background_opacity=0, scene=scene, controls=[pjs.OrbitControls(controlling=camera), click_picker], width=self.width, height=self.height) display_things = [top_msg, renderer_obj, out] 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(VBox(display_things))
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 plot(self, backend="pythreejs", width=800, height=500, background="black", mesh=False, use_as_color=["red", "green", "blue"], cmap="hsv", return_scene=False, output_name="pyntcloud_plot", polylines={}): """Visualize a PyntCloud using different backends. Parameters ---------- backend: {"pythreejs", "threejs"}, optional Default: "pythreejs" Used to select one of the available libraries for plotting. width: int, optional Default: 800 height: int, optional Default: 500 background: str, optional Default: "black" Used to select the default color of the background. In some backends, i.e "pythreejs" the background can be dynamically changed. use_as_color: str or ["red", "green", "blue"], optional Default: ["red", "green", "blue"] Indicates which scalar fields will be used to colorize the rendered point cloud. cmap: str, optional Default: "hsv" Color map that will be used to convert a single scalar field into rgb. Check matplotlib cmaps. return_scene: bool, optional Default: False. Used with "pythreejs" backend in order to return the pythreejs.Scene object polylines: dict, optional Default {}. Mapping hexadecimal colors to a list of list(len(3)) representing the points of the polyline. Example: polylines={ "0xFFFFFF": [[0, 0, 0], [0, 0, 1]], "0xFF00FF": [[1, 0, 0], [1, 0, 1], [1, 1, 1]] } Returns ------- pythreejs.Scene if return_scene else None """ try: colors = self.points[use_as_color].values except KeyError: colors = None if use_as_color != ["red", "green", "blue"] and colors is not None: import matplotlib.pyplot as plt s_m = plt.cm.ScalarMappable(cmap=cmap) colors = s_m.to_rgba(colors)[:, :-1] * 255 elif colors is None: # default color orange colors = np.repeat([[255, 125, 0]], self.xyz.shape[0], axis=0) colors = colors.astype(np.uint8) ptp = self.xyz.ptp() if backend == "pythreejs": import ipywidgets import pythreejs from IPython.display import display if mesh: raise NotImplementedError( "Plotting mesh geometry with pythreejs backend is not supported yet." ) if polylines: raise NotImplementedError( "Plotting polylines with pythreejs backend is not supported yet." ) points_geometry = pythreejs.BufferGeometry(attributes=dict( position=pythreejs.BufferAttribute(self.xyz, normalized=False), color=pythreejs.BufferAttribute(list(map(tuple, colors))))) points_material = pythreejs.PointsMaterial( vertexColors='VertexColors') points = pythreejs.Points(geometry=points_geometry, material=points_material, position=tuple(self.centroid)) camera = pythreejs.PerspectiveCamera( fov=90, aspect=width / height, position=tuple( self.centroid + [0, abs(self.xyz.max(0)[1]), abs(self.xyz.max(0)[2]) * 2]), up=[0, 0, 1]) orbit_control = pythreejs.OrbitControls(controlling=camera) orbit_control.target = tuple(self.centroid) camera.lookAt(tuple(self.centroid)) scene = pythreejs.Scene(children=[points, camera], background=background) renderer = pythreejs.Renderer(scene=scene, camera=camera, controls=[orbit_control], width=width, height=height) display(renderer) size = ipywidgets.FloatSlider(min=0., max=(ptp / 100), step=(ptp / 1000)) ipywidgets.jslink((size, 'value'), (points_material, 'size')) color = ipywidgets.ColorPicker() ipywidgets.jslink((color, 'value'), (scene, 'background')) display( ipywidgets.HBox(children=[ ipywidgets.Label('Background color:'), color, ipywidgets.Label('Point size:'), size ])) return scene if return_scene else None elif backend == "threejs": points = pd.DataFrame(self.xyz, columns=["x", "y", "z"]) for n, i in enumerate(["red", "green", "blue"]): points[i] = colors[:, n] new_PyntCloud = PyntCloud(points) if mesh and self.mesh is not None: new_PyntCloud.mesh = self.mesh[["v1", "v2", "v3"]] return plot_PyntCloud(new_PyntCloud, IFrame_shape=(width, height), point_size=ptp / 100, point_opacity=0.9, output_name=output_name, polylines=polylines) else: raise NotImplementedError( "{} backend is not supported".format(backend))
def __init__(self, cmap=None, norm=None, figsize=None, unit=None, log=None, nan_color=None, masks=None, pixel_size=None, tick_size=None, background=None, show_outline=True, extend=None, xlabel=None, ylabel=None, zlabel=None): if figsize is None: figsize = (config.plot.width, config.plot.height) # Figure toolbar self.toolbar = PlotToolbar(ndim=3) # Prepare colormaps self.cmap = cmap self.cmap.set_bad(color=nan_color) self.scalar_map = cm.ScalarMappable(norm=norm, cmap=self.cmap) self.masks_scalar_map = None if len(masks) > 0: self.masks_cmap = masks["cmap"] self.masks_cmap.set_bad(color=nan_color) self.masks_scalar_map = cm.ScalarMappable(norm=norm, cmap=self.masks_cmap) self.axlabels = {"x": xlabel, "y": ylabel, "z": zlabel} self.positions = None self.pixel_size = pixel_size self.tick_size = tick_size self.show_outline = show_outline self.unit = unit # Create the colorbar image self.cbar_image = ipw.Image() self.cbar_fig, self.cbar = self._create_colorbar(figsize, extend) # Create the point cloud material with pythreejs self.points_material = self._create_points_material() self.points_geometry = None self.point_cloud = None self.outline = None self.axticks = None self.camera_reset = {} # Define camera self.camera = p3.PerspectiveCamera(position=[0, 0, 0], aspect=config.plot.width / config.plot.height) # Add red/green/blue axes helper self.axes_3d = p3.AxesHelper() # Create the pythreejs scene self.scene = p3.Scene(children=[self.camera, self.axes_3d], background=background) # Add camera controller self.controls = p3.OrbitControls(controlling=self.camera) # Render the scene into a widget self.renderer = p3.Renderer(camera=self.camera, scene=self.scene, controls=[self.controls], width=figsize[0], height=figsize[1])
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
def mesh_animation(times, xt, faces): """ Animate a mesh from a sequence of mesh vertex positions Args: times - a list of time values t_i at which the configuration x is specified xt - i.e., x(t). A list of arrays representing mesh vertex positions at times t_i. Dimensions of each array should be the same as that of mesh.geometry.array TODO nt - n(t) vertex normals faces - array of faces, with vertex loop for each face Side effects: displays rendering of mesh, with animation action Returns: None TODO renderer - THREE.Render to show the default scene TODO position_action - THREE.AnimationAction IPython widget """ position_morph_attrs = [] for pos in xt[ 1:]: # xt[0] uses as the Mesh's default/initial vertex position position_morph_attrs.append( THREE.BufferAttribute(pos, normalized=False)) # Testing mesh.geometry.morphAttributes = {'position': position_morph_attrs} geom = THREE.BufferGeometry( attributes={ 'position': THREE.BufferAttribute(xt[0], normalized=False), 'index': THREE.BufferAttribute(faces.ravel()) }, morphAttributes={'position': position_morph_attrs}) matl = THREE.MeshStandardMaterial(side='DoubleSide', color='red', wireframe=True, morphTargets=True) mesh = THREE.Mesh(geom, matl) # create key frames position_track = THREE.NumberKeyframeTrack( name='.morphTargetInfluences[0]', times=times, values=list(range(0, len(times)))) # create animation clip from the morph targets position_clip = THREE.AnimationClip(tracks=[position_track]) # create animation action position_action = THREE.AnimationAction(THREE.AnimationMixer(mesh), position_clip, mesh) # TESTING camera = THREE.PerspectiveCamera(position=[2, 1, 2], aspect=600 / 400) scene = THREE.Scene(children=[ mesh, camera, THREE.AxesHelper(0.2), THREE.DirectionalLight(position=[3, 5, 1], intensity=0.6), THREE.AmbientLight(intensity=0.5) ]) renderer = THREE.Renderer( camera=camera, scene=scene, controls=[THREE.OrbitControls(controlling=camera)], width=600, height=400) display(renderer, position_action)
def create_world_axes(camera, controls, initial_rotation=np.eye(3), length=30, width=3, camera_fov=10): """Create a renderer, containing an axes and camera that is synced to another camera. adapted from http://jsfiddle.net/aqnL1mx9/ Parameters ---------- camera : pythreejs.PerspectiveCamera controls : pythreejs.OrbitControls initial_rotation : list or numpy.array initial rotation of the axes length : int length of axes lines width : int line width of axes Returns ------- pythreejs.Renderer """ import pythreejs as pjs canvas_width = length * 2 canvas_height = length * 2 ax_scene = pjs.Scene() group_ax = pjs.Group() # NOTE: could use AxesHelper, but this does not allow for linewidth seletion # TODO: add arrow heads (ArrowHelper doesn't seem to work) ax_line_mat = pjs.LineMaterial(linewidth=width, vertexColors="VertexColors") ax_line_geo = pjs.LineSegmentsGeometry( positions=[[[0, 0, 0], length * r / np.linalg.norm(r)] for r in initial_rotation], colors=[[Color(c).rgb] * 2 for c in ("red", "green", "blue")], ) ax_lines = pjs.LineSegments2(geometry=ax_line_geo, material=ax_line_mat) group_ax.add(ax_lines) ax_scene.add([group_ax]) camera_dist = length / tan(radians(camera_fov / 2)) ax_camera = pjs.PerspectiveCamera(fov=camera_fov, aspect=canvas_width / canvas_height, near=1, far=1000) ax_camera.up = camera.up ax_renderer = pjs.Renderer( scene=ax_scene, camera=ax_camera, width=canvas_width, height=canvas_height, alpha=True, clearOpacity=0.0, clearColor="white", ) def align_axes(change=None): """Align axes to world.""" # TODO: this is not working correctly for TrackballControls, when rotated upside-down # (OrbitControls enforces the camera up direction, # so does not allow the camera to rotate upside-down). # TODO how could this be implemented on the client (js) side? new_position = np.array(camera.position) - np.array(controls.target) new_position = camera_dist * new_position / np.linalg.norm( new_position) ax_camera.position = new_position.tolist() ax_camera.lookAt(ax_scene.position) align_axes() camera.observe(align_axes, names="position") controls.observe(align_axes, names="target") ax_scene.observe(align_axes, names="position") return ax_renderer