1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 3
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 # ##### END GPL LICENSE BLOCK #####
18 # Contact for more information about the Addon:
19 # Email: germano.costa@ig.com.br
20 # Twitter: wii_mano @mano_wii
23 "name": "Extrude and Reshape",
24 "author": "Germano Cavalcante",
26 "blender": (2, 80, 0),
27 "location": "View3D > UI > Tools > Mesh Tools > Add: > Extrude Menu (Alt + E)",
28 "description": "Extrude face and merge edge intersections "
29 "between the mesh and the new edges",
30 "doc_url": "http://blenderartists.org/forum/"
31 "showthread.php?376618-Addon-Push-Pull-Face",
37 from mathutils
.geometry
import intersect_line_line
38 from bpy
.types
import Operator
51 def edges_BVH_overlap(bm
, edges
, epsilon
=0.0001):
61 bvh
.c1x
= co1
- epsilon
62 bvh
.c2x
= co2
+ epsilon
64 bvh
.c1x
= co2
- epsilon
65 bvh
.c2x
= co1
+ epsilon
69 bvh
.c1y
= co1
- epsilon
70 bvh
.c2y
= co2
+ epsilon
72 bvh
.c1y
= co2
- epsilon
73 bvh
.c2y
= co1
+ epsilon
77 bvh
.c1z
= co1
- epsilon
78 bvh
.c2z
= co2
+ epsilon
80 bvh
.c1z
= co2
- epsilon
81 bvh
.c2z
= co1
+ epsilon
97 if c1x
<= bvh
.c2x
and c2x
>= bvh
.c1x
:
106 if c1y
<= bvh
.c2y
and c2y
>= bvh
.c1y
:
115 if c1z
<= bvh
.c2z
and c2z
>= bvh
.c1z
:
118 overlap
[e1
] = oget(e1
, set()).union({e2}
)
122 def intersect_edges_edges(overlap
, precision
=4):
123 epsilon
= .1**precision
125 fpre_max
= 1 + epsilon
132 # print("***", ed1.index, "***")
133 for edg2
in overlap
[edg1
]:
140 if a1
in {b1
, b2
} or a2
in {b1
, b2
}:
144 aco1
, aco2
= a1
.co
, a2
.co
145 bco1
, bco2
= b1
.co
, b2
.co
146 tp
= intersect_line_line(aco1
, aco2
, bco1
, bco2
)
149 if (p1
- p2
).to_tuple(precision
) == (0, 0, 0):
152 x
, y
, z
= abs(v
.x
), abs(v
.y
), abs(v
.z
)
153 max1
= 0 if x
>= y
and x
>= z
else\
154 1 if y
>= x
and y
>= z
else 2
155 fac1
= f
[max1
] / v
[max1
]
159 x
, y
, z
= abs(v
.x
), abs(v
.y
), abs(v
.z
)
160 max2
= 0 if x
>= y
and x
>= z
else\
161 1 if y
>= x
and y
>= z
else 2
162 fac2
= f
[max2
] / v
[max2
]
164 if fpre_min
<= fac1
<= fpre_max
:
165 # print(edg1.index, 'can intersect', edg2.index)
169 for ed1
in splits
[edg1
]:
178 fac1
= f
[max1
] / v
[max1
]
179 if fpre_min
<= fac1
<= fpre_max
:
180 # print(e.index, 'can intersect', edg2.index)
183 # print(edg1.index, 'really does not intersect', edg2.index)
186 # print(edg1.index, 'not intersect', edg2.index)
189 if fpre_min
<= fac2
<= fpre_max
:
190 # print(ed1.index, 'actually intersect', edg2.index)
194 for ed2
in splits
[edg2
]:
203 fac2
= f
[max2
] / v
[max2
]
204 if fpre_min
<= fac2
<= fpre_max
:
205 # print(ed1.index, 'actually intersect', e.index)
208 # print(ed1.index, 'really does not intersect', ed2.index)
211 # print(ed1.index, 'not intersect', edg2.index)
217 if abs(fac1
) <= epsilon
:
219 elif fac1
+ epsilon
>= 1:
222 ne1
, nv1
= bmesh
.utils
.edge_split(ed1
, a1
, fac1
)
224 splits
[edg1
] = sp_get(edg1
, set()).union({ne1}
)
226 if abs(fac2
) <= epsilon
:
228 elif fac2
+ epsilon
>= 1:
231 ne2
, nv2
= bmesh
.utils
.edge_split(ed2
, b1
, fac2
)
233 splits
[edg2
] = sp_get(edg2
, set()).union({ne2}
)
235 if nv1
!= nv2
: # necessary?
238 return new_edges1
, new_edges2
, targetmap
241 class ER_OT_Extrude_and_Reshape(Operator
):
242 bl_idname
= "mesh.extrude_reshape"
243 bl_label
= "Extrude and Reshape"
244 bl_description
= "Push and pull face entities to sculpt 3d models"
245 bl_options
= {'REGISTER', 'GRAB_CURSOR', 'BLOCKING'}
248 def poll(cls
, context
):
249 if context
.mode
=='EDIT_MESH':
252 def modal(self
, context
, event
):
254 sface
= self
.bm
.faces
.active
256 for face
in self
.bm
.faces
:
257 if face
.select
is True:
264 [[edges
.add(ed
) for ed
in v
.link_edges
] for v
in sface
.verts
]
266 overlap
= edges_BVH_overlap(self
.bm
, edges
, epsilon
=0.0001)
267 overlap
= {k
: v
for k
, v
in overlap
.items() if k
not in edges
} # remove repetition
269 print([e.index for e in edges])
270 for a, b in overlap.items():
271 print(a.index, [e.index for e in b])
273 new_edges1
, new_edges2
, targetmap
= intersect_edges_edges(overlap
)
277 if v1
in targetmap
and v2
in targetmap
:
278 pos_weld
.add((targetmap
[v1
], targetmap
[v2
]))
280 bmesh
.ops
.weld_verts(self
.bm
, targetmap
=targetmap
)
282 print([e.is_valid for e in new_edges1])
283 print([e.is_valid for e in new_edges2])
288 lf1
= set(v1
.link_faces
)
289 lf2
= set(v2
.link_faces
)
290 rlfe
= lf1
.intersection(lf2
)
293 nf
= bmesh
.utils
.face_split(f
, v1
, v2
)
294 # sp_faces1.update({f, nf[0]})
300 lfe
= set(e
.link_faces
)
302 lf1
= set(v1
.link_faces
)
303 lf2
= set(v2
.link_faces
)
304 rlfe
= lf1
.intersection(lf2
)
305 for f
in rlfe
.difference(lfe
):
306 nf
= bmesh
.utils
.face_split(f
, v1
, v2
)
307 # sp_faces2.update({f, nf[0]})
309 bmesh
.update_edit_mesh(self
.mesh
, loop_triangles
=True, destructive
=True)
313 self
.cancel
= event
.type in {'ESC', 'NDOF_BUTTON_ESC'}
314 self
.confirm
= event
.type in {'LEFTMOUSE', 'RET', 'NUMPAD_ENTER'}
315 return {'PASS_THROUGH'}
317 def execute(self
, context
):
318 self
.mesh
= context
.object.data
319 self
.bm
= bmesh
.from_edit_mesh(self
.mesh
)
321 selection
= self
.bm
.select_history
[-1]
323 for face
in self
.bm
.faces
:
324 if face
.select
is True:
329 if not isinstance(selection
, bmesh
.types
.BMFace
):
330 bpy
.ops
.mesh
.extrude_region_move('INVOKE_DEFAULT')
334 # face.select = False
335 bpy
.ops
.mesh
.select_all(action
='DESELECT')
337 for edge
in face
.edges
:
338 if abs(edge
.calc_face_angle(0) - 1.5707963267948966) < 0.01: # self.angle_tolerance:
341 ret_dict
= bmesh
.ops
.extrude_discrete_faces(self
.bm
, faces
=[face
])
343 for face
in ret_dict
['faces']:
344 self
.bm
.faces
.active
= face
347 dfaces
= bmesh
.ops
.dissolve_edges(
348 self
.bm
, edges
=geom
, use_verts
=True, use_face_split
=False
350 bmesh
.update_edit_mesh(self
.mesh
, loop_triangles
=True, destructive
=True)
351 bpy
.ops
.transform
.translate(
352 'INVOKE_DEFAULT', constraint_axis
=(False, False, True),
353 orient_type
='NORMAL', release_confirm
=True
356 context
.window_manager
.modal_handler_add(self
)
360 return {'RUNNING_MODAL'}
363 def operator_draw(self
, context
):
365 col
= layout
.column(align
=True)
366 col
.operator("mesh.extrude_reshape")
370 bpy
.utils
.register_class(ER_OT_Extrude_and_Reshape
)
374 bpy
.utils
.unregister_class(ER_OT_Extrude_and_Reshape
)
377 if __name__
== "__main__":