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_extra_tools/mesh_edgetools.py')
-rw-r--r--mesh_extra_tools/mesh_edgetools.py1731
1 files changed, 890 insertions, 841 deletions
diff --git a/mesh_extra_tools/mesh_edgetools.py b/mesh_extra_tools/mesh_edgetools.py
index fde651cc..cc8b2dc8 100644
--- a/mesh_extra_tools/mesh_edgetools.py
+++ b/mesh_extra_tools/mesh_edgetools.py
@@ -1,7 +1,7 @@
-# ##### BEGIN GPL LICENSE BLOCK #####
-#
# The Blender Edgetools is to bring CAD tools to Blender.
# Copyright (C) 2012 Paul Marshall
+
+# ##### 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
@@ -23,14 +23,13 @@
bl_info = {
"name": "EdgeTools",
"author": "Paul Marshall",
- "version": (0, 9),
+ "version": (0, 9, 2),
"blender": (2, 68, 0),
"location": "View3D > Toolbar and View3D > Specials (W-key)",
"warning": "",
"description": "CAD style edge manipulation tools",
- "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"
+ "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/"
"Scripts/Modeling/EdgeTools",
- "tracker_url": "",
"category": "Mesh"}
@@ -92,10 +91,12 @@ Search for "@todo" to quickly find sections that need work
Note: lijenstina - modified this script in preparation for merging
fixed the needless jumping to object mode for bmesh creation
causing the crash with the Slice > Rip operator
+Removed the test operator since version 0.9.2
+added general error handling
"""
# Enable debug
-# Set to True to have the Interesect Line Face Test and debug prints available
+# Set to True to have the debug prints available
ENABLE_DEBUG = False
@@ -112,6 +113,51 @@ def is_parallel(v1, v2, v3, v4):
return result is None
+# Handle error notifications
+def error_handlers(self, op_name, error, reports="ERROR", func=False):
+ if self and reports:
+ self.report({'WARNING'}, reports + " (See Console for more info)")
+
+ is_func = "Function" if func else "Operator"
+ print("\n[Mesh EdgeTools]\n{}: {}\nError: {}\n".format(is_func, op_name, error))
+
+
+def flip_edit_mode():
+ bpy.ops.object.editmode_toggle()
+ bpy.ops.object.editmode_toggle()
+
+
+# check the appropriate selection condition
+# to prevent crashes with the index out of range errors
+# pass the bEdges and bVerts based selection tables here
+# types: Edge, Vertex, All
+def is_selected_enough(self, bEdges, bVerts, edges_n=1, verts_n=0, types="Edge"):
+ check = False
+ try:
+ if bEdges and types == "Edge":
+ check = (len(bEdges) >= edges_n)
+ elif bVerts and types == "Vertex":
+ check = (len(bVerts) >= verts_n)
+ elif bEdges and bVerts and types == "All":
+ check = (len(bEdges) >= edges_n and len(bVerts) >= verts_n)
+
+ if check is False:
+ strings = "%s Vertices and / or " % verts_n if verts_n != 0 else ""
+ self.report({'WARNING'},
+ "Needs at least " + strings + "%s Edge(s) selected. "
+ "Operation Cancelled" % edges_n)
+ flip_edit_mode()
+
+ return check
+
+ except Exception as e:
+ error_handlers(self, "is_selected_enough", e,
+ "No appropriate selection. Operation Cancelled", func=True)
+ return False
+
+ return False
+
+
# is_axial
# This is for the special case where the edge is parallel to an axis.
# The projection onto the XY plane will fail so it will have to be handled differently
@@ -362,7 +408,9 @@ def intersect_line_face(edge, face, is_infinite=False, error=0.000002):
flipB = False
for i in range(len(face.edges)):
- if face.edges[i].verts[0] not in edgeA.verts and face.edges[i].verts[1] not in edgeA.verts:
+ if face.edges[i].verts[0] not in edgeA.verts and \
+ face.edges[i].verts[1] not in edgeA.verts:
+
edgeB = face.edges[i]
break
@@ -494,8 +542,9 @@ def intersect_line_face(edge, face, is_infinite=False, error=0.000002):
# The line is along the y/z-axis but is not parallel to either:
else:
t12 = -(-(a33 - b33) * (-a32 + a12 * (1 - t) + b12 * t) + (a32 - b32) *
- (-a33 + a13 * (1 - t) + b13 * t)) / (-(a33 - b33) * ((a22 - a12) * (1 - t) + (b22 - b12) * t) +
- (a32 - b32) * ((a23 - a13) * (1 - t) + (b23 - b13) * t))
+ (-a33 + a13 * (1 - t) + b13 * t)) / (-(a33 - b33) *
+ ((a22 - a12) * (1 - t) + (b22 - b12) * t) + (a32 - b32) *
+ ((a23 - a13) * (1 - t) + (b23 - b13) * t))
elif a32 == b32:
# The line is parallel to the x-axis:
if a33 == b33:
@@ -515,13 +564,17 @@ def intersect_line_face(edge, face, is_infinite=False, error=0.000002):
# If block used to prevent a div by zero error.
t3 = 0
if a31 != b31:
- t3 = (-a11 + a31 + (a11 - b11) * t + (a11 - a21) * t12 + (a21 - a11 + b11 - b21) * t * t12) / (a31 - b31)
+ t3 = (-a11 + a31 + (a11 - b11) * t + (a11 - a21) *
+ t12 + (a21 - a11 + b11 - b21) * t * t12) / (a31 - b31)
elif a32 != b32:
- t3 = (-a12 + a32 + (a12 - b12) * t + (a12 - a22) * t12 + (a22 - a12 + b12 - b22) * t * t12) / (a32 - b32)
+ t3 = (-a12 + a32 + (a12 - b12) * t + (a12 - a22) *
+ t12 + (a22 - a12 + b12 - b22) * t * t12) / (a32 - b32)
elif a33 != b33:
- t3 = (-a13 + a33 + (a13 - b13) * t + (a13 - a23) * t12 + (a23 - a13 + b13 - b23) * t * t12) / (a33 - b33)
+ t3 = (-a13 + a33 + (a13 - b13) * t + (a13 - a23) *
+ t12 + (a23 - a13 + b13 - b23) * t * t12) / (a33 - b33)
else:
- print("The second edge is a zero-length edge")
+ if ENABLE_DEBUG:
+ print("The second edge is a zero-length edge")
return None
# Calculate the point of intersection:
@@ -576,7 +629,8 @@ def project_point_plane(pt, plane_co, plane_no):
print("project_point_plane was called")
proj_co = intersect_line_plane(pt, pt + plane_no, plane_co, plane_no)
proj_ve = proj_co - pt
- print("project_point_plane: proj_co is {}\nproj_ve is {}".format(proj_co, proj_ve))
+ if ENABLE_DEBUG:
+ print("project_point_plane: proj_co is {}\nproj_ve is {}".format(proj_co, proj_ve))
return (proj_ve, proj_co)
@@ -601,26 +655,29 @@ class Extend(Operator):
bl_options = {'REGISTER', 'UNDO'}
di1 = BoolProperty(
- name="Forwards",
- description="Extend the edge forwards",
- default=True
- )
+ name="Forwards",
+ description="Extend the edge forwards",
+ default=True
+ )
di2 = BoolProperty(
- name="Backwards",
- description="Extend the edge backwards",
- default=False
- )
+ name="Backwards",
+ description="Extend the edge backwards",
+ default=False
+ )
length = FloatProperty(
- name="Length",
- description="Length to extend the edge",
- min=0.0, max=1024.0,
- default=1.0
- )
+ name="Length",
+ description="Length to extend the edge",
+ min=0.0, max=1024.0,
+ default=1.0
+ )
def draw(self, context):
layout = self.layout
- layout.prop(self, "di1")
- layout.prop(self, "di2")
+
+ row = layout.row(align=True)
+ row.prop(self, "di1", toggle=True)
+ row.prop(self, "di2", toggle=True)
+
layout.prop(self, "length")
@classmethod
@@ -632,67 +689,76 @@ class Extend(Operator):
return self.execute(context)
def execute(self, context):
- me = context.object.data
- bm = bmesh.from_edit_mesh(me)
- bm.normal_update()
+ try:
+ me = context.object.data
+ bm = bmesh.from_edit_mesh(me)
+ bm.normal_update()
+
+ bEdges = bm.edges
+ bVerts = bm.verts
- bEdges = bm.edges
- bVerts = bm.verts
+ edges = [e for e in bEdges if e.select]
+ verts = [v for v in bVerts if v.select]
- edges = [e for e in bEdges if e.select]
- verts = [v for v in bVerts if v.select]
+ if not is_selected_enough(self, edges, 0, edges_n=1, verts_n=0, types="Edge"):
+ return {'CANCELLED'}
- if len(edges) > 0:
- for e in edges:
- vector = e.verts[0].co - e.verts[1].co
+ if len(edges) > 0:
+ for e in edges:
+ vector = e.verts[0].co - e.verts[1].co
+ vector.length = self.length
+
+ if self.di1:
+ v = bVerts.new()
+ if (vector[0] + vector[1] + vector[2]) < 0:
+ v.co = e.verts[1].co - vector
+ newE = bEdges.new((e.verts[1], v))
+ bEdges.ensure_lookup_table()
+ else:
+ v.co = e.verts[0].co + vector
+ newE = bEdges.new((e.verts[0], v))
+ bEdges.ensure_lookup_table()
+ if self.di2:
+ v = bVerts.new()
+ if (vector[0] + vector[1] + vector[2]) < 0:
+ v.co = e.verts[0].co + vector
+ newE = bEdges.new((e.verts[0], v))
+ bEdges.ensure_lookup_table()
+ else:
+ v.co = e.verts[1].co - vector
+ newE = bEdges.new((e.verts[1], v))
+ bEdges.ensure_lookup_table()
+ else:
+ vector = verts[0].co - verts[1].co
vector.length = self.length
if self.di1:
v = bVerts.new()
if (vector[0] + vector[1] + vector[2]) < 0:
- v.co = e.verts[1].co - vector
- newE = bEdges.new((e.verts[1], v))
+ v.co = verts[1].co - vector
+ e = bEdges.new((verts[1], v))
bEdges.ensure_lookup_table()
else:
- v.co = e.verts[0].co + vector
- newE = bEdges.new((e.verts[0], v))
+ v.co = verts[0].co + vector
+ e = bEdges.new((verts[0], v))
bEdges.ensure_lookup_table()
if self.di2:
v = bVerts.new()
if (vector[0] + vector[1] + vector[2]) < 0:
- v.co = e.verts[0].co + vector
- newE = bEdges.new((e.verts[0], v))
+ v.co = verts[0].co + vector
+ e = bEdges.new((verts[0], v))
bEdges.ensure_lookup_table()
else:
- v.co = e.verts[1].co - vector
- newE = bEdges.new((e.verts[1], v))
+ v.co = verts[1].co - vector
+ e = bEdges.new((verts[1], v))
bEdges.ensure_lookup_table()
- else:
- vector = verts[0].co - verts[1].co
- vector.length = self.length
- if self.di1:
- v = bVerts.new()
- if (vector[0] + vector[1] + vector[2]) < 0:
- v.co = verts[1].co - vector
- e = bEdges.new((verts[1], v))
- bEdges.ensure_lookup_table()
- else:
- v.co = verts[0].co + vector
- e = bEdges.new((verts[0], v))
- bEdges.ensure_lookup_table()
- if self.di2:
- v = bVerts.new()
- if (vector[0] + vector[1] + vector[2]) < 0:
- v.co = verts[0].co + vector
- e = bEdges.new((verts[0], v))
- bEdges.ensure_lookup_table()
- else:
- v.co = verts[1].co - vector
- e = bEdges.new((verts[1], v))
- bEdges.ensure_lookup_table()
+ bmesh.update_edit_mesh(me)
- bmesh.update_edit_mesh(me)
+ except Exception as e:
+ error_handlers(self, "mesh.edgetools_extend", e,
+ reports="Extend Operator failed", func=False)
+ return {'CANCELLED'}
return {'FINISHED'}
@@ -711,56 +777,60 @@ class Spline(Operator):
bl_options = {'REGISTER', 'UNDO'}
alg = EnumProperty(
- name="Spline Algorithm",
- items=[('Blender', "Blender", "Interpolation provided through mathutils.geometry"),
- ('Hermite', "C-Spline", "C-spline interpolation"),
- ('Bezier', "Bezier", "Bezier interpolation"),
- ('B-Spline', "B-Spline", "B-Spline interpolation")],
- default='Bezier'
- )
+ name="Spline Algorithm",
+ items=[('Blender', "Blender", "Interpolation provided through mathutils.geometry"),
+ ('Hermite', "C-Spline", "C-spline interpolation"),
+ ('Bezier', "Bezier", "Bezier interpolation"),
+ ('B-Spline', "B-Spline", "B-Spline interpolation")],
+ default='Bezier'
+ )
segments = IntProperty(
- name="Segments",
- description="Number of segments to use in the interpolation",
- min=2, max=4096,
- soft_max=1024,
- default=32
- )
+ name="Segments",
+ description="Number of segments to use in the interpolation",
+ min=2, max=4096,
+ soft_max=1024,
+ default=32
+ )
flip1 = BoolProperty(
- name="Flip Edge",
- description="Flip the direction of the spline on Edge 1",
- default=False
- )
+ name="Flip Edge",
+ description="Flip the direction of the spline on Edge 1",
+ default=False
+ )
flip2 = BoolProperty(
- name="Flip Edge",
- description="Flip the direction of the spline on Edge 2",
- default=False
- )
+ name="Flip Edge",
+ description="Flip the direction of the spline on Edge 2",
+ default=False
+ )
ten1 = FloatProperty(
- name="Tension",
- description="Tension on Edge 1",
- min=-4096.0, max=4096.0,
- soft_min=-8.0, soft_max=8.0,
- default=1.0
- )
+ name="Tension",
+ description="Tension on Edge 1",
+ min=-4096.0, max=4096.0,
+ soft_min=-8.0, soft_max=8.0,
+ default=1.0
+ )
ten2 = FloatProperty(
- name="Tension",
- description="Tension on Edge 2",
- min=-4096.0, max=4096.0,
- soft_min=-8.0, soft_max=8.0,
- default=1.0
- )
+ name="Tension",
+ description="Tension on Edge 2",
+ min=-4096.0, max=4096.0,
+ soft_min=-8.0, soft_max=8.0,
+ default=1.0
+ )
def draw(self, context):
layout = self.layout
layout.prop(self, "alg")
layout.prop(self, "segments")
+
layout.label("Edge 1:")
- layout.prop(self, "ten1")
- layout.prop(self, "flip1")
+ split = layout.split(percentage=0.8, align=True)
+ split.prop(self, "ten1")
+ split.prop(self, "flip1", text="", icon="ALIGN", toggle=True)
+
layout.label("Edge 2:")
- layout.prop(self, "ten2")
- layout.prop(self, "flip2")
+ split = layout.split(percentage=0.8, align=True)
+ split.prop(self, "ten2")
+ split.prop(self, "flip2", text="", icon="ALIGN", toggle=True)
@classmethod
def poll(cls, context):
@@ -771,70 +841,88 @@ class Spline(Operator):
return self.execute(context)
def execute(self, context):
- me = context.object.data
- bm = bmesh.from_edit_mesh(me)
- bm.normal_update()
+ try:
+ me = context.object.data
+ bm = bmesh.from_edit_mesh(me)
+ bm.normal_update()
- bEdges = bm.edges
- bVerts = bm.verts
+ bEdges = bm.edges
+ bVerts = bm.verts
- seg = self.segments
- edges = [e for e in bEdges if e.select]
- verts = [edges[v // 2].verts[v % 2] for v in range(4)]
+ seg = self.segments
+ edges = [e for e in bEdges if e.select]
- if self.flip1:
- v1 = verts[1]
- p1_co = verts[1].co
- p1_dir = verts[1].co - verts[0].co
- else:
- v1 = verts[0]
- p1_co = verts[0].co
- p1_dir = verts[0].co - verts[1].co
- if self.ten1 < 0:
- p1_dir = -1 * p1_dir
- p1_dir.length = -self.ten1
- else:
- p1_dir.length = self.ten1
+ if not is_selected_enough(self, edges, 0, edges_n=2, verts_n=0, types="Edge"):
+ return {'CANCELLED'}
- if self.flip2:
- v2 = verts[3]
- p2_co = verts[3].co
- p2_dir = verts[2].co - verts[3].co
- else:
- v2 = verts[2]
- p2_co = verts[2].co
- p2_dir = verts[3].co - verts[2].co
- if self.ten2 < 0:
- p2_dir = -1 * p2_dir
- p2_dir.length = -self.ten2
- else:
- p2_dir.length = self.ten2
-
- # Get the interploted coordinates:
- if self.alg == 'Blender':
- pieces = interpolate_bezier(p1_co, p1_dir, p2_dir, p2_co, self.segments)
- elif self.alg == 'Hermite':
- pieces = interpolate_line_line(p1_co, p1_dir, p2_co, p2_dir, self.segments, 1, 'HERMITE')
- elif self.alg == 'Bezier':
- pieces = interpolate_line_line(p1_co, p1_dir, p2_co, p2_dir, self.segments, 1, 'BEZIER')
- elif self.alg == 'B-Spline':
- pieces = interpolate_line_line(p1_co, p1_dir, p2_co, p2_dir, self.segments, 1, 'BSPLINE')
-
- verts = []
- verts.append(v1)
- # Add vertices and set the points:
- for i in range(seg - 1):
- v = bVerts.new()
- v.co = pieces[i]
- bVerts.ensure_lookup_table()
- verts.append(v)
- verts.append(v2)
- # Connect vertices:
- for i in range(seg):
- e = bEdges.new((verts[i], verts[i + 1]))
- bEdges.ensure_lookup_table()
-
- bmesh.update_edit_mesh(me)
+ verts = [edges[v // 2].verts[v % 2] for v in range(4)]
+
+ if self.flip1:
+ v1 = verts[1]
+ p1_co = verts[1].co
+ p1_dir = verts[1].co - verts[0].co
+ else:
+ v1 = verts[0]
+ p1_co = verts[0].co
+ p1_dir = verts[0].co - verts[1].co
+ if self.ten1 < 0:
+ p1_dir = -1 * p1_dir
+ p1_dir.length = -self.ten1
+ else:
+ p1_dir.length = self.ten1
+
+ if self.flip2:
+ v2 = verts[3]
+ p2_co = verts[3].co
+ p2_dir = verts[2].co - verts[3].co
+ else:
+ v2 = verts[2]
+ p2_co = verts[2].co
+ p2_dir = verts[3].co - verts[2].co
+ if self.ten2 < 0:
+ p2_dir = -1 * p2_dir
+ p2_dir.length = -self.ten2
+ else:
+ p2_dir.length = self.ten2
+
+ # Get the interploted coordinates:
+ if self.alg == 'Blender':
+ pieces = interpolate_bezier(
+ p1_co, p1_dir, p2_dir, p2_co, self.segments
+ )
+ elif self.alg == 'Hermite':
+ pieces = interpolate_line_line(
+ p1_co, p1_dir, p2_co, p2_dir, self.segments, 1, 'HERMITE'
+ )
+ elif self.alg == 'Bezier':
+ pieces = interpolate_line_line(
+ p1_co, p1_dir, p2_co, p2_dir, self.segments, 1, 'BEZIER'
+ )
+ elif self.alg == 'B-Spline':
+ pieces = interpolate_line_line(
+ p1_co, p1_dir, p2_co, p2_dir, self.segments, 1, 'BSPLINE'
+ )
+
+ verts = []
+ verts.append(v1)
+ # Add vertices and set the points:
+ for i in range(seg - 1):
+ v = bVerts.new()
+ v.co = pieces[i]
+ bVerts.ensure_lookup_table()
+ verts.append(v)
+ verts.append(v2)
+ # Connect vertices:
+ for i in range(seg):
+ e = bEdges.new((verts[i], verts[i + 1]))
+ bEdges.ensure_lookup_table()
+
+ bmesh.update_edit_mesh(me)
+
+ except Exception as e:
+ error_handlers(self, "mesh.edgetools_spline", e,
+ reports="Spline Operator failed", func=False)
+ return {'CANCELLED'}
return {'FINISHED'}
@@ -860,56 +948,55 @@ class Ortho(Operator):
bl_options = {'REGISTER', 'UNDO'}
vert1 = BoolProperty(
- name="Vertice 1",
- description="Enable edge creation for Vertice 1",
- default=True
- )
+ name="Vertice 1",
+ description="Enable edge creation for Vertice 1",
+ default=True
+ )
vert2 = BoolProperty(
- name="Vertice 2",
- description="Enable edge creation for Vertice 2",
- default=True
- )
+ name="Vertice 2",
+ description="Enable edge creation for Vertice 2",
+ default=True
+ )
vert3 = BoolProperty(
- name="Vertice 3",
- description="Enable edge creation for Vertice 3",
- default=True
- )
+ name="Vertice 3",
+ description="Enable edge creation for Vertice 3",
+ default=True
+ )
vert4 = BoolProperty(
- name="Vertice 4",
- description="Enable edge creation for Vertice 4",
- default=True
- )
+ name="Vertice 4",
+ description="Enable edge creation for Vertice 4",
+ default=True
+ )
pos = BoolProperty(
- name="Positive",
- description="Enable creation of positive direction edges",
- default=True
- )
+ name="Positive",
+ description="Enable creation of positive direction edges",
+ default=True
+ )
neg = BoolProperty(
- name="Negative",
- description="Enable creation of negative direction edges",
- default=True
- )
+ name="Negative",
+ description="Enable creation of negative direction edges",
+ default=True
+ )
angle = FloatProperty(
- name="Angle",
- description="Define the angle off of the originating edge",
- min=0.0, max=180.0,
- default=90.0
- )
+ name="Angle",
+ description="Define the angle off of the originating edge",
+ min=0.0, max=180.0,
+ default=90.0
+ )
length = FloatProperty(
- name="Length",
- description="Length of created edges",
- min=0.0, max=1024.0,
- default=1.0
- )
-
+ name="Length",
+ description="Length of created edges",
+ min=0.0, max=1024.0,
+ default=1.0
+ )
# For when only one edge is selected (Possible feature to be testd):
plane = EnumProperty(
- name="Plane",
- items=[("XY", "X-Y Plane", "Use the X-Y plane as the plane of creation"),
- ("XZ", "X-Z Plane", "Use the X-Z plane as the plane of creation"),
- ("YZ", "Y-Z Plane", "Use the Y-Z plane as the plane of creation")],
- default="XY"
- )
+ name="Plane",
+ items=[("XY", "X-Y Plane", "Use the X-Y plane as the plane of creation"),
+ ("XZ", "X-Z Plane", "Use the X-Z plane as the plane of creation"),
+ ("YZ", "Y-Z Plane", "Use the Y-Z plane as the plane of creation")],
+ default="XY"
+ )
def draw(self, context):
layout = self.layout
@@ -932,8 +1019,10 @@ class Ortho(Operator):
row.prop(self, "neg")
layout.separator()
- layout.prop(self, "angle")
- layout.prop(self, "length")
+
+ col = layout.column(align=True)
+ col.prop(self, "angle")
+ col.prop(self, "length")
@classmethod
def poll(cls, context):
@@ -944,98 +1033,100 @@ class Ortho(Operator):
return self.execute(context)
def execute(self, context):
- me = context.object.data
- bm = bmesh.from_edit_mesh(me)
- bm.normal_update()
-
- bVerts = bm.verts
- bEdges = bm.edges
- edges = [e for e in bEdges if e.select]
- vectors = []
-
- # Until I can figure out a better way of handling it:
- if len(edges) < 2:
- bpy.ops.object.editmode_toggle()
- self.report({'ERROR_INVALID_INPUT'}, "You must select two edges")
- return {'CANCELLED'}
+ try:
+ me = context.object.data
+ bm = bmesh.from_edit_mesh(me)
+ bm.normal_update()
- verts = [edges[0].verts[0],
- edges[0].verts[1],
- edges[1].verts[0],
- edges[1].verts[1]]
+ bVerts = bm.verts
+ bEdges = bm.edges
+ edges = [e for e in bEdges if e.select]
+ vectors = []
- cos = intersect_line_line(verts[0].co, verts[1].co, verts[2].co, verts[3].co)
+ if not is_selected_enough(self, edges, 0, edges_n=2, verts_n=0, types="Edge"):
+ return {'CANCELLED'}
- # If the two edges are parallel:
- if cos is None:
- self.report({'WARNING'},
- "Selected lines are parallel: results may be unpredictable")
- vectors.append(verts[0].co - verts[1].co)
- vectors.append(verts[0].co - verts[2].co)
- vectors.append(vectors[0].cross(vectors[1]))
- vectors.append(vectors[2].cross(vectors[0]))
- vectors.append(-vectors[3])
- else:
- # Warn the user if they have not chosen two planar edges:
- if not is_same_co(cos[0], cos[1]):
+ verts = [edges[0].verts[0],
+ edges[0].verts[1],
+ edges[1].verts[0],
+ edges[1].verts[1]]
+
+ cos = intersect_line_line(verts[0].co, verts[1].co, verts[2].co, verts[3].co)
+
+ # If the two edges are parallel:
+ if cos is None:
self.report({'WARNING'},
- "Selected lines are not planar: results may be unpredictable")
-
- # This makes the +/- behavior predictable:
- if (verts[0].co - cos[0]).length < (verts[1].co - cos[0]).length:
- verts[0], verts[1] = verts[1], verts[0]
- if (verts[2].co - cos[0]).length < (verts[3].co - cos[0]).length:
- verts[2], verts[3] = verts[3], verts[2]
-
- vectors.append(verts[0].co - verts[1].co)
- vectors.append(verts[2].co - verts[3].co)
-
- # Normal of the plane formed by vector1 and vector2:
- vectors.append(vectors[0].cross(vectors[1]))
-
- # Possible directions:
- vectors.append(vectors[2].cross(vectors[0]))
- vectors.append(vectors[1].cross(vectors[2]))
-
- # Set the length:
- vectors[3].length = self.length
- vectors[4].length = self.length
-
- # Perform any additional rotations:
- matrix = Matrix.Rotation(radians(90 + self.angle), 3, vectors[2])
- vectors.append(matrix * -vectors[3]) # vectors[5]
- matrix = Matrix.Rotation(radians(90 - self.angle), 3, vectors[2])
- vectors.append(matrix * vectors[4]) # vectors[6]
- vectors.append(matrix * vectors[3]) # vectors[7]
- matrix = Matrix.Rotation(radians(90 + self.angle), 3, vectors[2])
- vectors.append(matrix * -vectors[4]) # vectors[8]
-
- # Perform extrusions and displacements:
- # There will be a total of 8 extrusions. One for each vert of each edge.
- # It looks like an extrusion will add the new vert to the end of the verts
- # list and leave the rest in the same location.
- # ----------- EDIT -----------
- # It looks like I might be able to do this within "bpy.data" with the ".add" function
-
- for v in range(len(verts)):
- vert = verts[v]
- if ((v == 0 and self.vert1) or (v == 1 and self.vert2) or
- (v == 2 and self.vert3) or (v == 3 and self.vert4)):
-
- if self.pos:
- new = bVerts.new()
- new.co = vert.co - vectors[5 + (v // 2) + ((v % 2) * 2)]
- bVerts.ensure_lookup_table()
- bEdges.new((vert, new))
- bEdges.ensure_lookup_table()
- if self.neg:
- new = bVerts.new()
- new.co = vert.co + vectors[5 + (v // 2) + ((v % 2) * 2)]
- bVerts.ensure_lookup_table()
- bEdges.new((vert, new))
- bEdges.ensure_lookup_table()
+ "Selected lines are parallel: results may be unpredictable")
+ vectors.append(verts[0].co - verts[1].co)
+ vectors.append(verts[0].co - verts[2].co)
+ vectors.append(vectors[0].cross(vectors[1]))
+ vectors.append(vectors[2].cross(vectors[0]))
+ vectors.append(-vectors[3])
+ else:
+ # Warn the user if they have not chosen two planar edges:
+ if not is_same_co(cos[0], cos[1]):
+ self.report({'WARNING'},
+ "Selected lines are not planar: results may be unpredictable")
+
+ # This makes the +/- behavior predictable:
+ if (verts[0].co - cos[0]).length < (verts[1].co - cos[0]).length:
+ verts[0], verts[1] = verts[1], verts[0]
+ if (verts[2].co - cos[0]).length < (verts[3].co - cos[0]).length:
+ verts[2], verts[3] = verts[3], verts[2]
+
+ vectors.append(verts[0].co - verts[1].co)
+ vectors.append(verts[2].co - verts[3].co)
+
+ # Normal of the plane formed by vector1 and vector2:
+ vectors.append(vectors[0].cross(vectors[1]))
+
+ # Possible directions:
+ vectors.append(vectors[2].cross(vectors[0]))
+ vectors.append(vectors[1].cross(vectors[2]))
+
+ # Set the length:
+ vectors[3].length = self.length
+ vectors[4].length = self.length
+
+ # Perform any additional rotations:
+ matrix = Matrix.Rotation(radians(90 + self.angle), 3, vectors[2])
+ vectors.append(matrix * -vectors[3]) # vectors[5]
+ matrix = Matrix.Rotation(radians(90 - self.angle), 3, vectors[2])
+ vectors.append(matrix * vectors[4]) # vectors[6]
+ vectors.append(matrix * vectors[3]) # vectors[7]
+ matrix = Matrix.Rotation(radians(90 + self.angle), 3, vectors[2])
+ vectors.append(matrix * -vectors[4]) # vectors[8]
+
+ # Perform extrusions and displacements:
+ # There will be a total of 8 extrusions. One for each vert of each edge.
+ # It looks like an extrusion will add the new vert to the end of the verts
+ # list and leave the rest in the same location.
+ # -- EDIT --
+ # It looks like I might be able to do this within "bpy.data" with the ".add" function
+
+ for v in range(len(verts)):
+ vert = verts[v]
+ if ((v == 0 and self.vert1) or (v == 1 and self.vert2) or
+ (v == 2 and self.vert3) or (v == 3 and self.vert4)):
+
+ if self.pos:
+ new = bVerts.new()
+ new.co = vert.co - vectors[5 + (v // 2) + ((v % 2) * 2)]
+ bVerts.ensure_lookup_table()
+ bEdges.new((vert, new))
+ bEdges.ensure_lookup_table()
+ if self.neg:
+ new = bVerts.new()
+ new.co = vert.co + vectors[5 + (v // 2) + ((v % 2) * 2)]
+ bVerts.ensure_lookup_table()
+ bEdges.new((vert, new))
+ bEdges.ensure_lookup_table()
- bmesh.update_edit_mesh(me)
+ bmesh.update_edit_mesh(me)
+ except Exception as e:
+ error_handlers(self, "mesh.edgetools_ortho", e,
+ reports="Angle Off Edge Operator failed", func=False)
+ return {'CANCELLED'}
return {'FINISHED'}
@@ -1056,49 +1147,49 @@ class Shaft(Operator):
# For tracking if the user has changed selection:
last_edge = IntProperty(
- name="Last Edge",
- description="Tracks if user has changed selected edges",
- min=0, max=1,
- default=0
- )
+ name="Last Edge",
+ description="Tracks if user has changed selected edges",
+ min=0, max=1,
+ default=0
+ )
last_flip = False
edge = IntProperty(
- name="Edge",
- description="Edge to shaft around",
- min=0, max=1,
- default=0
- )
+ name="Edge",
+ description="Edge to shaft around",
+ min=0, max=1,
+ default=0
+ )
flip = BoolProperty(
- name="Flip Second Edge",
- description="Flip the percieved direction of the second edge",
- default=False
- )
+ name="Flip Second Edge",
+ description="Flip the percieved direction of the second edge",
+ default=False
+ )
radius = FloatProperty(
- name="Radius",
- description="Shaft Radius",
- min=0.0, max=1024.0,
- default=1.0
- )
+ name="Radius",
+ description="Shaft Radius",
+ min=0.0, max=1024.0,
+ default=1.0
+ )
start = FloatProperty(
- name="Starting Angle",
- description="Angle to start the shaft at",
- min=-360.0, max=360.0,
- default=0.0
- )
+ name="Starting Angle",
+ description="Angle to start the shaft at",
+ min=-360.0, max=360.0,
+ default=0.0
+ )
finish = FloatProperty(
- name="Ending Angle",
- description="Angle to end the shaft at",
- min=-360.0, max=360.0,
- default=360.0
- )
+ name="Ending Angle",
+ description="Angle to end the shaft at",
+ min=-360.0, max=360.0,
+ default=360.0
+ )
segments = IntProperty(
- name="Shaft Segments",
- description="Number of segments to use in the shaft",
- min=1, max=4096,
- soft_max=512,
- default=32
- )
+ name="Shaft Segments",
+ description="Number of segments to use in the shaft",
+ min=1, max=4096,
+ soft_max=512,
+ default=32
+ )
def draw(self, context):
layout = self.layout
@@ -1126,221 +1217,228 @@ class Shaft(Operator):
return self.execute(context)
def execute(self, context):
- me = context.object.data
- bm = bmesh.from_edit_mesh(me)
- bm.normal_update()
+ try:
+ me = context.object.data
+ bm = bmesh.from_edit_mesh(me)
+ bm.normal_update()
- bFaces = bm.faces
- bEdges = bm.edges
- bVerts = bm.verts
+ bFaces = bm.faces
+ bEdges = bm.edges
+ bVerts = bm.verts
- active = None
- edges, verts = [], []
+ active = None
+ edges, verts = [], []
- # Pre-caclulated values:
- rotRange = [radians(self.start), radians(self.finish)]
- rads = radians((self.finish - self.start) / self.segments)
+ # Pre-caclulated values:
+ rotRange = [radians(self.start), radians(self.finish)]
+ rads = radians((self.finish - self.start) / self.segments)
- numV = self.segments + 1
- numE = self.segments
+ numV = self.segments + 1
+ numE = self.segments
- edges = [e for e in bEdges if e.select]
+ edges = [e for e in bEdges if e.select]
- # Robustness check: there should at least be one edge selected
- if len(edges) < 1:
- bpy.ops.object.editmode_toggle()
- self.report({'ERROR_INVALID_INPUT'},
- "At least one edge must be selected")
- return {'CANCELLED'}
+ # Robustness check: there should at least be one edge selected
+ if not is_selected_enough(self, edges, 0, edges_n=1, verts_n=0, types="Edge"):
+ return {'CANCELLED'}
- # If two edges are selected:
- if len(edges) == 2:
- # default:
- edge = [0, 1]
- vert = [0, 1]
-
- # By default, we want to shaft around the last selected edge (it
- # will be the active edge). We know we are using the default if
- # the user has not changed which edge is being shafted around (as
- # is tracked by self.last_edge). When they are not the same, then
- # the user has changed selection.
- # We then need to make sure that the active object really is an edge
- # (robustness check)
- # Finally, if the active edge is not the inital one, we flip them
- # and have the GUI reflect that
- if self.last_edge == self.edge:
+ # If two edges are selected:
+ if len(edges) == 2:
+ # default:
+ edge = [0, 1]
+ vert = [0, 1]
+
+ # By default, we want to shaft around the last selected edge (it
+ # will be the active edge). We know we are using the default if
+ # the user has not changed which edge is being shafted around (as
+ # is tracked by self.last_edge). When they are not the same, then
+ # the user has changed selection.
+ # We then need to make sure that the active object really is an edge
+ # (robustness check)
+ # Finally, if the active edge is not the inital one, we flip them
+ # and have the GUI reflect that
+ if self.last_edge == self.edge:
+ if isinstance(bm.select_history.active, bmesh.types.BMEdge):
+ if bm.select_history.active != edges[edge[0]]:
+ self.last_edge, self.edge = edge[1], edge[1]
+ edge = [edge[1], edge[0]]
+ else:
+ flip_edit_mode()
+ self.report({'WARNING'},
+ "Active geometry is not an edge. Operation Cancelled")
+ return {'CANCELLED'}
+ elif self.edge == 1:
+ edge = [1, 0]
+
+ verts.append(edges[edge[0]].verts[0])
+ verts.append(edges[edge[0]].verts[1])
+
+ if self.flip:
+ verts = [1, 0]
+
+ verts.append(edges[edge[1]].verts[vert[0]])
+ verts.append(edges[edge[1]].verts[vert[1]])
+
+ self.shaftType = 0
+ # If there is more than one edge selected:
+ # There are some issues with it ATM, so don't expose is it to normal users
+ # @todo Fix edge connection ordering issue
+ elif ENABLE_DEBUG and len(edges) > 2:
if isinstance(bm.select_history.active, bmesh.types.BMEdge):
- if bm.select_history.active != edges[edge[0]]:
- self.last_edge, self.edge = edge[1], edge[1]
- edge = [edge[1], edge[0]]
+ active = bm.select_history.active
+ edges.remove(active)
+ # Get all the verts:
+ # edges = order_joined_edges(edges[0])
+ verts = []
+ for e in edges:
+ if verts.count(e.verts[0]) == 0:
+ verts.append(e.verts[0])
+ if verts.count(e.verts[1]) == 0:
+ verts.append(e.verts[1])
else:
- bpy.ops.object.editmode_toggle()
- self.report({'ERROR_INVALID_INPUT'},
- "Active geometry is not an edge")
+ flip_edit_mode()
+ self.report({'WARNING'},
+ "Active geometry is not an edge. Operation Cancelled")
return {'CANCELLED'}
- elif self.edge == 1:
- edge = [1, 0]
-
- verts.append(edges[edge[0]].verts[0])
- verts.append(edges[edge[0]].verts[1])
-
- if self.flip:
- verts = [1, 0]
-
- verts.append(edges[edge[1]].verts[vert[0]])
- verts.append(edges[edge[1]].verts[vert[1]])
-
- self.shaftType = 0
- # If there is more than one edge selected:
- # There are some issues with it ATM, so don't expose is it to normal users
- # @todo Fix edge connection ordering issue
- elif ENABLE_DEBUG and len(edges) > 2:
- if isinstance(bm.select_history.active, bmesh.types.BMEdge):
- active = bm.select_history.active
- edges.remove(active)
- # Get all the verts:
- # edges = order_joined_edges(edges[0])
- verts = []
- for e in edges:
- if verts.count(e.verts[0]) == 0:
- verts.append(e.verts[0])
- if verts.count(e.verts[1]) == 0:
- verts.append(e.verts[1])
+ self.shaftType = 1
else:
- bpy.ops.object.editmode_toggle()
- self.report({'ERROR_INVALID_INPUT'},
- "Active geometry is not an edge")
- return {'CANCELLED'}
- self.shaftType = 1
- else:
- verts.append(edges[0].verts[0])
- verts.append(edges[0].verts[1])
-
- for v in bVerts:
- if v.select and verts.count(v) == 0:
- verts.append(v)
- v.select = False
- if len(verts) == 2:
- self.shaftType = 3
- else:
- self.shaftType = 2
-
- # The vector denoting the axis of rotation:
- if self.shaftType == 1:
- axis = active.verts[1].co - active.verts[0].co
- else:
- axis = verts[1].co - verts[0].co
+ verts.append(edges[0].verts[0])
+ verts.append(edges[0].verts[1])
- # We will need a series of rotation matrices. We could use one which
- # would be faster but also might cause propagation of error
- # matrices = []
- # for i in range(numV):
- # matrices.append(Matrix.Rotation((rads * i) + rotRange[0], 3, axis))
- matrices = [Matrix.Rotation((rads * i) + rotRange[0], 3, axis) for i in range(numV)]
-
- # New vertice coordinates:
- verts_out = []
+ for v in bVerts:
+ if v.select and verts.count(v) == 0:
+ verts.append(v)
+ v.select = False
+ if len(verts) == 2:
+ self.shaftType = 3
+ else:
+ self.shaftType = 2
- # If two edges were selected:
- # - If the lines are not parallel, then it will create a cone-like shaft
- if self.shaftType == 0:
- for i in range(len(verts) - 2):
- init_vec = distance_point_line(verts[i + 2].co, verts[0].co, verts[1].co)
- co = init_vec + verts[i + 2].co
- # These will be rotated about the orgin so will need to be shifted:
- for j in range(numV):
- verts_out.append(co - (matrices[j] * init_vec))
- elif self.shaftType == 1:
- for i in verts:
- init_vec = distance_point_line(i.co, active.verts[0].co, active.verts[1].co)
- co = init_vec + i.co
+ # The vector denoting the axis of rotation:
+ if self.shaftType == 1:
+ axis = active.verts[1].co - active.verts[0].co
+ else:
+ axis = verts[1].co - verts[0].co
+
+ # We will need a series of rotation matrices. We could use one which
+ # would be faster but also might cause propagation of error
+ # matrices = []
+ # for i in range(numV):
+ # matrices.append(Matrix.Rotation((rads * i) + rotRange[0], 3, axis))
+ matrices = [Matrix.Rotation((rads * i) + rotRange[0], 3, axis) for i in range(numV)]
+
+ # New vertice coordinates:
+ verts_out = []
+
+ # If two edges were selected:
+ # - If the lines are not parallel, then it will create a cone-like shaft
+ if self.shaftType == 0:
+ for i in range(len(verts) - 2):
+ init_vec = distance_point_line(verts[i + 2].co, verts[0].co, verts[1].co)
+ co = init_vec + verts[i + 2].co
+ # These will be rotated about the orgin so will need to be shifted:
+ for j in range(numV):
+ verts_out.append(co - (matrices[j] * init_vec))
+ elif self.shaftType == 1:
+ for i in verts:
+ init_vec = distance_point_line(i.co, active.verts[0].co, active.verts[1].co)
+ co = init_vec + i.co
+ # These will be rotated about the orgin so will need to be shifted:
+ for j in range(numV):
+ verts_out.append(co - (matrices[j] * init_vec))
+ # Else if a line and a point was selected:
+ elif self.shaftType == 2:
+ init_vec = distance_point_line(verts[2].co, verts[0].co, verts[1].co)
# These will be rotated about the orgin so will need to be shifted:
- for j in range(numV):
- verts_out.append(co - (matrices[j] * init_vec))
- # Else if a line and a point was selected:
- elif self.shaftType == 2:
- init_vec = distance_point_line(verts[2].co, verts[0].co, verts[1].co)
- # These will be rotated about the orgin so will need to be shifted:
- verts_out = [(verts[i].co - (matrices[j] * init_vec)) for i in range(2) for j in range(numV)]
- else:
- # Else the above are not possible, so we will just use the edge:
- # - The vector defined by the edge is the normal of the plane for the shaft
- # - The shaft will have radius "radius"
- if is_axial(verts[0].co, verts[1].co) is None:
- proj = (verts[1].co - verts[0].co)
- proj[2] = 0
- norm = proj.cross(verts[1].co - verts[0].co)
- vec = norm.cross(verts[1].co - verts[0].co)
- vec.length = self.radius
- elif is_axial(verts[0].co, verts[1].co) == 'Z':
- vec = verts[0].co + Vector((0, 0, self.radius))
+ verts_out = [
+ (verts[i].co - (matrices[j] * init_vec)) for i in range(2) for j in range(numV)
+ ]
else:
- vec = verts[0].co + Vector((0, self.radius, 0))
- init_vec = distance_point_line(vec, verts[0].co, verts[1].co)
- # These will be rotated about the orgin so will need to be shifted:
- verts_out = [(verts[i].co - (matrices[j] * init_vec)) for i in range(2) for j in range(numV)]
+ # Else the above are not possible, so we will just use the edge:
+ # - The vector defined by the edge is the normal of the plane for the shaft
+ # - The shaft will have radius "radius"
+ if is_axial(verts[0].co, verts[1].co) is None:
+ proj = (verts[1].co - verts[0].co)
+ proj[2] = 0
+ norm = proj.cross(verts[1].co - verts[0].co)
+ vec = norm.cross(verts[1].co - verts[0].co)
+ vec.length = self.radius
+ elif is_axial(verts[0].co, verts[1].co) == 'Z':
+ vec = verts[0].co + Vector((0, 0, self.radius))
+ else:
+ vec = verts[0].co + Vector((0, self.radius, 0))
+ init_vec = distance_point_line(vec, verts[0].co, verts[1].co)
+ # These will be rotated about the orgin so will need to be shifted:
+ verts_out = [
+ (verts[i].co - (matrices[j] * init_vec)) for i in range(2) for j in range(numV)
+ ]
- # We should have the coordinates for a bunch of new verts
- # Now add the verts and build the edges and then the faces
+ # We should have the coordinates for a bunch of new verts
+ # Now add the verts and build the edges and then the faces
- newVerts = []
+ newVerts = []
- if self.shaftType == 1:
- # Vertices:
- for i in range(numV * len(verts)):
- new = bVerts.new()
- new.co = verts_out[i]
- bVerts.ensure_lookup_table()
- new.select = True
- newVerts.append(new)
- # Edges:
- for i in range(numE):
- for j in range(len(verts)):
- e = bEdges.new((newVerts[i + (numV * j)], newVerts[i + (numV * j) + 1]))
+ if self.shaftType == 1:
+ # Vertices:
+ for i in range(numV * len(verts)):
+ new = bVerts.new()
+ new.co = verts_out[i]
+ bVerts.ensure_lookup_table()
+ new.select = True
+ newVerts.append(new)
+ # Edges:
+ for i in range(numE):
+ for j in range(len(verts)):
+ e = bEdges.new((newVerts[i + (numV * j)], newVerts[i + (numV * j) + 1]))
+ bEdges.ensure_lookup_table()
+ e.select = True
+ for i in range(numV):
+ for j in range(len(verts) - 1):
+ e = bEdges.new((newVerts[i + (numV * j)], newVerts[i + (numV * (j + 1))]))
+ bEdges.ensure_lookup_table()
+ e.select = True
+
+ # Faces: There is a problem with this right now
+ """
+ for i in range(len(edges)):
+ for j in range(numE):
+ f = bFaces.new((newVerts[i], newVerts[i + 1],
+ newVerts[i + (numV * j) + 1], newVerts[i + (numV * j)]))
+ f.normal_update()
+ """
+ else:
+ # Vertices:
+ for i in range(numV * 2):
+ new = bVerts.new()
+ new.co = verts_out[i]
+ new.select = True
+ bVerts.ensure_lookup_table()
+ newVerts.append(new)
+ # Edges:
+ for i in range(numE):
+ e = bEdges.new((newVerts[i], newVerts[i + 1]))
+ e.select = True
bEdges.ensure_lookup_table()
+ e = bEdges.new((newVerts[i + numV], newVerts[i + numV + 1]))
e.select = True
- for i in range(numV):
- for j in range(len(verts) - 1):
- e = bEdges.new((newVerts[i + (numV * j)], newVerts[i + (numV * (j + 1))]))
bEdges.ensure_lookup_table()
+ for i in range(numV):
+ e = bEdges.new((newVerts[i], newVerts[i + numV]))
e.select = True
-
- # Faces: There is a problem with this right now
- """
- for i in range(len(edges)):
- for j in range(numE):
+ bEdges.ensure_lookup_table()
+ # Faces:
+ for i in range(numE):
f = bFaces.new((newVerts[i], newVerts[i + 1],
- newVerts[i + (numV * j) + 1], newVerts[i + (numV * j)]))
+ newVerts[i + numV + 1], newVerts[i + numV]))
+ bFaces.ensure_lookup_table()
f.normal_update()
- """
- else:
- # Vertices:
- for i in range(numV * 2):
- new = bVerts.new()
- new.co = verts_out[i]
- new.select = True
- bVerts.ensure_lookup_table()
- newVerts.append(new)
- # Edges:
- for i in range(numE):
- e = bEdges.new((newVerts[i], newVerts[i + 1]))
- e.select = True
- bEdges.ensure_lookup_table()
- e = bEdges.new((newVerts[i + numV], newVerts[i + numV + 1]))
- e.select = True
- bEdges.ensure_lookup_table()
- for i in range(numV):
- e = bEdges.new((newVerts[i], newVerts[i + numV]))
- e.select = True
- bEdges.ensure_lookup_table()
- # Faces:
- for i in range(numE):
- f = bFaces.new((newVerts[i], newVerts[i + 1],
- newVerts[i + numV + 1], newVerts[i + numV]))
- bFaces.ensure_lookup_table()
- f.normal_update()
- bmesh.update_edit_mesh(me)
+ bmesh.update_edit_mesh(me)
+
+ except Exception as e:
+ error_handlers(self, "mesh.edgetools_shaft", e,
+ reports="Shaft Operator failed", func=False)
+ return {'CANCELLED'}
return {'FINISHED'}
@@ -1354,25 +1452,25 @@ class Slice(Operator):
bl_options = {'REGISTER', 'UNDO'}
make_copy = BoolProperty(
- name="Make Copy",
- description="Make new vertices at intersection points instead of spliting the edge",
- default=False
- )
+ name="Make Copy",
+ description="Make new vertices at intersection points instead of spliting the edge",
+ default=False
+ )
rip = BoolProperty(
- name="Rip",
- description="Split into two edges that DO NOT share an intersection vertex",
- default=True
- )
+ name="Rip",
+ description="Split into two edges that DO NOT share an intersection vertex",
+ default=True
+ )
pos = BoolProperty(
- name="Positive",
- description="Remove the portion on the side of the face normal",
- default=False
- )
+ name="Positive",
+ description="Remove the portion on the side of the face normal",
+ default=False
+ )
neg = BoolProperty(
- name="Negative",
- description="Remove the portion on the side opposite of the face normal",
- default=False
- )
+ name="Negative",
+ description="Remove the portion on the side opposite of the face normal",
+ default=False
+ )
def draw(self, context):
layout = self.layout
@@ -1393,136 +1491,142 @@ class Slice(Operator):
return self.execute(context)
def execute(self, context):
- me = context.object.data
- bm = bmesh.from_edit_mesh(me)
- bm.normal_update()
-
- bVerts = bm.verts
- bEdges = bm.edges
- bFaces = bm.faces
-
- face, normal = None, None
-
- # Find the selected face. This will provide the plane to project onto:
- # - First check to use the active face. Allows users to just
- # select a bunch of faces with the last being the cutting plane
- # - If that fails, then use the first found selected face in the BMesh face list
- if isinstance(bm.select_history.active, bmesh.types.BMFace):
- face = bm.select_history.active
- normal = bm.select_history.active.normal
- bm.select_history.active.select = False
- else:
- for f in bFaces:
- if f.select:
- face = f
- normal = f.normal
- f.select = False
- break
-
- # If we don't find a selected face exit:
- if face is None:
- bpy.ops.object.editmode_toggle()
- self.report({'ERROR_INVALID_INPUT'},
- "You must select a face as the cutting plane")
- return {'CANCELLED'}
-
- # Warn the user if they are using an n-gon might lead to some odd results
- elif len(face.verts) > 4 and not is_face_planar(face):
- self.report({'WARNING'},
- "Selected face is an N-gon. Results may be unpredictable")
+ try:
+ me = context.object.data
+ bm = bmesh.from_edit_mesh(me)
+ bm.normal_update()
+
+ bVerts = bm.verts
+ bEdges = bm.edges
+ bFaces = bm.faces
+
+ face, normal = None, None
+
+ # Find the selected face. This will provide the plane to project onto:
+ # - First check to use the active face. Allows users to just
+ # select a bunch of faces with the last being the cutting plane
+ # - If that fails, then use the first found selected face in the BMesh face list
+ if isinstance(bm.select_history.active, bmesh.types.BMFace):
+ face = bm.select_history.active
+ normal = bm.select_history.active.normal
+ bm.select_history.active.select = False
+ else:
+ for f in bFaces:
+ if f.select:
+ face = f
+ normal = f.normal
+ f.select = False
+ break
+
+ # If we don't find a selected face exit:
+ if face is None:
+ flip_edit_mode()
+ self.report({'WARNING'},
+ "Please select a face as the cutting plane. Operation Cancelled")
+ return {'CANCELLED'}
- if ENABLE_DEBUG:
- dbg = 0
- print("Number of Edges: ", len(bEdges))
+ # Warn the user if they are using an n-gon might lead to some odd results
+ elif len(face.verts) > 4 and not is_face_planar(face):
+ self.report({'WARNING'},
+ "Selected face is an N-gon. Results may be unpredictable")
- for e in bEdges:
if ENABLE_DEBUG:
- print("Looping through Edges - ", dbg)
- dbg = dbg + 1
-
- # Get the end verts on the edge:
- v1 = e.verts[0]
- v2 = e.verts[1]
-
- # Make sure that verts are not a part of the cutting plane:
- if e.select and (v1 not in face.verts and v2 not in face.verts):
- if len(face.verts) < 5: # Not an n-gon
- intersection = intersect_line_face(e, face, True)
- else:
- intersection = intersect_line_plane(v1.co, v2.co, face.verts[0].co, normal)
+ dbg = 0
+ print("Number of Edges: ", len(bEdges))
+ for e in bEdges:
if ENABLE_DEBUG:
- print("Intersection: ", intersection)
+ print("Looping through Edges - ", dbg)
+ dbg = dbg + 1
- # If an intersection exists find the distance of each of the end
- # points from the plane, with "positive" being in the direction
- # of the cutting plane's normal. If the points are on opposite
- # side of the plane, then it intersects and we need to cut it
- if intersection is not None:
- bVerts.ensure_lookup_table()
- bEdges.ensure_lookup_table()
- bFaces.ensure_lookup_table()
-
- d1 = distance_point_to_plane(v1.co, face.verts[0].co, normal)
- d2 = distance_point_to_plane(v2.co, face.verts[0].co, normal)
- # If they have different signs, then the edge crosses the cutting plane:
- if abs(d1 + d2) < abs(d1 - d2):
- # Make the first vertex the positive one:
- if d1 < d2:
- v2, v1 = v1, v2
-
- if self.make_copy:
- new = bVerts.new()
- new.co = intersection
- new.select = True
- bVerts.ensure_lookup_table()
- elif self.rip:
- if ENABLE_DEBUG:
- print("Branch rip engaged")
- newV1 = bVerts.new()
- newV1.co = intersection
- bVerts.ensure_lookup_table()
- if ENABLE_DEBUG:
- print("newV1 created", end='; ')
-
- newV2 = bVerts.new()
- newV2.co = intersection
- bVerts.ensure_lookup_table()
-
- if ENABLE_DEBUG:
- print("newV2 created", end='; ')
-
- newE1 = bEdges.new((v1, newV1))
- newE2 = bEdges.new((v2, newV2))
- bEdges.ensure_lookup_table()
+ # Get the end verts on the edge:
+ v1 = e.verts[0]
+ v2 = e.verts[1]
- if ENABLE_DEBUG:
- print("new edges created", end='; ')
+ # Make sure that verts are not a part of the cutting plane:
+ if e.select and (v1 not in face.verts and v2 not in face.verts):
+ if len(face.verts) < 5: # Not an n-gon
+ intersection = intersect_line_face(e, face, True)
+ else:
+ intersection = intersect_line_plane(v1.co, v2.co, face.verts[0].co, normal)
- if e.is_valid:
- bEdges.remove(e)
+ if ENABLE_DEBUG:
+ print("Intersection: ", intersection)
- bEdges.ensure_lookup_table()
+ # If an intersection exists find the distance of each of the end
+ # points from the plane, with "positive" being in the direction
+ # of the cutting plane's normal. If the points are on opposite
+ # side of the plane, then it intersects and we need to cut it
+ if intersection is not None:
+ bVerts.ensure_lookup_table()
+ bEdges.ensure_lookup_table()
+ bFaces.ensure_lookup_table()
+
+ d1 = distance_point_to_plane(v1.co, face.verts[0].co, normal)
+ d2 = distance_point_to_plane(v2.co, face.verts[0].co, normal)
+ # If they have different signs, then the edge crosses the cutting plane:
+ if abs(d1 + d2) < abs(d1 - d2):
+ # Make the first vertex the positive one:
+ if d1 < d2:
+ v2, v1 = v1, v2
+
+ if self.make_copy:
+ new = bVerts.new()
+ new.co = intersection
+ new.select = True
+ bVerts.ensure_lookup_table()
+ elif self.rip:
+ if ENABLE_DEBUG:
+ print("Branch rip engaged")
+ newV1 = bVerts.new()
+ newV1.co = intersection
+ bVerts.ensure_lookup_table()
+ if ENABLE_DEBUG:
+ print("newV1 created", end='; ')
+
+ newV2 = bVerts.new()
+ newV2.co = intersection
+ bVerts.ensure_lookup_table()
+
+ if ENABLE_DEBUG:
+ print("newV2 created", end='; ')
+
+ newE1 = bEdges.new((v1, newV1))
+ newE2 = bEdges.new((v2, newV2))
+ bEdges.ensure_lookup_table()
+
+ if ENABLE_DEBUG:
+ print("new edges created", end='; ')
+
+ if e.is_valid:
+ bEdges.remove(e)
+
+ bEdges.ensure_lookup_table()
+
+ if ENABLE_DEBUG:
+ print("Old edge removed.\nWe're done with this edge")
+ else:
+ new = list(bmesh.utils.edge_split(e, v1, 0.5))
+ bEdges.ensure_lookup_table()
+ new[1].co = intersection
+ e.select = False
+ new[0].select = False
+ if self.pos:
+ bEdges.remove(new[0])
+ if self.neg:
+ bEdges.remove(e)
+ bEdges.ensure_lookup_table()
- if ENABLE_DEBUG:
- print("Old edge removed.\nWe're done with this edge")
- else:
- new = list(bmesh.utils.edge_split(e, v1, 0.5))
- bEdges.ensure_lookup_table()
- new[1].co = intersection
- e.select = False
- new[0].select = False
- if self.pos:
- bEdges.remove(new[0])
- if self.neg:
- bEdges.remove(e)
- bEdges.ensure_lookup_table()
+ if ENABLE_DEBUG:
+ print("The Edge Loop has exited. Now to update the bmesh")
+ dbg = 0
- if ENABLE_DEBUG:
- print("The Edge Loop has exited. Now to update the bmesh")
- dbg = 0
+ bmesh.update_edit_mesh(me)
- bmesh.update_edit_mesh(me)
+ except Exception as e:
+ error_handlers(self, "mesh.edgetools_slice", e,
+ reports="Slice Operator failed", func=False)
+ return {'CANCELLED'}
return {'FINISHED'}
@@ -1538,10 +1642,10 @@ class Project(Operator):
bl_options = {'REGISTER', 'UNDO'}
make_copy = BoolProperty(
- name="Make Copy",
- description="Make duplicates of the vertices instead of altering them",
- default=False
- )
+ name="Make Copy",
+ description="Make duplicates of the vertices instead of altering them",
+ default=False
+ )
def draw(self, context):
layout = self.layout
@@ -1556,42 +1660,49 @@ class Project(Operator):
return self.execute(context)
def execute(self, context):
- me = context.object.data
- bm = bmesh.from_edit_mesh(me)
- bm.normal_update()
-
- bFaces = bm.faces
- bVerts = bm.verts
-
- fVerts = []
-
- # Find the selected face. This will provide the plane to project onto:
- # @todo Check first for an active face
- for f in bFaces:
- if f.select:
- for v in f.verts:
- fVerts.append(v)
- normal = f.normal
- f.select = False
- break
+ try:
+ me = context.object.data
+ bm = bmesh.from_edit_mesh(me)
+ bm.normal_update()
+
+ bFaces = bm.faces
+ bVerts = bm.verts
+
+ fVerts = []
- for v in bVerts:
- if v.select:
- if v in fVerts:
+ # Find the selected face. This will provide the plane to project onto:
+ # @todo Check first for an active face
+ for f in bFaces:
+ if f.select:
+ for v in f.verts:
+ fVerts.append(v)
+ normal = f.normal
+ f.select = False
+ break
+
+ for v in bVerts:
+ if v.select:
+ if v in fVerts:
+ v.select = False
+ continue
+ d = distance_point_to_plane(v.co, fVerts[0].co, normal)
+ if self.make_copy:
+ temp = v
+ v = bVerts.new()
+ v.co = temp.co
+ bVerts.ensure_lookup_table()
+ vector = normal
+ vector.length = abs(d)
+ v.co = v.co - (vector * sign(d))
v.select = False
- continue
- d = distance_point_to_plane(v.co, fVerts[0].co, normal)
- if self.make_copy:
- temp = v
- v = bVerts.new()
- v.co = temp.co
- bVerts.ensure_lookup_table()
- vector = normal
- vector.length = abs(d)
- v.co = v.co - (vector * sign(d))
- v.select = False
- bmesh.update_edit_mesh(me)
+ bmesh.update_edit_mesh(me)
+
+ except Exception as e:
+ error_handlers(self, "mesh.edgetools_project", e,
+ reports="Project Operator failed", func=False)
+
+ return {'CANCELLED'}
return {'FINISHED'}
@@ -1609,25 +1720,25 @@ class Project_End(Operator):
bl_options = {'REGISTER', 'UNDO'}
make_copy = BoolProperty(
- name="Make Copy",
- description="Make a duplicate of the vertice instead of moving it",
- default=False
- )
+ name="Make Copy",
+ description="Make a duplicate of the vertice instead of moving it",
+ default=False
+ )
keep_length = BoolProperty(
- name="Keep Edge Length",
- description="Maintain edge lengths",
- default=False
- )
+ name="Keep Edge Length",
+ description="Maintain edge lengths",
+ default=False
+ )
use_force = BoolProperty(
- name="Use opposite vertices",
- description="Force the usage of the vertices at the other end of the edge",
- default=False
- )
+ name="Use opposite vertices",
+ description="Force the usage of the vertices at the other end of the edge",
+ default=False
+ )
use_normal = BoolProperty(
- name="Project along normal",
- description="Use the plane's normal as the projection direction",
- default=False
- )
+ name="Project along normal",
+ description="Use the plane's normal as the projection direction",
+ default=False
+ )
def draw(self, context):
layout = self.layout
@@ -1646,134 +1757,77 @@ class Project_End(Operator):
return self.execute(context)
def execute(self, context):
- me = context.object.data
- bm = bmesh.from_edit_mesh(me)
- bm.normal_update()
-
- bFaces = bm.faces
- bEdges = bm.edges
- bVerts = bm.verts
-
- fVerts = []
-
- # Find the selected face. This will provide the plane to project onto:
- for f in bFaces:
- if f.select:
- for v in f.verts:
- fVerts.append(v)
- normal = f.normal
- f.select = False
- break
-
- for e in bEdges:
- if e.select:
- v1 = e.verts[0]
- v2 = e.verts[1]
- if v1 in fVerts or v2 in fVerts:
- e.select = False
- continue
- intersection = intersect_line_plane(v1.co, v2.co, fVerts[0].co, normal)
- if intersection is not None:
- # Use abs because we don't care what side of plane we're on:
- d1 = distance_point_to_plane(v1.co, fVerts[0].co, normal)
- d2 = distance_point_to_plane(v2.co, fVerts[0].co, normal)
- # If d1 is closer than we use v1 as our vertice:
- # "xor" with 'use_force':
- if (abs(d1) < abs(d2)) is not self.use_force:
- if self.make_copy:
- v1 = bVerts.new()
- v1.co = e.verts[0].co
- bVerts.ensure_lookup_table()
- bEdges.ensure_lookup_table()
- if self.keep_length:
- v1.co = intersection
- elif self.use_normal:
- vector = normal
- vector.length = abs(d1)
- v1.co = v1.co - (vector * sign(d1))
- else:
- v1.co = intersection
- else:
- if self.make_copy:
- v2 = bVerts.new()
- v2.co = e.verts[1].co
- bVerts.ensure_lookup_table()
- bEdges.ensure_lookup_table()
- if self.keep_length:
- v2.co = intersection
- elif self.use_normal:
- vector = normal
- vector.length = abs(d2)
- v2.co = v2.co - (vector * sign(d2))
- else:
- v2.co = intersection
- e.select = False
-
- bmesh.update_edit_mesh(me)
-
- return {'FINISHED'}
-
+ try:
+ me = context.object.data
+ bm = bmesh.from_edit_mesh(me)
+ bm.normal_update()
-# For testing the mess that is "intersect_line_face" for possible math errors
-# This will NOT be directly exposed to end users: it will always require
-# switching the ENABLE_DEBUG global to True
-# So far no errors have been found. Thanks to anyone who tests and reports bugs!
-# @todo: this is will be removed before final merge
+ bFaces = bm.faces
+ bEdges = bm.edges
+ bVerts = bm.verts
-class Intersect_Line_Face(Operator):
- bl_idname = "mesh.edgetools_ilf"
- bl_label = "Interesect Line Face Test"
- bl_description = "TEST ONLY: Intersect line face"
- bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}
+ fVerts = []
- @classmethod
- def poll(cls, context):
- ob = context.active_object
- return (ob and ob.type == 'MESH' and context.mode == 'EDIT_MESH')
-
- def invoke(self, context, event):
- return self.execute(context)
-
- def execute(self, context):
- # Switch to true to have access to this operator:
- if not ENABLE_DEBUG:
- self.report({'ERROR_INVALID_INPUT'},
- "This is for debugging only: you should not be able to run this!")
- return {'CANCELLED'}
-
- me = context.object.data
- bm = bmesh.from_edit_mesh(me)
- bm.normal_update()
-
- bFaces = bm.faces
- bEdges = bm.edges
- bVerts = bm.verts
-
- face = None
- for f in bFaces:
- if f.select:
- face = f
- break
+ # Find the selected face. This will provide the plane to project onto:
+ for f in bFaces:
+ if f.select:
+ for v in f.verts:
+ fVerts.append(v)
+ normal = f.normal
+ f.select = False
+ break
- edge = None
- for e in bEdges:
- if e.select and e not in face.edges:
- edge = e
- break
+ for e in bEdges:
+ if e.select:
+ v1 = e.verts[0]
+ v2 = e.verts[1]
+ if v1 in fVerts or v2 in fVerts:
+ e.select = False
+ continue
+ intersection = intersect_line_plane(v1.co, v2.co, fVerts[0].co, normal)
+ if intersection is not None:
+ # Use abs because we don't care what side of plane we're on:
+ d1 = distance_point_to_plane(v1.co, fVerts[0].co, normal)
+ d2 = distance_point_to_plane(v2.co, fVerts[0].co, normal)
+ # If d1 is closer than we use v1 as our vertice:
+ # "xor" with 'use_force':
+ if (abs(d1) < abs(d2)) is not self.use_force:
+ if self.make_copy:
+ v1 = bVerts.new()
+ v1.co = e.verts[0].co
+ bVerts.ensure_lookup_table()
+ bEdges.ensure_lookup_table()
+ if self.keep_length:
+ v1.co = intersection
+ elif self.use_normal:
+ vector = normal
+ vector.length = abs(d1)
+ v1.co = v1.co - (vector * sign(d1))
+ else:
+ v1.co = intersection
+ else:
+ if self.make_copy:
+ v2 = bVerts.new()
+ v2.co = e.verts[1].co
+ bVerts.ensure_lookup_table()
+ bEdges.ensure_lookup_table()
+ if self.keep_length:
+ v2.co = intersection
+ elif self.use_normal:
+ vector = normal
+ vector.length = abs(d2)
+ v2.co = v2.co - (vector * sign(d2))
+ else:
+ v2.co = intersection
+ e.select = False
- point = intersect_line_face(edge, face, True)
+ bmesh.update_edit_mesh(me)
- if point is not None:
- new = bVerts.new()
- new.co = point
- bVerts.ensure_lookup_table()
- else:
- bpy.ops.object.editmode_toggle()
- self.report({'ERROR_INVALID_INPUT'}, "point was \"None\"")
+ except Exception as e:
+ error_handlers(self, "mesh.edgetools_project_end", e,
+ reports="Project (End Point) Operator failed", func=False)
return {'CANCELLED'}
- bmesh.update_edit_mesh(me)
-
return {'FINISHED'}
@@ -1793,14 +1847,10 @@ class VIEW3D_MT_edit_mesh_edgetools(Menu):
layout.operator("mesh.edgetools_project")
layout.operator("mesh.edgetools_project_end")
- if ENABLE_DEBUG:
- # For internal testing ONLY:
- layout.separator()
- layout.operator("mesh.edgetools_ilf")
# define classes for registration
-classes = [
+classes = (
VIEW3D_MT_edit_mesh_edgetools,
Extend,
Spline,
@@ -1809,20 +1859,19 @@ classes = [
Slice,
Project,
Project_End,
- Intersect_Line_Face
- ]
+ )
# registering and menu integration
def register():
- for c in classes:
- bpy.utils.register_class(c)
+ for cls in classes:
+ bpy.utils.register_class(cls)
# unregistering and removing menus
def unregister():
- for c in classes:
- bpy.utils.unregister_class(c)
+ for cls in classes:
+ bpy.utils.unregister_class(cls)
if __name__ == "__main__":