1 # SPDX-FileCopyrightText: 2019-2022 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 # Contact for more information about the Addon:
6 # Email: germano.costa@ig.com.br
7 # Twitter: wii_mano @mano_wii
10 "name": "Extrude and Reshape",
11 "author": "Germano Cavalcante",
13 "blender": (2, 80, 0),
14 "location": "View3D > UI > Tools > Mesh Tools > Add: > Extrude Menu (Alt + E)",
15 "description": "Extrude face and merge edge intersections "
16 "between the mesh and the new edges",
17 "doc_url": "http://blenderartists.org/forum/"
18 "showthread.php?376618-Addon-Push-Pull-Face",
24 from mathutils
.geometry
import intersect_line_line
25 from bpy
.types
import Operator
38 def edges_BVH_overlap(bm
, edges
, epsilon
=0.0001):
48 bvh
.c1x
= co1
- epsilon
49 bvh
.c2x
= co2
+ epsilon
51 bvh
.c1x
= co2
- epsilon
52 bvh
.c2x
= co1
+ epsilon
56 bvh
.c1y
= co1
- epsilon
57 bvh
.c2y
= co2
+ epsilon
59 bvh
.c1y
= co2
- epsilon
60 bvh
.c2y
= co1
+ epsilon
64 bvh
.c1z
= co1
- epsilon
65 bvh
.c2z
= co2
+ epsilon
67 bvh
.c1z
= co2
- epsilon
68 bvh
.c2z
= co1
+ epsilon
84 if c1x
<= bvh
.c2x
and c2x
>= bvh
.c1x
:
93 if c1y
<= bvh
.c2y
and c2y
>= bvh
.c1y
:
102 if c1z
<= bvh
.c2z
and c2z
>= bvh
.c1z
:
105 overlap
[e1
] = oget(e1
, set()).union({e2}
)
109 def intersect_edges_edges(overlap
, precision
=4):
110 epsilon
= .1**precision
112 fpre_max
= 1 + epsilon
119 # print("***", ed1.index, "***")
120 for edg2
in overlap
[edg1
]:
127 if a1
in {b1
, b2
} or a2
in {b1
, b2
}:
131 aco1
, aco2
= a1
.co
, a2
.co
132 bco1
, bco2
= b1
.co
, b2
.co
133 tp
= intersect_line_line(aco1
, aco2
, bco1
, bco2
)
136 if (p1
- p2
).to_tuple(precision
) == (0, 0, 0):
139 x
, y
, z
= abs(v
.x
), abs(v
.y
), abs(v
.z
)
140 max1
= 0 if x
>= y
and x
>= z
else\
141 1 if y
>= x
and y
>= z
else 2
142 fac1
= f
[max1
] / v
[max1
]
146 x
, y
, z
= abs(v
.x
), abs(v
.y
), abs(v
.z
)
147 max2
= 0 if x
>= y
and x
>= z
else\
148 1 if y
>= x
and y
>= z
else 2
149 fac2
= f
[max2
] / v
[max2
]
151 if fpre_min
<= fac1
<= fpre_max
:
152 # print(edg1.index, 'can intersect', edg2.index)
156 for ed1
in splits
[edg1
]:
165 fac1
= f
[max1
] / v
[max1
]
166 if fpre_min
<= fac1
<= fpre_max
:
167 # print(e.index, 'can intersect', edg2.index)
170 # print(edg1.index, 'really does not intersect', edg2.index)
173 # print(edg1.index, 'not intersect', edg2.index)
176 if fpre_min
<= fac2
<= fpre_max
:
177 # print(ed1.index, 'actually intersect', edg2.index)
181 for ed2
in splits
[edg2
]:
190 fac2
= f
[max2
] / v
[max2
]
191 if fpre_min
<= fac2
<= fpre_max
:
192 # print(ed1.index, 'actually intersect', e.index)
195 # print(ed1.index, 'really does not intersect', ed2.index)
198 # print(ed1.index, 'not intersect', edg2.index)
204 if abs(fac1
) <= epsilon
:
206 elif fac1
+ epsilon
>= 1:
209 ne1
, nv1
= bmesh
.utils
.edge_split(ed1
, a1
, fac1
)
211 splits
[edg1
] = sp_get(edg1
, set()).union({ne1}
)
213 if abs(fac2
) <= epsilon
:
215 elif fac2
+ epsilon
>= 1:
218 ne2
, nv2
= bmesh
.utils
.edge_split(ed2
, b1
, fac2
)
220 splits
[edg2
] = sp_get(edg2
, set()).union({ne2}
)
222 if nv1
!= nv2
: # necessary?
225 return new_edges1
, new_edges2
, targetmap
228 class ER_OT_Extrude_and_Reshape(Operator
):
229 bl_idname
= "mesh.extrude_reshape"
230 bl_label
= "Extrude and Reshape"
231 bl_description
= "Push and pull face entities to sculpt 3d models"
232 bl_options
= {'REGISTER', 'GRAB_CURSOR', 'BLOCKING'}
235 def poll(cls
, context
):
236 if context
.mode
=='EDIT_MESH':
239 def modal(self
, context
, event
):
241 sface
= self
.bm
.faces
.active
243 for face
in self
.bm
.faces
:
244 if face
.select
is True:
251 [[edges
.add(ed
) for ed
in v
.link_edges
] for v
in sface
.verts
]
253 overlap
= edges_BVH_overlap(self
.bm
, edges
, epsilon
=0.0001)
254 overlap
= {k
: v
for k
, v
in overlap
.items() if k
not in edges
} # remove repetition
256 print([e.index for e in edges])
257 for a, b in overlap.items():
258 print(a.index, [e.index for e in b])
260 new_edges1
, new_edges2
, targetmap
= intersect_edges_edges(overlap
)
264 if v1
in targetmap
and v2
in targetmap
:
265 pos_weld
.add((targetmap
[v1
], targetmap
[v2
]))
267 bmesh
.ops
.weld_verts(self
.bm
, targetmap
=targetmap
)
269 print([e.is_valid for e in new_edges1])
270 print([e.is_valid for e in new_edges2])
275 lf1
= set(v1
.link_faces
)
276 lf2
= set(v2
.link_faces
)
277 rlfe
= lf1
.intersection(lf2
)
280 nf
= bmesh
.utils
.face_split(f
, v1
, v2
)
281 # sp_faces1.update({f, nf[0]})
287 lfe
= set(e
.link_faces
)
289 lf1
= set(v1
.link_faces
)
290 lf2
= set(v2
.link_faces
)
291 rlfe
= lf1
.intersection(lf2
)
292 for f
in rlfe
.difference(lfe
):
293 nf
= bmesh
.utils
.face_split(f
, v1
, v2
)
294 # sp_faces2.update({f, nf[0]})
296 bmesh
.update_edit_mesh(self
.mesh
, loop_triangles
=True, destructive
=True)
300 self
.cancel
= event
.type in {'ESC', 'NDOF_BUTTON_ESC'}
301 self
.confirm
= event
.type in {'LEFTMOUSE', 'RET', 'NUMPAD_ENTER'}
302 return {'PASS_THROUGH'}
304 def execute(self
, context
):
305 self
.mesh
= context
.object.data
306 self
.bm
= bmesh
.from_edit_mesh(self
.mesh
)
308 selection
= self
.bm
.select_history
[-1]
310 for face
in self
.bm
.faces
:
311 if face
.select
is True:
316 if not isinstance(selection
, bmesh
.types
.BMFace
):
317 bpy
.ops
.mesh
.extrude_region_move('INVOKE_DEFAULT')
321 # face.select = False
322 bpy
.ops
.mesh
.select_all(action
='DESELECT')
324 for edge
in face
.edges
:
325 if abs(edge
.calc_face_angle(0) - 1.5707963267948966) < 0.01: # self.angle_tolerance:
328 ret_dict
= bmesh
.ops
.extrude_discrete_faces(self
.bm
, faces
=[face
])
330 for face
in ret_dict
['faces']:
331 self
.bm
.faces
.active
= face
334 dfaces
= bmesh
.ops
.dissolve_edges(
335 self
.bm
, edges
=geom
, use_verts
=True, use_face_split
=False
337 bmesh
.update_edit_mesh(self
.mesh
, loop_triangles
=True, destructive
=True)
338 bpy
.ops
.transform
.translate(
339 'INVOKE_DEFAULT', constraint_axis
=(False, False, True),
340 orient_type
='NORMAL', release_confirm
=True
343 context
.window_manager
.modal_handler_add(self
)
347 return {'RUNNING_MODAL'}
350 def operator_draw(self
, context
):
352 col
= layout
.column(align
=True)
353 col
.operator("mesh.extrude_reshape")
357 bpy
.utils
.register_class(ER_OT_Extrude_and_Reshape
)
361 bpy
.utils
.unregister_class(ER_OT_Extrude_and_Reshape
)
364 if __name__
== "__main__":