1 # gpl author: Stanislav Blinov
5 "author": "Stanislav Blinov",
8 "description": "Cut Faces and Deselect Boundary operators",
13 from bpy
.types
import Operator
14 from bpy
.props
import (
21 def bmesh_from_object(object):
23 if object.mode
== 'EDIT':
24 bm
= bmesh
.from_edit_mesh(mesh
)
31 def bmesh_release(bm
, object):
33 bm
.select_flush_mode()
34 if object.mode
== 'EDIT':
35 bmesh
.update_edit_mesh(mesh
, True)
41 def calc_face(face
, keep_caps
=True):
45 def radial_loops(loop
):
46 next
= loop
.link_loop_radial_next
48 result
, next
= next
, next
.link_loop_radial_next
56 for loop
in face
.loops
:
58 # Iterate over selected adjacent faces
59 for radial_loop
in filter(lambda l
: l
.face
.select
, radial_loops(loop
)):
60 # Tag the edge if no other face done so already
65 adjacent_face
= radial_loop
.face
66 # Only walk adjacent face if current face tagged the edge
67 if adjacent_face
.tag
and self_selected
:
68 result
+= calc_face(adjacent_face
, keep_caps
)
71 (selected
, to_select
)[self_selected
].append(loop
)
73 for loop
in to_select
:
74 result
.append(loop
.edge
)
77 # Select opposite edge in quads
78 if keep_caps
and len(selected
) == 1 and len(face
.verts
) == 4:
79 result
.append(selected
[0].link_loop_next
.link_loop_next
.edge
)
84 def get_edge_rings(bm
, keep_caps
=True):
89 for edge
in face
.edges
:
93 # fetch selected faces while setting up tags
94 selected_faces
= [f
for f
in bm
.faces
if tag_face(f
)]
99 # generate a list of edges to select:
100 # traversing only tagged faces, since calc_face can walk and untag islands
101 for face
in filter(lambda f
: f
.tag
, selected_faces
):
102 edges
+= calc_face(face
, keep_caps
)
104 # housekeeping: clear tags
105 for face
in selected_faces
:
107 for edge
in face
.edges
:
113 class MESH_xOT_deselect_boundary(Operator
):
114 bl_idname
= "mesh.ext_deselect_boundary"
115 bl_label
= "Deselect Boundary"
116 bl_description
= ("Deselect boundary edges of selected faces\n"
117 "Note: if all Faces are selected there is no boundary,\n"
118 "so the tool will not have results")
119 bl_options
= {'REGISTER', 'UNDO'}
121 keep_cap_edges
: BoolProperty(
122 name
="Keep Cap Edges",
123 description
="Keep quad strip cap edges selected",
128 def poll(cls
, context
):
129 active_object
= context
.active_object
130 return active_object
and active_object
.type == 'MESH' and active_object
.mode
== 'EDIT'
132 def execute(self
, context
):
133 object = context
.active_object
134 bm
= bmesh_from_object(object)
137 edges
= get_edge_rings(bm
, keep_caps
=self
.keep_cap_edges
)
139 self
.report({'WARNING'}, "No suitable Face selection found. Operation cancelled")
142 bpy
.ops
.mesh
.select_all(action
='DESELECT')
143 bm
.select_mode
= {'EDGE'}
147 context
.tool_settings
.mesh_select_mode
[:] = False, True, False
150 bmesh_release(bm
, object)
155 class MESH_xOT_cut_faces(Operator
):
156 bl_idname
= "mesh.ext_cut_faces"
157 bl_label
= "Cut Faces"
158 bl_description
= "Cut selected faces, connected through their adjacent edges"
159 bl_options
= {'REGISTER', 'UNDO'}
161 # from bmesh_operators.h
165 SUBD_STRAIGHT_CUT
= 3
167 num_cuts
: IntProperty(
168 name
="Number of Cuts",
174 use_single_edge
: BoolProperty(
175 name
="Quad/Tri Mode",
176 description
="Cut boundary faces",
179 corner_type
: EnumProperty(
180 items
=[('SUBD_INNERVERT', "Inner Vert", ""),
181 ('SUBD_PATH', "Path", ""),
182 ('SUBD_FAN', "Fan", ""),
183 ('SUBD_STRAIGHT_CUT', "Straight Cut", ""),
185 name
="Quad Corner Type",
186 description
="How to subdivide quad corners",
187 default
='SUBD_STRAIGHT_CUT'
189 use_grid_fill
: BoolProperty(
190 name
="Use Grid Fill",
191 description
="Fill fully enclosed faces with a grid",
196 def poll(cls
, context
):
197 active_object
= context
.active_object
198 return active_object
and active_object
.type == 'MESH' and active_object
.mode
== 'EDIT'
200 def draw(self
, context
):
203 layout
.label(text
="Number of Cuts:")
204 layout
.prop(self
, "num_cuts", text
="")
206 layout
.prop(self
, "use_single_edge")
207 layout
.prop(self
, "use_grid_fill")
209 layout
.label(text
="Quad Corner Type:")
210 layout
.prop(self
, "corner_type", text
="")
212 def cut_edges(self
, context
):
213 object = context
.active_object
214 bm
= bmesh_from_object(object)
217 edges
= get_edge_rings(bm
, keep_caps
=True)
219 self
.report({'WARNING'},
220 "No suitable Face selection found. Operation cancelled")
223 result
= bmesh
.ops
.subdivide_edges(
226 cuts
=int(self
.num_cuts
),
227 use_grid_fill
=bool(self
.use_grid_fill
),
228 use_single_edge
=bool(self
.use_single_edge
),
229 quad_corner_type
=eval("self." + self
.corner_type
)
231 bpy
.ops
.mesh
.select_all(action
='DESELECT')
232 bm
.select_mode
= {'EDGE'}
234 inner
= result
['geom_inner']
235 for edge
in filter(lambda e
: isinstance(e
, bmesh
.types
.BMEdge
), inner
):
239 bmesh_release(bm
, object)
243 def execute(self
, context
):
245 if not self
.cut_edges(context
):
248 context
.tool_settings
.mesh_select_mode
[:] = False, True, False
249 # Try to select all possible loops
250 bpy
.ops
.mesh
.loop_multi_select(ring
=False)
256 bpy
.utils
.register_class(MESH_xOT_deselect_boundary
)
257 bpy
.utils
.register_class(MESH_xOT_cut_faces
)
261 bpy
.utils
.unregister_class(MESH_xOT_deselect_boundary
)
262 bpy
.utils
.unregister_class(MESH_xOT_cut_faces
)
265 if __name__
== "__main__":