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 "wiki_url": "http://blenderartists.org/forum/"
31 "showthread.php?376618-Addon-Push-Pull-Face",
36 from mathutils
.geometry
import intersect_line_line
37 from bpy
.types
import Operator
50 def edges_BVH_overlap(bm
, edges
, epsilon
=0.0001):
60 bvh
.c1x
= co1
- epsilon
61 bvh
.c2x
= co2
+ epsilon
63 bvh
.c1x
= co2
- epsilon
64 bvh
.c2x
= co1
+ epsilon
68 bvh
.c1y
= co1
- epsilon
69 bvh
.c2y
= co2
+ epsilon
71 bvh
.c1y
= co2
- epsilon
72 bvh
.c2y
= co1
+ epsilon
76 bvh
.c1z
= co1
- epsilon
77 bvh
.c2z
= co2
+ epsilon
79 bvh
.c1z
= co2
- epsilon
80 bvh
.c2z
= co1
+ epsilon
96 if c1x
<= bvh
.c2x
and c2x
>= bvh
.c1x
:
105 if c1y
<= bvh
.c2y
and c2y
>= bvh
.c1y
:
114 if c1z
<= bvh
.c2z
and c2z
>= bvh
.c1z
:
117 overlap
[e1
] = oget(e1
, set()).union({e2}
)
121 def intersect_edges_edges(overlap
, precision
=4):
122 epsilon
= .1**precision
124 fpre_max
= 1 + epsilon
131 # print("***", ed1.index, "***")
132 for edg2
in overlap
[edg1
]:
139 if a1
in {b1
, b2
} or a2
in {b1
, b2
}:
143 aco1
, aco2
= a1
.co
, a2
.co
144 bco1
, bco2
= b1
.co
, b2
.co
145 tp
= intersect_line_line(aco1
, aco2
, bco1
, bco2
)
148 if (p1
- p2
).to_tuple(precision
) == (0, 0, 0):
151 x
, y
, z
= abs(v
.x
), abs(v
.y
), abs(v
.z
)
152 max1
= 0 if x
>= y
and x
>= z
else\
153 1 if y
>= x
and y
>= z
else 2
154 fac1
= f
[max1
] / v
[max1
]
158 x
, y
, z
= abs(v
.x
), abs(v
.y
), abs(v
.z
)
159 max2
= 0 if x
>= y
and x
>= z
else\
160 1 if y
>= x
and y
>= z
else 2
161 fac2
= f
[max2
] / v
[max2
]
163 if fpre_min
<= fac1
<= fpre_max
:
164 # print(edg1.index, 'can intersect', edg2.index)
168 for ed1
in splits
[edg1
]:
177 fac1
= f
[max1
] / v
[max1
]
178 if fpre_min
<= fac1
<= fpre_max
:
179 # print(e.index, 'can intersect', edg2.index)
182 # print(edg1.index, 'really does not intersect', edg2.index)
185 # print(edg1.index, 'not intersect', edg2.index)
188 if fpre_min
<= fac2
<= fpre_max
:
189 # print(ed1.index, 'actually intersect', edg2.index)
193 for ed2
in splits
[edg2
]:
202 fac2
= f
[max2
] / v
[max2
]
203 if fpre_min
<= fac2
<= fpre_max
:
204 # print(ed1.index, 'actually intersect', e.index)
207 # print(ed1.index, 'really does not intersect', ed2.index)
210 # print(ed1.index, 'not intersect', edg2.index)
216 if abs(fac1
) <= epsilon
:
218 elif fac1
+ epsilon
>= 1:
221 ne1
, nv1
= bmesh
.utils
.edge_split(ed1
, a1
, fac1
)
223 splits
[edg1
] = sp_get(edg1
, set()).union({ne1}
)
225 if abs(fac2
) <= epsilon
:
227 elif fac2
+ epsilon
>= 1:
230 ne2
, nv2
= bmesh
.utils
.edge_split(ed2
, b1
, fac2
)
232 splits
[edg2
] = sp_get(edg2
, set()).union({ne2}
)
234 if nv1
!= nv2
: # necessary?
237 return new_edges1
, new_edges2
, targetmap
240 class ER_OT_Extrude_and_Reshape(Operator
):
241 bl_idname
= "mesh.extrude_reshape"
242 bl_label
= "Extrude and Reshape"
243 bl_description
= "Push and pull face entities to sculpt 3d models"
244 bl_options
= {'REGISTER', 'GRAB_CURSOR', 'BLOCKING'}
247 def poll(cls
, context
):
248 if context
.mode
=='EDIT_MESH':
251 def modal(self
, context
, event
):
253 sface
= self
.bm
.faces
.active
255 for face
in self
.bm
.faces
:
256 if face
.select
is True:
263 [[edges
.add(ed
) for ed
in v
.link_edges
] for v
in sface
.verts
]
265 overlap
= edges_BVH_overlap(self
.bm
, edges
, epsilon
=0.0001)
266 overlap
= {k
: v
for k
, v
in overlap
.items() if k
not in edges
} # remove repetition
268 print([e.index for e in edges])
269 for a, b in overlap.items():
270 print(a.index, [e.index for e in b])
272 new_edges1
, new_edges2
, targetmap
= intersect_edges_edges(overlap
)
276 if v1
in targetmap
and v2
in targetmap
:
277 pos_weld
.add((targetmap
[v1
], targetmap
[v2
]))
279 bmesh
.ops
.weld_verts(self
.bm
, targetmap
=targetmap
)
281 print([e.is_valid for e in new_edges1])
282 print([e.is_valid for e in new_edges2])
287 lf1
= set(v1
.link_faces
)
288 lf2
= set(v2
.link_faces
)
289 rlfe
= lf1
.intersection(lf2
)
292 nf
= bmesh
.utils
.face_split(f
, v1
, v2
)
293 # sp_faces1.update({f, nf[0]})
299 lfe
= set(e
.link_faces
)
301 lf1
= set(v1
.link_faces
)
302 lf2
= set(v2
.link_faces
)
303 rlfe
= lf1
.intersection(lf2
)
304 for f
in rlfe
.difference(lfe
):
305 nf
= bmesh
.utils
.face_split(f
, v1
, v2
)
306 # sp_faces2.update({f, nf[0]})
308 bmesh
.update_edit_mesh(self
.mesh
, loop_triangles
=True, destructive
=True)
312 self
.cancel
= event
.type in {'ESC', 'NDOF_BUTTON_ESC'}
313 self
.confirm
= event
.type in {'LEFTMOUSE', 'RET', 'NUMPAD_ENTER'}
314 return {'PASS_THROUGH'}
316 def execute(self
, context
):
317 self
.mesh
= context
.object.data
318 self
.bm
= bmesh
.from_edit_mesh(self
.mesh
)
320 selection
= self
.bm
.select_history
[-1]
322 for face
in self
.bm
.faces
:
323 if face
.select
is True:
328 if not isinstance(selection
, bmesh
.types
.BMFace
):
329 bpy
.ops
.mesh
.extrude_region_move('INVOKE_DEFAULT')
333 # face.select = False
334 bpy
.ops
.mesh
.select_all(action
='DESELECT')
336 for edge
in face
.edges
:
337 if abs(edge
.calc_face_angle(0) - 1.5707963267948966) < 0.01: # self.angle_tolerance:
340 ret_dict
= bmesh
.ops
.extrude_discrete_faces(self
.bm
, faces
=[face
])
342 for face
in ret_dict
['faces']:
343 self
.bm
.faces
.active
= face
346 dfaces
= bmesh
.ops
.dissolve_edges(
347 self
.bm
, edges
=geom
, use_verts
=True, use_face_split
=False
349 bmesh
.update_edit_mesh(self
.mesh
, loop_triangles
=True, destructive
=True)
350 bpy
.ops
.transform
.translate(
351 'INVOKE_DEFAULT', constraint_axis
=(False, False, True),
352 orient_type
='NORMAL', release_confirm
=True
355 context
.window_manager
.modal_handler_add(self
)
359 return {'RUNNING_MODAL'}
362 def operator_draw(self
, context
):
364 col
= layout
.column(align
=True)
365 col
.operator("mesh.extrude_reshape")
369 bpy
.utils
.register_class(ER_OT_Extrude_and_Reshape
)
373 bpy
.utils
.unregister_class(ER_OT_Extrude_and_Reshape
)
376 if __name__
== "__main__":