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:
authormeta-androcto <meta.androcto1@gmail.com>2019-06-15 07:06:26 +0300
committermeta-androcto <meta.androcto1@gmail.com>2019-06-15 07:06:26 +0300
commit682d48cefcc35a6784d23c59b5d3333ffb1415c4 (patch)
tree0c791215b66b7a45484b995d7563a6af759a8bb0 /mesh_tools/mesh_edge_roundifier.py
parentc7b5ffcfd924a358b37aa16b2bb576b6244fe1d3 (diff)
mesh_tools: restore to release: T63750 9e99e90f08c9
Diffstat (limited to 'mesh_tools/mesh_edge_roundifier.py')
-rw-r--r--mesh_tools/mesh_edge_roundifier.py1397
1 files changed, 1397 insertions, 0 deletions
diff --git a/mesh_tools/mesh_edge_roundifier.py b/mesh_tools/mesh_edge_roundifier.py
new file mode 100644
index 00000000..704a260d
--- /dev/null
+++ b/mesh_tools/mesh_edge_roundifier.py
@@ -0,0 +1,1397 @@
+# ##### 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 2
+# 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, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+bl_info = {
+ "name": "Edge Roundifier",
+ "category": "Mesh",
+ "author": "Piotr Komisarczyk (komi3D), PKHG",
+ "version": (1, 0, 2),
+ "blender": (2, 80, 0),
+ "location": "SPACE > Edge Roundifier or CTRL-E > "
+ "Edge Roundifier or Tools > Addons > Edge Roundifier",
+ "description": "Mesh editing script allowing edge rounding",
+ "wiki_url": "",
+ "category": "Mesh"
+}
+
+import bpy
+import bmesh
+from bpy.types import Operator
+from bpy.props import (
+ BoolProperty,
+ FloatProperty,
+ EnumProperty,
+ IntProperty,
+ )
+from math import (
+ sqrt, acos, pi,
+ radians, degrees, sin,
+ )
+from mathutils import (
+ Vector, Euler,
+ Quaternion,
+ )
+
+# CONSTANTS
+two_pi = 2 * pi
+XY = "XY"
+XZ = "XZ"
+YZ = "YZ"
+SPIN_END_THRESHOLD = 0.001
+LINE_TOLERANCE = 0.0001
+d_XABS_YABS = False
+d_Edge_Info = False
+d_Plane = False
+d_Radius_Angle = False
+d_Roots = False
+d_RefObject = False
+d_LineAB = False
+d_Selected_edges = False
+d_Rotate_Around_Spin_Center = False
+
+# Enable debug prints
+DEBUG = False
+
+
+# for debugging PKHG #
+def debugPrintNew(debugs, *text):
+ if DEBUG and debugs:
+ tmp = [el for el in text]
+ for row in tmp:
+ print(row)
+
+
+# Geometry and math calculation methods #
+
+class CalculationHelper:
+
+ def __init__(self):
+ """
+ Constructor
+ """
+ def getLineCoefficientsPerpendicularToVectorInPoint(self, point, vector, plane):
+ x, y, z = point
+ xVector, yVector, zVector = vector
+ destinationPoint = (x + yVector, y - xVector, z)
+ if plane == 'YZ':
+ destinationPoint = (x, y + zVector, z - yVector)
+ if plane == 'XZ':
+ destinationPoint = (x + zVector, y, z - xVector)
+ return self.getCoefficientsForLineThrough2Points(point, destinationPoint, plane)
+
+ def getQuadraticRoots(self, coef):
+ if len(coef) != 3:
+ return None # Replaced NaN with None
+ else:
+ a, b, c = coef
+ delta = b ** 2 - 4 * a * c
+ if delta == 0:
+ x = -b / (2 * a)
+ return (x, x)
+ elif delta < 0:
+ return None
+ else:
+ x1 = (-b - sqrt(delta)) / (2 * a)
+ x2 = (-b + sqrt(delta)) / (2 * a)
+ return (x1, x2)
+
+ def getCoefficientsForLineThrough2Points(self, point1, point2, plane):
+ x1, y1, z1 = point1
+ x2, y2, z2 = point2
+
+ # mapping x1,x2, y1,y2 to proper values based on plane
+ if plane == YZ:
+ x1 = y1
+ x2 = y2
+ y1 = z1
+ y2 = z2
+ if plane == XZ:
+ y1 = z1
+ y2 = z2
+
+ # Further calculations the same as for XY plane
+ xabs = abs(x2 - x1)
+ yabs = abs(y2 - y1)
+ debugPrintNew(d_XABS_YABS, "XABS = " + str(xabs) + " YABS = " + str(yabs))
+
+ if xabs <= LINE_TOLERANCE:
+ return None # this means line x = edgeCenterX
+ if yabs <= LINE_TOLERANCE:
+ A = 0
+ B = y1
+ return A, B
+ A = (y2 - y1) / (x2 - x1)
+ B = y1 - (A * x1)
+ return (A, B)
+
+ def getLineCircleIntersections(self, lineAB, circleMidPoint, radius):
+ # (x - a)**2 + (y - b)**2 = r**2 - circle equation
+ # y = A*x + B - line equation
+ # f * x**2 + g * x + h = 0 - quadratic equation
+ A, B = lineAB
+ a, b = circleMidPoint
+ f = 1 + (A ** 2)
+ g = -2 * a + 2 * A * B - 2 * A * b
+ h = (B ** 2) - 2 * b * B - (radius ** 2) + (a ** 2) + (b ** 2)
+ coef = [f, g, h]
+ roots = self.getQuadraticRoots(coef)
+ if roots is not None:
+ x1 = roots[0]
+ x2 = roots[1]
+ point1 = [x1, A * x1 + B]
+ point2 = [x2, A * x2 + B]
+ return [point1, point2]
+ else:
+ return None
+
+ def getLineCircleIntersectionsWhenXPerpendicular(self, edgeCenter,
+ circleMidPoint, radius, plane):
+ # (x - a)**2 + (y - b)**2 = r**2 - circle equation
+ # x = xValue - line equation
+ # f * x**2 + g * x + h = 0 - quadratic equation
+ xValue = edgeCenter[0]
+ if plane == YZ:
+ xValue = edgeCenter[1]
+ if plane == XZ:
+ xValue = edgeCenter[0]
+
+ a, b = circleMidPoint
+ f = 1
+ g = -2 * b
+ h = (a ** 2) + (b ** 2) + (xValue ** 2) - 2 * a * xValue - (radius ** 2)
+ coef = [f, g, h]
+ roots = self.getQuadraticRoots(coef)
+ if roots is not None:
+ y1 = roots[0]
+ y2 = roots[1]
+ point1 = [xValue, y1]
+ point2 = [xValue, y2]
+ return [point1, point2]
+ else:
+ return None
+
+ # point1 is the point near 90 deg angle
+ def getAngle(self, point1, point2, point3):
+ distance1 = (Vector(point1) - Vector(point2)).length
+ distance2 = (Vector(point2) - Vector(point3)).length
+ cos = distance1 / distance2
+
+ if abs(cos) > 1: # prevents Domain Error
+ cos = round(cos)
+
+ alpha = acos(cos)
+ return (alpha, degrees(alpha))
+
+ # get two of three coordinates used for further calculation of spin center
+ # PKHG>nice if rescriction to these 3 types or planes is to be done
+ # komi3D> from 0.0.2 there is a restriction. In future I would like Edge
+ # komi3D> Roundifier to work on Normal and View coordinate systems
+ def getCircleMidPointOnPlane(self, V1, plane):
+ X = V1[0]
+ Y = V1[1]
+ if plane == 'XZ':
+ X = V1[0]
+ Y = V1[2]
+ elif plane == 'YZ':
+ X = V1[1]
+ Y = V1[2]
+ return [X, Y]
+
+ def getEdgeReference(self, edge, edgeCenter, plane):
+ vert1 = edge.verts[1].co
+ V = vert1 - edgeCenter
+ orthoVector = Vector((V[1], -V[0], V[2]))
+ if plane == 'XZ':
+ orthoVector = Vector((V[2], V[1], -V[0]))
+ elif plane == 'YZ':
+ orthoVector = Vector((V[0], V[2], -V[1]))
+ refPoint = edgeCenter + orthoVector
+ return refPoint
+
+
+# Selection Methods #
+
+class SelectionHelper:
+
+ def selectVertexInMesh(self, mesh, vertex):
+ bpy.ops.object.mode_set(mode="OBJECT")
+ for v in mesh.vertices:
+ if v.co == vertex:
+ v.select = True
+ break
+
+ bpy.ops.object.mode_set(mode="EDIT")
+
+ def getSelectedVertex(self, mesh):
+ bpy.ops.object.mode_set(mode="OBJECT")
+ for v in mesh.vertices:
+ if v.select is True:
+ bpy.ops.object.mode_set(mode="EDIT")
+ return v
+
+ bpy.ops.object.mode_set(mode="EDIT")
+ return None
+
+ def refreshMesh(self, bm, mesh):
+ bpy.ops.object.mode_set(mode='OBJECT')
+ bm.to_mesh(mesh)
+ bpy.ops.object.mode_set(mode='EDIT')
+
+
+# Operator
+
+class EdgeRoundifier(Operator):
+ bl_idname = "mesh.edge_roundifier"
+ bl_label = "Edge Roundifier"
+ bl_description = "Mesh modeling tool for building arcs on selected Edges"
+ bl_options = {'REGISTER', 'UNDO', 'PRESET'}
+
+ threshold = 0.0005
+ obj = None
+
+ edgeScaleFactor: FloatProperty(
+ name="",
+ description="Set the Factor of scaling",
+ default=1.0,
+ min=0.00001, max=100000.0,
+ step=0.5,
+ precision=5
+ )
+ r: FloatProperty(
+ name="",
+ description="User Defined arc steepness by a Radius\n"
+ "Enabled only if Entry mode is set to Radius\n",
+ default=1,
+ min=0.00001, max=1000.0,
+ step=0.1,
+ precision=3
+ )
+ a: FloatProperty(
+ name="",
+ description="User defined arc steepness calculated from an Angle\n"
+ "Enabled only if Entry mode is set to Angle and\n"
+ "Angle presets is set Other",
+ default=180.0,
+ min=0.1, max=180.0,
+ step=0.5,
+ precision=1
+ )
+ n: IntProperty(
+ name="",
+ description="Arc subdivision level",
+ default=4,
+ min=1, max=100,
+ step=1
+ )
+ flip: BoolProperty(
+ name="Flip",
+ description="If True, flip the side of the selected edges where the arcs are drawn",
+ default=False
+ )
+ invertAngle: BoolProperty(
+ name="Invert",
+ description="If True, uses an inverted angle to draw the arc (360 degrees - angle)",
+ default=False
+ )
+ fullCircles: BoolProperty(
+ name="Circles",
+ description="If True, uses an angle of 360 degrees to draw the arcs",
+ default=False
+ )
+ bothSides: BoolProperty(
+ name="Both sides",
+ description="If True, draw arcs on both sides of the selected edges",
+ default=False
+ )
+ drawArcCenters: BoolProperty(
+ name="Centers",
+ description="If True, draws a vertex for each spin center",
+ default=False
+ )
+ removeEdges: BoolProperty(
+ name="Edges",
+ description="If True removes the Original selected edges",
+ default=False
+ )
+ removeScaledEdges: BoolProperty(
+ name="Scaled edges",
+ description="If True removes the Scaled edges (not part of the arcs)",
+ default=False
+ )
+ connectArcWithEdge: BoolProperty(
+ name="Arc - Edge",
+ description="Connect Arcs to Edges",
+ default=False
+ )
+ connectArcs: BoolProperty(
+ name="Arcs",
+ description="Connect subsequent Arcs",
+ default=False
+ )
+ connectScaledAndBase: BoolProperty(
+ name="Scaled - Base Edge",
+ description="Connect Scaled to Base Edge",
+ default=False
+ )
+ connectArcsFlip: BoolProperty(
+ name="Flip Arcs",
+ description="Flip the connection of subsequent Arcs",
+ default=False
+ )
+ connectArcWithEdgeFlip: BoolProperty(
+ name="Flip Arc - Edge",
+ description="Flip the connection of the Arcs to Edges",
+ default=False
+ )
+ axisAngle: FloatProperty(
+ name="",
+ description="Rotate Arc around the perpendicular axis",
+ default=0.0,
+ min=-180.0, max=180.0,
+ step=0.5,
+ precision=1
+ )
+ edgeAngle: FloatProperty(
+ name="",
+ description="Rotate Arc around the Edge (Edge acts like as the axis)",
+ default=0.0,
+ min=-180.0, max=180.0,
+ step=0.5,
+ precision=1
+ )
+ offset: FloatProperty(
+ name="",
+ description="Offset Arc perpendicular the Edge",
+ default=0.0,
+ min=-1000000.0, max=1000000.0,
+ step=0.1,
+ precision=5
+ )
+ offset2: FloatProperty(
+ name="",
+ description="Offset Arc in parallel to the Edge",
+ default=0.0,
+ min=-1000000.0, max=1000000.0,
+ step=0.1,
+ precision=5
+ )
+ ellipticFactor: FloatProperty(
+ name="",
+ description="Make Arc elliptic",
+ default=0.0,
+ min=-1000000.0, max=1000000.0,
+ step=0.1,
+ precision=5
+ )
+ workModeItems = [("Normal", "Normal", ""), ("Reset", "Reset", "")]
+ workMode: EnumProperty(
+ items=workModeItems,
+ name="",
+ default='Normal',
+ description="Normal work with the current given parameters set by the user\n"
+ "Reset - changes back the parameters to their default values"
+ )
+ entryModeItems = [("Radius", "Radius", ""), ("Angle", "Angle", "")]
+ entryMode: EnumProperty(
+ items=entryModeItems,
+ name="",
+ default='Angle',
+ description="Entry mode switch between Angle and Radius\n"
+ "If Angle is selected, arc radius is calculated from it"
+ )
+ rotateCenterItems = [
+ ("Spin", "Spin", ""), ("V1", "V1", ""),
+ ("Edge", "Edge", ""), ("V2", "V2", "")
+ ]
+ rotateCenter: EnumProperty(
+ items=rotateCenterItems,
+ name="",
+ default='Edge',
+ description="Rotate center for spin axis rotate"
+ )
+ arcModeItems = [("FullEdgeArc", "Full", "Full"), ('HalfEdgeArc', "Half", "Half")]
+ arcMode: EnumProperty(
+ items=arcModeItems,
+ name="",
+ default='FullEdgeArc',
+ description="Arc mode - switch between Full and Half arcs"
+ )
+ angleItems = [
+ ('Other', "Other", "User defined angle"), ('180', "180", "HemiCircle (2 sides)"),
+ ('120', "120", "TriangleCircle (3 sides)"), ('90', "90", "QuadCircle (4 sides)"),
+ ('72', "72", "PentagonCircle (5 sides)"), ('60', "60", "HexagonCircle (6 sides)"),
+ ('45', "45", "OctagonCircle (8 sides)"), ('30', "30", "DodecagonCircle (12 sides)")
+ ]
+ angleEnum: EnumProperty(
+ items=angleItems,
+ name="",
+ default='180',
+ description="Presets prepare standard angles and calculate proper ray"
+ )
+ refItems = [('ORG', "Origin", "Use Origin Location"), ('CUR', "3D Cursor", "Use 3DCursor Location"),
+ ('EDG', "Edge", "Use Individual Edge Reference")]
+ referenceLocation: EnumProperty(
+ items=refItems,
+ name="",
+ default='ORG',
+ description="Reference location used to calculate initial centers of drawn arcs"
+ )
+ planeItems = [
+ (XY, "XY", "XY Plane (Z=0)"),
+ (YZ, "YZ", "YZ Plane (X=0)"),
+ (XZ, "XZ", "XZ Plane (Y=0)")
+ ]
+ planeEnum: EnumProperty(
+ items=planeItems,
+ name="",
+ default='XY',
+ description="Plane used to calculate spin plane of drawn arcs"
+ )
+ edgeScaleCenterItems = [
+ ('V1', "V1", "v1 - First Edge's Vertex"),
+ ('CENTER', "Center", "Center of the Edge"),
+ ('V2', "V2", "v2 - Second Edge's Vertex")
+ ]
+ edgeScaleCenterEnum: EnumProperty(
+ items=edgeScaleCenterItems,
+ name="Edge scale center",
+ default='CENTER',
+ description="Center used for scaling the initial edge"
+ )
+
+ calc = CalculationHelper()
+ sel = SelectionHelper()
+
+ @classmethod
+ def poll(cls, context):
+ obj = context.active_object
+ return (obj and obj.type == 'MESH' and
+ obj.mode == 'EDIT')
+
+ def prepareMesh(self, context):
+ bpy.ops.object.mode_set(mode='OBJECT')
+ bpy.ops.object.mode_set(mode='EDIT')
+
+ mesh = context.view_layer.objects.active.data
+ bm = bmesh.new()
+ bm.from_mesh(mesh)
+
+ edges = [ele for ele in bm.edges if ele.select]
+ return edges, mesh, bm
+
+ def prepareParameters(self):
+ parameters = {"a": "a"}
+ parameters["arcMode"] = self.arcMode
+ parameters["edgeScaleFactor"] = self.edgeScaleFactor
+ parameters["edgeScaleCenterEnum"] = self.edgeScaleCenterEnum
+ parameters["plane"] = self.planeEnum
+ parameters["radius"] = self.r
+ parameters["angle"] = self.a
+ parameters["segments"] = self.n
+ parameters["fullCircles"] = self.fullCircles
+ parameters["invertAngle"] = self.invertAngle
+ parameters["bothSides"] = self.bothSides
+ parameters["angleEnum"] = self.angleEnum
+ parameters["entryMode"] = self.entryMode
+ parameters["workMode"] = self.workMode
+ parameters["refObject"] = self.referenceLocation
+ parameters["flip"] = self.flip
+ parameters["drawArcCenters"] = self.drawArcCenters
+ parameters["removeEdges"] = self.removeEdges
+ parameters["removeScaledEdges"] = self.removeScaledEdges
+ parameters["connectArcWithEdge"] = self.connectArcWithEdge
+ parameters["connectScaledAndBase"] = self.connectScaledAndBase
+ parameters["connectArcs"] = self.connectArcs
+ parameters["connectArcsFlip"] = self.connectArcsFlip
+ parameters["connectArcWithEdgeFlip"] = self.connectArcWithEdgeFlip
+ parameters["axisAngle"] = self.axisAngle
+ parameters["edgeAngle"] = self.edgeAngle
+ parameters["offset"] = self.offset
+ parameters["offset2"] = self.offset2
+ parameters["ellipticFactor"] = self.ellipticFactor
+ parameters["rotateCenter"] = self.rotateCenter
+ return parameters
+
+ def draw(self, context):
+ layout = self.layout
+ box = layout.box()
+ uiPercentage = 0.333
+
+ self.addEnumParameterToUI(box, False, uiPercentage, 'Mode:', 'workMode')
+ self.addEnumParameterToUI(box, False, uiPercentage, 'Plane:', 'planeEnum')
+ self.addEnumParameterToUI(box, False, uiPercentage, 'Reference:', 'referenceLocation')
+
+ box = layout.box()
+ self.addEnumParameterToUI(box, False, uiPercentage, 'Scale base:', 'edgeScaleCenterEnum')
+ self.addParameterToUI(box, False, uiPercentage, 'Scale factor:', 'edgeScaleFactor')
+
+ box = layout.box()
+ self.addEnumParameterToUI(box, False, uiPercentage, 'Entry mode:', 'entryMode')
+
+ row = box.row(align=False)
+ row.prop(self, 'angleEnum', expand=True, text="Angle presets")
+
+ disable_a = bool(self.entryMode == 'Angle' and self.angleEnum == 'Other')
+ disable_r = bool(self.entryMode == 'Radius')
+
+ self.addParameterToUI(box, False, uiPercentage, 'Angle:', 'a', disable_a)
+ self.addParameterToUI(box, False, uiPercentage, 'Radius:', 'r', disable_r)
+ self.addParameterToUI(box, False, uiPercentage, 'Segments:', 'n')
+
+ box = layout.box()
+ self.addCheckboxToUI(box, True, 'Options:', 'flip', 'invertAngle')
+ self.addCheckboxToUI(box, True, '', 'bothSides', 'fullCircles')
+ self.addCheckboxToUI(box, True, '', 'drawArcCenters')
+
+ box = layout.box()
+ self.addCheckboxToUI(box, True, 'Remove:', 'removeEdges', 'removeScaledEdges')
+
+ box = layout.box()
+ self.addCheckboxToUI(box, True, 'Connect:', 'connectArcs', 'connectArcsFlip')
+ self.addCheckboxToUI(box, True, '', 'connectArcWithEdge', 'connectArcWithEdgeFlip')
+ self.addCheckboxToUI(box, True, '', 'connectScaledAndBase')
+
+ box = layout.box()
+ self.addParameterToUI(box, False, uiPercentage, 'Orhto offset:', 'offset')
+ self.addParameterToUI(box, False, uiPercentage, 'Parallel offset:', 'offset2')
+
+ box = layout.box()
+ self.addParameterToUI(box, False, uiPercentage, 'Edge rotate :', 'edgeAngle')
+ self.addEnumParameterToUI(box, False, uiPercentage, 'Axis rotate center:', 'rotateCenter')
+ self.addParameterToUI(box, False, uiPercentage, 'Axis rotate:', 'axisAngle')
+
+ box = layout.box()
+ self.addParameterToUI(box, False, uiPercentage, 'Elliptic factor:', 'ellipticFactor')
+
+ def addParameterToUI(self, layout, alignment, percent, label, properties, disable=True):
+ row = layout.row(align=alignment)
+ split = row.split(factor=percent)
+ col = split.column()
+
+ col.label(text=label)
+ col2 = split.column()
+ row = col2.row(align=alignment)
+ row.enabled = disable
+ row.prop(self, properties)
+
+ def addCheckboxToUI(self, layout, alignment, label, property1, property2=None):
+ if label not in (""):
+ row = layout.row()
+ row.label(text=label)
+ row2 = layout.row(align=alignment)
+ if property2:
+ split = row2.split(factor=0.5)
+ split.prop(self, property1, toggle=True)
+ split.prop(self, property2, toggle=True)
+ else:
+ row2.prop(self, property1, toggle=True)
+ layout.separator()
+
+ def addEnumParameterToUI(self, layout, alignment, percent, label, properties):
+ row = layout.row(align=alignment)
+ split = row.split(factor=percent)
+ col = split.column()
+
+ col.label(text=label)
+ col2 = split.column()
+ row = col2.row(align=alignment)
+ row.prop(self, properties, expand=True, text="a")
+
+ def execute(self, context):
+
+ edges, mesh, bm = self.prepareMesh(context)
+ parameters = self.prepareParameters()
+
+ self.resetValues(parameters["workMode"])
+
+ self.obj = context.view_layer.objects.active
+ scaledEdges = self.scaleDuplicatedEdges(bm, edges, parameters)
+
+ if len(scaledEdges) > 0:
+ self.roundifyEdges(scaledEdges, parameters, bm, mesh)
+
+ if parameters["connectScaledAndBase"]:
+ self.connectScaledEdgesWithBaseEdge(scaledEdges, edges, bm, mesh)
+
+ self.sel.refreshMesh(bm, mesh)
+ self.selectEdgesAfterRoundifier(context, scaledEdges)
+ else:
+ debugPrintNew(True, "No edges selected!")
+
+ if parameters["removeEdges"]:
+ bmesh.ops.delete(bm, geom=edges, context='EDGES')
+
+ if parameters["removeScaledEdges"] and self.edgeScaleFactor != 1.0:
+ bmesh.ops.delete(bm, geom=scaledEdges, context='EDGES')
+
+ bpy.ops.object.mode_set(mode='OBJECT')
+ bm.to_mesh(mesh)
+ bpy.ops.object.mode_set(mode='EDIT')
+ bpy.ops.mesh.select_all(action='SELECT')
+ bpy.ops.mesh.remove_doubles()
+
+ bm.free()
+
+ return {'FINISHED'}
+
+ def resetValues(self, workMode):
+ if workMode == "Reset":
+ self.setAllParamsToDefaults()
+
+ def setAllParamsToDefaults(self):
+ try:
+ self.edgeScaleFactor = 1.0
+ self.r = 1
+ self.a = 180.0
+ self.n = 4
+ self.flip = False
+ self.invertAngle = False
+ self.fullCircles = False
+ self.bothSides = False
+ self.drawArcCenters = False
+ self.removeEdges = False
+ self.removeScaledEdges = False
+
+ self.connectArcWithEdge = False
+ self.connectArcs = False
+ self.connectScaledAndBase = False
+ self.connectArcsFlip = False
+ self.connectArcWithEdgeFlip = False
+
+ self.axisAngle = 0.0
+ self.edgeAngle = 0.0
+ self.offset = 0.0
+ self.offset2 = 0.0
+ self.ellipticFactor = 0.0
+
+ self.workMode = 'Normal'
+ self.entryMode = 'Angle'
+ self.angleEnum = '180'
+ self.referenceLocation = 'ORG'
+ self.planeEnum = 'XY'
+ self.edgeScaleCenterEnum = 'CENTER'
+ self.rotateCenter = 'Edge'
+
+ self.report({'INFO'}, "The parameters have been reset to default values")
+ except Exception as e:
+ self.report({'WARNING'}, "The parameters could not be reset")
+ debugPrintNew(True, "\n[setAllParamsToDefaults]\n parameter reset error\n" + e)
+
+ def scaleDuplicatedEdges(self, bm, edges, parameters):
+ scaleCenter = parameters["edgeScaleCenterEnum"]
+ factor = parameters["edgeScaleFactor"]
+ # this code is based on Zeffi's answer to my question
+ duplicateEdges = []
+ if factor == 1:
+ duplicateEdges = edges
+ else:
+ for e in edges:
+ v1 = e.verts[0].co
+ v2 = e.verts[1].co
+ origin = None
+ if scaleCenter == 'CENTER':
+ origin = (v1 + v2) * 0.5
+ elif scaleCenter == 'V1':
+ origin = v1
+ elif scaleCenter == 'V2':
+ origin = v2
+
+ bmv1 = bm.verts.new(((v1 - origin) * factor) + origin)
+ bmv2 = bm.verts.new(((v2 - origin) * factor) + origin)
+ bme = bm.edges.new([bmv1, bmv2])
+ duplicateEdges.append(bme)
+ return duplicateEdges
+
+ def roundifyEdges(self, edges, parameters, bm, mesh):
+ arcs = []
+ for e in edges:
+ arcVerts = self.roundify(e, parameters, bm, mesh)
+ arcs.append(arcVerts)
+
+ if parameters["connectArcs"]:
+ self.connectArcsTogether(arcs, bm, mesh, parameters)
+
+ def getNormalizedEdgeVector(self, edge):
+ V1 = edge.verts[0].co
+ V2 = edge.verts[1].co
+ edgeVector = V2 - V1
+ normEdge = edgeVector.normalized()
+ return normEdge
+
+ def getEdgePerpendicularVector(self, edge, plane):
+ normEdge = self.getNormalizedEdgeVector(edge)
+
+ edgePerpendicularVector = Vector((normEdge[1], -normEdge[0], 0))
+ if plane == YZ:
+ edgePerpendicularVector = Vector((0, normEdge[2], -normEdge[1]))
+ if plane == XZ:
+ edgePerpendicularVector = Vector((normEdge[2], 0, -normEdge[0]))
+ return edgePerpendicularVector
+
+ def getEdgeInfo(self, edge):
+ V1 = edge.verts[0].co
+ V2 = edge.verts[1].co
+ edgeVector = V2 - V1
+ edgeLength = edgeVector.length
+ edgeCenter = (V2 + V1) * 0.5
+ return V1, V2, edgeVector, edgeLength, edgeCenter
+
+ def roundify(self, edge, parameters, bm, mesh):
+ V1, V2, edgeVector, edgeLength, edgeCenter = self.getEdgeInfo(edge)
+ if self.skipThisEdge(V1, V2, parameters["plane"]):
+ return
+
+ roundifyParams = None
+ arcVerts = None
+ roundifyParams = self.calculateRoundifyParams(edge, parameters, bm, mesh)
+ if roundifyParams is None:
+ return
+
+ arcVerts = self.spinAndPostprocess(edge, parameters, bm, mesh, edgeCenter, roundifyParams)
+ return arcVerts
+
+ def spinAndPostprocess(self, edge, parameters, bm, mesh, edgeCenter, roundifyParams):
+ spinnedVerts, roundifyParamsUpdated = self.drawSpin(
+ edge, edgeCenter,
+ roundifyParams,
+ parameters, bm, mesh
+ )
+ postProcessedArcVerts = self.arcPostprocessing(
+ edge, parameters, bm, mesh,
+ roundifyParamsUpdated,
+ spinnedVerts, edgeCenter
+ )
+ return postProcessedArcVerts
+
+ def rotateArcAroundEdge(self, bm, mesh, arcVerts, parameters):
+ angle = parameters["edgeAngle"]
+ if angle != 0:
+ self.arc_rotator(arcVerts, angle, parameters)
+
+ # arc_rotator method was created by PKHG, I (komi3D) adjusted it to fit the rest
+ def arc_rotator(self, arcVerts, extra_rotation, parameters):
+ bpy.ops.object.mode_set(mode='OBJECT')
+ old_location = self.obj.location.copy()
+ bpy.ops.transform.translate(
+ value=-old_location,
+ constraint_axis=(False, False, False),
+ orient_type='GLOBAL',
+ mirror=False,
+ use_proportional_edit=False,
+ )
+ bpy.ops.object.mode_set(mode='EDIT')
+ adjust_matrix = self.obj.matrix_parent_inverse
+ bm = bmesh.from_edit_mesh(self.obj.data)
+ lastVert = len(arcVerts) - 1
+ if parameters["drawArcCenters"]:
+ lastVert = lastVert - 1 # center gets added as last vert of arc
+ v0_old = adjust_matrix @ arcVerts[0].co.copy()
+
+ # PKHG>INFO move if necessary v0 to origin such that the axis gos through origin and v1
+ if v0_old != Vector((0, 0, 0)):
+ for i, ele in enumerate(arcVerts):
+ arcVerts[i].co += - v0_old
+
+ axis = arcVerts[0].co - arcVerts[lastVert].co
+ a_mat = Quaternion(axis, radians(extra_rotation)).normalized().to_matrix()
+
+ for ele in arcVerts:
+ ele.co = a_mat @ ele.co
+
+ # PKHG>INFO move back if needed
+ if v0_old != Vector((0, 0, 0)):
+ for i, ele in enumerate(arcVerts):
+ arcVerts[i].co += + v0_old
+
+ bpy.ops.object.mode_set(mode='OBJECT')
+ # PKHG>INFO move origin object back print("old location = " , old_location)
+ bpy.ops.transform.translate(
+ value=old_location,
+ constraint_axis=(False, False, False),
+ orient_type='GLOBAL',
+ mirror=False,
+ use_proportional_edit=False,
+ )
+ bpy.ops.object.mode_set(mode='EDIT')
+
+ def makeElliptic(self, bm, mesh, arcVertices, parameters):
+ if parameters["ellipticFactor"] != 0: # if 0 then nothing has to be done
+ lastVert = len(arcVertices) - 1
+ if parameters["drawArcCenters"]:
+ lastVert = lastVert - 1 # center gets added as last vert of arc
+ v0co = arcVertices[0].co
+ v1co = arcVertices[lastVert].co
+
+ for vertex in arcVertices: # range(len(res_list)):
+ # PKHg>INFO compute the base on the edge of the height-vector
+ top = vertex.co # res_list[nr].co
+ t = 0
+ if v1co - v0co != 0:
+ t = (v1co - v0co).dot(top - v0co) / (v1co - v0co).length ** 2
+ h_bottom = v0co + t * (v1co - v0co)
+ height = (h_bottom - top)
+ vertex.co = top + parameters["ellipticFactor"] * height
+
+ return arcVertices
+
+ def arcPostprocessing(self, edge, parameters, bm, mesh, roundifyParams, spinnedVerts, edgeCenter):
+ [chosenSpinCenter, otherSpinCenter, spinAxis, angle, steps, refObjectLocation] = roundifyParams
+ rotatedVerts = []
+ if parameters["rotateCenter"] == 'Edge':
+ rotatedVerts = self.rotateArcAroundSpinAxis(
+ bm, mesh, spinnedVerts, parameters, edgeCenter
+ )
+ elif parameters["rotateCenter"] == 'Spin':
+ rotatedVerts = self.rotateArcAroundSpinAxis(
+ bm, mesh, spinnedVerts, parameters, chosenSpinCenter
+ )
+ elif parameters["rotateCenter"] == 'V1':
+ rotatedVerts = self.rotateArcAroundSpinAxis(
+ bm, mesh, spinnedVerts, parameters, edge.verts[0].co
+ )
+ elif parameters["rotateCenter"] == 'V2':
+ rotatedVerts = self.rotateArcAroundSpinAxis(
+ bm, mesh, spinnedVerts, parameters, edge.verts[1].co
+ )
+
+ offsetVerts = self.offsetArcPerpendicular(
+ bm, mesh, rotatedVerts, edge, parameters
+ )
+ offsetVerts2 = self.offsetArcParallel(
+ bm, mesh, offsetVerts, edge, parameters
+ )
+ ellipticVerts = self.makeElliptic(
+ bm, mesh, offsetVerts2, parameters
+ )
+ self.rotateArcAroundEdge(bm, mesh, ellipticVerts, parameters)
+
+ if parameters["connectArcWithEdge"]:
+ self.connectArcTogetherWithEdge(
+ edge, offsetVerts2, bm, mesh, parameters
+ )
+ return offsetVerts2
+
+ def connectArcTogetherWithEdge(self, edge, arcVertices, bm, mesh, parameters):
+ lastVert = len(arcVertices) - 1
+ if parameters["drawArcCenters"]:
+ lastVert = lastVert - 1 # center gets added as last vert of arc
+ edgeV1 = edge.verts[0].co
+ edgeV2 = edge.verts[1].co
+ arcV1 = arcVertices[0].co
+ arcV2 = arcVertices[lastVert].co
+
+ bmv1 = bm.verts.new(edgeV1)
+ bmv2 = bm.verts.new(arcV1)
+
+ bmv3 = bm.verts.new(edgeV2)
+ bmv4 = bm.verts.new(arcV2)
+
+ if parameters["connectArcWithEdgeFlip"] is False:
+ bme = bm.edges.new([bmv1, bmv2])
+ bme2 = bm.edges.new([bmv3, bmv4])
+ else:
+ bme = bm.edges.new([bmv1, bmv4])
+ bme2 = bm.edges.new([bmv3, bmv2])
+ self.sel.refreshMesh(bm, mesh)
+
+ def connectScaledEdgesWithBaseEdge(self, scaledEdges, baseEdges, bm, mesh):
+ for i in range(0, len(scaledEdges)):
+ scaledEdgeV1 = scaledEdges[i].verts[0].co
+ baseEdgeV1 = baseEdges[i].verts[0].co
+ scaledEdgeV2 = scaledEdges[i].verts[1].co
+ baseEdgeV2 = baseEdges[i].verts[1].co
+
+ bmv1 = bm.verts.new(baseEdgeV1)
+ bmv2 = bm.verts.new(scaledEdgeV1)
+ bme = bm.edges.new([bmv1, bmv2])
+
+ bmv3 = bm.verts.new(scaledEdgeV2)
+ bmv4 = bm.verts.new(baseEdgeV2)
+ bme = bm.edges.new([bmv3, bmv4])
+ self.sel.refreshMesh(bm, mesh)
+
+ def connectArcsTogether(self, arcs, bm, mesh, parameters):
+ for i in range(0, len(arcs) - 1):
+ # in case on XZ or YZ there are no arcs drawn
+ if arcs[i] is None or arcs[i + 1] is None:
+ return
+
+ lastVert = len(arcs[i]) - 1
+ if parameters["drawArcCenters"]:
+ lastVert = lastVert - 1 # center gets added as last vert of arc
+ # take last vert of arc i and first vert of arc i+1
+
+ V1 = arcs[i][lastVert].co
+ V2 = arcs[i + 1][0].co
+
+ if parameters["connectArcsFlip"]:
+ V1 = arcs[i][0].co
+ V2 = arcs[i + 1][lastVert].co
+
+ bmv1 = bm.verts.new(V1)
+ bmv2 = bm.verts.new(V2)
+ bme = bm.edges.new([bmv1, bmv2])
+
+ # connect last arc and first one
+ lastArcId = len(arcs) - 1
+ lastVertIdOfLastArc = len(arcs[lastArcId]) - 1
+ if parameters["drawArcCenters"]:
+ # center gets added as last vert of arc
+ lastVertIdOfLastArc = lastVertIdOfLastArc - 1
+
+ V1 = arcs[lastArcId][lastVertIdOfLastArc].co
+ V2 = arcs[0][0].co
+ if parameters["connectArcsFlip"]:
+ V1 = arcs[lastArcId][0].co
+ V2 = arcs[0][lastVertIdOfLastArc].co
+
+ bmv1 = bm.verts.new(V1)
+ bmv2 = bm.verts.new(V2)
+ bme = bm.edges.new([bmv1, bmv2])
+
+ self.sel.refreshMesh(bm, mesh)
+
+ def offsetArcPerpendicular(self, bm, mesh, Verts, edge, parameters):
+ perpendicularVector = self.getEdgePerpendicularVector(edge, parameters["plane"])
+ offset = parameters["offset"]
+ translation = offset * perpendicularVector
+
+ try:
+ bmesh.ops.translate(bm, verts=Verts, vec=translation)
+ except ValueError:
+ print("[Edge Roundifier]: Perpendicular translate value error - "
+ "multiple vertices in list - try unchecking 'Centers'")
+
+ indexes = [v.index for v in Verts]
+ self.sel.refreshMesh(bm, mesh)
+ offsetVertices = [bm.verts[i] for i in indexes]
+ return offsetVertices
+
+ def offsetArcParallel(self, bm, mesh, Verts, edge, parameters):
+ edgeVector = self.getNormalizedEdgeVector(edge)
+ offset = parameters["offset2"]
+ translation = offset * edgeVector
+
+ try:
+ bmesh.ops.translate(bm, verts=Verts, vec=translation)
+ except ValueError:
+ print("[Edge Roundifier]: Parallel translate value error - "
+ "multiple vertices in list - try unchecking 'Centers'")
+
+ indexes = [v.index for v in Verts]
+ self.sel.refreshMesh(bm, mesh)
+ offsetVertices = [bm.verts[i] for i in indexes]
+ return offsetVertices
+
+ def skipThisEdge(self, V1, V2, plane):
+ # Check If It is possible to spin selected verts on this plane if not exit roundifier
+ if(plane == XY):
+ if (V1[0] == V2[0] and V1[1] == V2[1]):
+ return True
+ elif(plane == YZ):
+ if (V1[1] == V2[1] and V1[2] == V2[2]):
+ return True
+ elif(plane == XZ):
+ if (V1[0] == V2[0] and V1[2] == V2[2]):
+ return True
+ return False
+
+ def calculateRoundifyParams(self, edge, parameters, bm, mesh):
+ # Because all data from mesh is in local coordinates
+ # and spin operator works on global coordinates
+ # We first need to translate all input data by vector equal
+ # to origin position and then perform calculations
+ # At least that is my understanding :) <komi3D>
+
+ # V1 V2 stores Local Coordinates
+ V1, V2, edgeVector, edgeLength, edgeCenter = self.getEdgeInfo(edge)
+
+ debugPrintNew(d_Plane, "PLANE: " + parameters["plane"])
+ lineAB = self.calc.getLineCoefficientsPerpendicularToVectorInPoint(
+ edgeCenter, edgeVector,
+ parameters["plane"]
+ )
+ circleMidPoint = V1
+ circleMidPointOnPlane = self.calc.getCircleMidPointOnPlane(
+ V1, parameters["plane"]
+ )
+ radius = parameters["radius"]
+
+ angle = 0
+ if (parameters["entryMode"] == 'Angle'):
+ if (parameters["angleEnum"] != 'Other'):
+ radius, angle = self.CalculateRadiusAndAngleForAnglePresets(
+ parameters["angleEnum"], radius,
+ angle, edgeLength
+ )
+ else:
+ radius, angle = self.CalculateRadiusAndAngle(edgeLength)
+ debugPrintNew(d_Radius_Angle, "RADIUS = " + str(radius) + " ANGLE = " + str(angle))
+ roots = None
+ if angle != pi: # mode other than 180
+ if lineAB is None:
+ roots = self.calc.getLineCircleIntersectionsWhenXPerpendicular(
+ edgeCenter, circleMidPointOnPlane,
+ radius, parameters["plane"]
+ )
+ else:
+ roots = self.calc.getLineCircleIntersections(
+ lineAB, circleMidPointOnPlane, radius
+ )
+
+ if roots is None:
+ debugPrintNew(True,
+ "[Edge Roundifier]: No centers were found. Change radius to higher value")
+ return None
+ roots = self.addMissingCoordinate(roots, V1, parameters["plane"]) # adds X, Y or Z coordinate
+ else:
+ roots = [edgeCenter, edgeCenter]
+ debugPrintNew(d_Roots, "roots=" + str(roots))
+
+ refObjectLocation = None
+ objectLocation = bpy.context.active_object.location # Origin Location
+
+ if parameters["refObject"] == "ORG":
+ refObjectLocation = [0, 0, 0]
+ elif parameters["refObject"] == "CUR":
+ refObjectLocation = bpy.context.scene.cursor.location - objectLocation
+ else:
+ refObjectLocation = self.calc.getEdgeReference(edge, edgeCenter, parameters["plane"])
+
+ debugPrintNew(d_RefObject, parameters["refObject"], refObjectLocation)
+ chosenSpinCenter, otherSpinCenter = self.getSpinCenterClosestToRefCenter(
+ refObjectLocation, roots
+ )
+
+ if (parameters["entryMode"] == "Radius"):
+ halfAngle = self.calc.getAngle(edgeCenter, chosenSpinCenter, circleMidPoint)
+ angle = 2 * halfAngle[0] # in radians
+ self.a = degrees(angle) # in degrees
+
+ spinAxis = self.getSpinAxis(parameters["plane"])
+ steps = parameters["segments"]
+ angle = -angle # rotate clockwise by default
+
+ return [chosenSpinCenter, otherSpinCenter, spinAxis, angle, steps, refObjectLocation]
+
+ def drawSpin(self, edge, edgeCenter, roundifyParams, parameters, bm, mesh):
+ [chosenSpinCenter, otherSpinCenter, spinAxis, angle, steps, refObjectLocation] = roundifyParams
+
+ v0org, v1org = (edge.verts[0], edge.verts[1])
+
+ if parameters["flip"]:
+ angle = -angle
+ spinCenterTemp = chosenSpinCenter
+ chosenSpinCenter = otherSpinCenter
+ otherSpinCenter = spinCenterTemp
+
+ if(parameters["invertAngle"]):
+ if angle < 0:
+ angle = two_pi + angle
+ elif angle > 0:
+ angle = -two_pi + angle
+ else:
+ angle = two_pi
+
+ if(parameters["fullCircles"]):
+ angle = two_pi
+
+ v0 = bm.verts.new(v0org.co)
+
+ result = bmesh.ops.spin(
+ bm, geom=[v0], cent=chosenSpinCenter, axis=spinAxis,
+ angle=angle, steps=steps, use_duplicate=False
+ )
+
+ # it seems there is something wrong with last index of this spin
+ # I need to calculate the last index manually here
+ vertsLength = len(bm.verts)
+ bm.verts.ensure_lookup_table()
+ lastVertIndex = bm.verts[vertsLength - 1].index
+ lastSpinVertIndices = self.getLastSpinVertIndices(steps, lastVertIndex)
+
+ self.sel.refreshMesh(bm, mesh)
+
+ alternativeLastSpinVertIndices = []
+ bothSpinVertices = []
+ spinVertices = []
+ alternate = False
+
+ if ((angle == pi or angle == -pi) and not parameters["bothSides"]):
+
+ midVertexIndex = lastVertIndex - round(steps / 2)
+ bm.verts.ensure_lookup_table()
+ midVert = bm.verts[midVertexIndex].co
+
+ midVertexDistance = (Vector(refObjectLocation) - Vector(midVert)).length
+ midEdgeDistance = (Vector(refObjectLocation) - Vector(edgeCenter)).length
+
+ if ((parameters["invertAngle"]) or (parameters["flip"])):
+ if (midVertexDistance > midEdgeDistance):
+ alternativeLastSpinVertIndices = self.alternateSpin(
+ bm, mesh, angle, chosenSpinCenter,
+ spinAxis, steps, v0, v1org, lastSpinVertIndices
+ )
+ else:
+ if (midVertexDistance < midEdgeDistance):
+ alternativeLastSpinVertIndices = self.alternateSpin(
+ bm, mesh, angle, chosenSpinCenter,
+ spinAxis, steps, v0, v1org, lastSpinVertIndices
+ )
+ elif (angle != two_pi): # to allow full circles
+ if (result['geom_last'][0].co - v1org.co).length > SPIN_END_THRESHOLD:
+ alternativeLastSpinVertIndices = self.alternateSpin(
+ bm, mesh, angle, chosenSpinCenter,
+ spinAxis, steps, v0, v1org, lastSpinVertIndices
+ )
+ alternate = True
+
+ self.sel.refreshMesh(bm, mesh)
+ if alternativeLastSpinVertIndices != []:
+ lastSpinVertIndices = alternativeLastSpinVertIndices
+
+ if lastSpinVertIndices.stop <= len(bm.verts): # make sure arc was added to bmesh
+ spinVertices = [bm.verts[i] for i in lastSpinVertIndices]
+ if alternativeLastSpinVertIndices != []:
+ spinVertices = spinVertices + [v0]
+ else:
+ spinVertices = [v0] + spinVertices
+
+ if (parameters["bothSides"]):
+ # do some more testing here!!!
+ if (angle == pi or angle == -pi):
+ alternativeLastSpinVertIndices = self.alternateSpinNoDelete(
+ bm, mesh, -angle, chosenSpinCenter,
+ spinAxis, steps, v0, v1org, []
+ )
+ elif alternate:
+ alternativeLastSpinVertIndices = self.alternateSpinNoDelete(
+ bm, mesh, angle, otherSpinCenter,
+ spinAxis, steps, v0, v1org, []
+ )
+ elif not alternate:
+ alternativeLastSpinVertIndices = self.alternateSpinNoDelete(
+ bm, mesh, -angle, otherSpinCenter,
+ spinAxis, steps, v0, v1org, []
+ )
+ bothSpinVertices = [bm.verts[i] for i in lastSpinVertIndices]
+ alternativeSpinVertices = [bm.verts[i] for i in alternativeLastSpinVertIndices]
+ bothSpinVertices = [v0] + bothSpinVertices + alternativeSpinVertices
+ spinVertices = bothSpinVertices
+
+ if (parameters["fullCircles"]):
+ v1 = bm.verts.new(v1org.co)
+ spinVertices = spinVertices + [v1]
+
+ if (parameters['drawArcCenters']):
+ centerVert = bm.verts.new(chosenSpinCenter)
+ spinVertices.append(centerVert)
+
+ return spinVertices, [chosenSpinCenter, otherSpinCenter, spinAxis, angle, steps, refObjectLocation]
+
+ def deleteSpinVertices(self, bm, mesh, lastSpinVertIndices):
+ verticesForDeletion = []
+ bm.verts.ensure_lookup_table()
+ for i in lastSpinVertIndices:
+ vi = bm.verts[i]
+ vi.select = True
+ debugPrintNew(True, str(i) + ") " + str(vi))
+ verticesForDeletion.append(vi)
+
+ bmesh.ops.delete(bm, geom=verticesForDeletion, context = 'VERTS')
+ bmesh.update_edit_mesh(mesh, True)
+ bpy.ops.object.mode_set(mode='OBJECT')
+ bpy.ops.object.mode_set(mode='EDIT')
+
+ def alternateSpinNoDelete(self, bm, mesh, angle, chosenSpinCenter,
+ spinAxis, steps, v0, v1org, lastSpinVertIndices):
+ v0prim = v0
+
+ result2 = bmesh.ops.spin(bm, geom=[v0prim], cent=chosenSpinCenter, axis=spinAxis,
+ angle=angle, steps=steps, use_duplicate=False)
+ vertsLength = len(bm.verts)
+ bm.verts.ensure_lookup_table()
+ lastVertIndex2 = bm.verts[vertsLength - 1].index
+
+ lastSpinVertIndices2 = self.getLastSpinVertIndices(steps, lastVertIndex2)
+ return lastSpinVertIndices2
+
+ def alternateSpin(self, bm, mesh, angle, chosenSpinCenter,
+ spinAxis, steps, v0, v1org, lastSpinVertIndices):
+
+ self.deleteSpinVertices(bm, mesh, lastSpinVertIndices)
+ v0prim = v0
+
+ result2 = bmesh.ops.spin(
+ bm, geom=[v0prim], cent=chosenSpinCenter, axis=spinAxis,
+ angle=-angle, steps=steps, use_duplicate=False
+ )
+ # it seems there is something wrong with last index of this spin
+ # I need to calculate the last index manually here
+ vertsLength = len(bm.verts)
+ bm.verts.ensure_lookup_table()
+ lastVertIndex2 = bm.verts[vertsLength - 1].index
+
+ lastSpinVertIndices2 = self.getLastSpinVertIndices(steps, lastVertIndex2)
+ # second spin also does not hit the v1org
+ if (result2['geom_last'][0].co - v1org.co).length > SPIN_END_THRESHOLD:
+
+ self.deleteSpinVertices(bm, mesh, lastSpinVertIndices2)
+ self.deleteSpinVertices(bm, mesh, range(v0.index, v0.index + 1))
+ return []
+ else:
+ return lastSpinVertIndices2
+
+ def getLastSpinVertIndices(self, steps, lastVertIndex):
+ arcfirstVertexIndex = lastVertIndex - steps + 1
+ lastSpinVertIndices = range(arcfirstVertexIndex, lastVertIndex + 1)
+ return lastSpinVertIndices
+
+ def rotateArcAroundSpinAxis(self, bm, mesh, vertices, parameters, edgeCenter):
+ axisAngle = parameters["axisAngle"]
+ plane = parameters["plane"]
+ # compensate rotation center
+ objectLocation = bpy.context.active_object.location
+ center = objectLocation + edgeCenter
+
+ rot = Euler((0.0, 0.0, radians(axisAngle)), 'XYZ').to_matrix()
+ if plane == YZ:
+ rot = Euler((radians(axisAngle), 0.0, 0.0), 'XYZ').to_matrix()
+ if plane == XZ:
+ rot = Euler((0.0, radians(axisAngle), 0.0), 'XYZ').to_matrix()
+
+ indexes = [v.index for v in vertices]
+
+ bmesh.ops.rotate(
+ bm,
+ cent=center,
+ matrix=rot,
+ verts=vertices,
+ space=bpy.context.edit_object.matrix_world
+ )
+ self.sel.refreshMesh(bm, mesh)
+ bm.verts.ensure_lookup_table()
+ rotatedVertices = [bm.verts[i] for i in indexes]
+
+ return rotatedVertices
+
+ def CalculateRadiusAndAngle(self, edgeLength):
+ degAngle = self.a
+ angle = radians(degAngle)
+ self.r = radius = edgeLength / (2 * sin(angle / 2))
+ return radius, angle
+
+ def CalculateRadiusAndAngleForAnglePresets(self, angleEnum, initR, initA, edgeLength):
+ radius = initR
+ angle = initA
+ try:
+ # Note - define an integer string in the angleEnum
+ angle_convert = int(angleEnum)
+ self.a = angle_convert
+ except:
+ self.a = 180 # fallback
+ debugPrintNew(True,
+ "CalculateRadiusAndAngleForAnglePresets problem with int conversion")
+
+ return self.CalculateRadiusAndAngle(edgeLength)
+
+ def getSpinCenterClosestToRefCenter(self, objLocation, roots):
+ root0Distance = (Vector(objLocation) - Vector(roots[0])).length
+ root1Distance = (Vector(objLocation) - Vector(roots[1])).length
+
+ chosenId = 0
+ rejectedId = 1
+ if (root0Distance > root1Distance):
+ chosenId = 1
+ rejectedId = 0
+ return roots[chosenId], roots[rejectedId]
+
+ def addMissingCoordinate(self, roots, startVertex, plane):
+ if roots is not None:
+ a, b = roots[0]
+ c, d = roots[1]
+ if plane == XY:
+ roots[0] = Vector((a, b, startVertex[2]))
+ roots[1] = Vector((c, d, startVertex[2]))
+ if plane == YZ:
+ roots[0] = Vector((startVertex[0], a, b))
+ roots[1] = Vector((startVertex[0], c, d))
+ if plane == XZ:
+ roots[0] = Vector((a, startVertex[1], b))
+ roots[1] = Vector((c, startVertex[1], d))
+ return roots
+
+ def selectEdgesAfterRoundifier(self, context, edges):
+ bpy.ops.object.mode_set(mode='OBJECT')
+ bpy.ops.object.mode_set(mode='EDIT')
+ mesh = context.view_layer.objects.active.data
+ bmnew = bmesh.new()
+ bmnew.from_mesh(mesh)
+
+ self.deselectEdges(bmnew)
+ for selectedEdge in edges:
+ for e in bmnew.edges:
+ if (e.verts[0].co - selectedEdge.verts[0].co).length <= self.threshold \
+ and (e.verts[1].co - selectedEdge.verts[1].co).length <= self.threshold:
+ e.select_set(True)
+
+ bpy.ops.object.mode_set(mode='OBJECT')
+ bmnew.to_mesh(mesh)
+ bmnew.free()
+ bpy.ops.object.mode_set(mode='EDIT')
+
+ def deselectEdges(self, bm):
+ for edge in bm.edges:
+ edge.select_set(False)
+
+ def getSpinAxis(self, plane):
+ axis = (0, 0, 1)
+ if plane == YZ:
+ axis = (1, 0, 0)
+ if plane == XZ:
+ axis = (0, 1, 0)
+ return axis
+
+
+ @classmethod
+ def poll(cls, context):
+ return (context.view_layer.objects.active.type == 'MESH') and (context.view_layer.objects.active.mode == 'EDIT')
+
+def draw_item(self, context):
+ self.layout.operator_context = 'INVOKE_DEFAULT'
+ self.layout.operator('mesh.edge_roundifier')
+
+
+classes = (
+ EdgeRoundifier,
+ )
+
+reg_cls, unreg_cls = bpy.utils.register_classes_factory(classes)
+
+
+def register():
+ reg_cls()
+ bpy.types.VIEW3D_MT_edit_mesh_edges.append(draw_item)
+
+
+def unregister():
+ unreg_cls()
+ bpy.types.VIEW3D_MT_edit_mesh_edges.remove(draw_item)
+
+if __name__ == "__main__":
+ register()