def plot(self): """ Plot the contours. """ import bpy import numpy as np from skimage import measure from blendaviz import colors # Check if there is any time array. if not self.time is None: if not isinstance(self.time, np.ndarray): print("Error: time is not a valid array.") return -1 elif self.time.ndim != 1: print("Error: time array must be 1d.") return -1 # Determine the time index. self.time_index = np.argmin( abs(bpy.context.scene.frame_float - self.time)) else: self.time = np.array([0]) self.time_index = 0 # Check the validity of the input arrays. if not isinstance(self.x, np.ndarray) or not isinstance(self.y, np.ndarray) \ or not isinstance(self.z, np.ndarray): print("Error: x OR y OR z array invalid.") return -1 if not ((self.x.shape[0], self.y.shape[0], self.z.shape[0]) == self.phi.shape[:3]): print("Error: input array shapes invalid.") return -1 if not self.psi is None: if not isinstance(self.psi, np.ndarray): print("Error: psi is not a numpy array.") return -1 if not self.psi.shape[:3] == self.phi.shape[:3]: print("Error: psi and phi must of of the same shape.") # Point the local variables to the correct arrays. if self.x.ndim == 2: self._x = self.x[:, self.time_index] else: self._x = self.x if self.y.ndim == 2: self._y = self.y[:, self.time_index] else: self._y = self.y if self.z.ndim == 2: self._z = self.z[:, self.time_index] else: self._z = self.z if self.phi.ndim == 4: self._phi = self.phi[:, :, :, self.time_index] else: self._phi = self.phi if not self.psi is None: if self.psi.ndim == 4: self._psi = self.psi[:, :, :, self.time_index] else: self._psi = self.psi else: self._psi = None # Prepare the isosurface levels. if isinstance(self.contours, int): level_list = np.linspace(self._phi.min(), self._phi.max(), self.contours + 2)[1:-1] elif isinstance(self.contours, list): level_list = np.array(self.contours) elif isinstance(self.contours, np.ndarray): level_list = self.contours.ravel() else: print("Error: countours invalid. \ Must be either integer or 1d array/list.") return -1 # Prepare the material colors. color_rgba = colors.make_rgba_array(self.color, level_list.shape[0], self.color_map, self.vmin, self.vmax) # Determine the grid spacing. dx = np.partition(np.array(list(set(list(self._x.ravel())))), 1)[1] - \ self._x.min() dy = np.partition(np.array(list(set(list(self._y.ravel())))), 1)[1] - \ self._y.min() dz = np.partition(np.array(list(set(list(self._z.ravel())))), 1)[1] - \ self._z.min() # Delete existing meshes. if not self.mesh_object is None: bpy.ops.object.select_all(action='DESELECT') for mesh_object in self.mesh_object: mesh_object.select_set(state=True) bpy.ops.object.delete() self.mesh_object = None # Delete existing materials. if not self.mesh_material is None: for mesh_material in self.mesh_material: bpy.data.materials.remove(mesh_material) # Prepare the lists of mashes and materials. self.mesh_data = [] self.mesh_object = [] self.mesh_material = [] for idx, level in enumerate(level_list): # Find the vertices and faces of the isosurfaces. vertices, faces = measure.marching_cubes_classic(self._phi, level, spacing=(dx, dy, dz)) vertices[:, 0] += self._x.min() vertices[:, 1] += self._y.min() vertices[:, 2] += self._z.min() # Create mesh and object. self.mesh_data.append(bpy.data.meshes.new("DataMesh")) self.mesh_object.append( bpy.data.objects.new("ObjMesh", self.mesh_data[-1])) # Create mesh from the given data. self.mesh_data[-1].from_pydata(list(vertices), [], list(faces)) self.mesh_data[-1].update(calc_edges=True) # Set the material/color. if self._psi is None: self.__set_material(idx, color_rgba, len(level_list)) else: self.__color_vertices(idx, vertices) self.mesh_data[-1].materials.append(self.mesh_material[-1]) # Link the mesh object with the scene. bpy.context.scene.collection.objects.link(self.mesh_object[-1]) # Group the meshes together. for mesh in self.mesh_object[::-1]: mesh.select_set(state=True) bpy.context.view_layer.objects.active = mesh bpy.ops.object.join() self.mesh_object = bpy.context.object self.mesh_object.select_set(state=False) # Make the grouped meshes the deletable object. self.deletable_object = self.mesh_object return 0
def plot(self): """ Plot the arrows. """ import bpy import numpy as np from mathutils import Vector from blendaviz import colors # Check if there is any time array. if not self.time is None: if not isinstance(self.time, np.ndarray): print("Error: time is not a valid array.") return -1 elif self.time.ndim != 1: print("Error: time array must be 1d.") return -1 # Determine the time index. self.time_index = np.argmin( abs(bpy.context.scene.frame_float - self.time)) else: self.time = np.array([0]) self.time_index = 0 # Check the validity of the input arrays. if not isinstance(self.x, np.ndarray) or not isinstance(self.y, np.ndarray) \ or not isinstance(self.z, np.ndarray): print("Error: x OR y OR z array invalid.") return -1 if not isinstance(self.u, np.ndarray) or not isinstance(self.v, np.ndarray) \ or not isinstance(self.w, np.ndarray): print("Error: u OR v OR w array invalid.") return -1 if not (self.x.shape == self.y.shape == self.z.shape == \ self.u.shape == self.v.shape == self.w.shape): print("Error: input array shapes invalid.") return -1 # Perhaps it is better not to have these as arrays. # # Check the shape of the optional arrays. # if isinstance(self.length, np.ndarray): # if not (self.x.shape == self.length.shape): # print("Error: length array invalid.") # return -1 # else: # self.length = self.length.ravel() # if isinstance(self.radius_shaft, np.ndarray): # if not (self.x.shape == self.radius_shaft.shape): # print("Error: radius_shaft array invalid.") # return -1 # else: # self.radius_shaft = self.radius_shaft.ravel() # if isinstance(self.radius_tip, np.ndarray): # if not (self.x.shape == self.radius_tip.shape): # print("Error: radius_tip array invalid.") # return -1 # else: # self.radius_tip = self.radius_tip.ravel() # Point the local variables to the correct arrays. arrays_with_time_list = ['x', 'y', 'z', 'u', 'v', 'w'] for array_with_time in arrays_with_time_list: array_value = getattr(self, array_with_time) if array_value.ndim == 3: setattr(self, '_' + array_with_time, array_value.ravel()) else: setattr(self, '_' + array_with_time, array_value[..., self.time_index].ravel()) # Delete existing meshes. if not self.arrow_mesh is None: bpy.ops.object.select_all(action='DESELECT') self.arrow_mesh.select_set(True) bpy.ops.object.delete() self.arrow_mesh = None self.arrow_mesh = [] # Delete existing materials. if not self.mesh_material is None: if isinstance(self.mesh_material, list): for mesh_material in self.mesh_material: bpy.data.materials.remove(mesh_material) else: bpy.data.materials.remove(self.mesh_material) # Prepare the material colors. if isinstance(self.color, str): if self.color == 'magnitude': self.color = np.sqrt(self._u**2 + self._v**2 + self._w**2) color_rgba = colors.make_rgba_array(self.color, self._x.shape[0], self.color_map, self.vmin, self.vmax) # Prepare the materials list. self.mesh_material = [] # Plot the arrows. for idx in range(self._x.shape[0]): # Determine the length of the arrow. magnitude = np.sqrt(self._u[idx]**2 + self._v[idx]**2 + self._w[idx]**2) normed = np.array([self._u[idx], self._v[idx], self._w[idx] ]) / magnitude rotation = Vector((0, 0, 1)).rotation_difference( [self._u[idx], self._v[idx], self._w[idx]]).to_euler() # Define the arrow's length. if self.length == 'magnitude': length = magnitude else: length = self.length length *= self.scale # Define the arrows' radii. radius_shaft = self.radius_shaft radius_shaft *= self.scale # Define the arrows' scale. radius_tip = self.radius_tip radius_tip *= self.scale if self.pivot == 'tail': location = [ self._x[idx] + length * normed[0] / 2, self._y[idx] + length * normed[1] / 2, self._z[idx] + length * normed[2] / 2 ] if self.pivot == 'tip': location = [ self._x[idx] - length * normed[0] / 2, self._y[idx] - length * normed[1] / 2, self._z[idx] - length * normed[2] / 2 ] if self.pivot == 'mid' or self.pivot == 'middle': location = [self._x[idx], self._y[idx], self._z[idx]] location = np.array(location) # Construct the arrow using a cylinder and cone. bpy.ops.mesh.primitive_cylinder_add(radius=radius_shaft, depth=length / 2, location=location - normed * length / 4, rotation=rotation) self.arrow_mesh.append(bpy.context.object) bpy.ops.mesh.primitive_cone_add(radius1=radius_tip, radius2=0, depth=length / 2, location=location + normed * length / 4, rotation=rotation) self.arrow_mesh.append(bpy.context.object) self.__set_material(idx, color_rgba) # Group the meshes together. for mesh in self.arrow_mesh[::-1]: mesh.select_set(state=True) bpy.ops.object.join() self.arrow_mesh = bpy.context.object self.arrow_mesh.select_set(state=False) # Make the mesh the deletable object. self.deletable_object = self.arrow_mesh return 0
def plot(self): """ Plot the streamlines. """ import bpy from blendaviz import colors # Delete existing curves. bpy.ops.object.select_all(action='DESELECT') if self.mesh is not None: bpy.ops.object.select_all(action='DESELECT') self.mesh.select_set(True) bpy.ops.object.delete() self.curve_data = None self.curve_object = None # Delete existing materials. if self.mesh_material is not None: bpy.ops.object.select_all(action='DESELECT') for mesh_material in self.mesh_material: bpy.data.materials.remove(mesh_material) self.mesh_material = None # Prepare the seeds. self.__generate_seed_points() # Prepare the material colors. if self.color_scalar is None: color_rgba = colors.make_rgba_array(self.color, self.n_seeds, self.color_map, self.vmin, self.vmax) self.prepare_field_function() # Empty the tracers before calculating new. self.tracers = [] if self.n_proc == 1: # Compute the traces serially for tracer_idx in range(self.n_seeds): self.tracers.append(self.__tracer(xx=self.seeds[tracer_idx])) else: # Compute the positions along the streamlines. import multiprocessing as mp queue = mp.Queue() processes = [] results = [] for i_proc in range(self.n_proc): processes.append( mp.Process(target=self.__tracer_multi, args=(queue, i_proc, self.n_proc))) for i_proc in range(self.n_proc): processes[i_proc].start() for i_proc in range(self.n_proc): results.append(queue.get()) for i_proc in range(self.n_proc): processes[i_proc].join() # set the record straight result_order = [] for i_proc in range(self.n_proc): result_order.append(results[i_proc][1]) for i in range(self.n_proc): ith_result = result_order.index(i) self.tracers.extend(results[ith_result][0]) # tracers # Plot the streamlines/tracers. self.curve_data = [] self.curve_object = [] self.poly_line = [] self.mesh_material = [] self.mesh_texture = [] for tracer_idx in range(self.n_seeds): self.curve_data.append( bpy.data.curves.new('DataCurve', type='CURVE')) self.curve_data[-1].dimensions = '3D' self.curve_object.append( bpy.data.objects.new('ObjCurve', self.curve_data[-1])) # Set the origin to the last point. self.curve_object[-1].location = tuple( (self.tracers[tracer_idx][-1, 0], self.tracers[tracer_idx][-1, 1], self.tracers[tracer_idx][-1, 2])) # Add the rest of the curve. self.poly_line.append(self.curve_data[-1].splines.new('POLY')) self.poly_line[-1].points.add(self.tracers[tracer_idx].shape[0]) for param in range(self.tracers[tracer_idx].shape[0]): self.poly_line[-1].points[param].co = ( self.tracers[tracer_idx][param, 0] - self.tracers[tracer_idx][-1, 0], self.tracers[tracer_idx][param, 1] - self.tracers[tracer_idx][-1, 1], self.tracers[tracer_idx][param, 2] - self.tracers[tracer_idx][-1, 2], 0) # Add 3d structure. self.curve_data[-1].splines.data.bevel_depth = self.radius self.curve_data[-1].splines.data.bevel_resolution = self.resolution self.curve_data[-1].splines.data.fill_mode = 'FULL' # Set the material/color. if self.color_scalar is None: self.__set_material_color(tracer_idx, color_rgba) else: self.__set_material_texture(tracer_idx) # Link the curve object with the scene. bpy.context.scene.collection.objects.link(self.curve_object[-1]) # Group the curves together. bpy.ops.object.select_all( action='DESELECT') # deselect any already selected objects for curve_object in self.curve_object[::-1]: curve_object.select_set(state=True) bpy.context.view_layer.objects.active = curve_object bpy.ops.object.join() self.mesh = bpy.context.selected_objects[0] self.mesh.select_set(False) # Make the grouped meshes the deletable object. self.deletable_object = self.mesh return 0
def __color_vertices(self, idx, vertices): """ Set the mesh texture. call signature: __color_vertices(idx, vertices): Keyword arguments: *idx*: Index of the material. *vertices*: Vertices of the isosurfaces. """ import bpy import numpy as np from blendaviz import colors # Find interpolated values for Psi on the vertex location. psi_vertices = np.zeros(vertices.shape[0]) for vertex_idx in range(vertices.shape[0]): vertex = vertices[vertex_idx] # Find the xyz indices for the interpolation. ix1 = sum(self._x <= vertex[0]) - 1 iy1 = sum(self._y <= vertex[1]) - 1 iz1 = sum(self._z <= vertex[2]) - 1 ix2 = ix1 + 1 iy2 = iy1 + 1 iz2 = iz1 + 1 if ix2 >= self._phi.shape[0]: ix2 = self._phi.shape[0] if iy2 >= self._phi.shape[1]: iy2 = self._phi.shape[1] if iz2 >= self._phi.shape[2]: iz2 = self._phi.shape[2] # Perform a trilinear interpolation. psi_vertices[vertex_idx] = np.mean(self._psi[ix1:ix2 + 1, iy1:iy2 + 1, iz1:iz2 + 1]) # Generate the colors for the vertices. color_rgba = colors.make_rgba_array(psi_vertices, vertices.shape[0], self.color_map, self.vmin, self.vmax) # Create a vertex color layer for the mesh. vcol_layer = self.mesh_object[idx].data.vertex_colors.new() # Add a new material. self.mesh_material.append(bpy.data.materials.new('material')) self.mesh_material[-1].use_nodes = True node_tree = self.mesh_material[-1].node_tree nodes = node_tree.nodes nodes.remove(nodes[1]) node_diffuse = nodes.new(type='ShaderNodeBsdfDiffuse') node_tree.links.new(node_diffuse.outputs['BSDF'], nodes[0].inputs['Surface']) node_vertex_shader = nodes.new(type='ShaderNodeVertexColor') node_tree.links.new(node_vertex_shader.outputs['Color'], node_diffuse.inputs['Color']) node_vertex_shader.layer_name = 'Col' # Change the color of the vertices. for poly in self.mesh_object[idx].data.polygons: for loop_index in poly.loop_indices: loop_vert_index = self.mesh_object[idx].data.loops[ loop_index].vertex_index vcol_layer.data[loop_index].color = color_rgba[loop_vert_index]
def plot(self): """ Plot a as a line, tube or shapes. """ import bpy import numpy as np from blendaviz import colors # Check if there is any time array. if not self.time is None: if not isinstance(self.time, np.ndarray): print("Error: time is not a valid array.") return -1 elif self.time.ndim != 1: print("Error: time array must be 1d.") return -1 # Determine the time index. self.time_index = np.argmin(abs(bpy.context.scene.frame_float - self.time)) else: self.time = np.array([0]) self.time_index = 0 # Point the local variables to the correct time index. arrays_with_time_list = ['x', 'y', 'z', 'radius', 'rotation_x', 'rotation_y', 'rotation_z'] for array_with_time in arrays_with_time_list: array_value = getattr(self, array_with_time) if not isinstance(array_value, np.ndarray): setattr(self, '_' + array_with_time, array_value*np.ones(self.x.shape[0])) elif array_value.ndim == 1: setattr(self, '_' + array_with_time, array_value) else: setattr(self, '_' + array_with_time, array_value[:, self.time_index]) # Delete existing curve. if not self.curve_data is None: bpy.data.curves.remove(self.curve_data) self.curve_data = None # Delete existing meshes. self.__delete_meshes__() # Delete existing materials. self.__delete_materials__() # Create the bezier curve. if self.marker is None: # Transform color string into rgba. color_rgba = colors.make_rgba_array(self.color, 1) self.curve_data = bpy.data.curves.new('DataCurve', type='CURVE') self.curve_data.dimensions = '3D' self.curve_object = bpy.data.objects.new('ObjCurve', self.curve_data) # Set the origin to the last point. self.curve_object.location = tuple((self._x[-1], self._y[-1], self._z[-1])) # Add the rest of the curve. self.poly_line = self.curve_data.splines.new('POLY') self.poly_line.points.add(self._x.shape[0]) for param in range(self._x.shape[0]): self.poly_line.points[param].co = (self._x[param] - self._x[-1], self._y[param] - self._y[-1], self._z[param] - self._z[-1], 0) # Add 3d structure. self.curve_data.splines.data.bevel_depth = self._radius[0] self.curve_data.splines.data.bevel_resolution = self.resolution self.curve_data.splines.data.fill_mode = 'FULL' # Set the material/color. self.mesh_material = bpy.data.materials.new('material') self.mesh_material.diffuse_color = color_rgba[0] self.mesh_material.roughness = self.roughness self.curve_object.active_material = self.mesh_material # Set the emission. if not self.emission is None: self.mesh_material.use_nodes = True node_tree = self.mesh_material.node_tree nodes = node_tree.nodes # Remove and Diffusive BSDF node. nodes.remove(nodes[1]) node_emission = nodes.new(type='ShaderNodeEmission') # Change the input of the ouput node to emission. node_tree.links.new(node_emission.outputs['Emission'], nodes[0].inputs['Surface']) # Adapt emission and color. node_emission.inputs['Color'].default_value = color_rgba node_emission.inputs['Strength'].default_value = self.emission # Link the curve object with the scene. bpy.context.scene.collection.objects.link(self.curve_object) # Make this curve the object to be deleted. self.deletable_object = self.curve_object # Transform color string into rgb. color_rgba = colors.make_rgba_array(self.color, self._x.shape[0]) # Plot the markers. if not self.marker is None: self.marker_mesh = [] if self.marker == 'cone': for idx in range(self._x.shape[0]): bpy.ops.mesh.primitive_cone_add(location=(self._x[idx], self._y[idx], self._z[idx]), radius1=self._radius[idx], depth=2*self._radius[idx], rotation=(self._rotation_x[idx], self._rotation_y[idx], self._rotation_z[idx])) self.marker_mesh.append(bpy.context.object) if self.marker == 'cube': for idx in range(self._x.shape[0]): bpy.ops.mesh.primitive_cube_add(location=(self._x[idx], self._y[idx], self._z[idx]), size=self._radius[idx], rotation=(self._rotation_x[idx], self._rotation_y[idx], self._rotation_z[idx])) self.marker_mesh.append(bpy.context.object) if self.marker == 'cylinder': for idx in range(self._x.shape[0]): bpy.ops.mesh.primitive_cylinder_add(location=(self._x[idx], self._y[idx], self._z[idx]), radius=self._radius[idx], depth=2*self._radius[idx], rotation=(self._rotation_x[idx], self._rotation_y[idx], self._rotation_z[idx])) self.marker_mesh.append(bpy.context.object) if self.marker == 'ico_sphere': for idx in range(self._x.shape[0]): bpy.ops.mesh.primitive_ico_sphere_add(location=(self._x[idx], self._y[idx], self._z[idx]), radius=self._radius[idx], rotation=(self._rotation_x[idx], self._rotation_y[idx], self._rotation_z[idx])) self.marker_mesh.append(bpy.context.object) if self.marker == 'monkey': for idx in range(self._x): bpy.ops.mesh.primitive_monkey_add(location=(self._x[idx], self._y[idx], self._z[idx]), size=self._radius[idx], rotation=(self._rotation_x[idx], self._rotation_y[idx], self._rotation_z[idx])) self.marker_mesh.append(bpy.context.object) if self.marker == 'torus': for idx in range(self._x.shape[0]): bpy.ops.mesh.primitive_torus_add(location=(self._x[idx], self._y[idx], self._z[idx]), major_radius=self._radius[idx], minor_radius=0.25*self._radius[idx], abso_major_rad=1.25*self._radius[idx], abso_minor_rad=0.75*self._radius[idx], rotation=(self._rotation_x[idx], self._rotation_y[idx], self._rotation_z[idx])) self.marker_mesh.append(bpy.context.object) if self.marker == 'uv_sphere': for idx in range(self._x.shape[0]): bpy.ops.mesh.primitive_uv_sphere_add(location=(self._x[idx], self._y[idx], self._z[idx]), radius=self._radius[idx], rotation=(self._rotation_x[idx], self._rotation_y[idx], self._rotation_z[idx])) self.marker_mesh.append(bpy.context.object) if isinstance(self.marker, bpy.types.Object): if self.marker.type == 'MESH': bpy.context.object.select = False self.marker.select = True for idx in range(self._x.shape[0]): bpy.ops.object.duplicate_move(OBJECT_OT_duplicate={"linked":False, "mode":'TRANSLATION'}) bpy.context.object.location = (self._x[idx], self._y[idx], self._z[idx]) bpy.context.object.rotation_euler = (self._rotation_x[idx], self._rotation_y[idx], self._rotation_z[idx]) self.marker.select = False self.marker_mesh.append(bpy.context.object) # Set the material and color. if not self.marker is None: color_is_array = False if isinstance(color_rgba, np.ndarray): if color_rgba.ndim == 2: color_is_array = True if any([color_is_array, isinstance(self.roughness, np.ndarray), isinstance(self.emission, np.ndarray)]): self.mesh_material = [] for idx in range(self._x.shape[0]): self.mesh_material.append(bpy.data.materials.new('material')) if color_is_array: self.mesh_material[idx].diffuse_color = tuple(color_rgba[idx]) else: self.mesh_material[idx].diffuse_color = color_rgba if isinstance(self.roughness, np.ndarray): self.mesh_material[idx].roughness = self.roughness[idx] else: self.mesh_material[idx].roughness = self.roughness if isinstance(self.emission, np.ndarray): self.mesh_material[idx].use_nodes = True node_tree = self.mesh_material[idx].node_tree nodes = node_tree.nodes # Remove and Diffusive BSDF node. nodes.remove(nodes[1]) node_emission = nodes.new(type='ShaderNodeEmission') # Change the input of the ouput node to emission. node_tree.links.new(node_emission.outputs['Emission'], nodes[0].inputs['Surface']) # Adapt emission and color. if color_is_array: node_emission.inputs['Color'].default_value = tuple(color_rgba[idx]) else: node_emission.inputs['Color'].default_value = color_rgba node_emission.inputs['Strength'].default_value = self.emission[idx] self.marker_mesh[idx].active_material = self.mesh_material[idx] else: self.mesh_material = bpy.data.materials.new('material') self.mesh_material.diffuse_color = color_rgba self.mesh_material.roughness = self.roughness if not self.emission is None: self.mesh_material.use_nodes = True node_tree = self.mesh_material.node_tree nodes = node_tree.nodes # Remove and Diffusive BSDF node. nodes.remove(nodes[1]) node_emission = nodes.new(type='ShaderNodeEmission') # Change the input of the ouput node to emission. node_tree.links.new(node_emission.outputs['Emission'], nodes[0].inputs['Surface']) # Adapt emission and color. node_emission.inputs['Color'].default_value = color_rgba node_emission.inputs['Strength'].default_value = self.emission for idx, mesh in enumerate(self.marker_mesh): mesh.active_material = self.mesh_material # Group the meshes together. if not self.marker is None: for mesh in self.marker_mesh[::-1]: mesh.select_set(state=True) bpy.ops.object.join() self.marker_mesh = bpy.context.object self.marker_mesh.select_set(state=False) # Make the grouped meshes the deletable object. self.deletable_object = self.marker_mesh self.update_globals() return 0