Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'mesh_tools/mesh_extrude_and_reshape.py')
-rw-r--r--mesh_tools/mesh_extrude_and_reshape.py377
1 files changed, 377 insertions, 0 deletions
diff --git a/mesh_tools/mesh_extrude_and_reshape.py b/mesh_tools/mesh_extrude_and_reshape.py
new file mode 100644
index 00000000..f4245ac2
--- /dev/null
+++ b/mesh_tools/mesh_extrude_and_reshape.py
@@ -0,0 +1,377 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 3
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# Contact for more information about the Addon:
+# Email: germano.costa@ig.com.br
+# Twitter: wii_mano @mano_wii
+
+bl_info = {
+ "name": "Extrude and Reshape",
+ "author": "Germano Cavalcante",
+ "version": (0, 8, 1),
+ "blender": (2, 80, 0),
+ "location": "View3D > UI > Tools > Mesh Tools > Add: > Extrude Menu (Alt + E)",
+ "description": "Extrude face and merge edge intersections "
+ "between the mesh and the new edges",
+ "wiki_url": "http://blenderartists.org/forum/"
+ "showthread.php?376618-Addon-Push-Pull-Face",
+ "category": "Mesh"}
+
+import bpy
+import bmesh
+from mathutils.geometry import intersect_line_line
+from bpy.types import Operator
+
+
+class BVHco():
+ i = 0
+ c1x = 0.0
+ c1y = 0.0
+ c1z = 0.0
+ c2x = 0.0
+ c2y = 0.0
+ c2z = 0.0
+
+
+def edges_BVH_overlap(bm, edges, epsilon=0.0001):
+ bco = set()
+ for e in edges:
+ bvh = BVHco()
+ bvh.i = e.index
+ b1 = e.verts[0]
+ b2 = e.verts[1]
+ co1 = b1.co.x
+ co2 = b2.co.x
+ if co1 <= co2:
+ bvh.c1x = co1 - epsilon
+ bvh.c2x = co2 + epsilon
+ else:
+ bvh.c1x = co2 - epsilon
+ bvh.c2x = co1 + epsilon
+ co1 = b1.co.y
+ co2 = b2.co.y
+ if co1 <= co2:
+ bvh.c1y = co1 - epsilon
+ bvh.c2y = co2 + epsilon
+ else:
+ bvh.c1y = co2 - epsilon
+ bvh.c2y = co1 + epsilon
+ co1 = b1.co.z
+ co2 = b2.co.z
+ if co1 <= co2:
+ bvh.c1z = co1 - epsilon
+ bvh.c2z = co2 + epsilon
+ else:
+ bvh.c1z = co2 - epsilon
+ bvh.c2z = co1 + epsilon
+ bco.add(bvh)
+ del edges
+ overlap = {}
+ oget = overlap.get
+ for e1 in bm.edges:
+ by = bz = True
+ a1 = e1.verts[0]
+ a2 = e1.verts[1]
+ c1x = a1.co.x
+ c2x = a2.co.x
+ if c1x > c2x:
+ tm = c1x
+ c1x = c2x
+ c2x = tm
+ for bvh in bco:
+ if c1x <= bvh.c2x and c2x >= bvh.c1x:
+ if by:
+ by = False
+ c1y = a1.co.y
+ c2y = a2.co.y
+ if c1y > c2y:
+ tm = c1y
+ c1y = c2y
+ c2y = tm
+ if c1y <= bvh.c2y and c2y >= bvh.c1y:
+ if bz:
+ bz = False
+ c1z = a1.co.z
+ c2z = a2.co.z
+ if c1z > c2z:
+ tm = c1z
+ c1z = c2z
+ c2z = tm
+ if c1z <= bvh.c2z and c2z >= bvh.c1z:
+ e2 = bm.edges[bvh.i]
+ if e1 != e2:
+ overlap[e1] = oget(e1, set()).union({e2})
+ return overlap
+
+
+def intersect_edges_edges(overlap, precision=4):
+ epsilon = .1**precision
+ fpre_min = -epsilon
+ fpre_max = 1 + epsilon
+ splits = {}
+ sp_get = splits.get
+ new_edges1 = set()
+ new_edges2 = set()
+ targetmap = {}
+ for edg1 in overlap:
+ # print("***", ed1.index, "***")
+ for edg2 in overlap[edg1]:
+ a1 = edg1.verts[0]
+ a2 = edg1.verts[1]
+ b1 = edg2.verts[0]
+ b2 = edg2.verts[1]
+
+ # test if are linked
+ if a1 in {b1, b2} or a2 in {b1, b2}:
+ # print('linked')
+ continue
+
+ aco1, aco2 = a1.co, a2.co
+ bco1, bco2 = b1.co, b2.co
+ tp = intersect_line_line(aco1, aco2, bco1, bco2)
+ if tp:
+ p1, p2 = tp
+ if (p1 - p2).to_tuple(precision) == (0, 0, 0):
+ v = aco2 - aco1
+ f = p1 - aco1
+ x, y, z = abs(v.x), abs(v.y), abs(v.z)
+ max1 = 0 if x >= y and x >= z else\
+ 1 if y >= x and y >= z else 2
+ fac1 = f[max1] / v[max1]
+
+ v = bco2 - bco1
+ f = p2 - bco1
+ x, y, z = abs(v.x), abs(v.y), abs(v.z)
+ max2 = 0 if x >= y and x >= z else\
+ 1 if y >= x and y >= z else 2
+ fac2 = f[max2] / v[max2]
+
+ if fpre_min <= fac1 <= fpre_max:
+ # print(edg1.index, 'can intersect', edg2.index)
+ ed1 = edg1
+
+ elif edg1 in splits:
+ for ed1 in splits[edg1]:
+ a1 = ed1.verts[0]
+ a2 = ed1.verts[1]
+
+ vco1 = a1.co
+ vco2 = a2.co
+
+ v = vco2 - vco1
+ f = p1 - vco1
+ fac1 = f[max1] / v[max1]
+ if fpre_min <= fac1 <= fpre_max:
+ # print(e.index, 'can intersect', edg2.index)
+ break
+ else:
+ # print(edg1.index, 'really does not intersect', edg2.index)
+ continue
+ else:
+ # print(edg1.index, 'not intersect', edg2.index)
+ continue
+
+ if fpre_min <= fac2 <= fpre_max:
+ # print(ed1.index, 'actually intersect', edg2.index)
+ ed2 = edg2
+
+ elif edg2 in splits:
+ for ed2 in splits[edg2]:
+ b1 = ed2.verts[0]
+ b2 = ed2.verts[1]
+
+ vco1 = b1.co
+ vco2 = b2.co
+
+ v = vco2 - vco1
+ f = p2 - vco1
+ fac2 = f[max2] / v[max2]
+ if fpre_min <= fac2 <= fpre_max:
+ # print(ed1.index, 'actually intersect', e.index)
+ break
+ else:
+ # print(ed1.index, 'really does not intersect', ed2.index)
+ continue
+ else:
+ # print(ed1.index, 'not intersect', edg2.index)
+ continue
+
+ new_edges1.add(ed1)
+ new_edges2.add(ed2)
+
+ if abs(fac1) <= epsilon:
+ nv1 = a1
+ elif fac1 + epsilon >= 1:
+ nv1 = a2
+ else:
+ ne1, nv1 = bmesh.utils.edge_split(ed1, a1, fac1)
+ new_edges1.add(ne1)
+ splits[edg1] = sp_get(edg1, set()).union({ne1})
+
+ if abs(fac2) <= epsilon:
+ nv2 = b1
+ elif fac2 + epsilon >= 1:
+ nv2 = b2
+ else:
+ ne2, nv2 = bmesh.utils.edge_split(ed2, b1, fac2)
+ new_edges2.add(ne2)
+ splits[edg2] = sp_get(edg2, set()).union({ne2})
+
+ if nv1 != nv2: # necessary?
+ targetmap[nv1] = nv2
+
+ return new_edges1, new_edges2, targetmap
+
+
+class ER_OT_Extrude_and_Reshape(Operator):
+ bl_idname = "mesh.extrude_reshape"
+ bl_label = "Extrude and Reshape"
+ bl_description = "Push and pull face entities to sculpt 3d models"
+ bl_options = {'REGISTER', 'GRAB_CURSOR', 'BLOCKING'}
+
+ @classmethod
+ def poll(cls, context):
+ if context.mode=='EDIT_MESH':
+ return True
+
+ def modal(self, context, event):
+ if self.confirm:
+ sface = self.bm.faces.active
+ if not sface:
+ for face in self.bm.faces:
+ if face.select is True:
+ sface = face
+ break
+ else:
+ return {'FINISHED'}
+ # edges to intersect
+ edges = set()
+ [[edges.add(ed) for ed in v.link_edges] for v in sface.verts]
+
+ overlap = edges_BVH_overlap(self.bm, edges, epsilon=0.0001)
+ overlap = {k: v for k, v in overlap.items() if k not in edges} # remove repetition
+ """
+ print([e.index for e in edges])
+ for a, b in overlap.items():
+ print(a.index, [e.index for e in b])
+ """
+ new_edges1, new_edges2, targetmap = intersect_edges_edges(overlap)
+ pos_weld = set()
+ for e in new_edges1:
+ v1, v2 = e.verts
+ if v1 in targetmap and v2 in targetmap:
+ pos_weld.add((targetmap[v1], targetmap[v2]))
+ if targetmap:
+ bmesh.ops.weld_verts(self.bm, targetmap=targetmap)
+ """
+ print([e.is_valid for e in new_edges1])
+ print([e.is_valid for e in new_edges2])
+ sp_faces1 = set()
+ """
+ for e in pos_weld:
+ v1, v2 = e
+ lf1 = set(v1.link_faces)
+ lf2 = set(v2.link_faces)
+ rlfe = lf1.intersection(lf2)
+ for f in rlfe:
+ try:
+ nf = bmesh.utils.face_split(f, v1, v2)
+ # sp_faces1.update({f, nf[0]})
+ except:
+ pass
+
+ # sp_faces2 = set()
+ for e in new_edges2:
+ lfe = set(e.link_faces)
+ v1, v2 = e.verts
+ lf1 = set(v1.link_faces)
+ lf2 = set(v2.link_faces)
+ rlfe = lf1.intersection(lf2)
+ for f in rlfe.difference(lfe):
+ nf = bmesh.utils.face_split(f, v1, v2)
+ # sp_faces2.update({f, nf[0]})
+
+ bmesh.update_edit_mesh(self.mesh, loop_triangles=True, destructive=True)
+ return {'FINISHED'}
+ if self.cancel:
+ return {'FINISHED'}
+ self.cancel = event.type in {'ESC', 'NDOF_BUTTON_ESC'}
+ self.confirm = event.type in {'LEFTMOUSE', 'RET', 'NUMPAD_ENTER'}
+ return {'PASS_THROUGH'}
+
+ def execute(self, context):
+ self.mesh = context.object.data
+ self.bm = bmesh.from_edit_mesh(self.mesh)
+ try:
+ selection = self.bm.select_history[-1]
+ except:
+ for face in self.bm.faces:
+ if face.select is True:
+ selection = face
+ break
+ else:
+ return {'FINISHED'}
+ if not isinstance(selection, bmesh.types.BMFace):
+ bpy.ops.mesh.extrude_region_move('INVOKE_DEFAULT')
+ return {'FINISHED'}
+ else:
+ face = selection
+ # face.select = False
+ bpy.ops.mesh.select_all(action='DESELECT')
+ geom = []
+ for edge in face.edges:
+ if abs(edge.calc_face_angle(0) - 1.5707963267948966) < 0.01: # self.angle_tolerance:
+ geom.append(edge)
+
+ ret_dict = bmesh.ops.extrude_discrete_faces(self.bm, faces=[face])
+
+ for face in ret_dict['faces']:
+ self.bm.faces.active = face
+ face.select = True
+ sface = face
+ dfaces = bmesh.ops.dissolve_edges(
+ self.bm, edges=geom, use_verts=True, use_face_split=False
+ )
+ bmesh.update_edit_mesh(self.mesh, loop_triangles=True, destructive=True)
+ bpy.ops.transform.translate(
+ 'INVOKE_DEFAULT', constraint_axis=(False, False, True),
+ orient_type='NORMAL', release_confirm=True
+ )
+
+ context.window_manager.modal_handler_add(self)
+
+ self.cancel = False
+ self.confirm = False
+ return {'RUNNING_MODAL'}
+
+
+def operator_draw(self, context):
+ layout = self.layout
+ col = layout.column(align=True)
+ col.operator("mesh.extrude_reshape")
+
+
+def register():
+ bpy.utils.register_class(ER_OT_Extrude_and_Reshape)
+
+
+def unregister():
+ bpy.utils.unregister_class(ER_OT_Extrude_and_Reshape)
+
+
+if __name__ == "__main__":
+ register()