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, 76, 5),
27 "location": "View3D > TOOLS > 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 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 return context
.mode
is not 'EDIT_MESH'
250 def modal(self
, context
, event
):
252 sface
= self
.bm
.faces
.active
254 for face
in self
.bm
.faces
:
255 if face
.select
is True:
262 [[edges
.add(ed
) for ed
in v
.link_edges
] for v
in sface
.verts
]
264 overlap
= edges_BVH_overlap(self
.bm
, edges
, epsilon
=0.0001)
265 overlap
= {k
: v
for k
, v
in overlap
.items() if k
not in edges
} # remove repetition
267 print([e.index for e in edges])
268 for a, b in overlap.items():
269 print(a.index, [e.index for e in b])
271 new_edges1
, new_edges2
, targetmap
= intersect_edges_edges(overlap
)
275 if v1
in targetmap
and v2
in targetmap
:
276 pos_weld
.add((targetmap
[v1
], targetmap
[v2
]))
278 bmesh
.ops
.weld_verts(self
.bm
, targetmap
=targetmap
)
280 print([e.is_valid for e in new_edges1])
281 print([e.is_valid for e in new_edges2])
286 lf1
= set(v1
.link_faces
)
287 lf2
= set(v2
.link_faces
)
288 rlfe
= lf1
.intersection(lf2
)
291 nf
= bmesh
.utils
.face_split(f
, v1
, v2
)
292 # sp_faces1.update({f, nf[0]})
298 lfe
= set(e
.link_faces
)
300 lf1
= set(v1
.link_faces
)
301 lf2
= set(v2
.link_faces
)
302 rlfe
= lf1
.intersection(lf2
)
303 for f
in rlfe
.difference(lfe
):
304 nf
= bmesh
.utils
.face_split(f
, v1
, v2
)
305 # sp_faces2.update({f, nf[0]})
307 bmesh
.update_edit_mesh(self
.mesh
, tessface
=True, destructive
=True)
311 self
.cancel
= event
.type in {'ESC', 'NDOF_BUTTON_ESC'}
312 self
.confirm
= event
.type in {'LEFTMOUSE', 'RET', 'NUMPAD_ENTER'}
313 return {'PASS_THROUGH'}
315 def execute(self
, context
):
316 self
.mesh
= context
.object.data
317 self
.bm
= bmesh
.from_edit_mesh(self
.mesh
)
319 selection
= self
.bm
.select_history
[-1]
321 for face
in self
.bm
.faces
:
322 if face
.select
is True:
327 if not isinstance(selection
, bmesh
.types
.BMFace
):
328 bpy
.ops
.mesh
.extrude_region_move('INVOKE_DEFAULT')
332 # face.select = False
333 bpy
.ops
.mesh
.select_all(action
='DESELECT')
335 for edge
in face
.edges
:
336 if abs(edge
.calc_face_angle(0) - 1.5707963267948966) < 0.01: # self.angle_tolerance:
339 ret_dict
= bmesh
.ops
.extrude_discrete_faces(self
.bm
, faces
=[face
])
341 for face
in ret_dict
['faces']:
342 self
.bm
.faces
.active
= face
345 dfaces
= bmesh
.ops
.dissolve_edges(
346 self
.bm
, edges
=geom
, use_verts
=True, use_face_split
=False
348 bmesh
.update_edit_mesh(self
.mesh
, tessface
=True, destructive
=True)
349 bpy
.ops
.transform
.translate(
350 'INVOKE_DEFAULT', constraint_axis
=(False, False, True),
351 constraint_orientation
='NORMAL', release_confirm
=True
354 context
.window_manager
.modal_handler_add(self
)
358 return {'RUNNING_MODAL'}
361 def operator_draw(self
, context
):
363 col
= layout
.column(align
=True)
364 col
.operator("mesh.extrude_reshape", text
="Extrude and Reshape")
368 bpy
.utils
.register_class(Extrude_and_Reshape
)
369 bpy
.types
.VIEW3D_MT_edit_mesh_extrude
.append(operator_draw
)
373 bpy
.types
.VIEW3D_MT_edit_mesh_extrude
.remove(operator_draw
)
374 bpy
.utils
.unregister_class(Extrude_and_Reshape
)
377 if __name__
== "__main__":