def scale_verts_by_bone(self, pbone, armature, mesh_object, vert_co, weight=1.0): # verts = mesh_object.data.vertices bone = armature.data.bones[pbone.name] bone_head = self.init_bone_positions[bone.name]["head"] bone_tail = self.init_bone_positions[bone.name]["tail"] bone_axis_x = (bone_tail - bone_head).normalized().xz bone_axis_y = bone_axis_x.orthogonal().normalized() world_axis_x = Vector((bone_axis_x.dot(Vector((1, 0))), bone_axis_y.dot(Vector((1, 0))))) world_axis_y = Vector((bone_axis_x.dot(Vector((0, 1))), bone_axis_y.dot(Vector((0, 1))))) bone_system_origin = (mesh_object.matrix_world.inverted() * (armature.matrix_world * bone_head)).xz bone_scale = pbone.matrix.to_scale() bone_scale_2d = Vector(( self.lerp( 1.0, bone_scale.y, weight), self.lerp(1.0, bone_scale.x, weight) )) vert_delta_co = vert_co.xz vert_delta_co -= bone_system_origin vert_delta_co = Vector((bone_axis_x.dot(vert_delta_co), bone_axis_y.dot(vert_delta_co))) vert_delta_co = Vector((vert_delta_co.x * bone_scale_2d.x, vert_delta_co.y * bone_scale_2d.y)) vert_delta_co = Vector((world_axis_x.dot(vert_delta_co), world_axis_y.dot(vert_delta_co))) vert_delta_co += bone_system_origin scaled_vert_co = Vector((vert_delta_co.x, 0, vert_delta_co.y)) return scaled_vert_co
def _clip_face(self, verts, clip_planes, clip_side='negative'): for clip in clip_planes: clip_normal = Vector(clip.normal) clip_distance = clip.distance if clip_side == 'positive': clip_normal = -clip_normal clip_distance = -clip_distance new_verts = [] for cur_index, cur_vert in enumerate(verts): prev_index = (cur_index + len(verts) - 1) % len(verts) prev_vert = verts[prev_index] dist_cur = clip_normal.dot(cur_vert) + clip_distance dist_prev = clip_normal.dot(prev_vert) + clip_distance if dist_cur >= 0 and dist_prev >= 0: new_verts.append(cur_vert) elif dist_cur >= 0 and dist_prev < 0: lerp = -dist_prev / (dist_cur - dist_prev) new_verts.append(prev_vert + lerp * (cur_vert - prev_vert)) new_verts.append(cur_vert) elif dist_cur < 0 and dist_prev >= 0: lerp = -dist_prev / (dist_cur - dist_prev) new_verts.append(prev_vert + lerp * (cur_vert - prev_vert)) verts = new_verts return verts
def autoscale(self): symmetry = nmv.physics.hex_symmetry_space if self.hex_mode else nmv.physics.symmetry_space for vert in self.bm.verts: u = Vector(self.field[vert.index]) v = u.cross(vert.normal) ang = 0 last_vec = u for loop in vert.link_loops: vert1 = loop.link_loop_next.vert vert2 = loop.link_loop_next.link_loop_next.vert if not last_vec: vert1_vec = Vector(self.field[vert1.index]) else: vert1_vec = last_vec vert2_vec = nmv.physics.best_matching_vector( symmetry(self.field[vert2.index], vert2.normal), vert1_vec) vert1_vec = Vector((vert1_vec.dot(u), vert1_vec.dot(v))) vert2_vec = Vector((vert2_vec.dot(u), vert2_vec.dot(v))) ang += vert1_vec.angle_signed(vert2_vec) self.scale[vert.index] = ang for i in range(20): self.scale += self.scale[self.walk_edges(0)] self.scale /= 2 self.scale -= self.scale.min() self.scale /= self.scale.max()
def uvproj_boxmap(co, normal, uv_scale, uv_offset): an = Vector([fabs(x) for x in normal]) box_mask = hlsl_step(an.yzx, an) box_mask = Vector(hlsl_product(box_mask, hlsl_step(an.zxy, an))) sn = hlsl_sign(normal) box_mask = hlsl_product(box_mask, Vector((sn.x, -sn.y, 1.0))) uv_x = box_mask.dot(co.yxx) * uv_scale.x box_mask = hlsl_product(box_mask, Vector((sn.x, -sn.y, sn.z))) uv_y = box_mask.dot(co.zzy) * uv_scale.y return uv_x, uv_y
def add_object(self, context): verts = [] edges = [] faces = [] errorOffset = Vector((0.02, 0.02, 0.02)) AI_X = self.AIRadius * cos(self.AIAngle) AI_Y = self.AIRadius * sin(self.AIAngle) AIV = Vector((AI_X, AI_Y, 0)) Player_X = self.PlayerDistance * cos(self.PlayerAngle) Player_Y = self.PlayerDistance * sin(self.PlayerAngle) PV = Vector((Player_X, Player_Y, 0)) try: AngularDistance = acos( (AIV.dot(PV) / (self.AIRadius * self.PlayerDistance))) * 180 / pi except ValueError: AIV -= errorOffset AngularDistance = acos( (AIV.dot(PV) / (self.AIRadius * self.PlayerDistance))) * 180 / pi print("Angular Distance: ", AngularDistance - self.AIFOV) Total_X = AI_X + Player_X Total_Y = AI_Y + Player_Y AIFOV_LX = self.AIRadius * cos(self.AIAngle + self.AIFOV) AIFOV_LY = self.AIRadius * sin(self.AIAngle + self.AIFOV) AIFOV_RX = self.AIRadius * cos(self.AIAngle - self.AIFOV) AIFOV_RY = self.AIRadius * sin(self.AIAngle - self.AIFOV) verts.append(Vector((0, 0, 0))) verts.append(Vector((AIFOV_LX, AIFOV_LY, 0))) verts.append(Vector((AIFOV_RX, AIFOV_RY, 0))) edges.append([0, 1]) edges.append([1, 2]) edges.append([2, 0]) verts.append(Vector((Player_X, Player_Y, 0))) edges.append([0, len(verts) - 1]) if (AngularDistance - self.AIFOV * 180 / pi <= 0 and self.AIRadius >= self.PlayerDistance): faces.append([2, 1, 0]) mesh = bpy.data.meshes.new(name="New Object Mesh") mesh.from_pydata(verts, edges, faces) object_data_add(context, mesh, operator=self)
def detect_singularities(self): symmetry = nmv.physics.hex_symmetry_space if self.hex_mode else nmv.physics.symmetry_space cache = {} def symmetry_cached(vert): if vert in cache: return cache[vert] else: s = symmetry(self.field[vert.index], vert.normal) cache[vert] = s return s singularities = [] if not self.hex_mode: for face in self.bm.faces: v0 = face.verts[0] v1 = face.verts[1] v2 = face.verts[2] vec0 = self.field[v0.index] vec1 = nmv.physics.best_matching_vector( symmetry_cached(v1), vec0) v2_symmetry = symmetry_cached(v2) match0 = nmv.physics.best_matching_vector(v2_symmetry, vec0) match1 = nmv.physics.best_matching_vector(v2_symmetry, vec1) if match0.dot(match1) < 0.5: singularities.append(face.calc_center_median()) else: for vert in self.bm.verts: ang = 0 u = nmv.physics.random_tangent_vector(vert.normal) v = u.cross(vert.normal) last_vec = None for loop in vert.link_loops: vert1 = loop.link_loop_next.vert vert2 = loop.link_loop_next.link_loop_next.vert if not last_vec: vert1_vec = symmetry_cached(vert1)[0] else: vert1_vec = last_vec vert2_vec = nmv.physics.best_matching_vector( symmetry_cached(vert2), vert1_vec) last_vec = vert2_vec vert1_vec = Vector((vert1_vec.dot(u), vert1_vec.dot(v))) vert2_vec = Vector((vert2_vec.dot(u), vert2_vec.dot(v))) ang += vert1_vec.angle_signed(vert2_vec) if ang > 0.9: singularities.append(vert.co) self.singularities = singularities
def lookatlh(eye, target, up): mz = Vector((eye[0] - target[0], eye[1] - target[1], eye[2] - target[2])).normalized() # inverse line of sight mx = Vector(up.cross(mz)).normalized() my = Vector(mz.cross(mx)).normalized() tx = mx.dot(eye) ty = my.dot(eye) tz = mz.dot(eye) * -1 mat = Matrix() mat[0] = mx[0], mz[0], my[0], 0 mat[1] = mx[2], mz[2], my[2], 0 mat[2] = mx[1], mz[1], my[1], 0 mat[3] = tx, tz, ty, 1 return mat
def execute(self, context): # extract user direction val = int(self.mode[-3:]) if self.mode[0] == "N": val *= -1 # No idea what changed if bpy.app.version > (2, 92, 0): val *= -1 # get viewport rel to world axis and rotate rm = context.space_data.region_3d.view_matrix v = Vector(rm[2]).to_3d() x = v.dot(Vector((1, 0, 0))) y = v.dot(Vector((0, 1, 0))) z = v.dot(Vector((0, 0, 1))) xa, ya, za = abs(x), abs(y), abs(z) if xa > ya and xa > za: if val < 0 and x < 0 or val > 0 > x: val *= -1 axis = True, False, False oa = "X" elif ya > xa and ya > za: if val < 0 and y < 0 or val > 0 > y: val *= -1 axis = False, True, False oa = "Y" else: if val < 0 and z < 0 or val > 0 > z: val *= -1 axis = False, False, True oa = "Z" bpy.ops.transform.rotate(value=radians(val), orient_axis=oa, orient_type='GLOBAL', orient_matrix_type='GLOBAL', constraint_axis=axis, mirror=True, use_proportional_edit=False, proportional_edit_falloff='SMOOTH', proportional_size=1, use_proportional_connected=False, use_proportional_projected=False) return {"FINISHED"}
def cast_rays(obj_eval: Object, point: Vector, direction: Vector, mini_dist: float, round_type: str = "CEILING", edge_len: int = 0): """ obj_eval -- source object to test intersections for point -- starting point for ray casting direction -- cast ray in this direction mini_dist -- Vector with miniscule amount to add after intersection round_type -- round final intersection location Vector with this type edge_len -- distance to test for intersections """ # initialize variables first_direction = False first_intersection = None next_intersection_loc = None last_intersection = None edge_intersects = False edge_len2 = round(edge_len + 0.000001, 6) starting_point = point intersections = 0 # cast rays until no more rays to cast while True: _, location, normal, index = obj_eval.ray_cast( starting_point, direction) #distance=edge_len*1.00000000001) if index == -1: break if intersections == 0: first_direction = direction.dot(normal) if edge_len != 0: dist = (location - point).length # get first and last intersection (used when getting materials of nearest (first or last intersected) face) if dist <= edge_len2: if intersections == 0: edge_intersects = True first_intersection = { "idx": index, "dist": dist, "loc": location, "normal": normal } last_intersection = { "idx": index, "dist": edge_len - dist, "loc": location, "normal": normal } # set next_intersection_loc if next_intersection_loc is None: next_intersection_loc = location.copy() intersections += 1 location = vec_round(location, precision=6, round_type=round_type) starting_point = location + mini_dist if edge_len != 0: return intersections, first_direction, first_intersection, next_intersection_loc, last_intersection, edge_intersects else: return intersections, first_direction
def project(v, normal): p1, p2 = projectionAxis[tuple(normal)] p1, p2 = Vector(p1), Vector(p2) result = (p1.dot(v), p2.dot(v)) print("res", result, "normal", tuple(normal)) return result
def execute(self, context): bm = bmesh.new() bm.from_mesh(bpy.context.active_object.data) strength = bpy.context.scene.relax_strength tot = 50 wm = bpy.context.window_manager for i in range(strength): wm.progress_begin(0, tot) for i in range(tot): wm.progress_update(i) for vert in bm.verts: avg = Vector() for edge in vert.link_edges: other = edge.other_vert(vert) avg += other.co avg /= len(vert.link_edges) avg -= vert.co avg -= avg.dot(vert.normal) * vert.normal vert.co += avg bm.normal_update() wm.progress_end() bm.to_mesh(bpy.context.active_object.data) bpy.context.active_object.data.update() bpy.context.view_layer.update() return {'FINISHED'}
def by_edge_dir(self, vertices, edges, faces): percent = self.inputs['Percent'].sv_get(default=[1.0])[0][0] direction = self.inputs['Direction'].sv_get()[0][0] dirvector = Vector(direction) dirlength = dirvector.length if dirlength <= 0: raise ValueError("Direction vector must have nonzero length!") values = [] for i, j in edges: u = vertices[i] v = vertices[j] edge = Vector(u) - Vector(v) if edge.length > 0: value = abs(edge.dot(dirvector)) / (edge.length * dirlength) else: value = 0 values.append(value) threshold = self.map_percent(values, percent) out_edges_mask = [(value >= threshold) for value in values] out_edges = [ edge for (edge, mask) in zip(edges, out_edges_mask) if mask ] out_verts_mask = self.select_verts_by_faces(out_edges, vertices) out_faces_mask = self.select_faces_by_verts(out_verts_mask, faces) return out_verts_mask, out_edges_mask, out_faces_mask
def find_autocorrect_axis_angle( dir_vec_p_norm_b: Vector, bake_matrix: Matrix) -> Tuple[Vector, float]: """ Given a vector of where the light will be pointed in X-Plane and our real rotation, find the Axis-Angle for how we need to rotate via animation to make the difference """ def clamp(num: float, minimum: float, maximum: float) -> float: if num < minimum: return minimum elif num > maximum: return maximum else: return num # Multiple bake matrix by Vector to get the direction of the Blender object dir_vec_b_norm = self.get_light_direction_b() # P is start rotation, and B is stop. As such, we have our axis of rotation. # "We take the X-Plane light and turn it until it matches what the artist wanted" axis_angle_vec_b = dir_vec_p_norm_b.cross(dir_vec_b_norm) dot_product_p_b = dir_vec_p_norm_b.dot(dir_vec_b_norm) if dot_product_p_b < 0: axis_angle_theta = math.pi - math.asin( clamp(axis_angle_vec_b.magnitude, -1.0, 1.0)) else: axis_angle_theta = math.asin( clamp(axis_angle_vec_b.magnitude, -1.0, 1.0)) return axis_angle_vec_b, axis_angle_theta
def region_2d_to_vector_3d(region, rv3d, coord): """ Return a direction vector from the viewport at the specific 2d region coordinate. :arg region: region of the 3D viewport, typically bpy.context.region. :type region: :class:`bpy.types.Region` :arg rv3d: 3D region data, typically bpy.context.space_data.region_3d. :type rv3d: :class:`bpy.types.RegionView3D` :arg coord: 2d coordinates relative to the region: (event.mouse_region_x, event.mouse_region_y) for example. :type coord: 2d vector :return: normalized 3d vector. :rtype: :class:`mathutils.Vector` """ from mathutils import Vector viewinv = rv3d.view_matrix.inverted() if rv3d.is_perspective: persinv = rv3d.perspective_matrix.inverted() out = Vector(((2.0 * coord[0] / region.width) - 1.0, (2.0 * coord[1] / region.height) - 1.0, -0.5 )) w = out.dot(persinv[3].xyz) + persinv[3][3] return ((persinv * out) / w) - viewinv.translation else: return viewinv.col[2].xyz.normalized()
def signed_area(verts, normal): total = Vector((0, 0, 0)) l = len(verts) for i in range(0, l): total += verts[i].cross(verts[(i+1) % l]) result = total.dot(normal) return result / 2
def by_edge_dir(self, vertices, edges, faces): percent = self.inputs['Percent'].sv_get(default=[1.0])[0][0] direction = self.inputs['Direction'].sv_get()[0][0] dirvector = Vector(direction) dirlength = dirvector.length if dirlength <= 0: raise ValueError("Direction vector must have nonzero length!") values = [] for i, j in edges: u = vertices[i] v = vertices[j] edge = Vector(u) - Vector(v) if edge.length > 0: value = abs(edge.dot(dirvector)) / (edge.length * dirlength) else: value = 0 values.append(value) threshold = self.map_percent(values, percent) out_edges_mask = [(value >= threshold) for value in values] out_edges = [edge for (edge, mask) in zip (edges, out_edges_mask) if mask] out_verts_mask = self.select_verts_by_faces(out_edges, vertices) out_faces_mask = self.select_faces_by_verts(out_verts_mask, faces) return out_verts_mask, out_edges_mask, out_faces_mask
def region_2d_to_vector_3d(region, rv3d, coord): """ Return a direction vector from the viewport at the specific 2d region coordinate. :arg region: region of the 3D viewport, typically bpy.context.region. :type region: :class:`bpy.types.Region` :arg rv3d: 3D region data, typically bpy.context.space_data.region_3d. :type rv3d: :class:`bpy.types.RegionView3D` :arg coord: 2d coordinates relative to the region: (event.mouse_region_x, event.mouse_region_y) for example. :type coord: 2d vector :return: normalized 3d vector. :rtype: :class:`mathutils.Vector` """ from mathutils import Vector viewinv = rv3d.view_matrix.inverted() if rv3d.is_perspective: persinv = rv3d.perspective_matrix.inverted() width = region.width height = region.height out = Vector(((2.0 * coord[0] / width) - 1.0, (2.0 * coord[1] / height) - 1.0, -0.5)) w = out.dot(persinv[3].xyz) + persinv[3][3] view_vector = ((persinv @ out) / w) - viewinv.translation else: view_vector = -viewinv.col[2].xyz view_vector.normalize() return view_vector
def vector_to_mesh(vector, name='vector', width=1e-2, frac_length_head=0.2, frac_width_head=2, n=20): w = Vector(vector) length = w.length w.normalize() u = Vector((0, 0, 0)) i = numpy.argmin(numpy.absolute(vector)) u[i] = 1 u = (u - u.dot(w) * w).normalized() v = w.cross(u) # t = numpy.linspace(0, 2 * numpy.pi, n + 1) t = t[:n] # verts = [(width * (1 + frac_width_head * max(0, j - 1)) * (numpy.cos(t[i]) * u + numpy.sin(t[i]) * v) + length * (1 - frac_length_head) * min(1, j) * w) for j in range(3) for i in range(n)] verts.append((length * w)) # faces = [(j * n + i, j * n + (i + 1) % n, (j + 1) * n + (i + 1) % n, (j + 1) * n + i) for j in range(2) for i in range(n)] faces.extend([(3 * n, 2 * n + i, 2 * n + (i + 1) % n) for i in range(n)]) # return pydata_to_mesh(verts, faces, edges=[], name=name)
def generate_perpendicular_bone_direction(this_bone_matrix: Matrix, parent_dir: Vector): # Pick a vector that's sort of in the same direction we want the bone to point in # (i.e. we don't want the bone to go in/out, so don't pick (0, 0, 1)) target_dir = Vector((0, 1, 0)) if abs(parent_dir.dot(target_dir)) > 0.99: # Parent and proposed perpendicular direction are basically the same axis, cross product won't work # Choose a different one target_dir = Vector((1, 0, 0)) # parent_dir cross target_dir creates a vector that's guaranteed to be perpendicular to both of them. perp_dir = parent_dir.cross(target_dir).normalized() print(f"{parent_dir} X {target_dir} = {perp_dir}") # Then, parent_dir cross perp_dir will create a vector that is both # 1) perpendicular to parent_dir # 2) in the same sort of direction as target_dir # use this vector as our tail_delta tail_delta_dir = parent_dir.cross(perp_dir).normalized() print(f"{parent_dir} X {perp_dir} = {tail_delta_dir}") # Cross product can have bad symmetry - bones on opposite sides of the skeleton can get deltas that look weird # Fix this by picking the delta which moves the tail the farthest possible distance from the origin # This will choose consistent directions regardless of which side of the vertical axis you are on distance_from_origin_with_positive = ( this_bone_matrix @ (tail_delta_dir * 0.1)).length distance_from_origin_with_negative = ( this_bone_matrix @ (-tail_delta_dir * 0.1)).length if distance_from_origin_with_negative > distance_from_origin_with_positive: tail_delta_dir = -tail_delta_dir return tail_delta_dir
def region_2d_to_orig_and_view_vector(region, rv3d, coord): viewinv = rv3d.view_matrix.inverted() persinv = rv3d.perspective_matrix.inverted() x, y = region.width, region.height dx = (2.0 * coord[0] / x) - 1.0 dy = (2.0 * coord[1] / y) - 1.0 if rv3d.is_perspective: origin_start = viewinv.translation.copy() out = Vector((dx, dy, -0.5)) w = out.dot(persinv[3].xyz) + persinv[3][3] view_vector = ((persinv @ out) / w) - origin_start else: view_vector = -viewinv.col[2].xyz origin_start = ((persinv.col[0].xyz * dx) + (persinv.col[1].xyz * dy) + viewinv.translation) # S.L in ortho view, origin may be plain wrong so add arbitrary distance .. origin_start -= view_vector * 1000 view_vector.normalize() return view_vector, origin_start
def _is_flat_face(normal): a = Vector(normal[0]) for n in normal[1:]: dp = a.dot(Vector(n)) if dp < 0.99999 or dp > 1.00001: return False return True
def _region_2d_to_orig_and_vect(self, coord, clamp=None): viewinv = self._rv3d.view_matrix.inverted() persinv = self._rv3d.perspective_matrix.inverted() dx = (2.0 * coord[0] / self._region.width) - 1.0 dy = (2.0 * coord[1] / self._region.height) - 1.0 if self._rv3d.is_perspective: origin_start = viewinv.translation.copy() out = Vector((dx, dy, -0.5)) w = out.dot(persinv[3].xyz) + persinv[3][3] view_vector = ((persinv @ out) / w) - origin_start else: view_vector = -viewinv.col[2].xyz origin_start = ((persinv.col[0].xyz * dx) + (persinv.col[1].xyz * dy) + persinv.translation) if self._rv3d.view_perspective != 'CAMERA': # this value is scaled to the far clip already origin_offset = persinv.col[2].xyz if clamp is not None: # Since far clip can be a very large value, # the result may give with numeric precision issues. # To avoid this problem, you can optionally clamp the far clip to a # smaller value based on the data you're operating on. if clamp < 0.0: origin_offset.negate() clamp = -clamp if origin_offset.length > clamp: origin_offset.length = clamp origin_start -= origin_offset view_vector.normalize() return origin_start, view_vector
def castRays(obj: Object, point: Vector, direction: Vector, miniDist: float, roundType: str = "CEILING", edgeLen: int = 0): """ obj -- source object to test intersections for point -- origin point for ray casting direction -- cast ray in this direction miniDist -- Vector with miniscule amount to add after intersection roundType -- round final intersection location Vector with this type edgeLen -- distance to test for intersections """ # initialize variables firstDirection = False firstIntersection = None nextIntersection = None lastIntersection = None edgeIntersects = False edgeLen2 = edgeLen * 1.00001 orig = point intersections = 0 # cast rays until no more rays to cast while True: _, location, normal, index = obj.ray_cast( orig, direction) #distance=edgeLen*1.00000000001) if index == -1: break if intersections == 0: firstDirection = direction.dot(normal) if edgeLen != 0: dist = (location - point).length # get first and last intersection (used when getting materials of nearest (first or last intersected) face) if dist <= edgeLen2: if intersections == 0: edgeIntersects = True firstIntersection = { "idx": index, "dist": dist, "loc": location, "normal": normal } lastIntersection = { "idx": index, "dist": edgeLen - dist, "loc": location, "normal": normal } # set nextIntersection if nextIntersection is None: nextIntersection = location.copy() intersections += 1 location = VectorRound(location, 5, roundType=roundType) orig = location + miniDist if edgeLen != 0: return intersections, firstDirection, firstIntersection, nextIntersection, lastIntersection, edgeIntersects else: return intersections, firstDirection
def vec_roll_to_mat3(axis, roll): """Computes 3x3 Matrix from rotation axis and its roll. :param axis: Rotation :type axis: Vector :param roll: Roll :type roll: float :return: 3x3 Matrix :rtype: Matrix """ nor = axis.normalized() target = Vector((0, 1, 0)) axis = target.cross(nor) if axis.dot(axis) > 1.0e-9: axis.normalize() theta = _math_utils.angle_normalized_v3v3(target, nor) b_matrix = Matrix.Rotation(theta, 4, axis) else: if target.dot(nor) > 0: up_or_down = 1.0 else: up_or_down = -1.0 b_matrix = Matrix() b_matrix[0] = (up_or_down, 0, 0, 0) b_matrix[1] = (0, up_or_down, 0, 0) b_matrix[2] = (0, 0, 1, 0) b_matrix[3] = (0, 0, 0, 1) roll_matrix = Matrix.Rotation(roll, 4, nor) return (roll_matrix * b_matrix).to_3x3()
def points_inside(points, bm): """ https://blender.stackexchange.com/questions/31693/how-to-find-if-a-point-is-inside-a-mesh input: points - a list of vectors (can also be tuples/lists) bm - a manifold bmesh with verts and (edge/faces) for which the normals are calculated already. (add bm.normal_update() otherwise) returns: a list - a mask lists with True if the point is inside the bmesh, False otherwise """ rpoints = [] addp = rpoints.append bvh = BVHTree.FromBMesh(bm, epsilon=0.0001) # return points on polygons for point in points: fco, normal, _, _ = bvh.find_nearest(point) # calcula vector #p2 = fco - Vector(point) p2 = Vector(point) - fco # Si el producto escalar es negativo, "están en direccion opuesta" v = p2.dot(normal) addp(v < 0.0) # addp(v >= 0.0) ? return rpoints
def coplanar_pols(val, orig_val, threshold): equal_normals = equal_vectors(val[0], orig_val[0], threshold) if equal_normals: vector_base = Vector(val[1])- Vector(orig_val[1]) distance = vector_base.dot(Vector(val[0])) return abs(distance) < threshold else: return False
def mesh_diffusion(me, values, iter, diff=0.2, uv_dir=0): values = np.array(values) n_verts = len(me.vertices) n_edges = len(me.edges) edge_verts = [0]*n_edges*2 #me.edges.foreach_get("vertices", edge_verts) count = 0 edge_verts = [] uv_factor = {} uv_ang = (0.5 + uv_dir*0.5)*pi/2 uv_vec = Vector((cos(uv_ang), sin(uv_ang))) for i, f in enumerate(me.polygons): f_verts = len(f.vertices) for j0 in range(f_verts): j1 = (j0+1)%f_verts if uv_dir != 0: uv0 = me.uv_layers[0].data[count+j0].uv uv1 = me.uv_layers[0].data[count+j1].uv delta_uv = (uv1-uv0).normalized() delta_uv.x = abs(delta_uv.x) delta_uv.y = abs(delta_uv.y) dir = uv_vec.dot(delta_uv) else: dir = 1 #dir = abs(dir) #uv_factor.append(dir) edge_key = [f.vertices[j0], f.vertices[j1]] edge_key.sort() uv_factor[tuple(edge_key)] = dir count += f_verts id0 = [] id1 = [] uv_mult = [] for ek, val in uv_factor.items(): id0.append(ek[0]) id1.append(ek[1]) uv_mult.append(val) id0 = np.array(id0) id1 = np.array(id1) uv_mult = np.array(uv_mult) #edge_verts = np.array(edge_verts) #arr = np.arange(n_edges)*2 #id0 = edge_verts[arr] # first vertex indices for each edge #id1 = edge_verts[arr+1] # second vertex indices for each edge for ii in range(iter): lap = np.zeros(n_verts) if uv_dir != 0: lap0 = (values[id1] - values[id0])*uv_mult # laplacian increment for first vertex of each edge else: lap0 = (values[id1] - values[id0]) np.add.at(lap, id0, lap0) np.add.at(lap, id1, -lap0) values += diff*lap return values
def random_axes_from_normal(z): Z = z.normalized() x = Vector((random.random(), random.random(), random.random())) X = x - x.dot(Z) * Z X.normalize() Y = Z.cross(X) return X, Y, Z
def polygon_normal_angle_D(verts, poly, D): ''' The angle between the polygon normal and the given direction ''' N = polygon_normal(verts, poly) v1 = Vector(N) v2 = Vector(D) v1.normalize() v2.normalize() angle = acos(v1.dot(v2)) # the angle in radians return angle
def reciprocal_conversion(a1, a2, a3, b1, b2, b3): a1 = Vector(a1) a2 = Vector(a2) a3 = Vector(a3) vol = a1.dot(a2.cross(a3)) if vol != 0: b1[:] = a2.cross(a3) / vol b2[:] = a3.cross(a1) / vol b3[:] = a1.cross(a2) / vol return vol
def AdjustRoll_axisplane(bone, nor): props = bpy.context.scene.cyarigtools_props mat = bone.matrix z = Vector((mat[0][2], mat[1][2], mat[2][2])) z.normalize() #Xvectorを回転の正負判定に使う #X軸と法線の内積が正なら+、負ならー x = Vector((mat[0][0], mat[1][0], mat[2][0])) sign = x.dot(nor) / math.fabs(x.dot(nor)) cos_sita = z.dot(nor) sita = math.acos(cos_sita) if props.axis_plane == 'Z': bone.roll = sita * sign elif props.axis_plane == 'X': bone.roll = sita * sign + math.pi / 2
def handle_mouse_move(self, context, mouse_x, mouse_y): """ Update the gizmo for current mouse position if necessary. Otherwise remove the gizmo. """ if self.forward is not None: self.log.info("forward is not none") relative_mouse_pos = Vector((mouse_x, mouse_y)) - self.origin_2d t = relative_mouse_pos.dot(self.origin_normal_2d) projected = self.origin_2d + t * self.origin_normal_2d p_x = projected[0] p_y = projected[1] relative_mouse_pos = Vector((mouse_x, mouse_y)) - self.origin_2d t = relative_mouse_pos.dot(self.origin_normal_2d) projected = self.origin_2d + t * self.origin_normal_2d p_x = projected[0] p_y = projected[1] region = context.region region_data = context.space_data.region_3d view_vector = bpy_extras.view3d_utils.region_2d_to_vector_3d( region, region_data, (p_x, p_y)) ray_origin = bpy_extras.view3d_utils.region_2d_to_origin_3d( region, region_data, (p_x, p_y)) ray_target = ray_origin + view_vector points = mathutils.geometry.intersect_line_line( self.origin, self.origin + self.origin_normal, ray_origin, ray_target) self.height = (points[0] - self.origin).length self.refresh_preview(context, mouse_x, mouse_y) return {'RUNNING_MODAL'}
def polygon_normal_angle_P(verts, poly, P): ''' The angle between the polygon normal and the vector from polygon center to given point ''' N = polygon_normal(verts, poly) C = polygon_center(verts, poly) V = [P[0] - C[0], P[1] - C[1], P[2] - C[2]] v1 = Vector(N) v2 = Vector(V) v1.normalize() v2.normalize() angle = acos(v1.dot(v2)) # the angle in radians return angle
def vec_roll_to_mat3(vec, roll): target = Vector((0,1,0)) nor = vec.normalized() axis = target.cross(nor) if axis.dot(axis) > 0.000001: axis.normalize() theta = target.angle(nor) bMatrix = Matrix.Rotation(theta, 3, axis) else: updown = 1 if target.dot(nor) > 0 else -1 bMatrix = Matrix.Scale(updown, 3) rMatrix = Matrix.Rotation(roll, 3, nor) mat = rMatrix * bMatrix return mat
def polygon_area(verts, poly): ''' The area of the given polygon ''' if len(poly) < 3: # not a plane - no area return 0 total = Vector([0, 0, 0]) N = len(poly) for i in range(N): vi1 = Vector(verts[poly[i]]) vi2 = Vector(verts[poly[(i + 1) % N]]) prod = vi1.cross(vi2) total[0] += prod[0] total[1] += prod[1] total[2] += prod[2] normal = Vector(polygon_normal(verts, poly)) area = abs(total.dot(normal)) / 2 return area
def matchIkLeg(legIk, toeFk, mBall, mToe, mHeel): rmat = toeFk.matrix.to_3x3() tHead = Vector(toeFk.matrix.col[3][:3]) ty = rmat.col[1] tail = tHead + ty * toeFk.bone.length zBall = mBall.matrix.col[3][2] zToe = mToe.matrix.col[3][2] zHeel = mHeel.matrix.col[3][2] x = Vector(rmat.col[0]) y = Vector(rmat.col[1]) z = Vector(rmat.col[2]) if zHeel > zBall and zHeel > zToe: # 1. foot.ik is flat if abs(y[2]) > abs(z[2]): y = -z y[2] = 0 else: # 2. foot.ik starts at heel hHead = Vector(mHeel.matrix.col[3][:3]) y = tail - hHead y.normalize() x -= x.dot(y)*y x.normalize() if abs(x[2]) < 0.7: x[2] = 0 x.normalize() z = x.cross(y) head = tail - y * legIk.bone.length # Create matrix gmat = Matrix() gmat.col[0][:3] = x gmat.col[1][:3] = y gmat.col[2][:3] = z gmat.col[3][:3] = head pmat = getPoseMatrix(gmat, legIk) insertLocation(legIk, pmat) insertRotation(legIk, pmat)
def area_pol(poly): if len(poly) < 3: # not a plane - no area return 0 total = Vector((0, 0, 0)) for i in range(len(poly)): vi1 = Vector(poly[i]) if i is len(poly)-1: vi2 = Vector(poly[0]) else: vi2 = Vector(poly[i+1]) prod = vi1.cross(vi2)[:] total[0] += prod[0] total[1] += prod[1] total[2] += prod[2] result = total.dot(unit_normal(poly[0], poly[1], poly[2])) return abs(result/2)
def region_2d_to_orig_and_view_vector(region, rv3d, coord, clamp=None): viewinv = rv3d.view_matrix.inverted() persinv = rv3d.perspective_matrix.inverted() dx = (2.0 * coord[0] / region.width) - 1.0 dy = (2.0 * coord[1] / region.height) - 1.0 if rv3d.is_perspective: origin_start = viewinv.translation.copy() out = Vector((dx, dy, -0.5)) w = out.dot(persinv[3].xyz) + persinv[3][3] view_vector = ((persinv * out) / w) - origin_start else: view_vector = -viewinv.col[2].xyz origin_start = ((persinv.col[0].xyz * dx) + (persinv.col[1].xyz * dy) + viewinv.translation) if clamp != 0.0: if rv3d.view_perspective != 'CAMERA': # this value is scaled to the far clip already origin_offset = persinv.col[2].xyz if clamp is not None: if clamp < 0.0: origin_offset.negate() clamp = -clamp if origin_offset.length > clamp: origin_offset.length = clamp origin_start -= origin_offset view_vector.normalize() return origin_start, view_vector
def region_2d_to_orig_and_view_vector(region, rv3d, coord): viewinv = rv3d.view_matrix.inverted_safe() persinv = rv3d.perspective_matrix.inverted_safe() dx = (2.0 * coord[0] / region.width) - 1.0 dy = (2.0 * coord[1] / region.height) - 1.0 if rv3d.is_perspective: origin_start = viewinv.translation.copy() out = Vector((dx, dy, -0.5)) w = out.dot(persinv[3].xyz) + persinv[3][3] view_vector = ((persinv @ out) / w) - origin_start else: view_vector = -viewinv.col[2].xyz origin_start = ((persinv.col[0].xyz * dx) + (persinv.col[1].xyz * dy) + viewinv.translation) view_vector.normalize() return view_vector, origin_start
def _arc_segment(v_1, v_2): ELorigin = bpy.context.scene.objects['ELorigin'] ELground = bpy.context.scene.objects['ELground'] v = v_2 - v_1 d = v.length ELorigin.location = Vector((0, 0, 0)) ELground.location = Vector((0, 0, -d)) v_L = ELground.location - ELorigin.location q = Quaternion() c = Vector.cross(v_L, v) q.x = c.x q.y = c.y q.z = c.z q.w = sqrt((v_L.length ** 2) * (v.length ** 2)) + \ Vector.dot(v_L, v) q.normalize() euler = q.to_euler() bpy.ops.object.runfslg_operator() laALL = bpy.context.scene.objects['laALL'] laALL.name = 'lARC' laALL.rotation_euler = euler laALL.location = v_1 bpy.context.active_object.select = False laALL.select = True bpy.ops.object.transform_apply(location=True, rotation=True, scale=True) laALL.select = False bpy.context.active_object.select = True return laALL
def __new__(cls, location=Vector(), normal=ZAXIS, rotation=Quaternion()): loc = Vector(location) nor = Vector(normal).normalized() vector = nor.to_4d() vector[3] = -nor.dot(loc) return Vector.__new__(cls, vector)
def draw_molecule(molecule, center=(0, 0, 0), max_molecule_size=5, show_bonds=True): """Draw a molecule to blender. Uses loaded json molecule data.""" # Get scale factor - only scales large molecules down # max_coord = 1E-6 # for atom in molecule["atoms"]: # max_coord = max(max_coord, *[abs(a) for a in atom["location"]]) # scale = min(max_molecule_size / max_coord, 1) scale = 0.1 # if one gets the atomic positions through spd files, they are denoted in \AA, switch to nm => /10 in location # Scale location coordinates and add specified center for atom in molecule["atoms"]: atom["location"] = [c + x * scale for c, x in zip(center, atom["location"])] draw_vdW = True join = False # Keep references to all atoms and bonds shapes = [] # If using space-filling model, scale up atom size and remove bonds if show_bonds: bond_scale = 0.1 atom_scale = 0.5 else: atom_scale = 1 bond_scale = 0.2 molecule["bonds"] = [] # Add atom primitive bpy.ops.object.select_all(action='DESELECT') bpy.ops.mesh.primitive_uv_sphere_add() sphere = bpy.context.object # Add bond material and primitive if it's going to be used if molecule["bonds"]: key = "bond" bpy.data.materials.new(name=key) bpy.data.materials[key].diffuse_color = atom_data[key]["color"] bpy.data.materials[key].specular_intensity = 0.2 bpy.ops.mesh.primitive_cylinder_add() cylinder = bpy.context.object cylinder.active_material = bpy.data.materials["bond"] # Draw atoms for atom in molecule["atoms"]: # If element is not in dictionary, use undefined values if atom["element"] not in atom_data: atom["element"] = "undefined" # If material for atom type has not yet been defined, do so if atom["element"] not in bpy.data.materials: key = atom["element"] print("added material", key, "to materials") bpy.data.materials.new(name=key) bpy.data.materials[key].diffuse_color = atom_data[key]["color"] bpy.data.materials[key].specular_intensity = 0.2 #add vdW spheres to atoms if draw_vdW: keyvdW = atom["element"] + "-vdW" bpy.data.materials.new(name=keyvdW) bpy.data.materials[keyvdW].diffuse_color = atom_data[key]["color"] #same color as atom, but with tranparancy bpy.data.materials[keyvdW].use_transparency = 1 bpy.data.materials[keyvdW].transparency_method = "RAYTRACE" bpy.data.materials[keyvdW].alpha=0.3 print("added vdW material", key, "to materials") # Copy mesh primitive and edit to make atom atom_sphere = sphere.copy() atom_sphere.data = sphere.data.copy() atom_sphere.location = atom["location"] atom_sphere.dimensions = [atom_data[atom["element"]]["radius"] * atom_scale * 2] * 3 atom_sphere.active_material = bpy.data.materials[atom["element"]] bpy.context.scene.objects.link(atom_sphere) shapes.append(atom_sphere) keyvdW = atom["element"] + "-vdW" if draw_vdW: atom_sphere = sphere.copy() atom_sphere.data = sphere.data.copy() atom_sphere.location = atom["location"] atom_sphere.dimensions = [atom_data[atom["element"]]["vdWradius"] * atom_scale] * 3 atom_sphere.active_material = bpy.data.materials[keyvdW] bpy.context.scene.objects.link(atom_sphere) shapes.append(atom_sphere) # Draw bonds for bond in molecule["bonds"]: # Extracting locations first_loc = molecule["atoms"][bond["atoms"][0]]["location"] second_loc = molecule["atoms"][bond["atoms"][1]]["location"] diff = [c2 - c1 for c2, c1 in zip(first_loc, second_loc)] cent = [(c2 + c1) / 2 for c2, c1 in zip(first_loc, second_loc)] mag = sum([(c2 - c1) ** 2 for c1, c2 in zip(first_loc, second_loc)]) ** 0.5 # Euler rotation calculation v_axis = Vector(diff).normalized() v_obj = Vector((0, 0, 1)) v_rot = v_obj.cross(v_axis) # This check prevents gimbal lock (ie. weird behavior when v_axis is # close to (0, 0, 1)) if v_rot.length > 0.01: v_rot = v_rot.normalized() axis_angle = [acos(v_obj.dot(v_axis))] + list(v_rot) else: v_rot = Vector((1, 0, 0)) axis_angle = [0] * 4 # Check that the number of bonds is logical if bond["order"] not in range(1, 4): print("Improper number of bonds! Defaulting to 1.") bond["order"] = 1 # Specify locations of each bond in every scenario if bond["order"] == 1: trans = [[0] * 3] elif bond["order"] == 2: trans = [[1.4 * atom_data["bond"]["radius"] * x for x in v_rot], [-1.4 * atom_data["bond"]["radius"] * x for x in v_rot]] elif bond["order"] == 3: trans = [[0] * 3, [2.2 * atom_data["bond"]["radius"] * x for x in v_rot], [-2.2 * atom_data["bond"]["radius"] * x for x in v_rot]] # Draw bonds for i in range(bond["order"]): bond_cylinder = cylinder.copy() bond_cylinder.data = cylinder.data.copy() bond_cylinder.dimensions = [atom_data["bond"]["radius"] * bond_scale] * 2 + [mag] bond_cylinder.location = [c + scale * v for c, v in zip(cent, trans[i])] bond_cylinder.rotation_mode = "AXIS_ANGLE" bond_cylinder.rotation_axis_angle = axis_angle bpy.context.scene.objects.link(bond_cylinder) shapes.append(bond_cylinder) # Remove primitive meshes bpy.ops.object.select_all(action='DESELECT') sphere.select = True if molecule["bonds"]: cylinder.select = True # If the starting cube is there, remove it if "Cube" in bpy.data.objects.keys(): bpy.data.objects.get("Cube").select = True bpy.ops.object.delete() # Smooth and join molecule shapes for shape in shapes: shape.select = True bpy.context.scene.objects.active = shapes[0] bpy.ops.object.shade_smooth() if join: bpy.ops.object.join() # Center object origin to geometry bpy.ops.object.origin_set(type="ORIGIN_GEOMETRY", center="MEDIAN") # Refresh scene bpy.context.scene.update()
def setupJoints (self): """ Evaluate symbolic expressions for joint locations and store them in self.locations. Joint locations are specified symbolically in the *Joints list in the beginning of the rig_*.py files (e.g. ArmJoints in rig_arm.py). """ cfg = self.config for (key, type, data) in self.joints: if type == 'j': loc = self.jointLocs[data] self.locations[key] = loc self.locations[data] = loc elif type == 'a': vec = Vector((float(data[0]),float(data[1]),float(data[2]))) self.locations[key] = vec + self.offset elif type == 'v': v = int(data) self.locations[key] = self.coord[v] elif type == 'x': self.locations[key] = Vector((float(data[0]), float(data[2]), -float(data[1]))) elif type == 'vo': v = int(data[0]) offset = Vector((float(data[1]), float(data[3]), -float(data[2]))) self.locations[key] = (self.coord[v] + self.scale*offset) elif type == 'vl': ((k1, v1), (k2, v2)) = data loc1 = self.coord[int(v1)] loc2 = self.coord[int(v2)] self.locations[key] = (k1*loc1 + k2*loc2) elif type == 'f': (raw, head, tail, offs) = data rloc = self.locations[raw] hloc = self.locations[head] tloc = self.locations[tail] vec = tloc - hloc vraw = rloc - hloc x = vec.dot(vraw)/vec.dot(vec) self.locations[key] = hloc + x*vec + Vector(offs) elif type == 'n': (raw, j1, j2, j3) = data rloc = self.locations[raw] loc1 = self.locations[j1] loc2 = self.locations[j2] loc3 = self.locations[j3] e12 = loc2 - loc1 e13 = loc3 - loc1 n = e12.cross(e13).normalized() e1r = rloc - loc1 self.locations[key] = rloc - n*e1r.dot(n) elif type == 'b': self.locations[key] = self.jointLocs[key] = self.locations[data] elif type == 'p': x = self.locations[data[0]] y = self.locations[data[1]] z = self.locations[data[2]] self.locations[key] = Vector((x[0],y[1],z[2])) elif type == 'vz': v = int(data[0]) z = self.coord[v][2] loc = self.locations[data[1]] self.locations[key] = Vector((loc[0],loc[1],z)) elif type == 'X': r = self.locations[data[0]] (x,y,z) = data[1] r1 = Vector([float(x), float(y), float(z)]) self.locations[key] = r.cross(r1) elif type == 'l': ((k1, joint1), (k2, joint2)) = data self.locations[key] = k1*self.locations[joint1] + k2*self.locations[joint2] elif type == 'o': (joint, offsSym) = data if isinstance(offsSym, str): offs = self.locations[offsSym] else: offs = self.scale * Vector(offsSym) self.locations[key] = self.locations[joint] + offs else: raise NameError("Unknown %s" % type) return
def execute(self, context): region = getActiveRegion3d() active_obj = context.scene.objects.active select_and_change_mode(active_obj, 'EDIT') edit_obj = bpy.context.edit_object active_mesh = edit_obj.data bm = bmesh.from_edit_mesh(active_mesh) bm.verts.ensure_lookup_table() bm.faces.ensure_lookup_table() bm.verts.index_update() # Get all selected vertices (in their local space). selectedVerts = [v for v in bm.verts if v.select] if len(selectedVerts) < 3: self.report({'ERROR'}, "Not enough selected vertices found") return {'CANCELLED'} stroke2dPoints, stroke3dPoints = gpencil_to_screenpos(context, g_strokeResl, region) if len(stroke2dPoints) < 3: self.report({'ERROR'}, "Grease pencil stroke too short or not found") return {'CANCELLED'} #verts_global_3d = [Vector(obj.matrix_world * v.co) for v in selectedVerts] eo_mx_inv = edit_obj.matrix_world.inverted() eo_dir2cam = eo_mx_inv * (region.view_rotation * Vector((0.0, 0.0, 1.0))) eo_dir2cam = Vector(eo_dir2cam).normalized() #eo_mx_norm = eo_mx_inv.transposed().to_3x3() verts3dPoints = [v.co for v in selectedVerts] verts2dPoints = vectors_to_screenpos(context, verts3dPoints, edit_obj.matrix_world, region) vertsFieldDispersion, nearestStrokeInPoints = getVertsFD(verts2dPoints, verts3dPoints, stroke2dPoints, self.FacMid, self.FacPits, self.SlopePits, self.inpaintStrk) shiftDir = eo_dir2cam if self.push_trg == 'AVG_NORMLS': # points most near to begin/end of stroke to get movement vector normalsSumm = g_zeroVec.copy() normalsCount = 0 for i, v in enumerate(verts2dPoints): vnrm = (selectedVerts[i].normal).normalized() #(eo_mx_norm*selectedVerts[i].normal).normalized() normalsSumm = normalsSumm+vnrm*vertsFieldDispersion[i] normalsCount = normalsCount+1 shiftDir = normalsSumm/normalsCount if self.push_trg == 'LST_NORMLS': lastv = getLastSelectedVert(bm) if lastv is not None: shiftDir = (lastv.normal).normalized() #(eo_mx_norm*lastvn).normalized() for i, v in enumerate(selectedVerts): fac = 1.0 if self.invertFld: fac = self.influence*(1.0-vertsFieldDispersion[i]) else: fac = self.influence*vertsFieldDispersion[i] if self.limitFld2NV: fac = fac*eo_dir2cam.dot(v.normal) if fac is not None: if self.push_trg == 'ROTATE_CENTER': s3dpIdx = nearestStrokeInPoints[i][0] stroke3dp = stroke3dPoints[s3dpIdx] if s3dpIdx+1 < len(stroke3dPoints): stroke3dd = stroke3dPoints[s3dpIdx+1]-stroke3dp else: stroke3dd = stroke3dp-stroke3dPoints[s3dpIdx-1] if nearestStrokeInPoints[i][1]<0: stroke3dd = -1*stroke3dd rotmat = Matrix.Rotation(math.pi*fac,4,stroke3dd) v.co = rotmat*(v.co-stroke3dp)+stroke3dp fac = None if fac is not None: if self.push_trg == 'IND_NORMLS': shiftDir = v.normal if self.push_trg == 'SCALE_CENTER': s3dpIdx = nearestStrokeInPoints[i][0] stroke3dp = stroke3dPoints[s3dpIdx] shiftDir = stroke3dp - v.co v.co = v.co + shiftDir*fac # Recalculate mesh normals (so lighting looks right). for edge in bm.edges: edge.normal_update() # Push bmesh changes back to the actual mesh datablock. bmesh.update_edit_mesh(active_mesh, True) return {'FINISHED'}
def draw_network(network, edge_thickness=0.25, node_size=3, directed=True): """ Takes assembled network/molecule data and draws to blender """ # Add some mesh primitives bpy.ops.object.select_all(action="DESELECT") bpy.ops.mesh.primitive_uv_sphere_add() sphere = bpy.context.object bpy.ops.mesh.primitive_cylinder_add() cylinder = bpy.context.object cylinder.active_material = bpy.data.materials["light_gray"] bpy.ops.mesh.primitive_cone_add() cone = bpy.context.object cone.active_material = bpy.data.materials["light_gray"] # Keep references to all nodes and edges shapes = [] # Keep separate references to shapes to be smoothed shapes_to_smooth = [] # Draw nodes for key, node in network["nodes"].items(): # Coloring rule for nodes. Edit this to suit your needs! col = node.get("color", choice(list(colors.keys()))) # Copy mesh primitive and edit to make node # (You can change the shape of drawn nodes here) node_sphere = sphere.copy() node_sphere.data = sphere.data.copy() node_sphere.location = node["location"] node_sphere.dimensions = [node_size] * 3 node_sphere.active_material = bpy.data.materials[col] bpy.context.scene.objects.link(node_sphere) shapes.append(node_sphere) shapes_to_smooth.append(node_sphere) # Draw edges for edge in network["edges"]: # Get source and target locations by drilling down into data structure source_loc = network["nodes"][edge["source"]]["location"] target_loc = network["nodes"][edge["target"]]["location"] diff = [c2 - c1 for c2, c1 in zip(source_loc, target_loc)] cent = [(c2 + c1) / 2 for c2, c1 in zip(source_loc, target_loc)] mag = sum([(c2 - c1) ** 2 for c1, c2 in zip(source_loc, target_loc)]) ** 0.5 # Euler rotation calculation v_axis = Vector(diff).normalized() v_obj = Vector((0, 0, 1)) v_rot = v_obj.cross(v_axis) angle = acos(v_obj.dot(v_axis)) # Copy mesh primitive to create edge edge_cylinder = cylinder.copy() edge_cylinder.data = cylinder.data.copy() edge_cylinder.dimensions = [edge_thickness] * 2 + [mag - node_size] edge_cylinder.location = cent edge_cylinder.rotation_mode = "AXIS_ANGLE" edge_cylinder.rotation_axis_angle = [angle] + list(v_rot) bpy.context.scene.objects.link(edge_cylinder) shapes.append(edge_cylinder) shapes_to_smooth.append(edge_cylinder) # Copy another mesh primitive to make an arrow head if directed: arrow_cone = cone.copy() arrow_cone.data = cone.data.copy() arrow_cone.dimensions = [edge_thickness * 4.0] * 3 arrow_cone.location = cent arrow_cone.rotation_mode = "AXIS_ANGLE" arrow_cone.rotation_axis_angle = [angle + pi] + list(v_rot) bpy.context.scene.objects.link(arrow_cone) shapes.append(arrow_cone) # Remove primitive meshes bpy.ops.object.select_all(action="DESELECT") sphere.select = True cylinder.select = True cone.select = True # If the starting cube is there, remove it if "Cube" in bpy.data.objects.keys(): bpy.data.objects.get("Cube").select = True bpy.ops.object.delete() # Smooth specified shapes for shape in shapes_to_smooth: shape.select = True bpy.context.scene.objects.active = shapes_to_smooth[0] bpy.ops.object.shade_smooth() # Join shapes for shape in shapes: shape.select = True bpy.context.scene.objects.active = shapes[0] bpy.ops.object.join() # Center object origin to geometry bpy.ops.object.origin_set(type="ORIGIN_GEOMETRY", center="MEDIAN") # Refresh scene bpy.context.scene.update()
def execute(self, context): object = context.object object.update_from_editmode() object_bm = bmesh.from_edit_mesh(object.data) object_bm.verts.ensure_lookup_table() object_bm.edges.ensure_lookup_table() object_bm.faces.ensure_lookup_table() selected_faces = [f for f in object_bm.faces if f.select] selected_edges = [e for e in object_bm.edges if e.select] selected_verts = [v for v in object_bm.verts if v.select] selection_center = Vector() if len(selected_edges) == 0: self.report({'WARNING'}, "Please select edges.") return {'CANCELLED'} try: cache_loops = get_cache(self.as_pointer(), "loops") loops = [] for (loop_verts, loop_edges, loop_faces), is_loop_cyclic, is_loop_boundary in cache_loops: loops.append((([object_bm.verts[v] for v in loop_verts], [object_bm.edges[e] for e in loop_edges], [object_bm.faces[f] for f in loop_faces]), is_loop_cyclic, is_loop_boundary)) except CacheException: loops = get_loops(selected_edges, selected_faces) if loops: cache_loops = [] for (loop_verts, loop_edges, loop_faces), is_loop_cyclic, is_loop_boundary in loops: cache_loops.append((([v.index for v in loop_verts], [e.index for e in loop_edges], [f.index for f in loop_faces]), is_loop_cyclic, is_loop_boundary)) set_cache(self.as_pointer(), "loops", cache_loops) if loops is None: self.report({'WARNING'}, "Please select boundary loop(s) of selected area(s).") return {'CANCELLED'} for vert in selected_verts: selection_center += vert.co selection_center /= len(selected_verts) object_bvh = mathutils.bvhtree.BVHTree.FromObject(object, context.scene, deform=False) refresh_icons() shape_bm = bmesh.new() for loop_idx, ((loop_verts, loop_edges, loop_faces), is_loop_cyclic, is_loop_boundary) in enumerate(loops): if len(loop_edges) < 3: continue loop_verts_len = len(loop_verts) shape_verts = None shape_edges = None try: cache_verts = get_cache(self.as_pointer(), "shape_verts_{}".format(loop_idx)) tmp_vert = None for i, cache_vert in enumerate(cache_verts): new_vert = shape_bm.verts.new(cache_vert) if i > 0: shape_bm.edges.new((tmp_vert, new_vert)) tmp_vert = new_vert shape_verts = shape_bm.verts[:] shape_bm.edges.new((shape_verts[-1], shape_verts[0])) shape_edges = shape_bm.edges[:] except CacheException: if self.shape == "CIRCLE": a = sum([e.calc_length() for e in loop_edges]) / loop_verts_len diameter = a / (2 * math.sin(math.pi / loop_verts_len)) shape_segments = loop_verts_len + self.span shape_verts = bmesh.ops.create_circle(shape_bm, segments=shape_segments, diameter=diameter) shape_verts = shape_verts["verts"] shape_edges = shape_bm.edges[:] elif self.shape == "RECTANGLE": if loop_verts_len % 2 > 0: self.report({'WARNING'}, "An odd number of edges.") del shape_bm return {'FINISHED'} size = sum([e.calc_length() for e in loop_edges]) size_a = (size / 2) / (self.ratio_a + self.ratio_b) * self.ratio_a size_b = (size / 2) / (self.ratio_a + self.ratio_b) * self.ratio_b seg_a = (loop_verts_len / 2) / (self.ratio_a + self.ratio_b) * self.ratio_a seg_b = int((loop_verts_len / 2) / (self.ratio_a + self.ratio_b) * self.ratio_b) if seg_a % 1 > 0: self.report({'WARNING'}, "Incorrect sides ratio.") seg_a += 1 seg_b += 2 seg_a = int(seg_a) if self.is_square: size_a = (size_a + size_b) / 2 size_b = size_a seg_len_a = size_a / seg_a seg_len_b = size_b / seg_b for i in range(seg_a): shape_bm.verts.new(Vector((size_b / 2 * -1, seg_len_a * i - (size_a / 2), 0))) for i in range(seg_b): shape_bm.verts.new(Vector((seg_len_b * i - (size_b / 2), size_a / 2, 0))) for i in range(seg_a, 0, -1): shape_bm.verts.new(Vector((size_b / 2, seg_len_a * i - (size_a / 2), 0))) for i in range(seg_b, 0, -1): shape_bm.verts.new(Vector((seg_len_b * i - (size_b / 2), size_a / 2 * -1, 0))) shape_verts = shape_bm.verts[:] for i in range(len(shape_verts)): shape_bm.edges.new((shape_verts[i], shape_verts[(i + 1) % len(shape_verts)])) shape_edges = shape_bm.edges[:] elif self.shape == "PATTERN": pattern_idx = context.scene.perfect_shape.active_pattern pattern = context.scene.perfect_shape.patterns[int(pattern_idx)] if len(pattern.verts) == 0: self.report({'WARNING'}, "Empty Pattern Data.") del shape_bm return {'FINISHED'} if len(pattern.verts) != len(loop_verts): self.report({'WARNING'}, "Pattern and loop vertices count must be the same.") del shape_bm return {'FINISHED'} for pattern_vert in pattern.verts: shape_bm.verts.new(Vector(pattern_vert.co)) shape_verts = shape_bm.verts[:] for i in range(len(shape_verts)): shape_bm.edges.new((shape_verts[i], shape_verts[(i + 1) % len(shape_verts)])) shape_edges = shape_bm.edges[:] elif self.shape == "OBJECT": if self.target in bpy.data.objects: shape_object = bpy.data.objects[self.target] shape_bm.from_object(shape_object, context.scene) loops = get_loops(shape_bm.edges[:]) if not loops or len(loops) > 1: self.report({'WARNING'}, "Wrong mesh data.") del shape_bm return {'FINISHED'} if len(loops[0][0][0]) > len(loop_verts): self.report({'WARNING'}, "Shape and loop vertices count must be the same.") del shape_bm return {'FINISHED'} shape_verts = loops[0][0][0] shape_edges = loops[0][0][1] if shape_verts: set_cache(self.as_pointer(), "shape_verts_{}".format(loop_idx), [v.co.copy() for v in shape_verts]) if shape_verts is not None and len(shape_verts) > 0: if context.space_data.pivot_point == "CURSOR": center = object.matrix_world.copy() * context.scene.cursor_location.copy() else: temp_bm = bmesh.new() for loop_vert in loop_verts: temp_bm.verts.new(loop_vert.co.copy()) temp_verts = temp_bm.verts[:] for i in range(len(temp_verts)): temp_bm.edges.new((temp_verts[i], temp_verts[(i + 1) % len(temp_verts)])) temp_bm.faces.new(temp_bm.verts) temp_bm.faces.ensure_lookup_table() if context.space_data.pivot_point == 'BOUNDING_BOX_CENTER': center = temp_bm.faces[0].calc_center_bounds() else: center = temp_bm.faces[0].calc_center_median() del temp_bm context.scene.perfect_shape.preview_verts_count = loop_verts_len + self.span if self.projection == "NORMAL": forward = calculate_normal([v.co.copy() for v in loop_verts]) normal_forward = reduce( lambda v1, v2: v1.normal.copy() + v2.normal.copy() if isinstance(v1, bmesh.types.BMVert) else v1.copy() + v2.normal.copy(), loop_verts).normalized() if normal_forward.angle(forward) - math.pi / 2 > 1e-6: forward.negate() else: forward = Vector([v == self.projection for v in ["X", "Y", "Z"]]) if self.invert_projection: forward.negate() matrix_rotation = forward.to_track_quat('Z', 'Y').to_matrix().to_4x4() matrix_translation = Matrix.Translation(center) bmesh.ops.scale(shape_bm, vec=Vector((1, 1, 1)) * (1 + self.offset), verts=shape_verts) bmesh.ops.transform(shape_bm, verts=shape_verts, matrix=matrix_translation * matrix_rotation) if not is_clockwise(forward, center, loop_verts): loop_verts.reverse() loop_edges.reverse() if not is_clockwise(forward, center, shape_verts): shape_verts.reverse() correct_angle_m = 1 shift_m = 1 if context.space_data.pivot_point != "INDIVIDUAL_ORIGINS": if selection_center.dot(center.cross(forward)) >= 0: # if (center.cross(forward)).angle(selection_center) - math.pi/2 <= 1e-6: correct_angle_m = -1 shift_m = -1 loop_verts_co_2d, shape_verts_co_2d = None, None if loop_verts_co_2d is None: loop_verts_co_2d = [(matrix_rotation.transposed() * v.co).to_2d() for v in loop_verts] shape_verts_co_2d = [(matrix_rotation.transposed() * v.co).to_2d() for v in shape_verts] loop_angle = box_fit_2d(loop_verts_co_2d) shape_angle = box_fit_2d(shape_verts_co_2d) # if round(loop_angle, 4) == round(math.pi, 4): # loop_angle = 0 correct_angle = 0 if self.loop_rotation: correct_angle = loop_angle * correct_angle_m if round(loop_angle, 3) == round(shape_angle, 3): correct_angle -= shape_angle if self.shape_rotation: correct_angle -= shape_angle if correct_angle != 0: bmesh.ops.rotate(shape_bm, verts=shape_verts, cent=center, matrix=Matrix.Rotation(-correct_angle * correct_angle_m, 3, forward)) kd_tree = mathutils.kdtree.KDTree(len(loop_verts)) for idx, loop_vert in enumerate(loop_verts): kd_tree.insert(loop_vert.co, idx) kd_tree.balance() shape_first_idx = kd_tree.find(shape_verts[0].co)[1] shift = shape_first_idx + self.shift * shift_m if shift != 0: loop_verts = loop_verts[shift % len(loop_verts):] + loop_verts[:shift % len(loop_verts)] if self.rotation != 0: bmesh.ops.rotate(shape_bm, verts=shape_verts, cent=center, matrix=Matrix.Rotation(-self.rotation * correct_angle_m, 3, forward)) bmesh.ops.translate(shape_bm, vec=self.shape_translation, verts=shape_bm.verts) center = Matrix.Translation(self.shape_translation) * center if not is_loop_boundary and self.use_ray_cast: for shape_vert in shape_verts: co = shape_vert.co ray_cast_data = object_bvh.ray_cast(co, forward) if ray_cast_data[0] is None: ray_cast_data = object_bvh.ray_cast(co, -forward) if ray_cast_data[0] is not None: shape_vert.co = ray_cast_data[0] for idx, vert in enumerate(loop_verts): vert.co = vert.co.lerp(shape_verts[idx].co, self.factor / 100) if not is_loop_boundary and is_loop_cyclic and loop_faces: if self.fill_type != "ORIGINAL": smooth = loop_faces[0].smooth bmesh.ops.delete(object_bm, geom=loop_faces, context=5) loop_faces = [] center_vert = object_bm.verts.new(center) if self.use_ray_cast: ray_cast_data = object_bvh.ray_cast(center_vert.co, forward) if ray_cast_data[0] is None: ray_cast_data = object_bvh.ray_cast(center_vert.co, -forward) if ray_cast_data[0] is not None: center_vert.co = ray_cast_data[0] for idx, vert in enumerate(loop_verts): new_face = object_bm.faces.new((center_vert, vert, loop_verts[(idx + 1) % loop_verts_len])) new_face.smooth = smooth loop_faces.append(new_face) bmesh.ops.recalc_face_normals(object_bm, faces=loop_faces) if self.outset > 0.0: outset_region_faces = bmesh.ops.inset_region(object_bm, faces=loop_faces, thickness=self.outset, use_even_offset=True, use_interpolate=True, use_outset=True) if self.extrude == 0: verts = loop_verts[:] for face in loop_faces: for vert in face.verts: if vert not in verts: verts.append(vert) if self.fill_flatten: matrix = Matrix.Translation(-center) bmesh.ops.rotate(object_bm, cent=center, matrix=matrix_rotation.transposed(), verts=loop_verts) bmesh.ops.scale(object_bm, vec=Vector((1, 1, +0)), space=matrix, verts=verts) bmesh.ops.rotate(object_bm, cent=center, matrix=matrix_rotation, verts=verts) if self.inset > 0.0: bmesh.ops.inset_region(object_bm, faces=loop_faces, thickness=self.inset, use_even_offset=True, use_interpolate=True) if self.fill_type == "HOLE": bmesh.ops.delete(object_bm, geom=loop_faces, context=5) elif self.fill_type == "NGON": bmesh.utils.face_join(loop_faces) else: verts = [] edges = [] faces = [] side_faces = [] side_edges = [] extrude_geom = bmesh.ops.extrude_face_region(object_bm, geom=loop_faces, use_keep_orig=True) bmesh.ops.delete(object_bm, geom=loop_faces, context=5) for geom in extrude_geom["geom"]: if isinstance(geom, bmesh.types.BMVert): verts.append(geom) elif isinstance(geom, bmesh.types.BMFace): faces.append(geom) elif isinstance(geom, bmesh.types.BMEdge): edges.append(geom) for edge in loop_edges: for face in edge.link_faces: if any((e for e in face.edges if e in edges)): side_faces.append(face) for edge in face.edges: if edge not in side_edges and edge not in edges and edge not in loop_edges: side_edges.append(edge) if self.fill_flatten: matrix = Matrix.Translation(-center) bmesh.ops.rotate(object_bm, cent=center, matrix=matrix_rotation.transposed(), verts=verts) bmesh.ops.scale(object_bm, vec=Vector((1.0, 1.0, 0.001)), space=matrix, verts=verts) bmesh.ops.rotate(object_bm, cent=center, matrix=matrix_rotation, verts=verts) bmesh.ops.translate(object_bm, verts=verts, vec=forward * self.extrude) cuts = max(self.cuts, self.cuts_rings) if cuts > 0: sub = bmesh.ops.subdivide_edges(object_bm, edges=side_edges, cuts=cuts) loop_verts = [] first_verts = loop_edges[0].verts[:] for edge in loop_edges: if edge == loop_edges[0]: continue if edge == loop_edges[1]: if first_verts[0] == edge.verts[0]: first_verts.reverse() loop_verts.extend(first_verts) for vert in edge.verts: if vert not in loop_verts: loop_verts.append(vert) split_edges = [] for geom in sub["geom_split"]: if isinstance(geom, bmesh.types.BMEdge): split_edges.append(geom) skip_edges = [] for vert in loop_verts: for edge in vert.link_edges: if edge not in skip_edges and edge not in split_edges: skip_edges.append(edge) start = self.cuts_shift % loop_verts_len stop = self.cuts_shift % loop_verts_len verts_list = loop_verts[start:] + loop_verts[:stop] for i in range(self.cuts): new_split_edges = [] for idx, vert in enumerate(verts_list): if idx < self.cuts_len + i or idx >= loop_verts_len - i: continue for edge in vert.link_edges: if edge in split_edges: other_vert = edge.other_vert(vert) bmesh.ops.weld_verts(object_bm, targetmap={other_vert: vert}) for edge in vert.link_edges: if edge not in new_split_edges and edge not in skip_edges: new_split_edges.append(edge) split_edges = new_split_edges cut_edges = [] dissolve_edges = [] cut_skip = [] prev_edge = None first_join = True for i in range(self.cuts_rings): if i >= self.cuts: break split_edges = [] for idx, vert in enumerate(verts_list): if idx < self.cuts_len + i or idx >= loop_verts_len - i: for edge in vert.link_edges: if edge not in skip_edges and edge not in cut_skip: if prev_edge is not None and idx not in range(self.cuts_len): if not any((v for v in edge.verts if v in prev_edge.verts)): if edge not in dissolve_edges: dissolve_edges.append(edge) if edge not in dissolve_edges and edge not in split_edges: split_edges.append(edge) cut_skip.append(edge) # edge.select_set(True) prev_edge = edge if first_join and len(cut_edges) == 1: cut_edges[0].extend(split_edges) first_join = False else: cut_edges.append(split_edges) # if dissolve_edges: # bmesh.ops.dissolve_edges(object_bm, edges=dissolve_edges) # inner_verts = [] # for i, split_edges in enumerate(cut_edges): # for edge in split_edges: # sub = bmesh.ops.subdivide_edges(object_bm, edges=[edge], cuts=self.cuts-i) # sub_verts = [v for v in sub["geom_inner"] if isinstance(v, bmesh.types.BMVert)] # inner_verts.append(sub_verts) if self.side_inset > 0.0: inset_region = bmesh.ops.inset_region(object_bm, faces=side_faces, thickness=self.side_inset, use_even_offset=True, use_interpolate=True) if self.inset > 0.0: inset_region_faces = bmesh.ops.inset_region(object_bm, faces=faces, thickness=self.inset, use_even_offset=True, use_interpolate=True) if self.fill_type == "HOLE": bmesh.ops.delete(object_bm, geom=faces, context=5) elif self.fill_type == "NGON": bmesh.utils.face_join(faces) shape_bm.clear() del object_bvh object_bm.normal_update() bmesh.update_edit_mesh(object.data) return {'FINISHED'}
def iterate(self, newendpointsper1000=0, maxtime=0.0): # maxtime still ignored for now endpointsadded=0.0 niterations=0.0 newendpointsper1000 /= 1000.0 t=expovariate(newendpointsper1000) if newendpointsper1000 > 0.0 else 1 # time to the first new 'endpoint add event' while self.NBP>0 and (len(self.endpoints)>0): self.NBP -= 1 closestsendpoints=dd(list) kill = set() for ei,e in enumerate(self.endpoints): distance = None closestbp = None for bi,b in enumerate(self.branchpoints): ddd = b.v-e ddd = ddd.dot(ddd) if ddd < self.KILLDIST: kill.add(ei) elif (ddd<self.INFLUENCE and b.shoot is None) and ((distance is None) or (ddd < distance)): closestbp = bi distance = ddd if not (closestbp is None): closestsendpoints[closestbp].append(ei) if len(closestsendpoints)<1: break for bi in closestsendpoints: sd=Vector((0,0,0)) n=0 for ei in closestsendpoints[bi]: dv=self.branchpoints[bi].v-self.endpoints[ei] ll=sqrt(dv.dot(dv)) sd-=dv/ll n+=1 sd/=n ll=sqrt(sd.dot(sd)) # don't know if this assumption is true: # if the unnormalised direction is very small, the endpoints are nearly coplanar/colinear and at roughly the same distance # so no endpoints will be killed and we might end up adding the same branch again and again if ll < 1e-3 : #print('SD very small') continue sd/=ll sd[2]+=self.TROPISM ll=sqrt(sd.dot(sd)) sd/=ll newp = self.branchpoints[bi].v+sd*self.d # the assumption we made earlier is not suffucient to prevent adding the same branch so we need an expensive check: tooclose = False for dbi in self.branchpoints: dddd = newp - dbi.v if dddd.dot(dddd) < 1e-3 : #print('BP to close to another') tooclose = True if tooclose : continue if not self.exclude(newp): bp = Branchpoint(newp,bi) self.branchpoints.append(bp) nbpi = len(self.branchpoints)-1 bp = self.branchpoints[bi] bp.connections+=1 if bp.apex is None: bp.apex = nbpi else: bp.shoot = nbpi while not (bp.parent is None): bp = self.branchpoints[bp.parent] bp.connections+=1 self.endpoints = [ep for ei,ep in enumerate(self.endpoints) if not(ei in kill)] if newendpointsper1000 > 0.0: # generate new endpoints with a poisson process # when we first arrive here, t already hold the time to the first event niterations+=1 while t < niterations: # we keep on adding endpoints as long as the next event still happens within this iteration self.endpoints.append(next(self.volumepoint)) endpointsadded+=1 t+=expovariate(newendpointsper1000) # time to new 'endpoint add event'
def draw_molecule(molecule, center=(0, 0, 0), show_bonds=True, join=True): """Draws a JSON-formatted molecule in Blender. This method uses a couple of tricks from [1] to improve rendering speed. In particular, it minimizes the amount of unique meshes and materials, and doesn't draw until all objects are initialized. [1] https://blenderartists.org/forum/showthread.php ?273149-Generating-a-large-number-of-mesh-primitives Args: molecule: The molecule to be drawn, as a python object following the JSON convention set in this project. center: (Optional, default (0, 0, 0)) Cartesian center of molecule. Use to draw multiple molecules in different locations. show_bonds: (Optional, default True) Draws a ball-and-stick model if True, and a space-filling model if False. join: (Optional, default True) Joins the molecule into a single object. Set to False if you want to individually manipulate atoms/bonds. Returns: If run in a blender context, will return a visual object of the molecule. """ shapes = [] # If using space-filling model, scale up atom size and remove bonds # Add atom primitive bpy.ops.object.select_all(action='DESELECT') bpy.ops.mesh.primitive_uv_sphere_add() sphere = bpy.context.object # Initialize bond material if it's going to be used. if show_bonds: bpy.data.materials.new(name='bond') bpy.data.materials['bond'].diffuse_color = atom_data['bond']['color'] bpy.data.materials['bond'].specular_intensity = 0.2 bpy.ops.mesh.primitive_cylinder_add() cylinder = bpy.context.object cylinder.active_material = bpy.data.materials['bond'] for atom in molecule['atoms']: if atom['element'] not in atom_data: atom['element'] = 'undefined' if atom['element'] not in bpy.data.materials: key = atom['element'] bpy.data.materials.new(name=key) bpy.data.materials[key].diffuse_color = atom_data[key]['color'] bpy.data.materials[key].specular_intensity = 0.2 atom_sphere = sphere.copy() atom_sphere.data = sphere.data.copy() atom_sphere.location = [l + c for l, c in zip(atom['location'], center)] scale = 1 if show_bonds else 2.5 atom_sphere.dimensions = [atom_data[atom['element']]['radius'] * scale * 2] * 3 atom_sphere.active_material = bpy.data.materials[atom['element']] bpy.context.scene.objects.link(atom_sphere) shapes.append(atom_sphere) for bond in (molecule['bonds'] if show_bonds else []): start = molecule['atoms'][bond['atoms'][0]]['location'] end = molecule['atoms'][bond['atoms'][1]]['location'] diff = [c2 - c1 for c2, c1 in zip(start, end)] cent = [(c2 + c1) / 2 for c2, c1 in zip(start, end)] mag = sum([(c2 - c1) ** 2 for c1, c2 in zip(start, end)]) ** 0.5 v_axis = Vector(diff).normalized() v_obj = Vector((0, 0, 1)) v_rot = v_obj.cross(v_axis) # This check prevents gimbal lock (ie. weird behavior when v_axis is # close to (0, 0, 1)) if v_rot.length > 0.01: v_rot = v_rot.normalized() axis_angle = [acos(v_obj.dot(v_axis))] + list(v_rot) else: v_rot = Vector((1, 0, 0)) axis_angle = [0] * 4 if bond['order'] not in range(1, 4): sys.stderr.write("Improper number of bonds! Defaulting to 1.\n") bond['order'] = 1 if bond['order'] == 1: trans = [[0] * 3] elif bond['order'] == 2: trans = [[1.4 * atom_data['bond']['radius'] * x for x in v_rot], [-1.4 * atom_data['bond']['radius'] * x for x in v_rot]] elif bond['order'] == 3: trans = [[0] * 3, [2.2 * atom_data['bond']['radius'] * x for x in v_rot], [-2.2 * atom_data['bond']['radius'] * x for x in v_rot]] for i in range(bond['order']): bond_cylinder = cylinder.copy() bond_cylinder.data = cylinder.data.copy() bond_cylinder.dimensions = [atom_data['bond']['radius'] * scale * 2] * 2 + [mag] bond_cylinder.location = [c + scale * v for c, v in zip(cent, trans[i])] bond_cylinder.rotation_mode = 'AXIS_ANGLE' bond_cylinder.rotation_axis_angle = axis_angle bpy.context.scene.objects.link(bond_cylinder) shapes.append(bond_cylinder) # Remove primitive meshes bpy.ops.object.select_all(action='DESELECT') sphere.select = True if show_bonds: cylinder.select = True # If the starting cube is there, remove it if 'Cube' in bpy.data.objects.keys(): bpy.data.objects.get('Cube').select = True bpy.ops.object.delete() for shape in shapes: shape.select = True bpy.context.scene.objects.active = shapes[0] bpy.ops.object.shade_smooth() if join: bpy.ops.object.join() bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY', center='MEDIAN') bpy.context.scene.update()
def draw_molecule(molecule, center=(0, 0, 0), max_molecule_size=5, show_bonds=True): """Draw a molecule to blender. Uses loaded json molecule data.""" # Get scale factor - only scales large molecules down max_coord = 1E-6 for atom in molecule["atoms"]: max_coord = max(max_coord, *[abs(a) for a in atom["location"]]) scale = min(max_molecule_size / max_coord, 1) # Scale location coordinates and add specified center for atom in molecule["atoms"]: atom["location"] = [c + x * scale for c, x in zip(center, atom["location"])] # Keep references to all atoms and bonds shapes = [] # If using space-filling model, scale up atom size and remove bonds if not show_bonds: scale *= 2.5 molecule["bonds"] = [] # Add atom primitive bpy.ops.object.select_all(action='DESELECT') bpy.ops.mesh.primitive_uv_sphere_add() sphere = bpy.context.object # Add bond material and primitive if it's going to be used if molecule["bonds"]: key = "bond" bpy.data.materials.new(name=key) bpy.data.materials[key].diffuse_color = colors[key] bpy.data.materials[key].specular_intensity = 0.2 bpy.ops.mesh.primitive_cylinder_add() cylinder = bpy.context.object cylinder.active_material = bpy.data.materials["bond"] # Draw atoms for atom in molecule["atoms"]: # If element is not in dictionary, use undefined values if atom["element"] not in available_atoms: atom["element"] = "undefined" # If material for atom type has not yet been defined, do so if atom["element"] not in bpy.data.materials: key = atom["element"] bpy.data.materials.new(name=key) bpy.data.materials[key].diffuse_color = colors[key] bpy.data.materials[key].specular_intensity = 0.2 # Copy mesh primitive and edit to make atom atom_sphere = sphere.copy() atom_sphere.data = sphere.data.copy() atom_sphere.location = atom["location"] atom_sphere.dimensions = [diameters[atom["element"]] * scale] * 3 atom_sphere.active_material = bpy.data.materials[atom["element"]] bpy.context.scene.objects.link(atom_sphere) shapes.append(atom_sphere) # Draw bonds for bond in molecule["bonds"]: # Extracting locations first_loc = molecule["atoms"][bond["atoms"][0]]["location"] second_loc = molecule["atoms"][bond["atoms"][1]]["location"] diff = [c2 - c1 for c2, c1 in zip(first_loc, second_loc)] cent = [(c2 + c1) / 2 for c2, c1 in zip(first_loc, second_loc)] mag = sum([(c2 - c1) ** 2 for c1, c2 in zip(first_loc, second_loc)]) ** 0.5 # Euler rotation calculation v_axis = Vector(diff).normalized() v_obj = Vector((0, 0, 1)) v_rot = v_obj.cross(v_axis) angle = acos(v_obj.dot(v_axis)) # Check that the number of bonds is logical if bond["order"] not in range(1, 4): print("Improper number of bonds! Defaulting to 1.") bond["order"] = 1 # Specify locations of each bond in every scenario if bond["order"] == 1: trans = [[0] * 3] elif bond["order"] == 2: trans = [[0.7 * diameters["bond"] * x for x in v_obj], [-0.7 * diameters["bond"] * x for x in v_obj]] elif bond["order"] == 3: trans = [[0] * 3, [1.1 * diameters["bond"] * x for x in v_obj], [-1.1 * diameters["bond"] * x for x in v_obj]] # Draw bonds for i in range(bond["order"]): bond_cylinder = cylinder.copy() bond_cylinder.data = cylinder.data.copy() bond_cylinder.dimensions = [diameters["bond"] * scale] * 2 + [mag] bond_cylinder.location = [c + scale * v for c, v in zip(cent, trans[i])] bond_cylinder.rotation_mode = "AXIS_ANGLE" bond_cylinder.rotation_axis_angle = [angle] + list(v_rot) bpy.context.scene.objects.link(bond_cylinder) shapes.append(bond_cylinder) # Remove primitive meshes bpy.ops.object.select_all(action='DESELECT') sphere.select = True if molecule["bonds"]: cylinder.select = True # If the starting cube is there, remove it if "Cube" in bpy.data.objects.keys(): bpy.data.objects.get("Cube").select = True bpy.ops.object.delete() # Smooth and join molecule shapes for shape in shapes: shape.select = True bpy.context.scene.objects.active = shapes[0] bpy.ops.object.shade_smooth() bpy.ops.object.join() # Center object origin to geometry bpy.ops.object.origin_set(type="ORIGIN_GEOMETRY", center="MEDIAN") # Refresh scene bpy.context.scene.update()
class PlaneVector(Vector): """ 三次元平面を表す四次元ベクトル。 attributes: location (Vector): 平面の位置ベクトル。 normal (Vector): 平面の法線ベクトル。 tip: 参考: http://marupeke296.com/COL_Basic_No3_WhatIsPlaneEquation.html 法線ベクトル= [a, b, c] # need normalize 位置ベクトル = [xo, yo, zo] 平面の方程式。 a(x - xo) + b(y - yo) + c(z - zo) = 0 これは[a, b, c]と[(x - xo),(y - yo),(z - zo)]の内積と見ることも出来る。 上式を展開 ax + by + cz - (a * xo + b * yo + c * zo) = 0 平面を表す四次元ベクトル pvec = [a, b, c, - (a * xo + b * yo + c * zo)] 平面とベクトルの距離 = dot_v4v4(vec, pvec) caution: normalの正規化は、__new__(), update(), _normal_set()でしか行われない。 スライスを使った変更(normal[:] = [1.0, 0.0, 0.0])をやると正規化が行われない。 copy()以外の、normalized()やto_3d()等は只のVector型になってしまうので注意 """ def __new__(cls, location=Vector(), normal=ZAXIS, rotation=Quaternion()): loc = Vector(location) nor = Vector(normal).normalized() vector = nor.to_4d() vector[3] = -nor.dot(loc) return Vector.__new__(cls, vector) def __init__(self, location=Vector(), normal=ZAXIS, rotation=None): """ location: <Vector> normal: <Vector> rotation: <Quaternion> or <None> """ self._location = Vector(location) self._normal = Vector(normal).normalized() if rotation: self._rotation = Quaternion(rotation) else: self._rotation = ZAXIS.rotation_difference(self._normal) @property def normal_isnan(self): return any((math.isnan(f) for f in self.normal)) @property def location_isnan(self): return any((math.isnan(f) for f in self.location)) def copy(self, other=None): return self.__class__(self.location, self.normal, self.rotation) def copy_to(self, other): """other: <PlaneVector>: 対象へ値を複製する""" other[:] = self[:] other._location[:] = self._location other._normal[:] = self._normal other._rotation[:] = self._rotation def update(self): """self.location,self.normal,self.rotationを変更した際に呼び出される。 x, y, z = self._location a, b, c = self._normal self[:] = [a, b, c, -(a * x + b * y + c * z)] """ self._normal.normalize() self[:3] = self._normal self[3] = -self._normal.dot(self._location) @property def location(self): return self._location @location.setter def location(self, value): """スライスで代入""" # self._location = Vector(value) self._location[:] = value self.update() @property def normal(self): return self._normal @normal.setter def normal(self, value): """スライスで代入""" # self._normal = Vector(value).normalized() self._normal[:] = value self.update() self._rotation = ZAXIS.rotation_difference(self._normal) @property def rotation(self): return self._rotation @rotation.setter def rotation(self, value): self._rotation[:] = value self._normal[:] = self._rotation * ZAXIS self.update() def distance(self, other:Vector): """平面とVectorの距離を返す""" return self.dot(other.to_4d()) def distance_normal(self, other:Vector): """normalの直線とVectorの距離を返す""" self.update() return self.normal.cross(other - self.location) def project(self, other:Vector): """上書き。Vectorを平面に投影する""" self.update() v = other - self.location return (v - v.project(self.normal)) + self.location def intersect(self, v1, v2): """平面とv1-v2からなる直線の交点を求める""" return geom.intersect_line_plane(v1, v2, self.location, self.normal) def same_radius_vectors(self, radius, num): """平面上にあってlocationを中心にした同一距離のベクトルを返す。""" if num == 0: return [] quat = ZAXIS.rotation_difference(self.normal) vectors = [] a = math.pi / 2 # 90度の位置から始める for i in range(num): vec = Vector((radius * math.cos(a), radius * math.sin(a), 0)) vectors.append(quat * vec + self.location) a -= math.pi * 2 / num # 反時計回り return vectors def to_matrix(self, use_rotation=False): """plane.normalがZ軸になるような行列。 PlaneVector.to_matrix().inverted() * Vectorでworld->plane座標への変換が 出来る。 use_rotation: <bool>: self.rotationを使う。 """ locmat = Matrix.Translation(self.location) if use_rotation: quat = self.rotation else: quat = ZAXIS.rotation_difference(self.normal) rotmat = quat.to_matrix().to_4x4() return locmat * rotmat
def iterate(self, newendpointsper1000=0, maxtime=0.0): # maxtime still ignored for now endpointsadded=0.0 niterations=0.0 newendpointsper1000 /= 1000.0 t=expovariate(newendpointsper1000) if newendpointsper1000 > 0.0 else 1 # time to the first new 'endpoint add event' while self.NBP>0 and (len(self.endpoints)>0): self.NBP -= 1 closestsendpoints=dd(list) kill = set() for ei,e in enumerate(self.endpoints): distance = None closestbp = None for bi,b in enumerate(self.branchpoints): ddd = b.v-e ddd = ddd.dot(ddd) if ddd < self.KILLDIST: kill.add(ei) elif (ddd<self.INFLUENCE) and ((distance is None) or (ddd < distance)): closestbp = bi distance = ddd if not (closestbp is None): closestsendpoints[closestbp].append(ei) if len(closestsendpoints)<1: break for bi in closestsendpoints: sd=Vector((0,0,0)) n=0 for ei in closestsendpoints[bi]: dv=self.branchpoints[bi].v-self.endpoints[ei] ll=sqrt(dv.dot(dv)) sd-=dv/ll n+=1 sd/=n ll=sqrt(sd.dot(sd)) sd/=ll sd[2]+=self.TROPISM ll=sqrt(sd.dot(sd)) sd/=ll if sd < 1e-7 : print('SD very small') bp = Branchpoint(self.branchpoints[bi].v+sd*self.d,bi) self.branchpoints.append(bp) bp = self.branchpoints[bi] bp.connections+=1 while not (bp.parent is None): bp = self.branchpoints[bp.parent] bp.connections+=1 self.endpoints = [ep for ei,ep in enumerate(self.endpoints) if not(ei in kill)] if newendpointsper1000 > 0.0: # generate new endpoints with a poisson process # when we first arrive here, t already hold the time to the first event niterations+=1 while t < niterations: # we keep on adding endpoints as long as the next event still happens within this iteration self.endpoints.append(next(self.volumepoint)) endpointsadded+=1 t+=expovariate(newendpointsper1000) # time to new 'endpoint add event' if newendpointsper1000 > 0.0: print("newendpoints/iteration %.3f, actual %.3f in %5.f iterations"%(newendpointsper1000,endpointsadded/niterations,niterations))
def grow(self): from numpy import sum, all, ones self.itt += 1 v_xyz,s_xyz = self.get_positions() dvv,dvs = self.get_distances(v_xyz,s_xyz) vs_map,sv_map = self.make_maps(dvv,dvs) nv,ns = dvs.shape nodes = self.nodes stp = self.stp killzone = self.killzone noise = self.noise spawn_count = self.spawn_count new_node_pos = [] for i,jj in vs_map.items(): if spawn_count[i]>2: continue mask = dvs[i,jj]>=killzone if all(mask): spawn_count[i] += 1 v = Vector(sum(s_xyz[jj,:]-v_xyz[i,:],axis=0)) v.normalize() p,pn = self.closest_point_on_geom(nodes[i]) projected = pn.cross(v.cross(pn)) projected.normalize() if v.dot(projected)<0.85: new = projected else: new = v new += random_unit_vector()*noise new.normalize() new = nodes[i] + new*stp new_node_pos.append(new) bpy.ops.mesh.primitive_ico_sphere_add( size=0.3, subdivisions=1, location=new) self.rotate(v) self.boolean_union(bpy.context.object) ## mask out dead source nodes mask = ones(ns,'bool') for j,ii in sv_map.items(): if all(dvs[ii,j]<=killzone): mask[j] = False self.num_sources -= 1 bpy.ops.mesh.primitive_ico_sphere_add( size=0.3, subdivisions=1, location=self.sources[j]) self.boolean_union(bpy.context.object) print('deleted source:',j) self.dead_sources.append(self.sources[j]) self.nodes.extend(new_node_pos) self.sources = self.sources[mask,:] return