def debug_boundmesh(self, subsets, skeldata): vert_offset = 0 for set, bone in zip(subsets, skeldata): if not set.faces: continue # Negate trans to account for flipped axes trans = Vector(bone.position) trans.negate() # Rotate rot quaternion to account for flipped axes rot = Quaternion(bone.rotation) rot.rotate( Quaternion((0,0,0,1)) ) relative_zero = rot * trans verts = [] faces = [] for vert in set.vertices: # Relative vertex position: Absolute position - bone position # I guess? verts.append( rot * Vector(vert) + relative_zero ) # Create object and mesh mesh = bpy.data.meshes.new(bone.name) object = bpy.data.objects.new(bone.name, mesh) bpy.context.scene.objects.link(object) bpy.context.scene.objects.active = object # Load vertices and faces mesh.from_pydata(verts, [], [])
def import_skeleton(self, data): skeldata = BoneData.build_bones(data) # Create armature and object name = 'Armature' bpy.ops.object.add(type='ARMATURE', enter_editmode=True, location=(0, 0, 0)) # Armature object ob = bpy.context.active_object ob.show_in_front = True ob.name = name # Armature amt = ob.data amt.display_type = 'STICK' # Create bones from skeleton data # Sims 2 bones seem to be reversed head to tail for bonedata in skeldata: bone = amt.edit_bones.new(bonedata.name) # Negate trans to account for flipped axes trans = Vector(bonedata.position) trans.negate() # Rotate rot quaternion to account for flipped axes rot = Quaternion(bonedata.rotation) rot.rotate(Quaternion((0, 0, 0, 1))) bone.head = rot @ trans if bonedata.parent != None: parent = amt.edit_bones[bonedata.parent] bone.parent = parent bone.tail = parent.head # Check if the length of a bone is too short for blender bonelen = bone.tail.length - bone.head.length if bonelen > -0.0005 and bonelen < 0.0005: # Blender does not support 0 length bones bone.head += Vector((0, 0, 0.00005)) # Enter custom properties for exporting later # # Translate Vector and Rotation Quaternion bone["translate"] = [ bonedata.position[0], bonedata.position[1], bonedata.position[2] ] bone["rotation"] = [ bonedata.rotation[0], bonedata.rotation[1], bonedata.rotation[2], bonedata.rotation[3] ] # Go back to Object mode bpy.ops.object.mode_set(mode='OBJECT') # Return the Armature object return ob
def spherize(context): print("in spherize") #influence influence = context.scene['unt_spherifyratio'] #gets the location of the center of the center object if context.scene.centerobject: center = bpy.data.objects[context.scene.centerobject].location else: center = bpy.context.scene.cursor_location #get world matrix for the object worldmatrix = bpy.context.object.matrix_world ob = bpy.context.object obdata = ob.data #mandatory stupid step, calculate normals split, in object mode bpy.ops.object.mode_set(mode="OBJECT") obdata.calc_normals_split() #prepare a list for all the normals. One per "loop"(vertice-per-faace) normals = [Vector()] * len(obdata.loops) #loop all the loops (subvertices) in the mesh for loop in obdata.loops: #obdata.calc_normals_split() vertexindex = loop.vertex_index normals[loop.index] = loop.normal print("normal: %s" % normals[loop.index]) #if the vertex is selected, normalize the normal if obdata.vertices[vertexindex].select: #get local coordinate of the related vertex localco = obdata.vertices[vertexindex].co #calculate the globla coordinates of the vertex globalco = worldmatrix * obdata.vertices[vertexindex].co #delta betwen the global location of the vertex and the center of the center object v = Vector([y - x for x, y in zip(globalco, center)]) v.negate() v.normalize() #resulting vector (v*influence + normal*(1-influence)) normals[loop.index] = v * float(influence) + normals[ loop.index] * (float(1) - float(influence)) #normals[loop.index].negate() normals[loop.index].normalize() print("new normal: %s" % normals[loop.index]) obdata.normals_split_custom_set(normals) bpy.ops.object.mode_set(mode="EDIT")
def spherize(context): print ("in spherize") #influence influence = context.scene['unt_spherifyratio'] #gets the location of the center of the center object if context.scene.centerobject: center = bpy.data.objects[context.scene.centerobject].location else: center = bpy.context.scene.cursor_location #get world matrix for the object worldmatrix = bpy.context.object.matrix_world ob = bpy.context.object obdata = ob.data #mandatory stupid step, calculate normals split, in object mode bpy.ops.object.mode_set(mode="OBJECT") obdata.calc_normals_split() #prepare a list for all the normals. One per "loop"(vertice-per-faace) normals = [Vector()]*len(obdata.loops) #loop all the loops (subvertices) in the mesh for loop in obdata.loops: #obdata.calc_normals_split() vertexindex = loop.vertex_index normals[loop.index] = loop.normal print ("normal: %s"% normals[loop.index]) #if the vertex is selected, normalize the normal if obdata.vertices[vertexindex].select: #get local coordinate of the related vertex localco = obdata.vertices[vertexindex].co #calculate the globla coordinates of the vertex globalco = worldmatrix * obdata.vertices[vertexindex].co #delta betwen the global location of the vertex and the center of the center object v= Vector([y-x for x,y in zip(globalco,center)]) v.negate() v.normalize() #resulting vector (v*influence + normal*(1-influence)) normals[loop.index] = v * float(influence) + normals[loop.index] * (float(1)-float(influence)) #normals[loop.index].negate() normals[loop.index].normalize() print ("new normal: %s"% normals[loop.index]) obdata.normals_split_custom_set(normals) bpy.ops.object.mode_set(mode="EDIT")
def get_obj_axis(obj, axis): ax = 0 if axis == 'Y' or axis == '-Y': ax = 1 if axis == 'Z' or axis == '-Z': ax = 2 obj_matrix = obj.matrix_world axis_tuple = (obj_matrix[0][ax], obj_matrix[1][ax], obj_matrix[2][ax]) axisResult = Vector(axis_tuple).normalized() if axis == '-X' or axis == '-Y' or axis == '-Z': axisResult.negate() return axisResult
def get_obj_axis(obj, axis): ax = 0 if axis == "Y" or axis == "-Y": ax = 1 if axis == "Z" or axis == "-Z": ax = 2 obj_matrix = obj.matrix_world axis_tuple = (obj_matrix[0][ax], obj_matrix[1][ax], obj_matrix[2][ax]) axisResult = Vector(axis_tuple).normalized() if axis == "-X" or axis == "-Y" or axis == "-Z": axisResult.negate() return axisResult
def get_obj_axis(obj, axis): ax = 0 if axis == 'Y' or axis == '-Y': ax = 1 if axis == 'Z' or axis == '-Z': ax = 2 obj_matrix = obj.matrix_world axis_tuple = ( obj_matrix[0][ax], obj_matrix[1][ax], obj_matrix[2][ax]) axisResult = Vector(axis_tuple).normalized() if axis == '-X' or axis == '-Y' or axis == '-Z': axisResult.negate() return axisResult
def to_plane(self, amplitude, coefficient, vertex, centers, direction): center = Vector(centers[0]) direction = Vector(direction) vertex = Vector(vertex) dirlength = direction.length if dirlength <= 0: raise ValueError("Direction vector must have nonzero length!") d = -direction.dot(center) # distance from vertex to plane rho = abs(vertex.dot(direction) + d) / dirlength from_center = center - vertex # vector is either direction or negative direction if from_center.dot(direction) >= 0: vector = direction.normalized() else: # for some reason mathutil's Vector does not have .negated() # thankfully we do not need direction itself anymore. direction.negate() vector = direction.normalized() return self.falloff(amplitude, coefficient, rho), vector
def execute(self, context): object = context.object object.update_from_editmode() self.pivot_point = context.space_data.pivot_point self.transform_orientation = context.space_data.transform_orientation 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] 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'} selection_center = Vector() 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, radius=diameter/2) 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: context.scene.perfect_shape.preview_verts_count = loop_verts_len + self.span try: center = get_cache(self.as_pointer(), "P_{}_{}".format(self.pivot_point, loop_idx)) except CacheException: if self.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 set_cache(self.as_pointer(), "P_{}_{}".format(self.pivot_point, loop_idx), center) 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 forward.angle(normal_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() rotation_m = 1 if context.space_data.pivot_point != "INDIVIDUAL_ORIGINS": if (center+selection_center).dot(forward) > 0: rotation_m = -1 # if center.cross(forward).angle(selection_center) >= math.pi / 2: # forward.negate() # if cross.dot(center) < 0: # forward.negate() # if(center + selection_center).dot(forward) < 0: # forward.negate() # matrix_rotation = forward.to_track_quat('Z', 'Y').to_matrix().to_4x4() # if (matrix_rotation * center).dot(matrix_rotation * (center + selection_center)) > 0: # forward.negate() # if loop_faces: # rotation_m = - 1 if not is_clockwise(forward, center, loop_verts): loop_verts.reverse() loop_edges.reverse() 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) loop_verts_co_2d = [(v.co * matrix_rotation).to_2d() for v in loop_verts] shape_verts_co_2d = [(v.co * matrix_rotation).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) correct_angle = 0 if self.loop_rotation: correct_angle = loop_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, 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 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*rotation_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) if not selected_faces and self.extrude != 0: self.report({'WARNING'}, "Please select faces to extrude.") shape_bm.clear() del object_bvh object_bm.normal_update() bmesh.update_edit_mesh(object.data) return {'FINISHED'}
def execute(self, context): orbiter = context.scene.orbiter template_context = { 'defname': os.path.splitext(os.path.basename(orbiter.header_file))[0].upper() } # First get the collision cage vert_list = [] with bmesh_object(bpy.data.objects[orbiter.ccage]) as bm: for v in bm.verts: # Conversion to orbiter is: z=-y,y=z,x=-x ; tri[1]<-> tri[2] ; v=1-v # Convert to orbiter coordinate system x, y, z = convert_to_orbiter(v.co) # Clean up the numbers a little x = round(x, 2) y = round(y, 2) z = round(z, 2) s = '{{_V({x}, {y}, {z}), {props}}}'.format( x=x, y=y, z=z, props='1e6, 1e5, 1.6') vert_list.append(s) template_context['ccage_verts'] = ',\n '.join(vert_list) template_context['ccage_vert_count'] = len(vert_list) template_context['ccage_suffix'] = orbiter.ccage_suffix # Now get the rockets all_rockets = set() for group in orbiter.rocket_groups: all_rockets.update(bpy.data.groups[group.group].objects) rockets = [] rockets_pos = [] rockets_dir = [] for rocket in all_rockets: if rocket.type == 'EMPTY': name = rocket.name.upper() mat = rocket.matrix_world * Matrix.Translation((0, 0, 1)) dir = Vector( (mat[0][3], mat[1][3], mat[2][3])) - rocket.location # The empties point in the direction the rocket fires, in orbiter it's the opposite. dir.negate() # index of this rocket index = len(rockets_pos) str_pos = '{{{}, {}, {}}}'.format( *convert_to_orbiter(rocket.location)) str_dir = '{{{}, {}, {}}}'.format(*convert_to_orbiter(dir)) str_rocket = '#define {prefix}{name} {index};'.format( prefix=orbiter.rocket_prefix, name=name, index=index) rockets_pos.append(str_pos) rockets_dir.append(str_dir) rockets.append(str_rocket) rocket_groups = '' for group in orbiter.rocket_groups: group_rockets = [] for rocket in bpy.data.groups[group.group].objects: group_rockets.append(orbiter.rocket_prefix + rocket.name) rocket_groups += 'const int {group} {{\n {rockets}\n}}\n'.format( group=orbiter.rocket_group_prefix + group.name, rockets=',\n '.join(group_rockets)) template_context['rocket_names'] = '\n'.join(rockets) template_context['rocket_pos_name'] = 'ROCKET_POSITIONS' template_context['rocket_pos'] = ',\n '.join(rockets_pos) template_context['rocket_dir_name'] = 'ROCKET_DIRECTIONS' template_context['rocket_dir'] = ',\n '.join(rockets_dir) template_context['rocket_groups'] = rocket_groups print(template_context) with open(bpy.path.abspath(orbiter.header_file), 'w') as f: f.write(_HEADER_FILE_TEMPLATE.format(**template_context)) return {'FINISHED'}
def execute(self, context): if self.reset_values is True: self.reset_all_values() active_obj = context.scene.objects.active bm = bmesh.from_edit_mesh(active_obj.data) bm.verts.ensure_lookup_table() #verts = [v for v in bm.verts if v.select] # get loops loops = loop_t.get_connected_input(bm) loops = loop_t.check_loops(loops, bm) if not loops: self.report({'WARNING'}, "No Loops!") return {'CANCELLED'} first_indexes = [] if isinstance(bm.select_history[0], bmesh.types.BMVert): for element in bm.select_history: first_indexes.append(element.index) elif isinstance(bm.select_history[0], bmesh.types.BMEdge): for element in bm.select_history: el_verts = element.verts first_indexes.append(el_verts[0].index) first_indexes.append(el_verts[1].index) for loop in loops: if loop[1] is True: continue loop_verts = [] for ind in loop[0]: loop_verts.append(bm.verts[ind]) # for the case if we need to reverse it if loop[0][-1] in first_indexes: loop_verts = list(reversed(loop_verts)) # reverse again for the direction if self.reverse_direction is True: loop_verts = list(reversed(loop_verts)) # positions first_vert_pos = active_obj.matrix_world * loop_verts[0].co last_vert_pos = active_obj.matrix_world * loop_verts[-1].co loop_centr_orig = first_vert_pos.lerp(last_vert_pos, 0.5) relative_dist = (first_vert_pos - loop_centr_orig).length sidevec = (first_vert_pos - last_vert_pos).normalized() obj_matrix = active_obj.matrix_world obj_matrix_inv = obj_matrix.inverted() if self.direction_vector == 'Custom': rot_dir = Vector((self.rotate_axis[0], self.rotate_axis[1], self.rotate_axis[2])).normalized() elif self.direction_vector == 'MiddleCrossed': middle_nor = loop_verts[int(len(loop_verts) / 2)].normal.copy().normalized() middle_nor = ut_base.get_normal_world(middle_nor, obj_matrix, obj_matrix_inv) rot_dir = middle_nor.cross(sidevec).normalized() # fix only for MiddleCrossed if not self.reverse_direction: rot_dir.negate() else: middle_nor = loop_verts[int(len(loop_verts) / 2)].normal.copy().normalized() middle_nor = ut_base.get_normal_world(middle_nor, obj_matrix, obj_matrix_inv) middle_nor = middle_nor.cross(sidevec).normalized() rot_dir = middle_nor.cross(sidevec).normalized() rot_dir.negate() upvec = rot_dir.cross(sidevec).normalized() loop_centr = ( self.upvec_offset * upvec * relative_dist ) + loop_centr_orig loop_angle = (first_vert_pos - loop_centr).normalized().angle((last_vert_pos - loop_centr).normalized()) if self.upvec_offset > 0: loop_angle = math.radians( (360 - math.degrees(loop_angle)) ) # even spread line_data = None if self.spread_mode == 'Even': world_verts = [active_obj.matrix_world * vert.co for vert in loop_verts] line_data = [] line_length = 0.0 for i, vec in enumerate(world_verts): if i == 0: line_data.append(0) else: line_length += (vec - world_verts[i-1]).length line_data.append(line_length) # make arc! for i, vert in enumerate(loop_verts): if i != 0 and i != len(loop_verts)-1: if self.spread_mode == 'Normal': rot_angle = loop_angle * (i / (len(loop_verts) - 1)) else: rot_angle = loop_angle * (line_data[i] / line_data[len(loop_verts)-1]) rot_mat = Matrix.Rotation(rot_angle, 3, rot_dir) vert_pos = (rot_mat * (first_vert_pos - loop_centr)) + loop_centr if self.scale_arc != 0: vert_rel_dist = mathu.geometry.distance_point_to_plane(vert_pos, loop_centr_orig, upvec) vert_rel_dist_max = self.upvec_offset + relative_dist if vert_rel_dist != 0 and vert_rel_dist_max != 0: vert_pos_offset = vert_rel_dist / vert_rel_dist_max vert_pos += (self.scale_arc * upvec * vert_pos_offset * vert_rel_dist_max) # rotate arc if self.rotate_arc_axis != 0: rot_mat_2 = Matrix.Rotation(math.radians(self.rotate_arc_axis), 3, sidevec) vert_pos = (rot_mat_2 * (vert_pos - loop_centr_orig)) + loop_centr_orig vert.co = active_obj.matrix_world.inverted() * vert_pos bm.normal_update() bmesh.update_edit_mesh(active_obj.data) return {'FINISHED'}
def execute(self, context): if self.reset_values is True: self.reset_all_values() active_obj = context.scene.objects.active bm = bmesh.from_edit_mesh(active_obj.data) bm.verts.ensure_lookup_table() #verts = [v for v in bm.verts if v.select] # get loops loops = loop_t.get_connected_input(bm) loops = loop_t.check_loops(loops, bm) if not loops: self.report({'WARNING'}, "No Loops!") return {'CANCELLED'} first_indexes = [] if isinstance(bm.select_history[0], bmesh.types.BMVert): for element in bm.select_history: first_indexes.append(element.index) elif isinstance(bm.select_history[0], bmesh.types.BMEdge): for element in bm.select_history: el_verts = element.verts first_indexes.append(el_verts[0].index) first_indexes.append(el_verts[1].index) for loop in loops: if loop[1] is True: continue loop_verts = [] for ind in loop[0]: loop_verts.append(bm.verts[ind]) # for the case if we need to reverse it if loop[0][-1] in first_indexes: loop_verts = list(reversed(loop_verts)) # reverse again for the direction if self.reverse_direction is True: loop_verts = list(reversed(loop_verts)) # positions first_vert_pos = active_obj.matrix_world * loop_verts[0].co last_vert_pos = active_obj.matrix_world * loop_verts[-1].co loop_centr_orig = first_vert_pos.lerp(last_vert_pos, 0.5) relative_dist = (first_vert_pos - loop_centr_orig).length sidevec = (first_vert_pos - last_vert_pos).normalized() obj_matrix = active_obj.matrix_world obj_matrix_inv = obj_matrix.inverted() if self.direction_vector == 'Custom': rot_dir = Vector((self.rotate_axis[0], self.rotate_axis[1], self.rotate_axis[2])).normalized() elif self.direction_vector == 'MiddleCrossed': middle_nor = loop_verts[int(len(loop_verts) / 2)].normal.copy().normalized() middle_nor = ut_base.get_normal_world(middle_nor, obj_matrix, obj_matrix_inv) rot_dir = middle_nor.cross(sidevec).normalized() # fix only for MiddleCrossed if not self.reverse_direction: rot_dir.negate() else: middle_nor = loop_verts[int(len(loop_verts) / 2)].normal.copy().normalized() middle_nor = ut_base.get_normal_world(middle_nor, obj_matrix, obj_matrix_inv) middle_nor = middle_nor.cross(sidevec).normalized() rot_dir = middle_nor.cross(sidevec).normalized() rot_dir.negate() upvec = rot_dir.cross(sidevec).normalized() loop_centr = (self.upvec_offset * upvec * relative_dist) + loop_centr_orig loop_angle = (first_vert_pos - loop_centr).normalized().angle( (last_vert_pos - loop_centr).normalized()) if self.upvec_offset > 0: loop_angle = math.radians((360 - math.degrees(loop_angle))) # even spread line_data = None if self.spread_mode == 'Even': world_verts = [ active_obj.matrix_world * vert.co for vert in loop_verts ] line_data = [] line_length = 0.0 for i, vec in enumerate(world_verts): if i == 0: line_data.append(0) else: line_length += (vec - world_verts[i - 1]).length line_data.append(line_length) # make arc! for i, vert in enumerate(loop_verts): if i != 0 and i != len(loop_verts) - 1: if self.spread_mode == 'Normal': rot_angle = loop_angle * (i / (len(loop_verts) - 1)) else: rot_angle = loop_angle * ( line_data[i] / line_data[len(loop_verts) - 1]) rot_mat = Matrix.Rotation(rot_angle, 3, rot_dir) vert_pos = (rot_mat * (first_vert_pos - loop_centr)) + loop_centr if self.scale_arc != 0: vert_rel_dist = mathu.geometry.distance_point_to_plane( vert_pos, loop_centr_orig, upvec) vert_rel_dist_max = self.upvec_offset + relative_dist if vert_rel_dist != 0 and vert_rel_dist_max != 0: vert_pos_offset = vert_rel_dist / vert_rel_dist_max vert_pos += (self.scale_arc * upvec * vert_pos_offset * vert_rel_dist_max) # rotate arc if self.rotate_arc_axis != 0: rot_mat_2 = Matrix.Rotation( math.radians(self.rotate_arc_axis), 3, sidevec) vert_pos = ( rot_mat_2 * (vert_pos - loop_centr_orig)) + loop_centr_orig vert.co = active_obj.matrix_world.inverted() * vert_pos bm.normal_update() bmesh.update_edit_mesh(active_obj.data) return {'FINISHED'}
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 execute(self, context): object = context.object object.update_from_editmode() object_bm = bmesh.from_edit_mesh(object.data) selected_edges = [e for e in object_bm.edges if e.select] selected_verts = [v for v in object_bm.verts if v.select] selected_verts_fin = [] if len(selected_edges) == 0: self.report({'WARNING'}, "Please select edges.") return {'CANCELLED'} loops = prepare_loops(selected_edges[:]) if loops is None: self.report({'WARNING'}, "Please select boundary loop(s) of selected area(s).") return {'CANCELLED'} object_bvh = mathutils.bvhtree.BVHTree.FromObject(object, context.scene, deform=False) refresh_icons() for (loop_verts, loop_edges), is_loop_cyclic, is_loop_boundary in loops: if len(loop_edges) < 3: continue loop_verts_len = len(loop_verts) context.window_manager.perfect_shape.preview_verts_count = loop_verts_len if self.projection == "NORMAL": if is_loop_boundary: forward = calculate_normal([v.co for v in loop_verts]) else: 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() else: forward = Vector([v == self.projection for v in ["X", "Y", "Z"]]) if self.invert_projection: forward.negate() shape_bm = bmesh.new() if context.space_data.pivot_point == "CURSOR": center = object.matrix_world.copy() * context.scene.cursor_location.copy() else: for loop_vert in loop_verts: shape_bm.verts.new(loop_vert.co.copy()) shape_bm.faces.new(shape_bm.verts) shape_bm.faces.ensure_lookup_table() if context.space_data.pivot_point == 'BOUNDING_BOX_CENTER': center = shape_bm.faces[0].calc_center_bounds() else: center = shape_bm.faces[0].calc_center_median() shape_bm.clear() matrix_rotation = forward.to_track_quat('Z', 'X').to_matrix().to_4x4() matrix_translation = Matrix.Translation(center) shape_bm = bmesh.new() shape_verts = None if self.shape == "CIRCLE": diameter = sum([e.calc_length() for e in loop_edges]) / (2*math.pi) diameter += self.offset shape_segments = loop_verts_len + self.span shape_verts = bmesh.ops.create_circle(shape_bm, segments=shape_segments, diameter=diameter, matrix=matrix_translation*matrix_rotation) shape_verts = shape_verts["verts"] 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[:] 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) elif self.shape == "PATTERN": if len(object.perfect_pattern.vertices) == 0: self.report({'WARNING'}, "Empty Pattern Data.") del shape_bm return {'FINISHED'} if len(object.perfect_pattern.vertices) != len(loop_verts): self.report({'WARNING'}, "Pattern and loop vertices count must be the same.") del shape_bm return {'FINISHED'} for pattern_vert in object.perfect_pattern.vertices: shape_bm.verts.new(Vector(pattern_vert.co)) shape_verts = shape_bm.verts[:] 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) 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 = prepare_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 = shape_bm.verts[:] 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 shape_verts is not None and len(shape_verts) > 0: 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() loop_angle = box_fit_2d([(matrix_rotation.transposed() * v.co).to_2d() for v in loop_verts]) shape_angle = box_fit_2d([(matrix_rotation.transposed() * v.co).to_2d() for v in shape_verts]) if abs(abs(loop_angle) - abs(shape_angle)) <= 0.01: loop_angle = 0 correct_angle = loop_angle + self.rotation if self.shape_rotation: correct_angle -= shape_angle if correct_angle != 0 and not self.active_as_first: bmesh.ops.rotate(shape_bm, verts=shape_verts, cent=center, matrix=Matrix.Rotation(-correct_angle, 3, forward)) active = object_bm.select_history.active if self.active_as_first and isinstance(active, bmesh.types.BMVert) and active in loop_verts: shift = loop_verts.index(active) else: 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 if shift != 0: loop_verts = loop_verts[shift % len(loop_verts):] + loop_verts[:shift % len(loop_verts)] 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: object_bm.select_flush_mode() select_only(object_bm, loop_edges, {"EDGE"}) bpy.ops.mesh.loop_to_region() # Ugly. inset_faces = [f for f in object_bm.faces[:] if f.select] if self.fill_type != "ORIGINAL": smooth = inset_faces[0].smooth bmesh.ops.delete(object_bm, geom=inset_faces, context=5) inset_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 inset_faces.append(new_face) bmesh.ops.recalc_face_normals(object_bm, faces=inset_faces) selected_co = [] for vert in selected_verts: if vert.is_valid: selected_co.append(vert.co.copy()) outset_region_faces = [] if self.outset > 0.0: outset_region_faces = bmesh.ops.inset_region(object_bm, faces=inset_faces, thickness=self.outset, use_even_offset=True, use_interpolate=True, use_outset=True) outset_region_faces = outset_region_faces["faces"] inset_region_faces = [] if self.inset > 0.0: inset_region_faces = bmesh.ops.inset_region(object_bm, faces=inset_faces, thickness=self.inset, use_even_offset=True, use_interpolate=True) inset_region_faces = inset_region_faces["faces"] new_selected_verts = [] for face in set(inset_region_faces+inset_faces): for vert in face.verts: if vert.co in selected_co: new_selected_verts.append(vert) selected_co.remove(vert.co) selected_verts_fin.append(new_selected_verts) select_only(object_bm, new_selected_verts, {"EDGE"}) if self.fill_type == "HOLE": bmesh.ops.delete(object_bm, geom=inset_faces, context=5) inset_faces = [] elif self.fill_type == "NGON": inset_faces = [bmesh.utils.face_join(inset_faces)] if self.fill_flatten and self.extrude == 0: if len(inset_region_faces + inset_faces) > 0: verts = list(set(reduce(lambda v1, v2: list(v1) + list(v2), [v.verts for v in inset_region_faces + inset_faces]))) 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, 1, +0)), space=matrix, verts=verts) bmesh.ops.rotate(object_bm, cent=center, matrix=matrix_rotation, verts=verts) bmesh.ops.recalc_face_normals(object_bm, faces=outset_region_faces+inset_region_faces+inset_faces) if self.extrude != 0: extrude_geom = bmesh.ops.extrude_face_region(object_bm, geom=inset_region_faces+inset_faces) verts = [v for v in extrude_geom['geom'] if isinstance(v, bmesh.types.BMVert)] 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.delete(object_bm, geom=inset_region_faces+inset_faces, context=5) bmesh.ops.translate(object_bm, verts=verts, vec=forward * self.extrude) del shape_bm if selected_verts_fin: select_only(object_bm, reduce(lambda x, y: x + y, selected_verts_fin), {"EDGE"}) object_bm.select_flush(True) del object_bvh bmesh.update_edit_mesh(object.data) return {'FINISHED'}
def execute(self, context): og_cursor_setting = str(context.scene.cursor.rotation_mode) self.cursorOP = bpy.context.scene.kekit.cursorfit if not self.cursorOP: # GRAB CURRENT ORIENT & PIVOT (to restore at the end) og_orientation = str(bpy.context.scene.transform_orientation_slots[0].type) og_pivot = str(bpy.context.scene.tool_settings.transform_pivot_point) if bpy.context.mode == "EDIT_MESH": sel_mode = bpy.context.tool_settings.mesh_select_mode[:] obj = bpy.context.edit_object obj_mtx = obj.matrix_world.copy() bm = bmesh.from_edit_mesh(obj.data) bm.verts.ensure_lookup_table() bm.edges.ensure_lookup_table() bm.faces.ensure_lookup_table() vert_mode = True sel_verts = [v for v in bm.verts if v.select] sel_count = len(sel_verts) if sel_count == 0: bpy.ops.view3d.snap_cursor_to_center() return {'FINISHED'} # POLY MODE ----------------------------------------------------------------------- if sel_mode[2]: sel_poly = [p for p in bm.faces if p.select] if sel_poly: # sel_islands = get_islands(bm, sel_verts) v_normals = [p.normal for p in sel_poly] v_tan = [p.calc_tangent_edge_pair() for p in sel_poly] face = sel_poly[-1] normal = correct_normal(obj_mtx, sum(v_normals, Vector()) / len(v_normals)) tangent = correct_normal(obj_mtx, sum(v_tan, Vector()) / len(v_tan)) if len(sel_poly) == 1: pos = obj_mtx @ bm.faces[face.index].calc_center_median() else: ps = [v.co for v in sel_verts] pos = obj_mtx @ average_vector(ps) # fallback for all faces selected type of scenarios if sum(normal) == 0: normal = Vector((0,0,1)) tangent = Vector((1,0,0)) rot_mtx = rotation_from_vector(normal, tangent, rw=False) set_cursor(rot_mtx, pos=pos) else: bpy.ops.view3d.snap_cursor_to_center() vert_mode = False # EDGE MODE ----------------------------------------------------------------------- if sel_mode[1]: loop_mode = False line_mode = False sel_edges = [e for e in bm.edges if e.select] e_count = len(sel_edges) if e_count > 1: vps = [e.verts[:] for e in sel_edges] loops = get_loops(vps, legacy=True) if len(loops) >= 1 and loops[0][0] != loops[0][-1]: fl = list(set(flatten(vps))) if len(fl) == len(loops[0]): line_mode = True if e_count == 1: ev = sel_edges[0].verts[:] n = Vector((ev[0].normal + ev[1].normal) * .5).normalized() t_v = Vector((ev[0].co - ev[1].co).normalized()) vert_mode = False elif e_count == 2 or line_mode: shared_face = [] for f in sel_edges[0].link_faces: for fe in sel_edges[1].link_faces: if fe == f: shared_face = f break ev = sel_edges[0].verts[:] etv = sel_edges[1].verts[:] n = Vector((ev[0].normal + ev[1].normal)*.5).normalized() t_v = Vector((etv[0].normal + etv[1].normal)*.5).normalized() if abs(round(n.dot(t_v),2)) == 1 or shared_face or line_mode: avg_v, avg_e = [], [] for e in sel_edges: avg_v.append((e.verts[0].normal + e.verts[1].normal) * .5) uv = Vector(e.verts[0].co - e.verts[1].co).normalized() avg_e.append(uv) n = average_vector(avg_v) # I forgot why i needed these checks? if sum(n) == 0: n = avg_v[0] t_v = average_vector(avg_e) if sum(t_v) == 0: t_v = avg_e[0] if shared_face: t_v = avg_e[0] vert_mode = False elif e_count > 2: loop_mode = True startv = Vector(sel_edges[0].verts[0].co - sel_edges[0].verts[1].co).normalized() cv1 = Vector(sel_edges[1].verts[0].co - sel_edges[1].verts[1].co).normalized() cdot = abs(round(startv.dot(cv1), 6)) for e in sel_edges[2:]: v = Vector(e.verts[0].co - e.verts[1].co).normalized() vdot = abs(round(startv.dot(v), 3)) if vdot < cdot: cv1 = v cdot = vdot n = startv t_v = cv1 n.negate() vert_mode = False # final pass n = correct_normal(obj_mtx, n) t_v = correct_normal(obj_mtx, t_v) n = n.cross(t_v) # vert average fallback if sum(t_v) == 0 or sum(n) == 0: vert_mode = True if not vert_mode: if loop_mode: rot_mtx = rotation_from_vector(n, t_v, rotate90=True) else: rot_mtx = rotation_from_vector(n, t_v, rotate90=False) set_cursor(rot_mtx) # VERT (& GENERAL AVERAGE) MODE ----------------------------------------------------------------------- if sel_mode[0] or vert_mode: if sel_count == 2: n = Vector(sel_verts[0].co - sel_verts[1].co).normalized() v_n = [v.normal for v in sel_verts] t_v = correct_normal(obj_mtx, sum(v_n, Vector()) / len(v_n)) n = correct_normal(obj_mtx, n) t_v = t_v.cross(n) rot_mtx = rotation_from_vector(n, t_v) set_cursor(rot_mtx) elif sel_count == 3: cv = [v.co for v in sel_verts] # make triangle vectors, sort to avoid the hypot.vector h = tri_points_order(cv) tri = sel_verts[h[0]].co, sel_verts[h[1]].co, sel_verts[h[2]].co v1 = Vector((tri[0] - tri[1])).normalized() v2 = Vector((tri[0] - tri[2])).normalized() v1 = correct_normal(obj_mtx, v1) v2 = correct_normal(obj_mtx, v2) n_v = v1.cross(v2) # flipcheck v_n = [v.normal for v in sel_verts] ncheck = correct_normal(obj_mtx, sum(v_n, Vector()) / len(v_n)) if ncheck.dot(n_v) < 0: n_v.negate() # tangentcheck c1 = n_v.cross(v1).normalized() c2 = n_v.cross(v2).normalized() if c1.dot(n_v) > c2.dot(n_v): u_v = c2 else: u_v = c1 t_v = u_v.cross(n_v).normalized() rot_mtx = rotation_from_vector(n_v, t_v) set_cursor(rot_mtx) elif sel_count != 0: v_n = [v.normal for v in sel_verts] n = correct_normal(obj_mtx, sum(v_n, Vector()) / len(v_n)) if sel_count >= 1: if sel_count == 1: if not sel_verts[0].link_edges: # floater vert check -> world rot t_c = Vector((1,0,0)) n = Vector((0,0,1)) else: t_c = sel_verts[0].co - sel_verts[0].link_edges[0].other_vert(sel_verts[0]).co else: t_c = sel_verts[0].co - sel_verts[1].co t_c = correct_normal(obj_mtx, t_c) t_v = n.cross(t_c).normalized() rot_mtx = rotation_from_vector(n, t_v) set_cursor(rot_mtx) elif sel_count == 0: bpy.ops.view3d.snap_cursor_to_center() bm.select_flush_mode() bmesh.update_edit_mesh(obj.data) # OBJECT MODE ----------------------------------------------------------------------- elif bpy.context.mode == "OBJECT": sel_obj = [o for o in context.selected_objects] hit_obj, hit_wloc, hit_normal, hit_face = mouse_raycast(context, self.mouse_pos) if hit_normal and hit_obj: obj_mtx = hit_obj.matrix_world.copy() bm = bmesh.new() bm.from_mesh(hit_obj.data) bm.faces.ensure_lookup_table() normal = bm.faces[hit_face].normal tangent = bm.faces[hit_face].calc_tangent_edge() pos = obj_mtx @ bm.faces[hit_face].calc_center_median() rot_mtx = rotation_from_vector(normal, tangent, rw=False) rot_mtx = obj_mtx @ rot_mtx set_cursor(rot_mtx, pos=pos) elif len(sel_obj) == 1 and hit_obj not in sel_obj: context.scene.cursor.location = sel_obj[0].location context.scene.cursor.rotation_euler = sel_obj[0].rotation_euler if self.cursorOP: bpy.ops.transform.select_orientation(orientation="CURSOR") bpy.context.tool_settings.transform_pivot_point = "CURSOR" elif len(sel_obj) > 1 and hit_obj not in sel_obj: v = Vector(sel_obj[0].location - sel_obj[-1].location).normalized() if round(abs(v.dot(Vector((1,0,0)) )),3) == 1: u = Vector((0,0,1)) else: u = Vector((-1,0,0)) t = v.cross(u).normalized() rot_mtx = rotation_from_vector(v, t, rw=False) context.scene.cursor.rotation_euler = rot_mtx.to_euler() context.scene.cursor.location = average_vector([o.location for o in sel_obj]) else: bpy.ops.view3d.snap_cursor_to_center() if not self.cursorOP: # RESET OP TRANSFORMS bpy.ops.transform.select_orientation(orientation=og_orientation) bpy.context.scene.tool_settings.transform_pivot_point = og_pivot if og_cursor_setting != "QUATERNION": # just gonna go ahead and assume no one uses this as default, for back-compatibility reasons... context.scene.cursor.rotation_mode = og_cursor_setting else: context.scene.cursor.rotation_mode = 'XYZ' return {'FINISHED'}
def execute(self, context): object = context.object object.update_from_editmode() object_bm = bmesh.from_edit_mesh(object.data) selected_edges = [e for e in object_bm.edges if e.select] selected_verts = [v for v in object_bm.verts if v.select] selected_verts_fin = [] if len(selected_edges) == 0: self.report({'WARNING'}, "Please select edges.") return {'CANCELLED'} loops = prepare_loops(selected_edges[:]) if loops is None: self.report({'WARNING'}, "Please select boundary loop(s) of selected area(s).") return {'CANCELLED'} object_bvh = mathutils.bvhtree.BVHTree.FromObject(object, context.scene, deform=False) for (loop_verts, loop_edges), is_loop_cyclic, is_loop_boundary in loops: if len(loop_edges) < 3: continue loop_verts_len = len(loop_verts) if self.projection == "NORMAL": if is_loop_boundary: forward = calculate_normal([v.co for v in loop_verts]) else: forward = reduce( lambda v1, v2: v1.normal.copy() if isinstance(v1, bmesh.types.BMVert) else v1.copy() + v2.normal.copy(), loop_verts).normalized() else: forward = Vector([v == self.projection for v in ["X", "Y", "Z"]]) if self.invert_projection: forward.negate() shape_bm = bmesh.new() if context.space_data.pivot_point == "CURSOR": center = object.matrix_world.copy() * context.scene.cursor_location.copy() else: for loop_vert in loop_verts: shape_bm.verts.new(loop_vert.co.copy()) shape_bm.faces.new(shape_bm.verts) shape_bm.faces.ensure_lookup_table() if context.space_data.pivot_point == 'BOUNDING_BOX_CENTER': center = shape_bm.faces[0].calc_center_bounds() else: center = shape_bm.faces[0].calc_center_median() shape_bm.clear() matrix_rotation = forward.to_track_quat('Z', 'X').to_matrix().to_4x4() matrix_translation = Matrix.Translation(center) shape_bm = bmesh.new() shape_verts = None if self.shape == "CIRCLE": diameter = sum([e.calc_length() for e in loop_edges]) / (2*math.pi) diameter += self.offset shape_segments = loop_verts_len + self.span shape_verts = bmesh.ops.create_circle(shape_bm, segments=shape_segments, diameter=diameter, matrix=matrix_translation*matrix_rotation) shape_verts = shape_verts["verts"] 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[:] 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) elif self.shape == "PATTERN": if len(object.perfect_pattern) == 0: self.report({'WARNING'}, "Empty Pattern Data.") del shape_bm return {'FINISHED'} if len(object.perfect_pattern) != len(loop_verts): self.report({'WARNING'}, "Pattern and loop vertices count must be the same.") del shape_bm return {'FINISHED'} for pattern_vert in object.perfect_pattern: shape_bm.verts.new(Vector(pattern_vert.co)) shape_verts = shape_bm.verts[:] 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) 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 = prepare_loops(shape_bm.edges[:]) if loops is None or len(loops) > 1: self.report({'WARNING'}, "Wrong mesh data.") del shape_bm return {'FINISHED'} shape_verts = shape_bm.verts[:] 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 shape_verts is not None and len(shape_verts) > 0: 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() loop_angle = box_fit_2d([(matrix_rotation.transposed() * v.co).to_2d() for v in loop_verts]) shape_angle = box_fit_2d([(matrix_rotation.transposed() * v.co).to_2d() for v in shape_verts]) if abs(abs(loop_angle) - abs(shape_angle)) <= 0.01: loop_angle = 0 correct_angle = loop_angle + self.rotation if self.shape_rotation: correct_angle -= shape_angle if correct_angle != 0 and not self.active_as_first: bmesh.ops.rotate(shape_bm, verts=shape_verts, cent=center, matrix=Matrix.Rotation(-correct_angle, 3, forward)) active = object_bm.select_history.active if self.active_as_first and isinstance(active, bmesh.types.BMVert) and active in loop_verts: shift = loop_verts.index(active) else: 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 if shift != 0: loop_verts = loop_verts[shift % len(loop_verts):] + loop_verts[:shift % len(loop_verts)] 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) if not is_loop_boundary and is_loop_cyclic: object_bm.select_flush_mode() select_only(object_bm, loop_edges, {"EDGE"}) bpy.ops.mesh.loop_to_region() # Ugly. inset_faces = [f for f in object_bm.faces[:] if f.select] if self.fill_type != "ORIGINAL": smooth = inset_faces[0].smooth bmesh.ops.delete(object_bm, geom=inset_faces, context=5) inset_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 inset_faces.append(new_face) bmesh.ops.recalc_face_normals(object_bm, faces=inset_faces) selected_co = [] for vert in selected_verts: if vert.is_valid: selected_co.append(vert.co.copy()) outset_region_faces = [] if self.outset > 0.0: outset_region_faces = bmesh.ops.inset_region(object_bm, faces=inset_faces, thickness=self.outset, use_even_offset=True, use_interpolate=True, use_outset=True) outset_region_faces = outset_region_faces["faces"] inset_region_faces = [] if self.inset > 0.0: inset_region_faces = bmesh.ops.inset_region(object_bm, faces=inset_faces, thickness=self.inset, use_even_offset=True, use_interpolate=True) inset_region_faces = inset_region_faces["faces"] new_selected_verts = [] for face in set(inset_region_faces+inset_faces): for vert in face.verts: if vert.co in selected_co: new_selected_verts.append(vert) selected_co.remove(vert.co) selected_verts_fin.append(new_selected_verts) select_only(object_bm, new_selected_verts, {"EDGE"}) if self.fill_type == "HOLE": bmesh.ops.delete(object_bm, geom=inset_faces, context=5) inset_faces = [] elif self.fill_type == "NGON": inset_faces = [bmesh.utils.face_join(inset_faces)] if self.fill_flatten and self.extrude == 0: verts = list(set(reduce(lambda v1, v2: list(v1) + list(v2), [v.verts for v in inset_region_faces + inset_faces]))) 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, 1, +0)), space=matrix, verts=verts) bmesh.ops.rotate(object_bm, cent=center, matrix=matrix_rotation, verts=verts) bmesh.ops.recalc_face_normals(object_bm, faces=outset_region_faces+inset_region_faces+inset_faces) if self.extrude != 0: extrude_geom = bmesh.ops.extrude_face_region(object_bm, geom=inset_region_faces+inset_faces) verts = [v for v in extrude_geom['geom'] if isinstance(v, bmesh.types.BMVert)] 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.delete(object_bm, geom=inset_region_faces+inset_faces, context=5) bmesh.ops.translate(object_bm, verts=verts, vec=forward * self.extrude) del shape_bm if selected_verts_fin: select_only(object_bm, reduce(lambda x, y: x + y, selected_verts_fin), {"EDGE"}) object_bm.select_flush(True) del object_bvh bmesh.update_edit_mesh(object.data) return {'FINISHED'}