1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
20 "name": "Edge Roundifier",
22 "author": "Piotr Komisarczyk (komi3D), PKHG",
25 "location": "SPACE > Edge Roundifier or CTRL-E > "
26 "Edge Roundifier or Tools > Addons > Edge Roundifier",
27 "description": "Mesh editing script allowing edge rounding",
34 from bpy
.types
import Operator
35 from bpy
.props
import (
43 radians
, degrees
, sin
,
45 from mathutils
import (
55 SPIN_END_THRESHOLD
= 0.001
56 LINE_TOLERANCE
= 0.0001
60 d_Radius_Angle
= False
64 d_Selected_edges
= False
65 d_Rotate_Around_Spin_Center
= False
71 # for debugging PKHG #
72 def debugPrintNew(debugs
, *text
):
74 tmp
= [el
for el
in text
]
79 # Geometry and math calculation methods #
81 class CalculationHelper
:
87 def getLineCoefficientsPerpendicularToVectorInPoint(self
, point
, vector
, plane
):
89 xVector
, yVector
, zVector
= vector
90 destinationPoint
= (x
+ yVector
, y
- xVector
, z
)
92 destinationPoint
= (x
, y
+ zVector
, z
- yVector
)
94 destinationPoint
= (x
+ zVector
, y
, z
- xVector
)
95 return self
.getCoefficientsForLineThrough2Points(point
, destinationPoint
, plane
)
97 def getQuadraticRoots(self
, coef
):
99 return None # Replaced NaN with None
102 delta
= b
** 2 - 4 * a
* c
109 x1
= (-b
- sqrt(delta
)) / (2 * a
)
110 x2
= (-b
+ sqrt(delta
)) / (2 * a
)
113 def getCoefficientsForLineThrough2Points(self
, point1
, point2
, plane
):
117 # mapping x1,x2, y1,y2 to proper values based on plane
127 # Further calculations the same as for XY plane
130 debugPrintNew(d_XABS_YABS
, "XABS = " + str(xabs
) + " YABS = " + str(yabs
))
132 if xabs
<= LINE_TOLERANCE
:
133 return None # this means line x = edgeCenterX
134 if yabs
<= LINE_TOLERANCE
:
138 A
= (y2
- y1
) / (x2
- x1
)
142 def getLineCircleIntersections(self
, lineAB
, circleMidPoint
, radius
):
143 # (x - a)**2 + (y - b)**2 = r**2 - circle equation
144 # y = A*x + B - line equation
145 # f * x**2 + g * x + h = 0 - quadratic equation
147 a
, b
= circleMidPoint
149 g
= -2 * a
+ 2 * A
* B
- 2 * A
* b
150 h
= (B
** 2) - 2 * b
* B
- (radius
** 2) + (a
** 2) + (b
** 2)
152 roots
= self
.getQuadraticRoots(coef
)
153 if roots
is not None:
156 point1
= [x1
, A
* x1
+ B
]
157 point2
= [x2
, A
* x2
+ B
]
158 return [point1
, point2
]
162 def getLineCircleIntersectionsWhenXPerpendicular(self
, edgeCenter
,
163 circleMidPoint
, radius
, plane
):
164 # (x - a)**2 + (y - b)**2 = r**2 - circle equation
165 # x = xValue - line equation
166 # f * x**2 + g * x + h = 0 - quadratic equation
167 xValue
= edgeCenter
[0]
169 xValue
= edgeCenter
[1]
171 xValue
= edgeCenter
[0]
173 a
, b
= circleMidPoint
176 h
= (a
** 2) + (b
** 2) + (xValue
** 2) - 2 * a
* xValue
- (radius
** 2)
178 roots
= self
.getQuadraticRoots(coef
)
179 if roots
is not None:
182 point1
= [xValue
, y1
]
183 point2
= [xValue
, y2
]
184 return [point1
, point2
]
188 # point1 is the point near 90 deg angle
189 def getAngle(self
, point1
, point2
, point3
):
190 distance1
= (Vector(point1
) - Vector(point2
)).length
191 distance2
= (Vector(point2
) - Vector(point3
)).length
192 cos
= distance1
/ distance2
194 if abs(cos
) > 1: # prevents Domain Error
198 return (alpha
, degrees(alpha
))
200 # get two of three coordinates used for further calculation of spin center
201 # PKHG>nice if rescriction to these 3 types or planes is to be done
202 # komi3D> from 0.0.2 there is a restriction. In future I would like Edge
203 # komi3D> Roundifier to work on Normal and View coordinate systems
204 def getCircleMidPointOnPlane(self
, V1
, plane
):
215 def getEdgeReference(self
, edge
, edgeCenter
, plane
):
216 vert1
= edge
.verts
[1].co
217 V
= vert1
- edgeCenter
218 orthoVector
= Vector((V
[1], -V
[0], V
[2]))
220 orthoVector
= Vector((V
[2], V
[1], -V
[0]))
222 orthoVector
= Vector((V
[0], V
[2], -V
[1]))
223 refPoint
= edgeCenter
+ orthoVector
227 # Selection Methods #
229 class SelectionHelper
:
231 def selectVertexInMesh(self
, mesh
, vertex
):
232 bpy
.ops
.object.mode_set(mode
="OBJECT")
233 for v
in mesh
.vertices
:
238 bpy
.ops
.object.mode_set(mode
="EDIT")
240 def getSelectedVertex(self
, mesh
):
241 bpy
.ops
.object.mode_set(mode
="OBJECT")
242 for v
in mesh
.vertices
:
244 bpy
.ops
.object.mode_set(mode
="EDIT")
247 bpy
.ops
.object.mode_set(mode
="EDIT")
250 def refreshMesh(self
, bm
, mesh
):
251 bpy
.ops
.object.mode_set(mode
='OBJECT')
253 bpy
.ops
.object.mode_set(mode
='EDIT')
258 class EdgeRoundifier(Operator
):
259 bl_idname
= "mesh.edge_roundifier"
260 bl_label
= "Edge Roundifier"
261 bl_description
= "Mesh modeling tool for building arcs on selected Edges"
262 bl_options
= {'REGISTER', 'UNDO', 'PRESET'}
267 edgeScaleFactor
= FloatProperty(
269 description
="Set the Factor of scaling",
271 min=0.00001, max=100000.0,
277 description
="User Defined arc steepness by a Radius\n"
278 "Enabled only if Entry mode is set to Radius\n",
280 min=0.00001, max=1000.0,
286 description
="User defined arc steepness calculated from an Angle\n"
287 "Enabled only if Entry mode is set to Angle and\n"
288 "Angle presets is set Other",
296 description
="Arc subdivision level",
303 description
="If True, flip the side of the selected edges where the arcs are drawn",
306 invertAngle
= BoolProperty(
308 description
="If True, uses an inverted angle to draw the arc (360 degrees - angle)",
311 fullCircles
= BoolProperty(
313 description
="If True, uses an angle of 360 degrees to draw the arcs",
316 bothSides
= BoolProperty(
318 description
="If True, draw arcs on both sides of the selected edges",
321 drawArcCenters
= BoolProperty(
323 description
="If True, draws a vertex for each spin center",
326 removeEdges
= BoolProperty(
328 description
="If True removes the Original selected edges",
331 removeScaledEdges
= BoolProperty(
333 description
="If True removes the Scaled edges (not part of the arcs)",
336 connectArcWithEdge
= BoolProperty(
338 description
="Connect Arcs to Edges",
341 connectArcs
= BoolProperty(
343 description
="Connect subsequent Arcs",
346 connectScaledAndBase
= BoolProperty(
347 name
="Scaled - Base Edge",
348 description
="Connect Scaled to Base Edge",
351 connectArcsFlip
= BoolProperty(
353 description
="Flip the connection of subsequent Arcs",
356 connectArcWithEdgeFlip
= BoolProperty(
357 name
="Flip Arc - Edge",
358 description
="Flip the connection of the Arcs to Edges",
361 axisAngle
= FloatProperty(
363 description
="Rotate Arc around the perpendicular axis",
365 min=-180.0, max=180.0,
369 edgeAngle
= FloatProperty(
371 description
="Rotate Arc around the Edge (Edge acts like as the axis)",
373 min=-180.0, max=180.0,
377 offset
= FloatProperty(
379 description
="Offset Arc perpendicular the Edge",
381 min=-1000000.0, max=1000000.0,
385 offset2
= FloatProperty(
387 description
="Offset Arc in parallel to the Edge",
389 min=-1000000.0, max=1000000.0,
393 ellipticFactor
= FloatProperty(
395 description
="Make Arc elliptic",
397 min=-1000000.0, max=1000000.0,
401 workModeItems
= [("Normal", "Normal", ""), ("Reset", "Reset", "")]
402 workMode
= EnumProperty(
406 description
="Normal work with the current given parameters set by the user\n"
407 "Reset - changes back the parameters to their default values"
409 entryModeItems
= [("Radius", "Radius", ""), ("Angle", "Angle", "")]
410 entryMode
= EnumProperty(
411 items
=entryModeItems
,
414 description
="Entry mode switch between Angle and Radius\n"
415 "If Angle is selected, arc radius is calculated from it"
417 rotateCenterItems
= [
418 ("Spin", "Spin", ""), ("V1", "V1", ""),
419 ("Edge", "Edge", ""), ("V2", "V2", "")
421 rotateCenter
= EnumProperty(
422 items
=rotateCenterItems
,
425 description
="Rotate center for spin axis rotate"
427 arcModeItems
= [("FullEdgeArc", "Full", "Full"), ('HalfEdgeArc', "Half", "Half")]
428 arcMode
= EnumProperty(
431 default
='FullEdgeArc',
432 description
="Arc mode - switch between Full and Half arcs"
435 ('Other', "Other", "User defined angle"), ('180', "180", "HemiCircle (2 sides)"),
436 ('120', "120", "TriangleCircle (3 sides)"), ('90', "90", "QuadCircle (4 sides)"),
437 ('72', "72", "PentagonCircle (5 sides)"), ('60', "60", "HexagonCircle (6 sides)"),
438 ('45', "45", "OctagonCircle (8 sides)"), ('30', "30", "DodecagonCircle (12 sides)")
440 angleEnum
= EnumProperty(
444 description
="Presets prepare standard angles and calculate proper ray"
446 refItems
= [('ORG', "Origin", "Use Origin Location"), ('CUR', "3D Cursor", "Use 3DCursor Location"),
447 ('EDG', "Edge", "Use Individual Edge Reference")]
448 referenceLocation
= EnumProperty(
452 description
="Reference location used to calculate initial centers of drawn arcs"
455 (XY
, "XY", "XY Plane (Z=0)"),
456 (YZ
, "YZ", "YZ Plane (X=0)"),
457 (XZ
, "XZ", "XZ Plane (Y=0)")
459 planeEnum
= EnumProperty(
463 description
="Plane used to calculate spin plane of drawn arcs"
465 edgeScaleCenterItems
= [
466 ('V1', "V1", "v1 - First Edge's Vertex"),
467 ('CENTER', "Center", "Center of the Edge"),
468 ('V2', "V2", "v2 - Second Edge's Vertex")
470 edgeScaleCenterEnum
= EnumProperty(
471 items
=edgeScaleCenterItems
,
472 name
="Edge scale center",
474 description
="Center used for scaling the initial edge"
477 calc
= CalculationHelper()
478 sel
= SelectionHelper()
481 def poll(cls
, context
):
482 obj
= context
.active_object
483 return (obj
and obj
.type == 'MESH' and
486 def prepareMesh(self
, context
):
487 bpy
.ops
.object.mode_set(mode
='OBJECT')
488 bpy
.ops
.object.mode_set(mode
='EDIT')
490 mesh
= context
.scene
.objects
.active
.data
494 edges
= [ele
for ele
in bm
.edges
if ele
.select
]
495 return edges
, mesh
, bm
497 def prepareParameters(self
):
498 parameters
= {"a": "a"}
499 parameters
["arcMode"] = self
.arcMode
500 parameters
["edgeScaleFactor"] = self
.edgeScaleFactor
501 parameters
["edgeScaleCenterEnum"] = self
.edgeScaleCenterEnum
502 parameters
["plane"] = self
.planeEnum
503 parameters
["radius"] = self
.r
504 parameters
["angle"] = self
.a
505 parameters
["segments"] = self
.n
506 parameters
["fullCircles"] = self
.fullCircles
507 parameters
["invertAngle"] = self
.invertAngle
508 parameters
["bothSides"] = self
.bothSides
509 parameters
["angleEnum"] = self
.angleEnum
510 parameters
["entryMode"] = self
.entryMode
511 parameters
["workMode"] = self
.workMode
512 parameters
["refObject"] = self
.referenceLocation
513 parameters
["flip"] = self
.flip
514 parameters
["drawArcCenters"] = self
.drawArcCenters
515 parameters
["removeEdges"] = self
.removeEdges
516 parameters
["removeScaledEdges"] = self
.removeScaledEdges
517 parameters
["connectArcWithEdge"] = self
.connectArcWithEdge
518 parameters
["connectScaledAndBase"] = self
.connectScaledAndBase
519 parameters
["connectArcs"] = self
.connectArcs
520 parameters
["connectArcsFlip"] = self
.connectArcsFlip
521 parameters
["connectArcWithEdgeFlip"] = self
.connectArcWithEdgeFlip
522 parameters
["axisAngle"] = self
.axisAngle
523 parameters
["edgeAngle"] = self
.edgeAngle
524 parameters
["offset"] = self
.offset
525 parameters
["offset2"] = self
.offset2
526 parameters
["ellipticFactor"] = self
.ellipticFactor
527 parameters
["rotateCenter"] = self
.rotateCenter
530 def draw(self
, context
):
535 self
.addEnumParameterToUI(box
, False, uiPercentage
, 'Mode:', 'workMode')
536 self
.addEnumParameterToUI(box
, False, uiPercentage
, 'Plane:', 'planeEnum')
537 self
.addEnumParameterToUI(box
, False, uiPercentage
, 'Reference:', 'referenceLocation')
540 self
.addEnumParameterToUI(box
, False, uiPercentage
, 'Scale base:', 'edgeScaleCenterEnum')
541 self
.addParameterToUI(box
, False, uiPercentage
, 'Scale factor:', 'edgeScaleFactor')
544 self
.addEnumParameterToUI(box
, False, uiPercentage
, 'Entry mode:', 'entryMode')
546 row
= box
.row(align
=False)
547 row
.prop(self
, 'angleEnum', expand
=True, text
="Angle presets")
549 disable_a
= bool(self
.entryMode
== 'Angle' and self
.angleEnum
== 'Other')
550 disable_r
= bool(self
.entryMode
== 'Radius')
552 self
.addParameterToUI(box
, False, uiPercentage
, 'Angle:', 'a', disable_a
)
553 self
.addParameterToUI(box
, False, uiPercentage
, 'Radius:', 'r', disable_r
)
554 self
.addParameterToUI(box
, False, uiPercentage
, 'Segments:', 'n')
557 self
.addCheckboxToUI(box
, True, 'Options:', 'flip', 'invertAngle')
558 self
.addCheckboxToUI(box
, True, '', 'bothSides', 'fullCircles')
559 self
.addCheckboxToUI(box
, True, '', 'drawArcCenters')
562 self
.addCheckboxToUI(box
, True, 'Remove:', 'removeEdges', 'removeScaledEdges')
565 self
.addCheckboxToUI(box
, True, 'Connect:', 'connectArcs', 'connectArcsFlip')
566 self
.addCheckboxToUI(box
, True, '', 'connectArcWithEdge', 'connectArcWithEdgeFlip')
567 self
.addCheckboxToUI(box
, True, '', 'connectScaledAndBase')
570 self
.addParameterToUI(box
, False, uiPercentage
, 'Orhto offset:', 'offset')
571 self
.addParameterToUI(box
, False, uiPercentage
, 'Parallel offset:', 'offset2')
574 self
.addParameterToUI(box
, False, uiPercentage
, 'Edge rotate :', 'edgeAngle')
575 self
.addEnumParameterToUI(box
, False, uiPercentage
, 'Axis rotate center:', 'rotateCenter')
576 self
.addParameterToUI(box
, False, uiPercentage
, 'Axis rotate:', 'axisAngle')
579 self
.addParameterToUI(box
, False, uiPercentage
, 'Elliptic factor:', 'ellipticFactor')
581 def addParameterToUI(self
, layout
, alignment
, percent
, label
, properties
, disable
=True):
582 row
= layout
.row(align
=alignment
)
583 split
= row
.split(percentage
=percent
)
587 col2
= split
.column()
588 row
= col2
.row(align
=alignment
)
589 row
.enabled
= disable
590 row
.prop(self
, properties
)
592 def addCheckboxToUI(self
, layout
, alignment
, label
, property1
, property2
=None):
593 if label
not in (""):
596 row2
= layout
.row(align
=alignment
)
598 split
= row2
.split(percentage
=0.5)
599 split
.prop(self
, property1
, toggle
=True)
600 split
.prop(self
, property2
, toggle
=True)
602 row2
.prop(self
, property1
, toggle
=True)
605 def addEnumParameterToUI(self
, layout
, alignment
, percent
, label
, properties
):
606 row
= layout
.row(align
=alignment
)
607 split
= row
.split(percentage
=percent
)
611 col2
= split
.column()
612 row
= col2
.row(align
=alignment
)
613 row
.prop(self
, properties
, expand
=True, text
="a")
615 def execute(self
, context
):
617 edges
, mesh
, bm
= self
.prepareMesh(context
)
618 parameters
= self
.prepareParameters()
620 self
.resetValues(parameters
["workMode"])
622 self
.obj
= context
.scene
.objects
.active
623 scaledEdges
= self
.scaleDuplicatedEdges(bm
, edges
, parameters
)
625 if len(scaledEdges
) > 0:
626 self
.roundifyEdges(scaledEdges
, parameters
, bm
, mesh
)
628 if parameters
["connectScaledAndBase"]:
629 self
.connectScaledEdgesWithBaseEdge(scaledEdges
, edges
, bm
, mesh
)
631 self
.sel
.refreshMesh(bm
, mesh
)
632 self
.selectEdgesAfterRoundifier(context
, scaledEdges
)
634 debugPrintNew(True, "No edges selected!")
636 if parameters
["removeEdges"]:
637 bmesh
.ops
.delete(bm
, geom
=edges
, context
=2)
639 if parameters
["removeScaledEdges"] and self
.edgeScaleFactor
!= 1.0:
640 bmesh
.ops
.delete(bm
, geom
=scaledEdges
, context
=2)
642 bpy
.ops
.object.mode_set(mode
='OBJECT')
644 bpy
.ops
.object.mode_set(mode
='EDIT')
645 bpy
.ops
.mesh
.select_all(action
='SELECT')
646 bpy
.ops
.mesh
.remove_doubles()
652 def resetValues(self
, workMode
):
653 if workMode
== "Reset":
654 self
.setAllParamsToDefaults()
656 def setAllParamsToDefaults(self
):
658 self
.edgeScaleFactor
= 1.0
663 self
.invertAngle
= False
664 self
.fullCircles
= False
665 self
.bothSides
= False
666 self
.drawArcCenters
= False
667 self
.removeEdges
= False
668 self
.removeScaledEdges
= False
670 self
.connectArcWithEdge
= False
671 self
.connectArcs
= False
672 self
.connectScaledAndBase
= False
673 self
.connectArcsFlip
= False
674 self
.connectArcWithEdgeFlip
= False
680 self
.ellipticFactor
= 0.0
682 self
.workMode
= 'Normal'
683 self
.entryMode
= 'Angle'
684 self
.angleEnum
= '180'
685 self
.referenceLocation
= 'ORG'
686 self
.planeEnum
= 'XY'
687 self
.edgeScaleCenterEnum
= 'CENTER'
688 self
.rotateCenter
= 'Edge'
690 self
.report({'INFO'}, "The parameters have been reset to default values")
691 except Exception as e
:
692 self
.report({'WARNING'}, "The parameters could not be reset")
693 debugPrintNew(True, "\n[setAllParamsToDefaults]\n parameter reset error\n" + e
)
695 def scaleDuplicatedEdges(self
, bm
, edges
, parameters
):
696 scaleCenter
= parameters
["edgeScaleCenterEnum"]
697 factor
= parameters
["edgeScaleFactor"]
698 # this code is based on Zeffi's answer to my question
701 duplicateEdges
= edges
707 if scaleCenter
== 'CENTER':
708 origin
= (v1
+ v2
) * 0.5
709 elif scaleCenter
== 'V1':
711 elif scaleCenter
== 'V2':
714 bmv1
= bm
.verts
.new(((v1
- origin
) * factor
) + origin
)
715 bmv2
= bm
.verts
.new(((v2
- origin
) * factor
) + origin
)
716 bme
= bm
.edges
.new([bmv1
, bmv2
])
717 duplicateEdges
.append(bme
)
718 return duplicateEdges
720 def roundifyEdges(self
, edges
, parameters
, bm
, mesh
):
723 arcVerts
= self
.roundify(e
, parameters
, bm
, mesh
)
724 arcs
.append(arcVerts
)
726 if parameters
["connectArcs"]:
727 self
.connectArcsTogether(arcs
, bm
, mesh
, parameters
)
729 def getNormalizedEdgeVector(self
, edge
):
730 V1
= edge
.verts
[0].co
731 V2
= edge
.verts
[1].co
733 normEdge
= edgeVector
.normalized()
736 def getEdgePerpendicularVector(self
, edge
, plane
):
737 normEdge
= self
.getNormalizedEdgeVector(edge
)
739 edgePerpendicularVector
= Vector((normEdge
[1], -normEdge
[0], 0))
741 edgePerpendicularVector
= Vector((0, normEdge
[2], -normEdge
[1]))
743 edgePerpendicularVector
= Vector((normEdge
[2], 0, -normEdge
[0]))
744 return edgePerpendicularVector
746 def getEdgeInfo(self
, edge
):
747 V1
= edge
.verts
[0].co
748 V2
= edge
.verts
[1].co
750 edgeLength
= edgeVector
.length
751 edgeCenter
= (V2
+ V1
) * 0.5
752 return V1
, V2
, edgeVector
, edgeLength
, edgeCenter
754 def roundify(self
, edge
, parameters
, bm
, mesh
):
755 V1
, V2
, edgeVector
, edgeLength
, edgeCenter
= self
.getEdgeInfo(edge
)
756 if self
.skipThisEdge(V1
, V2
, parameters
["plane"]):
759 roundifyParams
= None
761 roundifyParams
= self
.calculateRoundifyParams(edge
, parameters
, bm
, mesh
)
762 if roundifyParams
is None:
765 arcVerts
= self
.spinAndPostprocess(edge
, parameters
, bm
, mesh
, edgeCenter
, roundifyParams
)
768 def spinAndPostprocess(self
, edge
, parameters
, bm
, mesh
, edgeCenter
, roundifyParams
):
769 spinnedVerts
, roundifyParamsUpdated
= self
.drawSpin(
774 postProcessedArcVerts
= self
.arcPostprocessing(
775 edge
, parameters
, bm
, mesh
,
776 roundifyParamsUpdated
,
777 spinnedVerts
, edgeCenter
779 return postProcessedArcVerts
781 def rotateArcAroundEdge(self
, bm
, mesh
, arcVerts
, parameters
):
782 angle
= parameters
["edgeAngle"]
784 self
.arc_rotator(arcVerts
, angle
, parameters
)
786 # arc_rotator method was created by PKHG, I (komi3D) adjusted it to fit the rest
787 def arc_rotator(self
, arcVerts
, extra_rotation
, parameters
):
788 bpy
.ops
.object.mode_set(mode
='OBJECT')
789 old_location
= self
.obj
.location
.copy()
790 bpy
.ops
.transform
.translate(
791 value
=-old_location
, constraint_axis
=(False, False, False),
792 constraint_orientation
='GLOBAL', mirror
=False, proportional
='DISABLED',
793 proportional_edit_falloff
='SMOOTH', proportional_size
=1
795 bpy
.ops
.object.mode_set(mode
='EDIT')
796 adjust_matrix
= self
.obj
.matrix_parent_inverse
797 bm
= bmesh
.from_edit_mesh(self
.obj
.data
)
798 lastVert
= len(arcVerts
) - 1
799 if parameters
["drawArcCenters"]:
800 lastVert
= lastVert
- 1 # center gets added as last vert of arc
801 v0_old
= adjust_matrix
* arcVerts
[0].co
.copy()
803 # PKHG>INFO move if necessary v0 to origin such that the axis gos through origin and v1
804 if v0_old
!= Vector((0, 0, 0)):
805 for i
, ele
in enumerate(arcVerts
):
806 arcVerts
[i
].co
+= - v0_old
808 axis
= arcVerts
[0].co
- arcVerts
[lastVert
].co
809 a_mat
= Quaternion(axis
, radians(extra_rotation
)).normalized().to_matrix()
812 ele
.co
= a_mat
* ele
.co
814 # PKHG>INFO move back if needed
815 if v0_old
!= Vector((0, 0, 0)):
816 for i
, ele
in enumerate(arcVerts
):
817 arcVerts
[i
].co
+= + v0_old
819 bpy
.ops
.object.mode_set(mode
='OBJECT')
820 # PKHG>INFO move origin object back print("old location = " , old_location)
821 bpy
.ops
.transform
.translate(
822 value
=old_location
, constraint_axis
=(False, False, False),
823 constraint_orientation
='GLOBAL', mirror
=False, proportional
='DISABLED',
824 proportional_edit_falloff
='SMOOTH', proportional_size
=1
826 bpy
.ops
.object.mode_set(mode
='EDIT')
828 def makeElliptic(self
, bm
, mesh
, arcVertices
, parameters
):
829 if parameters
["ellipticFactor"] != 0: # if 0 then nothing has to be done
830 lastVert
= len(arcVertices
) - 1
831 if parameters
["drawArcCenters"]:
832 lastVert
= lastVert
- 1 # center gets added as last vert of arc
833 v0co
= arcVertices
[0].co
834 v1co
= arcVertices
[lastVert
].co
836 for vertex
in arcVertices
: # range(len(res_list)):
837 # PKHg>INFO compute the base on the edge of the height-vector
838 top
= vertex
.co
# res_list[nr].co
841 t
= (v1co
- v0co
).dot(top
- v0co
) / (v1co
- v0co
).length
** 2
842 h_bottom
= v0co
+ t
* (v1co
- v0co
)
843 height
= (h_bottom
- top
)
844 vertex
.co
= top
+ parameters
["ellipticFactor"] * height
848 def arcPostprocessing(self
, edge
, parameters
, bm
, mesh
, roundifyParams
, spinnedVerts
, edgeCenter
):
849 [chosenSpinCenter
, otherSpinCenter
, spinAxis
, angle
, steps
, refObjectLocation
] = roundifyParams
851 if parameters
["rotateCenter"] == 'Edge':
852 rotatedVerts
= self
.rotateArcAroundSpinAxis(
853 bm
, mesh
, spinnedVerts
, parameters
, edgeCenter
855 elif parameters
["rotateCenter"] == 'Spin':
856 rotatedVerts
= self
.rotateArcAroundSpinAxis(
857 bm
, mesh
, spinnedVerts
, parameters
, chosenSpinCenter
859 elif parameters
["rotateCenter"] == 'V1':
860 rotatedVerts
= self
.rotateArcAroundSpinAxis(
861 bm
, mesh
, spinnedVerts
, parameters
, edge
.verts
[0].co
863 elif parameters
["rotateCenter"] == 'V2':
864 rotatedVerts
= self
.rotateArcAroundSpinAxis(
865 bm
, mesh
, spinnedVerts
, parameters
, edge
.verts
[1].co
868 offsetVerts
= self
.offsetArcPerpendicular(
869 bm
, mesh
, rotatedVerts
, edge
, parameters
871 offsetVerts2
= self
.offsetArcParallel(
872 bm
, mesh
, offsetVerts
, edge
, parameters
874 ellipticVerts
= self
.makeElliptic(
875 bm
, mesh
, offsetVerts2
, parameters
877 self
.rotateArcAroundEdge(bm
, mesh
, ellipticVerts
, parameters
)
879 if parameters
["connectArcWithEdge"]:
880 self
.connectArcTogetherWithEdge(
881 edge
, offsetVerts2
, bm
, mesh
, parameters
885 def connectArcTogetherWithEdge(self
, edge
, arcVertices
, bm
, mesh
, parameters
):
886 lastVert
= len(arcVertices
) - 1
887 if parameters
["drawArcCenters"]:
888 lastVert
= lastVert
- 1 # center gets added as last vert of arc
889 edgeV1
= edge
.verts
[0].co
890 edgeV2
= edge
.verts
[1].co
891 arcV1
= arcVertices
[0].co
892 arcV2
= arcVertices
[lastVert
].co
894 bmv1
= bm
.verts
.new(edgeV1
)
895 bmv2
= bm
.verts
.new(arcV1
)
897 bmv3
= bm
.verts
.new(edgeV2
)
898 bmv4
= bm
.verts
.new(arcV2
)
900 if parameters
["connectArcWithEdgeFlip"] is False:
901 bme
= bm
.edges
.new([bmv1
, bmv2
])
902 bme2
= bm
.edges
.new([bmv3
, bmv4
])
904 bme
= bm
.edges
.new([bmv1
, bmv4
])
905 bme2
= bm
.edges
.new([bmv3
, bmv2
])
906 self
.sel
.refreshMesh(bm
, mesh
)
908 def connectScaledEdgesWithBaseEdge(self
, scaledEdges
, baseEdges
, bm
, mesh
):
909 for i
in range(0, len(scaledEdges
)):
910 scaledEdgeV1
= scaledEdges
[i
].verts
[0].co
911 baseEdgeV1
= baseEdges
[i
].verts
[0].co
912 scaledEdgeV2
= scaledEdges
[i
].verts
[1].co
913 baseEdgeV2
= baseEdges
[i
].verts
[1].co
915 bmv1
= bm
.verts
.new(baseEdgeV1
)
916 bmv2
= bm
.verts
.new(scaledEdgeV1
)
917 bme
= bm
.edges
.new([bmv1
, bmv2
])
919 bmv3
= bm
.verts
.new(scaledEdgeV2
)
920 bmv4
= bm
.verts
.new(baseEdgeV2
)
921 bme
= bm
.edges
.new([bmv3
, bmv4
])
922 self
.sel
.refreshMesh(bm
, mesh
)
924 def connectArcsTogether(self
, arcs
, bm
, mesh
, parameters
):
925 for i
in range(0, len(arcs
) - 1):
926 # in case on XZ or YZ there are no arcs drawn
927 if arcs
[i
] is None or arcs
[i
+ 1] is None:
930 lastVert
= len(arcs
[i
]) - 1
931 if parameters
["drawArcCenters"]:
932 lastVert
= lastVert
- 1 # center gets added as last vert of arc
933 # take last vert of arc i and first vert of arc i+1
935 V1
= arcs
[i
][lastVert
].co
936 V2
= arcs
[i
+ 1][0].co
938 if parameters
["connectArcsFlip"]:
940 V2
= arcs
[i
+ 1][lastVert
].co
942 bmv1
= bm
.verts
.new(V1
)
943 bmv2
= bm
.verts
.new(V2
)
944 bme
= bm
.edges
.new([bmv1
, bmv2
])
946 # connect last arc and first one
947 lastArcId
= len(arcs
) - 1
948 lastVertIdOfLastArc
= len(arcs
[lastArcId
]) - 1
949 if parameters
["drawArcCenters"]:
950 # center gets added as last vert of arc
951 lastVertIdOfLastArc
= lastVertIdOfLastArc
- 1
953 V1
= arcs
[lastArcId
][lastVertIdOfLastArc
].co
955 if parameters
["connectArcsFlip"]:
956 V1
= arcs
[lastArcId
][0].co
957 V2
= arcs
[0][lastVertIdOfLastArc
].co
959 bmv1
= bm
.verts
.new(V1
)
960 bmv2
= bm
.verts
.new(V2
)
961 bme
= bm
.edges
.new([bmv1
, bmv2
])
963 self
.sel
.refreshMesh(bm
, mesh
)
965 def offsetArcPerpendicular(self
, bm
, mesh
, Verts
, edge
, parameters
):
966 perpendicularVector
= self
.getEdgePerpendicularVector(edge
, parameters
["plane"])
967 offset
= parameters
["offset"]
968 translation
= offset
* perpendicularVector
971 bmesh
.ops
.translate(bm
, verts
=Verts
, vec
=translation
)
973 print("[Edge Roundifier]: Perpendicular translate value error - "
974 "multiple vertices in list - try unchecking 'Centers'")
976 indexes
= [v
.index
for v
in Verts
]
977 self
.sel
.refreshMesh(bm
, mesh
)
978 offsetVertices
= [bm
.verts
[i
] for i
in indexes
]
979 return offsetVertices
981 def offsetArcParallel(self
, bm
, mesh
, Verts
, edge
, parameters
):
982 edgeVector
= self
.getNormalizedEdgeVector(edge
)
983 offset
= parameters
["offset2"]
984 translation
= offset
* edgeVector
987 bmesh
.ops
.translate(bm
, verts
=Verts
, vec
=translation
)
989 print("[Edge Roundifier]: Parallel translate value error - "
990 "multiple vertices in list - try unchecking 'Centers'")
992 indexes
= [v
.index
for v
in Verts
]
993 self
.sel
.refreshMesh(bm
, mesh
)
994 offsetVertices
= [bm
.verts
[i
] for i
in indexes
]
995 return offsetVertices
997 def skipThisEdge(self
, V1
, V2
, plane
):
998 # Check If It is possible to spin selected verts on this plane if not exit roundifier
1000 if (V1
[0] == V2
[0] and V1
[1] == V2
[1]):
1003 if (V1
[1] == V2
[1] and V1
[2] == V2
[2]):
1006 if (V1
[0] == V2
[0] and V1
[2] == V2
[2]):
1010 def calculateRoundifyParams(self
, edge
, parameters
, bm
, mesh
):
1011 # Because all data from mesh is in local coordinates
1012 # and spin operator works on global coordinates
1013 # We first need to translate all input data by vector equal
1014 # to origin position and then perform calculations
1015 # At least that is my understanding :) <komi3D>
1017 # V1 V2 stores Local Coordinates
1018 V1
, V2
, edgeVector
, edgeLength
, edgeCenter
= self
.getEdgeInfo(edge
)
1020 debugPrintNew(d_Plane
, "PLANE: " + parameters
["plane"])
1021 lineAB
= self
.calc
.getLineCoefficientsPerpendicularToVectorInPoint(
1022 edgeCenter
, edgeVector
,
1026 circleMidPointOnPlane
= self
.calc
.getCircleMidPointOnPlane(
1027 V1
, parameters
["plane"]
1029 radius
= parameters
["radius"]
1032 if (parameters
["entryMode"] == 'Angle'):
1033 if (parameters
["angleEnum"] != 'Other'):
1034 radius
, angle
= self
.CalculateRadiusAndAngleForAnglePresets(
1035 parameters
["angleEnum"], radius
,
1039 radius
, angle
= self
.CalculateRadiusAndAngle(edgeLength
)
1040 debugPrintNew(d_Radius_Angle
, "RADIUS = " + str(radius
) + " ANGLE = " + str(angle
))
1042 if angle
!= pi
: # mode other than 180
1044 roots
= self
.calc
.getLineCircleIntersectionsWhenXPerpendicular(
1045 edgeCenter
, circleMidPointOnPlane
,
1046 radius
, parameters
["plane"]
1049 roots
= self
.calc
.getLineCircleIntersections(
1050 lineAB
, circleMidPointOnPlane
, radius
1055 "[Edge Roundifier]: No centers were found. Change radius to higher value")
1057 roots
= self
.addMissingCoordinate(roots
, V1
, parameters
["plane"]) # adds X, Y or Z coordinate
1059 roots
= [edgeCenter
, edgeCenter
]
1060 debugPrintNew(d_Roots
, "roots=" + str(roots
))
1062 refObjectLocation
= None
1063 objectLocation
= bpy
.context
.active_object
.location
# Origin Location
1065 if parameters
["refObject"] == "ORG":
1066 refObjectLocation
= [0, 0, 0]
1067 elif parameters
["refObject"] == "CUR":
1068 refObjectLocation
= bpy
.context
.scene
.cursor_location
- objectLocation
1070 refObjectLocation
= self
.calc
.getEdgeReference(edge
, edgeCenter
, parameters
["plane"])
1072 debugPrintNew(d_RefObject
, parameters
["refObject"], refObjectLocation
)
1073 chosenSpinCenter
, otherSpinCenter
= self
.getSpinCenterClosestToRefCenter(
1074 refObjectLocation
, roots
1077 if (parameters
["entryMode"] == "Radius"):
1078 halfAngle
= self
.calc
.getAngle(edgeCenter
, chosenSpinCenter
, circleMidPoint
)
1079 angle
= 2 * halfAngle
[0] # in radians
1080 self
.a
= degrees(angle
) # in degrees
1082 spinAxis
= self
.getSpinAxis(parameters
["plane"])
1083 steps
= parameters
["segments"]
1084 angle
= -angle
# rotate clockwise by default
1086 return [chosenSpinCenter
, otherSpinCenter
, spinAxis
, angle
, steps
, refObjectLocation
]
1088 def drawSpin(self
, edge
, edgeCenter
, roundifyParams
, parameters
, bm
, mesh
):
1089 [chosenSpinCenter
, otherSpinCenter
, spinAxis
, angle
, steps
, refObjectLocation
] = roundifyParams
1091 v0org
, v1org
= (edge
.verts
[0], edge
.verts
[1])
1093 if parameters
["flip"]:
1095 spinCenterTemp
= chosenSpinCenter
1096 chosenSpinCenter
= otherSpinCenter
1097 otherSpinCenter
= spinCenterTemp
1099 if(parameters
["invertAngle"]):
1101 angle
= two_pi
+ angle
1103 angle
= -two_pi
+ angle
1107 if(parameters
["fullCircles"]):
1110 v0
= bm
.verts
.new(v0org
.co
)
1112 result
= bmesh
.ops
.spin(
1113 bm
, geom
=[v0
], cent
=chosenSpinCenter
, axis
=spinAxis
,
1114 angle
=angle
, steps
=steps
, use_duplicate
=False
1117 # it seems there is something wrong with last index of this spin
1118 # I need to calculate the last index manually here
1119 vertsLength
= len(bm
.verts
)
1120 bm
.verts
.ensure_lookup_table()
1121 lastVertIndex
= bm
.verts
[vertsLength
- 1].index
1122 lastSpinVertIndices
= self
.getLastSpinVertIndices(steps
, lastVertIndex
)
1124 self
.sel
.refreshMesh(bm
, mesh
)
1126 alternativeLastSpinVertIndices
= []
1127 bothSpinVertices
= []
1131 if ((angle
== pi
or angle
== -pi
) and not parameters
["bothSides"]):
1133 midVertexIndex
= lastVertIndex
- round(steps
/ 2)
1134 bm
.verts
.ensure_lookup_table()
1135 midVert
= bm
.verts
[midVertexIndex
].co
1137 midVertexDistance
= (Vector(refObjectLocation
) - Vector(midVert
)).length
1138 midEdgeDistance
= (Vector(refObjectLocation
) - Vector(edgeCenter
)).length
1140 if ((parameters
["invertAngle"]) or (parameters
["flip"])):
1141 if (midVertexDistance
> midEdgeDistance
):
1142 alternativeLastSpinVertIndices
= self
.alternateSpin(
1143 bm
, mesh
, angle
, chosenSpinCenter
,
1144 spinAxis
, steps
, v0
, v1org
, lastSpinVertIndices
1147 if (midVertexDistance
< midEdgeDistance
):
1148 alternativeLastSpinVertIndices
= self
.alternateSpin(
1149 bm
, mesh
, angle
, chosenSpinCenter
,
1150 spinAxis
, steps
, v0
, v1org
, lastSpinVertIndices
1152 elif (angle
!= two_pi
): # to allow full circles
1153 if (result
['geom_last'][0].co
- v1org
.co
).length
> SPIN_END_THRESHOLD
:
1154 alternativeLastSpinVertIndices
= self
.alternateSpin(
1155 bm
, mesh
, angle
, chosenSpinCenter
,
1156 spinAxis
, steps
, v0
, v1org
, lastSpinVertIndices
1160 self
.sel
.refreshMesh(bm
, mesh
)
1161 if alternativeLastSpinVertIndices
!= []:
1162 lastSpinVertIndices
= alternativeLastSpinVertIndices
1164 if lastSpinVertIndices
.stop
<= len(bm
.verts
): # make sure arc was added to bmesh
1165 spinVertices
= [bm
.verts
[i
] for i
in lastSpinVertIndices
]
1166 if alternativeLastSpinVertIndices
!= []:
1167 spinVertices
= spinVertices
+ [v0
]
1169 spinVertices
= [v0
] + spinVertices
1171 if (parameters
["bothSides"]):
1172 # do some more testing here!!!
1173 if (angle
== pi
or angle
== -pi
):
1174 alternativeLastSpinVertIndices
= self
.alternateSpinNoDelete(
1175 bm
, mesh
, -angle
, chosenSpinCenter
,
1176 spinAxis
, steps
, v0
, v1org
, []
1179 alternativeLastSpinVertIndices
= self
.alternateSpinNoDelete(
1180 bm
, mesh
, angle
, otherSpinCenter
,
1181 spinAxis
, steps
, v0
, v1org
, []
1184 alternativeLastSpinVertIndices
= self
.alternateSpinNoDelete(
1185 bm
, mesh
, -angle
, otherSpinCenter
,
1186 spinAxis
, steps
, v0
, v1org
, []
1188 bothSpinVertices
= [bm
.verts
[i
] for i
in lastSpinVertIndices
]
1189 alternativeSpinVertices
= [bm
.verts
[i
] for i
in alternativeLastSpinVertIndices
]
1190 bothSpinVertices
= [v0
] + bothSpinVertices
+ alternativeSpinVertices
1191 spinVertices
= bothSpinVertices
1193 if (parameters
["fullCircles"]):
1194 v1
= bm
.verts
.new(v1org
.co
)
1195 spinVertices
= spinVertices
+ [v1
]
1197 if (parameters
['drawArcCenters']):
1198 centerVert
= bm
.verts
.new(chosenSpinCenter
)
1199 spinVertices
.append(centerVert
)
1201 return spinVertices
, [chosenSpinCenter
, otherSpinCenter
, spinAxis
, angle
, steps
, refObjectLocation
]
1203 def deleteSpinVertices(self
, bm
, mesh
, lastSpinVertIndices
):
1204 verticesForDeletion
= []
1205 bm
.verts
.ensure_lookup_table()
1206 for i
in lastSpinVertIndices
:
1209 debugPrintNew(True, str(i
) + ") " + str(vi
))
1210 verticesForDeletion
.append(vi
)
1212 bmesh
.ops
.delete(bm
, geom
=verticesForDeletion
, context
=1)
1213 bmesh
.update_edit_mesh(mesh
, True)
1214 bpy
.ops
.object.mode_set(mode
='OBJECT')
1215 bpy
.ops
.object.mode_set(mode
='EDIT')
1217 def alternateSpinNoDelete(self
, bm
, mesh
, angle
, chosenSpinCenter
,
1218 spinAxis
, steps
, v0
, v1org
, lastSpinVertIndices
):
1221 result2
= bmesh
.ops
.spin(bm
, geom
=[v0prim
], cent
=chosenSpinCenter
, axis
=spinAxis
,
1222 angle
=angle
, steps
=steps
, use_duplicate
=False)
1223 vertsLength
= len(bm
.verts
)
1224 bm
.verts
.ensure_lookup_table()
1225 lastVertIndex2
= bm
.verts
[vertsLength
- 1].index
1227 lastSpinVertIndices2
= self
.getLastSpinVertIndices(steps
, lastVertIndex2
)
1228 return lastSpinVertIndices2
1230 def alternateSpin(self
, bm
, mesh
, angle
, chosenSpinCenter
,
1231 spinAxis
, steps
, v0
, v1org
, lastSpinVertIndices
):
1233 self
.deleteSpinVertices(bm
, mesh
, lastSpinVertIndices
)
1236 result2
= bmesh
.ops
.spin(
1237 bm
, geom
=[v0prim
], cent
=chosenSpinCenter
, axis
=spinAxis
,
1238 angle
=-angle
, steps
=steps
, use_duplicate
=False
1240 # it seems there is something wrong with last index of this spin
1241 # I need to calculate the last index manually here
1242 vertsLength
= len(bm
.verts
)
1243 bm
.verts
.ensure_lookup_table()
1244 lastVertIndex2
= bm
.verts
[vertsLength
- 1].index
1246 lastSpinVertIndices2
= self
.getLastSpinVertIndices(steps
, lastVertIndex2
)
1247 # second spin also does not hit the v1org
1248 if (result2
['geom_last'][0].co
- v1org
.co
).length
> SPIN_END_THRESHOLD
:
1250 self
.deleteSpinVertices(bm
, mesh
, lastSpinVertIndices2
)
1251 self
.deleteSpinVertices(bm
, mesh
, range(v0
.index
, v0
.index
+ 1))
1254 return lastSpinVertIndices2
1256 def getLastSpinVertIndices(self
, steps
, lastVertIndex
):
1257 arcfirstVertexIndex
= lastVertIndex
- steps
+ 1
1258 lastSpinVertIndices
= range(arcfirstVertexIndex
, lastVertIndex
+ 1)
1259 return lastSpinVertIndices
1261 def rotateArcAroundSpinAxis(self
, bm
, mesh
, vertices
, parameters
, edgeCenter
):
1262 axisAngle
= parameters
["axisAngle"]
1263 plane
= parameters
["plane"]
1264 # compensate rotation center
1265 objectLocation
= bpy
.context
.active_object
.location
1266 center
= objectLocation
+ edgeCenter
1268 rot
= Euler((0.0, 0.0, radians(axisAngle
)), 'XYZ').to_matrix()
1270 rot
= Euler((radians(axisAngle
), 0.0, 0.0), 'XYZ').to_matrix()
1272 rot
= Euler((0.0, radians(axisAngle
), 0.0), 'XYZ').to_matrix()
1274 indexes
= [v
.index
for v
in vertices
]
1281 space
=bpy
.context
.edit_object
.matrix_world
1283 self
.sel
.refreshMesh(bm
, mesh
)
1284 bm
.verts
.ensure_lookup_table()
1285 rotatedVertices
= [bm
.verts
[i
] for i
in indexes
]
1287 return rotatedVertices
1289 def CalculateRadiusAndAngle(self
, edgeLength
):
1291 angle
= radians(degAngle
)
1292 self
.r
= radius
= edgeLength
/ (2 * sin(angle
/ 2))
1293 return radius
, angle
1295 def CalculateRadiusAndAngleForAnglePresets(self
, angleEnum
, initR
, initA
, edgeLength
):
1299 # Note - define an integer string in the angleEnum
1300 angle_convert
= int(angleEnum
)
1301 self
.a
= angle_convert
1303 self
.a
= 180 # fallback
1305 "CalculateRadiusAndAngleForAnglePresets problem with int conversion")
1307 return self
.CalculateRadiusAndAngle(edgeLength
)
1309 def getSpinCenterClosestToRefCenter(self
, objLocation
, roots
):
1310 root0Distance
= (Vector(objLocation
) - Vector(roots
[0])).length
1311 root1Distance
= (Vector(objLocation
) - Vector(roots
[1])).length
1315 if (root0Distance
> root1Distance
):
1318 return roots
[chosenId
], roots
[rejectedId
]
1320 def addMissingCoordinate(self
, roots
, startVertex
, plane
):
1321 if roots
is not None:
1325 roots
[0] = Vector((a
, b
, startVertex
[2]))
1326 roots
[1] = Vector((c
, d
, startVertex
[2]))
1328 roots
[0] = Vector((startVertex
[0], a
, b
))
1329 roots
[1] = Vector((startVertex
[0], c
, d
))
1331 roots
[0] = Vector((a
, startVertex
[1], b
))
1332 roots
[1] = Vector((c
, startVertex
[1], d
))
1335 def selectEdgesAfterRoundifier(self
, context
, edges
):
1336 bpy
.ops
.object.mode_set(mode
='OBJECT')
1337 bpy
.ops
.object.mode_set(mode
='EDIT')
1338 mesh
= context
.scene
.objects
.active
.data
1340 bmnew
.from_mesh(mesh
)
1342 self
.deselectEdges(bmnew
)
1343 for selectedEdge
in edges
:
1344 for e
in bmnew
.edges
:
1345 if (e
.verts
[0].co
- selectedEdge
.verts
[0].co
).length
<= self
.threshold \
1346 and (e
.verts
[1].co
- selectedEdge
.verts
[1].co
).length
<= self
.threshold
:
1349 bpy
.ops
.object.mode_set(mode
='OBJECT')
1352 bpy
.ops
.object.mode_set(mode
='EDIT')
1354 def deselectEdges(self
, bm
):
1355 for edge
in bm
.edges
:
1356 edge
.select_set(False)
1358 def getSpinAxis(self
, plane
):
1368 bpy
.utils
.register_class(EdgeRoundifier
)
1372 bpy
.utils
.unregister_class(EdgeRoundifier
)
1375 if __name__
== "__main__":