AnimAll: Rename UVMap and Vertex Group settings again
[blender-addons.git] / mesh_tools / mesh_extrude_and_reshape.py
blob3776f79ca6e02e919fdcad925fd34a8635c0a2d5
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
7 bl_info = {
8 "name": "Extrude and Reshape",
9 "author": "Germano Cavalcante",
10 "version": (0, 8, 1),
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",
17 "category": "Mesh",
20 import bpy
21 import bmesh
22 from mathutils.geometry import intersect_line_line
23 from bpy.types import Operator
26 class BVHco():
27 i = 0
28 c1x = 0.0
29 c1y = 0.0
30 c1z = 0.0
31 c2x = 0.0
32 c2y = 0.0
33 c2z = 0.0
36 def edges_BVH_overlap(bm, edges, epsilon=0.0001):
37 bco = set()
38 for e in edges:
39 bvh = BVHco()
40 bvh.i = e.index
41 b1 = e.verts[0]
42 b2 = e.verts[1]
43 co1 = b1.co.x
44 co2 = b2.co.x
45 if co1 <= co2:
46 bvh.c1x = co1 - epsilon
47 bvh.c2x = co2 + epsilon
48 else:
49 bvh.c1x = co2 - epsilon
50 bvh.c2x = co1 + epsilon
51 co1 = b1.co.y
52 co2 = b2.co.y
53 if co1 <= co2:
54 bvh.c1y = co1 - epsilon
55 bvh.c2y = co2 + epsilon
56 else:
57 bvh.c1y = co2 - epsilon
58 bvh.c2y = co1 + epsilon
59 co1 = b1.co.z
60 co2 = b2.co.z
61 if co1 <= co2:
62 bvh.c1z = co1 - epsilon
63 bvh.c2z = co2 + epsilon
64 else:
65 bvh.c1z = co2 - epsilon
66 bvh.c2z = co1 + epsilon
67 bco.add(bvh)
68 del edges
69 overlap = {}
70 oget = overlap.get
71 for e1 in bm.edges:
72 by = bz = True
73 a1 = e1.verts[0]
74 a2 = e1.verts[1]
75 c1x = a1.co.x
76 c2x = a2.co.x
77 if c1x > c2x:
78 tm = c1x
79 c1x = c2x
80 c2x = tm
81 for bvh in bco:
82 if c1x <= bvh.c2x and c2x >= bvh.c1x:
83 if by:
84 by = False
85 c1y = a1.co.y
86 c2y = a2.co.y
87 if c1y > c2y:
88 tm = c1y
89 c1y = c2y
90 c2y = tm
91 if c1y <= bvh.c2y and c2y >= bvh.c1y:
92 if bz:
93 bz = False
94 c1z = a1.co.z
95 c2z = a2.co.z
96 if c1z > c2z:
97 tm = c1z
98 c1z = c2z
99 c2z = tm
100 if c1z <= bvh.c2z and c2z >= bvh.c1z:
101 e2 = bm.edges[bvh.i]
102 if e1 != e2:
103 overlap[e1] = oget(e1, set()).union({e2})
104 return overlap
107 def intersect_edges_edges(overlap, precision=4):
108 epsilon = .1**precision
109 fpre_min = -epsilon
110 fpre_max = 1 + epsilon
111 splits = {}
112 sp_get = splits.get
113 new_edges1 = set()
114 new_edges2 = set()
115 targetmap = {}
116 for edg1 in overlap:
117 # print("***", ed1.index, "***")
118 for edg2 in overlap[edg1]:
119 a1 = edg1.verts[0]
120 a2 = edg1.verts[1]
121 b1 = edg2.verts[0]
122 b2 = edg2.verts[1]
124 # test if are linked
125 if a1 in {b1, b2} or a2 in {b1, b2}:
126 # print('linked')
127 continue
129 aco1, aco2 = a1.co, a2.co
130 bco1, bco2 = b1.co, b2.co
131 tp = intersect_line_line(aco1, aco2, bco1, bco2)
132 if tp:
133 p1, p2 = tp
134 if (p1 - p2).to_tuple(precision) == (0, 0, 0):
135 v = aco2 - aco1
136 f = p1 - aco1
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]
142 v = bco2 - bco1
143 f = p2 - bco1
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)
151 ed1 = edg1
153 elif edg1 in splits:
154 for ed1 in splits[edg1]:
155 a1 = ed1.verts[0]
156 a2 = ed1.verts[1]
158 vco1 = a1.co
159 vco2 = a2.co
161 v = vco2 - vco1
162 f = p1 - vco1
163 fac1 = f[max1] / v[max1]
164 if fpre_min <= fac1 <= fpre_max:
165 # print(e.index, 'can intersect', edg2.index)
166 break
167 else:
168 # print(edg1.index, 'really does not intersect', edg2.index)
169 continue
170 else:
171 # print(edg1.index, 'not intersect', edg2.index)
172 continue
174 if fpre_min <= fac2 <= fpre_max:
175 # print(ed1.index, 'actually intersect', edg2.index)
176 ed2 = edg2
178 elif edg2 in splits:
179 for ed2 in splits[edg2]:
180 b1 = ed2.verts[0]
181 b2 = ed2.verts[1]
183 vco1 = b1.co
184 vco2 = b2.co
186 v = vco2 - vco1
187 f = p2 - vco1
188 fac2 = f[max2] / v[max2]
189 if fpre_min <= fac2 <= fpre_max:
190 # print(ed1.index, 'actually intersect', e.index)
191 break
192 else:
193 # print(ed1.index, 'really does not intersect', ed2.index)
194 continue
195 else:
196 # print(ed1.index, 'not intersect', edg2.index)
197 continue
199 new_edges1.add(ed1)
200 new_edges2.add(ed2)
202 if abs(fac1) <= epsilon:
203 nv1 = a1
204 elif fac1 + epsilon >= 1:
205 nv1 = a2
206 else:
207 ne1, nv1 = bmesh.utils.edge_split(ed1, a1, fac1)
208 new_edges1.add(ne1)
209 splits[edg1] = sp_get(edg1, set()).union({ne1})
211 if abs(fac2) <= epsilon:
212 nv2 = b1
213 elif fac2 + epsilon >= 1:
214 nv2 = b2
215 else:
216 ne2, nv2 = bmesh.utils.edge_split(ed2, b1, fac2)
217 new_edges2.add(ne2)
218 splits[edg2] = sp_get(edg2, set()).union({ne2})
220 if nv1 != nv2: # necessary?
221 targetmap[nv1] = nv2
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'}
232 @classmethod
233 def poll(cls, context):
234 if context.mode=='EDIT_MESH':
235 return True
237 def modal(self, context, event):
238 if self.confirm:
239 sface = self.bm.faces.active
240 if not sface:
241 for face in self.bm.faces:
242 if face.select is True:
243 sface = face
244 break
245 else:
246 return {'FINISHED'}
247 # edges to intersect
248 edges = set()
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)
259 pos_weld = set()
260 for e in new_edges1:
261 v1, v2 = e.verts
262 if v1 in targetmap and v2 in targetmap:
263 pos_weld.add((targetmap[v1], targetmap[v2]))
264 if targetmap:
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])
269 sp_faces1 = set()
271 for e in pos_weld:
272 v1, v2 = e
273 lf1 = set(v1.link_faces)
274 lf2 = set(v2.link_faces)
275 rlfe = lf1.intersection(lf2)
276 for f in rlfe:
277 try:
278 nf = bmesh.utils.face_split(f, v1, v2)
279 # sp_faces1.update({f, nf[0]})
280 except:
281 pass
283 # sp_faces2 = set()
284 for e in new_edges2:
285 lfe = set(e.link_faces)
286 v1, v2 = e.verts
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)
295 return {'FINISHED'}
296 if self.cancel:
297 return {'FINISHED'}
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)
305 try:
306 selection = self.bm.select_history[-1]
307 except:
308 for face in self.bm.faces:
309 if face.select is True:
310 selection = face
311 break
312 else:
313 return {'FINISHED'}
314 if not isinstance(selection, bmesh.types.BMFace):
315 bpy.ops.mesh.extrude_region_move('INVOKE_DEFAULT')
316 return {'FINISHED'}
317 else:
318 face = selection
319 # face.select = False
320 bpy.ops.mesh.select_all(action='DESELECT')
321 geom = []
322 for edge in face.edges:
323 if abs(edge.calc_face_angle(0) - 1.5707963267948966) < 0.01: # self.angle_tolerance:
324 geom.append(edge)
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
330 face.select = True
331 sface = 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)
343 self.cancel = False
344 self.confirm = False
345 return {'RUNNING_MODAL'}
348 def operator_draw(self, context):
349 layout = self.layout
350 col = layout.column(align=True)
351 col.operator("mesh.extrude_reshape")
354 def register():
355 bpy.utils.register_class(ER_OT_Extrude_and_Reshape)
358 def unregister():
359 bpy.utils.unregister_class(ER_OT_Extrude_and_Reshape)
362 if __name__ == "__main__":
363 register()