def optiRotateUvIsland(faces): uv_points = [uv for f in faces for uv in f.uv] angle = geometry.box_fit_2d(uv_points) if angle != 0.0: mat = Matrix.Rotation(angle, 2) i = 0 # count the serialized uv/vectors for f in faces: for j, k in enumerate(range(i, len(f.v) + i)): f.uv[j][:] = mat * uv_points[k] i += len(f.v)
def optiRotateUvIsland(faces): uv_points = [uv for f in faces for uv in f.uv] angle = geometry.box_fit_2d(uv_points) if angle != 0.0: rotate_uvs(uv_points, angle) # orient them vertically (could be an option) minx, miny, maxx, maxy = boundsIsland(faces) w, h = maxx - minx, maxy - miny if h < w: from math import pi angle = pi / 2.0 rotate_uvs(uv_points, angle)
def optiRotateUvIsland(faces): uv_points = [uv for f in faces for uv in f.uv] angle = geometry.box_fit_2d(uv_points) if angle != 0.0: rotate_uvs(uv_points, angle) # orient them vertically (could be an option) minx, miny, maxx, maxy = boundsIsland(faces) w, h = maxx - minx, maxy - miny # use epsilon so we don't randomly rotate (almost) perfect squares. if h + 0.00001 < w: from math import pi angle = pi / 2.0 rotate_uvs(uv_points, angle)
def optiRotateUvIsland(faces): uv_points = [uv for f in faces for uv in f.uv] angle = geometry.box_fit_2d(uv_points) if angle != 0.0: rotate_uvs(uv_points, angle) # orient them vertically (could be an option) minx, miny, maxx, maxy = boundsIsland(faces) w, h = maxx - minx, maxy - miny # use epsilon so we dont randomly rotate (almost) perfect squares. if h + 0.00001 < w: from math import pi angle = pi / 2.0 rotate_uvs(uv_points, angle)
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): 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): 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'}