1 # SPDX-License-Identifier: GPL-2.0-or-later
3 # Contact for more information about the Addon:
4 # Email: germano.costa@ig.com.br
5 # Twitter: wii_mano @mano_wii
8 "name": "Extrude and Reshape",
9 "author": "Germano Cavalcante",
11 "blender": (2, 80, 0),
12 "location": "View3D > UI > Tools > Mesh Tools > Add: > Extrude Menu (Alt + E)",
13 "description": "Extrude face and merge edge intersections "
14 "between the mesh and the new edges",
15 "doc_url": "http://blenderartists.org/forum/"
16 "showthread.php?376618-Addon-Push-Pull-Face",
22 from mathutils
.geometry
import intersect_line_line
23 from bpy
.types
import Operator
36 def edges_BVH_overlap(bm
, edges
, epsilon
=0.0001):
46 bvh
.c1x
= co1
- epsilon
47 bvh
.c2x
= co2
+ epsilon
49 bvh
.c1x
= co2
- epsilon
50 bvh
.c2x
= co1
+ epsilon
54 bvh
.c1y
= co1
- epsilon
55 bvh
.c2y
= co2
+ epsilon
57 bvh
.c1y
= co2
- epsilon
58 bvh
.c2y
= co1
+ epsilon
62 bvh
.c1z
= co1
- epsilon
63 bvh
.c2z
= co2
+ epsilon
65 bvh
.c1z
= co2
- epsilon
66 bvh
.c2z
= co1
+ epsilon
82 if c1x
<= bvh
.c2x
and c2x
>= bvh
.c1x
:
91 if c1y
<= bvh
.c2y
and c2y
>= bvh
.c1y
:
100 if c1z
<= bvh
.c2z
and c2z
>= bvh
.c1z
:
103 overlap
[e1
] = oget(e1
, set()).union({e2}
)
107 def intersect_edges_edges(overlap
, precision
=4):
108 epsilon
= .1**precision
110 fpre_max
= 1 + epsilon
117 # print("***", ed1.index, "***")
118 for edg2
in overlap
[edg1
]:
125 if a1
in {b1
, b2
} or a2
in {b1
, b2
}:
129 aco1
, aco2
= a1
.co
, a2
.co
130 bco1
, bco2
= b1
.co
, b2
.co
131 tp
= intersect_line_line(aco1
, aco2
, bco1
, bco2
)
134 if (p1
- p2
).to_tuple(precision
) == (0, 0, 0):
137 x
, y
, z
= abs(v
.x
), abs(v
.y
), abs(v
.z
)
138 max1
= 0 if x
>= y
and x
>= z
else\
139 1 if y
>= x
and y
>= z
else 2
140 fac1
= f
[max1
] / v
[max1
]
144 x
, y
, z
= abs(v
.x
), abs(v
.y
), abs(v
.z
)
145 max2
= 0 if x
>= y
and x
>= z
else\
146 1 if y
>= x
and y
>= z
else 2
147 fac2
= f
[max2
] / v
[max2
]
149 if fpre_min
<= fac1
<= fpre_max
:
150 # print(edg1.index, 'can intersect', edg2.index)
154 for ed1
in splits
[edg1
]:
163 fac1
= f
[max1
] / v
[max1
]
164 if fpre_min
<= fac1
<= fpre_max
:
165 # print(e.index, 'can intersect', edg2.index)
168 # print(edg1.index, 'really does not intersect', edg2.index)
171 # print(edg1.index, 'not intersect', edg2.index)
174 if fpre_min
<= fac2
<= fpre_max
:
175 # print(ed1.index, 'actually intersect', edg2.index)
179 for ed2
in splits
[edg2
]:
188 fac2
= f
[max2
] / v
[max2
]
189 if fpre_min
<= fac2
<= fpre_max
:
190 # print(ed1.index, 'actually intersect', e.index)
193 # print(ed1.index, 'really does not intersect', ed2.index)
196 # print(ed1.index, 'not intersect', edg2.index)
202 if abs(fac1
) <= epsilon
:
204 elif fac1
+ epsilon
>= 1:
207 ne1
, nv1
= bmesh
.utils
.edge_split(ed1
, a1
, fac1
)
209 splits
[edg1
] = sp_get(edg1
, set()).union({ne1}
)
211 if abs(fac2
) <= epsilon
:
213 elif fac2
+ epsilon
>= 1:
216 ne2
, nv2
= bmesh
.utils
.edge_split(ed2
, b1
, fac2
)
218 splits
[edg2
] = sp_get(edg2
, set()).union({ne2}
)
220 if nv1
!= nv2
: # necessary?
223 return new_edges1
, new_edges2
, targetmap
226 class ER_OT_Extrude_and_Reshape(Operator
):
227 bl_idname
= "mesh.extrude_reshape"
228 bl_label
= "Extrude and Reshape"
229 bl_description
= "Push and pull face entities to sculpt 3d models"
230 bl_options
= {'REGISTER', 'GRAB_CURSOR', 'BLOCKING'}
233 def poll(cls
, context
):
234 if context
.mode
=='EDIT_MESH':
237 def modal(self
, context
, event
):
239 sface
= self
.bm
.faces
.active
241 for face
in self
.bm
.faces
:
242 if face
.select
is True:
249 [[edges
.add(ed
) for ed
in v
.link_edges
] for v
in sface
.verts
]
251 overlap
= edges_BVH_overlap(self
.bm
, edges
, epsilon
=0.0001)
252 overlap
= {k
: v
for k
, v
in overlap
.items() if k
not in edges
} # remove repetition
254 print([e.index for e in edges])
255 for a, b in overlap.items():
256 print(a.index, [e.index for e in b])
258 new_edges1
, new_edges2
, targetmap
= intersect_edges_edges(overlap
)
262 if v1
in targetmap
and v2
in targetmap
:
263 pos_weld
.add((targetmap
[v1
], targetmap
[v2
]))
265 bmesh
.ops
.weld_verts(self
.bm
, targetmap
=targetmap
)
267 print([e.is_valid for e in new_edges1])
268 print([e.is_valid for e in new_edges2])
273 lf1
= set(v1
.link_faces
)
274 lf2
= set(v2
.link_faces
)
275 rlfe
= lf1
.intersection(lf2
)
278 nf
= bmesh
.utils
.face_split(f
, v1
, v2
)
279 # sp_faces1.update({f, nf[0]})
285 lfe
= set(e
.link_faces
)
287 lf1
= set(v1
.link_faces
)
288 lf2
= set(v2
.link_faces
)
289 rlfe
= lf1
.intersection(lf2
)
290 for f
in rlfe
.difference(lfe
):
291 nf
= bmesh
.utils
.face_split(f
, v1
, v2
)
292 # sp_faces2.update({f, nf[0]})
294 bmesh
.update_edit_mesh(self
.mesh
, loop_triangles
=True, destructive
=True)
298 self
.cancel
= event
.type in {'ESC', 'NDOF_BUTTON_ESC'}
299 self
.confirm
= event
.type in {'LEFTMOUSE', 'RET', 'NUMPAD_ENTER'}
300 return {'PASS_THROUGH'}
302 def execute(self
, context
):
303 self
.mesh
= context
.object.data
304 self
.bm
= bmesh
.from_edit_mesh(self
.mesh
)
306 selection
= self
.bm
.select_history
[-1]
308 for face
in self
.bm
.faces
:
309 if face
.select
is True:
314 if not isinstance(selection
, bmesh
.types
.BMFace
):
315 bpy
.ops
.mesh
.extrude_region_move('INVOKE_DEFAULT')
319 # face.select = False
320 bpy
.ops
.mesh
.select_all(action
='DESELECT')
322 for edge
in face
.edges
:
323 if abs(edge
.calc_face_angle(0) - 1.5707963267948966) < 0.01: # self.angle_tolerance:
326 ret_dict
= bmesh
.ops
.extrude_discrete_faces(self
.bm
, faces
=[face
])
328 for face
in ret_dict
['faces']:
329 self
.bm
.faces
.active
= face
332 dfaces
= bmesh
.ops
.dissolve_edges(
333 self
.bm
, edges
=geom
, use_verts
=True, use_face_split
=False
335 bmesh
.update_edit_mesh(self
.mesh
, loop_triangles
=True, destructive
=True)
336 bpy
.ops
.transform
.translate(
337 'INVOKE_DEFAULT', constraint_axis
=(False, False, True),
338 orient_type
='NORMAL', release_confirm
=True
341 context
.window_manager
.modal_handler_add(self
)
345 return {'RUNNING_MODAL'}
348 def operator_draw(self
, context
):
350 col
= layout
.column(align
=True)
351 col
.operator("mesh.extrude_reshape")
355 bpy
.utils
.register_class(ER_OT_Extrude_and_Reshape
)
359 bpy
.utils
.unregister_class(ER_OT_Extrude_and_Reshape
)
362 if __name__
== "__main__":