def __init__(self, trimesh, uvs, width=512, height=512, duration=5, textureMap = None): self.viewer = TriMeshViewer(trimesh, width, height, textureMap) flatPosArray = None if (uvs.shape[1] == 2): flatPosArray = np.array(np.pad(uvs, [(0, 0), (0, 1)], 'constant'), dtype=np.float32) else: flatPosArray = np.array(uvs, dtype=np.float32) flatPos = pythreejs.BufferAttribute(array=flatPosArray, normalized=False) flatNormals = pythreejs.BufferAttribute(array=np.repeat(np.array([[0, 0, 1]], dtype=np.float32), uvs.shape[0], axis=0), normalized=False) geom = self.viewer.currMesh.geometry mat = self.viewer.currMesh.material geom.morphAttributes = {'position': [flatPos,], 'normal': [flatNormals,]} # Both of these material settings are needed or else our target positions/normals are ignored! mat.morphTargets, mat.morphNormals = True, True flatteningMesh = pythreejs.Mesh(geometry=geom, material=mat) amplitude = np.linspace(-1, 1, 20, dtype=np.float32) times = (np.arcsin(amplitude) / np.pi + 0.5) * duration blendWeights = 0.5 * (amplitude + 1) track = pythreejs.NumberKeyframeTrack('name=.morphTargetInfluences[0]', times = times, values = blendWeights, interpolation='InterpolateSmooth') self.action = pythreejs.AnimationAction(pythreejs.AnimationMixer(flatteningMesh), pythreejs.AnimationClip(tracks=[track]), flatteningMesh, loop='LoopPingPong') self.viewer.meshes.children = [flatteningMesh] self.layout = ipywidgets.VBox() self.layout.children = [self.viewer.renderer, self.action]
def update_keyframes(self): with self.output: fp = lambda x: "%f,%f,%f" % x fq = lambda x: "%f,%f,%f,%f" % x options = [(self.format_keyframe(t, p, q), i) for i, (t, p, q) in enumerate( zip(self.times, self.positions, self.quaternions))] self.select_keyframes.options = options self.position_track = pythreejs.VectorKeyframeTrack( name='.position', times=self.times, values=self.positions, interpolation=self.select_interpolation.value) self.rotation_track = pythreejs.QuaternionKeyframeTrack( name='.quaternion', times=self.times, values=self.quaternions, interpolation=self.select_interpolation.value) if len(self.positions): self.camera_clip = pythreejs.AnimationClip( tracks=[self.position_track, self.rotation_track]) self.mixer = pythreejs.AnimationMixer(self.camera) self.camera_action = pythreejs.AnimationAction( self.mixer, self.camera_clip, self.camera) self.camera_action_box.children = [self.camera_action] else: self.camera_action_box.children = []
def create_group_action(self, objs, transformations, times): # TODO: how to start multiple animations at once if len(transformations) != len(times): raise ValueError("Pass equal amount of transformations and times") x, y, z, w = objs[0].quaternion Tinit = Rotation.from_quaternion([w, x, y, z]) * Translation( objs[0].position) positions = [] quaternions = [] for M in transformations: Sc, Sh, R, T, P = (M * Tinit).decomposed() positions.append(list(T.translation)) quaternions.append(R.quaternion.xyzw) position_track = p3js.VectorKeyframeTrack(name='.position', times=times, values=list( flatten(positions))) rotation_track = p3js.QuaternionKeyframeTrack( name='.quaternion', times=times, values=list(flatten(quaternions))) animation_group = p3js.AnimationObjectGroup() animation_group.exec_three_obj_method('add', objs[0]) # this is not working obj_clip = p3js.AnimationClip(tracks=[position_track, rotation_track]) mixer = p3js.AnimationMixer(animation_group) obj_action = p3js.AnimationAction(mixer, obj_clip, animation_group) return obj_action
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 create_action(self, obj, transformations, times): if len(transformations) != len(times): raise ValueError("Pass equal amount of transformations and times") x, y, z, w = obj.quaternion Tinit = Rotation.from_quaternion([w, x, y, z]) * Translation(obj.position) positions = [] quaternions = [] for M in transformations: Sc, Sh, R, T, P = (M * Tinit).decompose() positions.append(list(T.translation)) quaternions.append(R.quaternion.xyzw) position_track = p3js.VectorKeyframeTrack(name='.position', times=times, values=list(flatten(positions))) rotation_track = p3js.QuaternionKeyframeTrack(name='.quaternion', times=times, values=list(flatten(quaternions))) obj_clip = p3js.AnimationClip(tracks=[position_track, rotation_track]) obj_action = p3js.AnimationAction(p3js.AnimationMixer(obj), obj_clip, obj) return obj_action
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 selectMode(self, modeNum, play = True): # Avoid flicker/partial redraws during updates self.renderer.pauseRendering() modeVector = None if (len(self.modeDoF.shape) == 1): if (modeNum != 0): raise Exception('modeNum should be zero; only a single mode was given.') modeVector = self.modeDoF else: modeVector = self.modeDoF[:, modeNum] # Rescale the modal shape so that the velocity it induces has # magnitude "self.amplitude" (relative to the structure's characteristic length scale) normalizedOffset = None if self.normalize and ("characteristicLength" in self.meshMethods) and ("approxLinfVelocity" in self.meshMethods): paramVelocity = self.mesh.approxLinfVelocity(modeVector) normalizedOffset = modeVector * (self.amplitude * self.mesh.characteristicLength() / paramVelocity) else: normalizedOffset = modeVector * self.amplitude morphTargetPositionsRaw = [] morphTargetNormalsRaw = [] modulations = np.linspace(-1, 1, self.numSteps, dtype=np.float32) # Animate the structure oscillating around its current degrees of freedom currVars = self.varGetter(); for modulation in modulations: self.varSetter(currVars + modulation * normalizedOffset) pts, tris, normals = self.mesh.visualizationGeometry() morphTargetPositionsRaw.append(pts) morphTargetNormalsRaw .append(normals) self.varSetter(currVars) if self.modeMesh is None: # We apparently need to create a new mesh to add our morph targets # (instead of reusing the viewer's mesh object, otherwise the mesh # does not display). geom = self.currMesh.geometry geom.morphAttributes = {'position': tuple(map(pythreejs.BufferAttribute, morphTargetPositionsRaw)), 'normal': tuple(map(pythreejs.BufferAttribute, morphTargetNormalsRaw))} self.modeMesh = pythreejs.Mesh(geometry=geom, material=self.morphMaterial) self.meshes.remove(self.currMesh) self.currMesh.close() self.currMesh = self.modeMesh self.meshes.add(self.currMesh) else: # Update the exisitng morph position/normal attribute arrays geom = self.currMesh.geometry assert(len(geom.morphAttributes['position']) == self.numSteps) assert(len(geom.morphAttributes['normal' ]) == self.numSteps) for rawArray, attrArray in zip(morphTargetPositionsRaw, geom.morphAttributes['position']): attrArray.array = rawArray for rawArray, attrArray in zip(morphTargetNormalsRaw, geom.morphAttributes['normal']): attrArray.array = rawArray t = np.arcsin(modulations) / np.pi + 0.5 I = np.identity(self.numSteps, dtype=np.float32) tracks = [pythreejs.NumberKeyframeTrack(f'name=.morphTargetInfluences[{i}]', times=t, values=I[:, i].ravel(), interpolation='InterpolateSmooth') for i in range(self.numSteps)] # Stop the old action (if it exists) so that the new animation is not superimposed atop it if (self.action is None): self.action = pythreejs.AnimationAction(pythreejs.AnimationMixer(self.modeMesh), pythreejs.AnimationClip(tracks=tracks), self.modeMesh, loop='LoopPingPong') # Currently it doesn't seem possible to animate both the wireframe and solid mesh synchronously without # nontrivial changes to pythreejs or three.js. # There are some ideas discussed in "https://github.com/jupyter-widgets/pythreejs/issues/262" but # I can't seem t get them to work... # if ((self.wireframeAction is None) and (self.wireframeMesh is not None)): # self.wireframeAction = pythreejs.AnimationAction(pythreejs.AnimationMixer(self.wireframeMesh), # pythreejs.AnimationClip(tracks=tracks), self.wireframeMesh, loop='LoopPingPong') # # self.wireframeAction.syncWith(self.action) # self.wireframeAction.play() controls = [self.action] if (self.mode_selector is not None): controls.append(self.mode_selector) self.controls_layout.children = controls self.layout.children = [self.renderer, self.controls_layout] # Start the animation if requested if (play): self.action.play() self.renderer.resumeRendering()
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