1 # SPDX-FileCopyrightText: 2019-2022 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
6 "name": "Edge Roundifier",
7 "author": "Piotr Komisarczyk (komi3D), PKHG",
10 "location": "SPACE > Edge Roundifier or CTRL-E > "
11 "Edge Roundifier or Tools > Addons > Edge Roundifier",
12 "description": "Mesh editing script allowing edge rounding",
19 from bpy
.types
import Operator
20 from bpy
.props
import (
28 radians
, degrees
, sin
,
30 from mathutils
import (
40 SPIN_END_THRESHOLD
= 0.001
41 LINE_TOLERANCE
= 0.0001
45 d_Radius_Angle
= False
49 d_Selected_edges
= False
50 d_Rotate_Around_Spin_Center
= False
56 # for debugging PKHG #
57 def debugPrintNew(debugs
, *text
):
59 tmp
= [el
for el
in text
]
64 # Geometry and math calculation methods #
66 class CalculationHelper
:
72 def getLineCoefficientsPerpendicularToVectorInPoint(self
, point
, vector
, plane
):
74 xVector
, yVector
, zVector
= vector
75 destinationPoint
= (x
+ yVector
, y
- xVector
, z
)
77 destinationPoint
= (x
, y
+ zVector
, z
- yVector
)
79 destinationPoint
= (x
+ zVector
, y
, z
- xVector
)
80 return self
.getCoefficientsForLineThrough2Points(point
, destinationPoint
, plane
)
82 def getQuadraticRoots(self
, coef
):
84 return None # Replaced NaN with None
87 delta
= b
** 2 - 4 * a
* c
94 x1
= (-b
- sqrt(delta
)) / (2 * a
)
95 x2
= (-b
+ sqrt(delta
)) / (2 * a
)
98 def getCoefficientsForLineThrough2Points(self
, point1
, point2
, plane
):
102 # mapping x1,x2, y1,y2 to proper values based on plane
112 # Further calculations the same as for XY plane
115 debugPrintNew(d_XABS_YABS
, "XABS = " + str(xabs
) + " YABS = " + str(yabs
))
117 if xabs
<= LINE_TOLERANCE
:
118 return None # this means line x = edgeCenterX
119 if yabs
<= LINE_TOLERANCE
:
123 A
= (y2
- y1
) / (x2
- x1
)
127 def getLineCircleIntersections(self
, lineAB
, circleMidPoint
, radius
):
128 # (x - a)**2 + (y - b)**2 = r**2 - circle equation
129 # y = A*x + B - line equation
130 # f * x**2 + g * x + h = 0 - quadratic equation
132 a
, b
= circleMidPoint
134 g
= -2 * a
+ 2 * A
* B
- 2 * A
* b
135 h
= (B
** 2) - 2 * b
* B
- (radius
** 2) + (a
** 2) + (b
** 2)
137 roots
= self
.getQuadraticRoots(coef
)
138 if roots
is not None:
141 point1
= [x1
, A
* x1
+ B
]
142 point2
= [x2
, A
* x2
+ B
]
143 return [point1
, point2
]
147 def getLineCircleIntersectionsWhenXPerpendicular(self
, edgeCenter
,
148 circleMidPoint
, radius
, plane
):
149 # (x - a)**2 + (y - b)**2 = r**2 - circle equation
150 # x = xValue - line equation
151 # f * x**2 + g * x + h = 0 - quadratic equation
152 xValue
= edgeCenter
[0]
154 xValue
= edgeCenter
[1]
156 xValue
= edgeCenter
[0]
158 a
, b
= circleMidPoint
161 h
= (a
** 2) + (b
** 2) + (xValue
** 2) - 2 * a
* xValue
- (radius
** 2)
163 roots
= self
.getQuadraticRoots(coef
)
164 if roots
is not None:
167 point1
= [xValue
, y1
]
168 point2
= [xValue
, y2
]
169 return [point1
, point2
]
173 # point1 is the point near 90 deg angle
174 def getAngle(self
, point1
, point2
, point3
):
175 distance1
= (Vector(point1
) - Vector(point2
)).length
176 distance2
= (Vector(point2
) - Vector(point3
)).length
177 cos
= distance1
/ distance2
179 if abs(cos
) > 1: # prevents Domain Error
183 return (alpha
, degrees(alpha
))
185 # get two of three coordinates used for further calculation of spin center
186 # PKHG>nice if rescriction to these 3 types or planes is to be done
187 # komi3D> from 0.0.2 there is a restriction. In future I would like Edge
188 # komi3D> Roundifier to work on Normal and View coordinate systems
189 def getCircleMidPointOnPlane(self
, V1
, plane
):
200 def getEdgeReference(self
, edge
, edgeCenter
, plane
):
201 vert1
= edge
.verts
[1].co
202 V
= vert1
- edgeCenter
203 orthoVector
= Vector((V
[1], -V
[0], V
[2]))
205 orthoVector
= Vector((V
[2], V
[1], -V
[0]))
207 orthoVector
= Vector((V
[0], V
[2], -V
[1]))
208 refPoint
= edgeCenter
+ orthoVector
212 # Selection Methods #
214 class SelectionHelper
:
216 def selectVertexInMesh(self
, mesh
, vertex
):
217 bpy
.ops
.object.mode_set(mode
="OBJECT")
218 for v
in mesh
.vertices
:
223 bpy
.ops
.object.mode_set(mode
="EDIT")
225 def getSelectedVertex(self
, mesh
):
226 bpy
.ops
.object.mode_set(mode
="OBJECT")
227 for v
in mesh
.vertices
:
229 bpy
.ops
.object.mode_set(mode
="EDIT")
232 bpy
.ops
.object.mode_set(mode
="EDIT")
235 def refreshMesh(self
, bm
, mesh
):
236 bpy
.ops
.object.mode_set(mode
='OBJECT')
238 bpy
.ops
.object.mode_set(mode
='EDIT')
243 class EdgeRoundifier(Operator
):
244 bl_idname
= "mesh.edge_roundifier"
245 bl_label
= "Edge Roundifier"
246 bl_description
= "Mesh modeling tool for building arcs on selected Edges"
247 bl_options
= {'REGISTER', 'UNDO', 'PRESET'}
252 edgeScaleFactor
: FloatProperty(
254 description
="Set the Factor of scaling",
256 min=0.00001, max=100000.0,
262 description
="User Defined arc steepness by a Radius\n"
263 "Enabled only if Entry mode is set to Radius\n",
265 min=0.00001, max=1000.0,
271 description
="User defined arc steepness calculated from an Angle\n"
272 "Enabled only if Entry mode is set to Angle and\n"
273 "Angle presets is set Other",
281 description
="Arc subdivision level",
288 description
="If True, flip the side of the selected edges where the arcs are drawn",
291 invertAngle
: BoolProperty(
293 description
="If True, uses an inverted angle to draw the arc (360 degrees - angle)",
296 fullCircles
: BoolProperty(
298 description
="If True, uses an angle of 360 degrees to draw the arcs",
301 bothSides
: BoolProperty(
303 description
="If True, draw arcs on both sides of the selected edges",
306 drawArcCenters
: BoolProperty(
308 description
="If True, draws a vertex for each spin center",
311 removeEdges
: BoolProperty(
313 description
="If True removes the Original selected edges",
316 removeScaledEdges
: BoolProperty(
318 description
="If True removes the Scaled edges (not part of the arcs)",
321 connectArcWithEdge
: BoolProperty(
323 description
="Connect Arcs to Edges",
326 connectArcs
: BoolProperty(
328 description
="Connect subsequent Arcs",
331 connectScaledAndBase
: BoolProperty(
332 name
="Scaled - Base Edge",
333 description
="Connect Scaled to Base Edge",
336 connectArcsFlip
: BoolProperty(
338 description
="Flip the connection of subsequent Arcs",
341 connectArcWithEdgeFlip
: BoolProperty(
342 name
="Flip Arc - Edge",
343 description
="Flip the connection of the Arcs to Edges",
346 axisAngle
: FloatProperty(
348 description
="Rotate Arc around the perpendicular axis",
350 min=-180.0, max=180.0,
354 edgeAngle
: FloatProperty(
356 description
="Rotate Arc around the Edge (Edge acts like as the axis)",
358 min=-180.0, max=180.0,
362 offset
: FloatProperty(
364 description
="Offset Arc perpendicular the Edge",
366 min=-1000000.0, max=1000000.0,
370 offset2
: FloatProperty(
372 description
="Offset Arc in parallel to the Edge",
374 min=-1000000.0, max=1000000.0,
378 ellipticFactor
: FloatProperty(
380 description
="Make Arc elliptic",
382 min=-1000000.0, max=1000000.0,
386 workModeItems
= [("Normal", "Normal", ""), ("Reset", "Reset", "")]
387 workMode
: EnumProperty(
391 description
="Normal work with the current given parameters set by the user\n"
392 "Reset - changes back the parameters to their default values"
394 entryModeItems
= [("Radius", "Radius", ""), ("Angle", "Angle", "")]
395 entryMode
: EnumProperty(
396 items
=entryModeItems
,
399 description
="Entry mode switch between Angle and Radius\n"
400 "If Angle is selected, arc radius is calculated from it"
402 rotateCenterItems
= [
403 ("Spin", "Spin", ""), ("V1", "V1", ""),
404 ("Edge", "Edge", ""), ("V2", "V2", "")
406 rotateCenter
: EnumProperty(
407 items
=rotateCenterItems
,
410 description
="Rotate center for spin axis rotate"
412 arcModeItems
= [("FullEdgeArc", "Full", "Full"), ('HalfEdgeArc', "Half", "Half")]
413 arcMode
: EnumProperty(
416 default
='FullEdgeArc',
417 description
="Arc mode - switch between Full and Half arcs"
420 ('Other', "Other", "User defined angle"), ('180', "180", "HemiCircle (2 sides)"),
421 ('120', "120", "TriangleCircle (3 sides)"), ('90', "90", "QuadCircle (4 sides)"),
422 ('72', "72", "PentagonCircle (5 sides)"), ('60', "60", "HexagonCircle (6 sides)"),
423 ('45', "45", "OctagonCircle (8 sides)"), ('30', "30", "DodecagonCircle (12 sides)")
425 angleEnum
: EnumProperty(
429 description
="Presets prepare standard angles and calculate proper ray"
431 refItems
= [('ORG', "Origin", "Use Origin Location"), ('CUR', "3D Cursor", "Use 3DCursor Location"),
432 ('EDG', "Edge", "Use Individual Edge Reference")]
433 referenceLocation
: EnumProperty(
437 description
="Reference location used to calculate initial centers of drawn arcs"
440 (XY
, "XY", "XY Plane (Z=0)"),
441 (YZ
, "YZ", "YZ Plane (X=0)"),
442 (XZ
, "XZ", "XZ Plane (Y=0)")
444 planeEnum
: EnumProperty(
448 description
="Plane used to calculate spin plane of drawn arcs"
450 edgeScaleCenterItems
= [
451 ('V1', "V1", "v1 - First Edge's Vertex"),
452 ('CENTER', "Center", "Center of the Edge"),
453 ('V2', "V2", "v2 - Second Edge's Vertex")
455 edgeScaleCenterEnum
: EnumProperty(
456 items
=edgeScaleCenterItems
,
457 name
="Edge scale center",
459 description
="Center used for scaling the initial edge"
462 calc
= CalculationHelper()
463 sel
= SelectionHelper()
466 def poll(cls
, context
):
467 obj
= context
.active_object
468 return (obj
and obj
.type == 'MESH' and
471 def prepareMesh(self
, context
):
472 bpy
.ops
.object.mode_set(mode
='OBJECT')
473 bpy
.ops
.object.mode_set(mode
='EDIT')
475 mesh
= context
.view_layer
.objects
.active
.data
479 edges
= [ele
for ele
in bm
.edges
if ele
.select
]
480 return edges
, mesh
, bm
482 def prepareParameters(self
):
483 parameters
= {"a": "a"}
484 parameters
["arcMode"] = self
.arcMode
485 parameters
["edgeScaleFactor"] = self
.edgeScaleFactor
486 parameters
["edgeScaleCenterEnum"] = self
.edgeScaleCenterEnum
487 parameters
["plane"] = self
.planeEnum
488 parameters
["radius"] = self
.r
489 parameters
["angle"] = self
.a
490 parameters
["segments"] = self
.n
491 parameters
["fullCircles"] = self
.fullCircles
492 parameters
["invertAngle"] = self
.invertAngle
493 parameters
["bothSides"] = self
.bothSides
494 parameters
["angleEnum"] = self
.angleEnum
495 parameters
["entryMode"] = self
.entryMode
496 parameters
["workMode"] = self
.workMode
497 parameters
["refObject"] = self
.referenceLocation
498 parameters
["flip"] = self
.flip
499 parameters
["drawArcCenters"] = self
.drawArcCenters
500 parameters
["removeEdges"] = self
.removeEdges
501 parameters
["removeScaledEdges"] = self
.removeScaledEdges
502 parameters
["connectArcWithEdge"] = self
.connectArcWithEdge
503 parameters
["connectScaledAndBase"] = self
.connectScaledAndBase
504 parameters
["connectArcs"] = self
.connectArcs
505 parameters
["connectArcsFlip"] = self
.connectArcsFlip
506 parameters
["connectArcWithEdgeFlip"] = self
.connectArcWithEdgeFlip
507 parameters
["axisAngle"] = self
.axisAngle
508 parameters
["edgeAngle"] = self
.edgeAngle
509 parameters
["offset"] = self
.offset
510 parameters
["offset2"] = self
.offset2
511 parameters
["ellipticFactor"] = self
.ellipticFactor
512 parameters
["rotateCenter"] = self
.rotateCenter
515 def draw(self
, context
):
520 self
.addEnumParameterToUI(box
, False, uiPercentage
, 'Mode:', 'workMode')
521 self
.addEnumParameterToUI(box
, False, uiPercentage
, 'Plane:', 'planeEnum')
522 self
.addEnumParameterToUI(box
, False, uiPercentage
, 'Reference:', 'referenceLocation')
525 self
.addEnumParameterToUI(box
, False, uiPercentage
, 'Scale base:', 'edgeScaleCenterEnum')
526 self
.addParameterToUI(box
, False, uiPercentage
, 'Scale factor:', 'edgeScaleFactor')
529 self
.addEnumParameterToUI(box
, False, uiPercentage
, 'Entry mode:', 'entryMode')
531 row
= box
.row(align
=False)
532 row
.prop(self
, 'angleEnum', expand
=True, text
="Angle presets")
534 disable_a
= bool(self
.entryMode
== 'Angle' and self
.angleEnum
== 'Other')
535 disable_r
= bool(self
.entryMode
== 'Radius')
537 self
.addParameterToUI(box
, False, uiPercentage
, 'Angle:', 'a', disable_a
)
538 self
.addParameterToUI(box
, False, uiPercentage
, 'Radius:', 'r', disable_r
)
539 self
.addParameterToUI(box
, False, uiPercentage
, 'Segments:', 'n')
542 self
.addCheckboxToUI(box
, True, 'Options:', 'flip', 'invertAngle')
543 self
.addCheckboxToUI(box
, True, '', 'bothSides', 'fullCircles')
544 self
.addCheckboxToUI(box
, True, '', 'drawArcCenters')
547 self
.addCheckboxToUI(box
, True, 'Remove:', 'removeEdges', 'removeScaledEdges')
550 self
.addCheckboxToUI(box
, True, 'Connect:', 'connectArcs', 'connectArcsFlip')
551 self
.addCheckboxToUI(box
, True, '', 'connectArcWithEdge', 'connectArcWithEdgeFlip')
552 self
.addCheckboxToUI(box
, True, '', 'connectScaledAndBase')
555 self
.addParameterToUI(box
, False, uiPercentage
, 'Orhto offset:', 'offset')
556 self
.addParameterToUI(box
, False, uiPercentage
, 'Parallel offset:', 'offset2')
559 self
.addParameterToUI(box
, False, uiPercentage
, 'Edge rotate :', 'edgeAngle')
560 self
.addEnumParameterToUI(box
, False, uiPercentage
, 'Axis rotate center:', 'rotateCenter')
561 self
.addParameterToUI(box
, False, uiPercentage
, 'Axis rotate:', 'axisAngle')
564 self
.addParameterToUI(box
, False, uiPercentage
, 'Elliptic factor:', 'ellipticFactor')
566 def addParameterToUI(self
, layout
, alignment
, percent
, label
, properties
, disable
=True):
567 row
= layout
.row(align
=alignment
)
568 split
= row
.split(factor
=percent
)
571 col
.label(text
=label
)
572 col2
= split
.column()
573 row
= col2
.row(align
=alignment
)
574 row
.enabled
= disable
575 row
.prop(self
, properties
)
577 def addCheckboxToUI(self
, layout
, alignment
, label
, property1
, property2
=None):
578 if label
not in (""):
580 row
.label(text
=label
)
581 row2
= layout
.row(align
=alignment
)
583 split
= row2
.split(factor
=0.5)
584 split
.prop(self
, property1
, toggle
=True)
585 split
.prop(self
, property2
, toggle
=True)
587 row2
.prop(self
, property1
, toggle
=True)
590 def addEnumParameterToUI(self
, layout
, alignment
, percent
, label
, properties
):
591 row
= layout
.row(align
=alignment
)
592 split
= row
.split(factor
=percent
)
595 col
.label(text
=label
)
596 col2
= split
.column()
597 row
= col2
.row(align
=alignment
)
598 row
.prop(self
, properties
, expand
=True, text
="a")
600 def execute(self
, context
):
602 edges
, mesh
, bm
= self
.prepareMesh(context
)
603 parameters
= self
.prepareParameters()
605 self
.resetValues(parameters
["workMode"])
607 self
.obj
= context
.view_layer
.objects
.active
608 scaledEdges
= self
.scaleDuplicatedEdges(bm
, edges
, parameters
)
610 if len(scaledEdges
) > 0:
611 self
.roundifyEdges(scaledEdges
, parameters
, bm
, mesh
)
613 if parameters
["connectScaledAndBase"]:
614 self
.connectScaledEdgesWithBaseEdge(scaledEdges
, edges
, bm
, mesh
)
616 self
.sel
.refreshMesh(bm
, mesh
)
617 self
.selectEdgesAfterRoundifier(context
, scaledEdges
)
619 debugPrintNew(True, "No edges selected!")
621 if parameters
["removeEdges"]:
622 bmesh
.ops
.delete(bm
, geom
=edges
, context
='EDGES')
624 if parameters
["removeScaledEdges"] and self
.edgeScaleFactor
!= 1.0:
625 bmesh
.ops
.delete(bm
, geom
=scaledEdges
, context
='EDGES')
627 bpy
.ops
.object.mode_set(mode
='OBJECT')
629 bpy
.ops
.object.mode_set(mode
='EDIT')
630 bpy
.ops
.mesh
.select_all(action
='SELECT')
631 bpy
.ops
.mesh
.remove_doubles()
637 def resetValues(self
, workMode
):
638 if workMode
== "Reset":
639 self
.setAllParamsToDefaults()
641 def setAllParamsToDefaults(self
):
643 self
.edgeScaleFactor
= 1.0
648 self
.invertAngle
= False
649 self
.fullCircles
= False
650 self
.bothSides
= False
651 self
.drawArcCenters
= False
652 self
.removeEdges
= False
653 self
.removeScaledEdges
= False
655 self
.connectArcWithEdge
= False
656 self
.connectArcs
= False
657 self
.connectScaledAndBase
= False
658 self
.connectArcsFlip
= False
659 self
.connectArcWithEdgeFlip
= False
665 self
.ellipticFactor
= 0.0
667 self
.workMode
= 'Normal'
668 self
.entryMode
= 'Angle'
669 self
.angleEnum
= '180'
670 self
.referenceLocation
= 'ORG'
671 self
.planeEnum
= 'XY'
672 self
.edgeScaleCenterEnum
= 'CENTER'
673 self
.rotateCenter
= 'Edge'
675 self
.report({'INFO'}, "The parameters have been reset to default values")
676 except Exception as e
:
677 self
.report({'WARNING'}, "The parameters could not be reset")
678 debugPrintNew(True, "\n[setAllParamsToDefaults]\n parameter reset error\n" + e
)
680 def scaleDuplicatedEdges(self
, bm
, edges
, parameters
):
681 scaleCenter
= parameters
["edgeScaleCenterEnum"]
682 factor
= parameters
["edgeScaleFactor"]
683 # this code is based on Zeffi's answer to my question
686 duplicateEdges
= edges
692 if scaleCenter
== 'CENTER':
693 origin
= (v1
+ v2
) * 0.5
694 elif scaleCenter
== 'V1':
696 elif scaleCenter
== 'V2':
699 bmv1
= bm
.verts
.new(((v1
- origin
) * factor
) + origin
)
700 bmv2
= bm
.verts
.new(((v2
- origin
) * factor
) + origin
)
701 bme
= bm
.edges
.new([bmv1
, bmv2
])
702 duplicateEdges
.append(bme
)
703 return duplicateEdges
705 def roundifyEdges(self
, edges
, parameters
, bm
, mesh
):
708 arcVerts
= self
.roundify(e
, parameters
, bm
, mesh
)
709 arcs
.append(arcVerts
)
711 if parameters
["connectArcs"]:
712 self
.connectArcsTogether(arcs
, bm
, mesh
, parameters
)
714 def getNormalizedEdgeVector(self
, edge
):
715 V1
= edge
.verts
[0].co
716 V2
= edge
.verts
[1].co
718 normEdge
= edgeVector
.normalized()
721 def getEdgePerpendicularVector(self
, edge
, plane
):
722 normEdge
= self
.getNormalizedEdgeVector(edge
)
724 edgePerpendicularVector
= Vector((normEdge
[1], -normEdge
[0], 0))
726 edgePerpendicularVector
= Vector((0, normEdge
[2], -normEdge
[1]))
728 edgePerpendicularVector
= Vector((normEdge
[2], 0, -normEdge
[0]))
729 return edgePerpendicularVector
731 def getEdgeInfo(self
, edge
):
732 V1
= edge
.verts
[0].co
733 V2
= edge
.verts
[1].co
735 edgeLength
= edgeVector
.length
736 edgeCenter
= (V2
+ V1
) * 0.5
737 return V1
, V2
, edgeVector
, edgeLength
, edgeCenter
739 def roundify(self
, edge
, parameters
, bm
, mesh
):
740 V1
, V2
, edgeVector
, edgeLength
, edgeCenter
= self
.getEdgeInfo(edge
)
741 if self
.skipThisEdge(V1
, V2
, parameters
["plane"]):
744 roundifyParams
= None
746 roundifyParams
= self
.calculateRoundifyParams(edge
, parameters
, bm
, mesh
)
747 if roundifyParams
is None:
750 arcVerts
= self
.spinAndPostprocess(edge
, parameters
, bm
, mesh
, edgeCenter
, roundifyParams
)
753 def spinAndPostprocess(self
, edge
, parameters
, bm
, mesh
, edgeCenter
, roundifyParams
):
754 spinnedVerts
, roundifyParamsUpdated
= self
.drawSpin(
759 postProcessedArcVerts
= self
.arcPostprocessing(
760 edge
, parameters
, bm
, mesh
,
761 roundifyParamsUpdated
,
762 spinnedVerts
, edgeCenter
764 return postProcessedArcVerts
766 def rotateArcAroundEdge(self
, bm
, mesh
, arcVerts
, parameters
):
767 angle
= parameters
["edgeAngle"]
769 self
.arc_rotator(arcVerts
, angle
, parameters
)
771 # arc_rotator method was created by PKHG, I (komi3D) adjusted it to fit the rest
772 def arc_rotator(self
, arcVerts
, extra_rotation
, parameters
):
773 bpy
.ops
.object.mode_set(mode
='OBJECT')
774 old_location
= self
.obj
.location
.copy()
775 bpy
.ops
.transform
.translate(
777 constraint_axis
=(False, False, False),
778 orient_type
='GLOBAL',
780 use_proportional_edit
=False,
782 bpy
.ops
.object.mode_set(mode
='EDIT')
783 adjust_matrix
= self
.obj
.matrix_parent_inverse
784 bm
= bmesh
.from_edit_mesh(self
.obj
.data
)
785 lastVert
= len(arcVerts
) - 1
786 if parameters
["drawArcCenters"]:
787 lastVert
= lastVert
- 1 # center gets added as last vert of arc
788 v0_old
= adjust_matrix
@ arcVerts
[0].co
.copy()
790 # PKHG>INFO move if necessary v0 to origin such that the axis gos through origin and v1
791 if v0_old
!= Vector((0, 0, 0)):
792 for i
, ele
in enumerate(arcVerts
):
793 arcVerts
[i
].co
+= - v0_old
795 axis
= arcVerts
[0].co
- arcVerts
[lastVert
].co
796 a_mat
= Quaternion(axis
, radians(extra_rotation
)).normalized().to_matrix()
799 ele
.co
= a_mat
@ ele
.co
801 # PKHG>INFO move back if needed
802 if v0_old
!= Vector((0, 0, 0)):
803 for i
, ele
in enumerate(arcVerts
):
804 arcVerts
[i
].co
+= + v0_old
806 bpy
.ops
.object.mode_set(mode
='OBJECT')
807 # PKHG>INFO move origin object back print("old location = " , old_location)
808 bpy
.ops
.transform
.translate(
810 constraint_axis
=(False, False, False),
811 orient_type
='GLOBAL',
813 use_proportional_edit
=False,
815 bpy
.ops
.object.mode_set(mode
='EDIT')
817 def makeElliptic(self
, bm
, mesh
, arcVertices
, parameters
):
818 if parameters
["ellipticFactor"] != 0: # if 0 then nothing has to be done
819 lastVert
= len(arcVertices
) - 1
820 if parameters
["drawArcCenters"]:
821 lastVert
= lastVert
- 1 # center gets added as last vert of arc
822 v0co
= arcVertices
[0].co
823 v1co
= arcVertices
[lastVert
].co
825 for vertex
in arcVertices
: # range(len(res_list)):
826 # PKHg>INFO compute the base on the edge of the height-vector
827 top
= vertex
.co
# res_list[nr].co
830 t
= (v1co
- v0co
).dot(top
- v0co
) / (v1co
- v0co
).length
** 2
831 h_bottom
= v0co
+ t
* (v1co
- v0co
)
832 height
= (h_bottom
- top
)
833 vertex
.co
= top
+ parameters
["ellipticFactor"] * height
837 def arcPostprocessing(self
, edge
, parameters
, bm
, mesh
, roundifyParams
, spinnedVerts
, edgeCenter
):
838 [chosenSpinCenter
, otherSpinCenter
, spinAxis
, angle
, steps
, refObjectLocation
] = roundifyParams
840 if parameters
["rotateCenter"] == 'Edge':
841 rotatedVerts
= self
.rotateArcAroundSpinAxis(
842 bm
, mesh
, spinnedVerts
, parameters
, edgeCenter
844 elif parameters
["rotateCenter"] == 'Spin':
845 rotatedVerts
= self
.rotateArcAroundSpinAxis(
846 bm
, mesh
, spinnedVerts
, parameters
, chosenSpinCenter
848 elif parameters
["rotateCenter"] == 'V1':
849 rotatedVerts
= self
.rotateArcAroundSpinAxis(
850 bm
, mesh
, spinnedVerts
, parameters
, edge
.verts
[0].co
852 elif parameters
["rotateCenter"] == 'V2':
853 rotatedVerts
= self
.rotateArcAroundSpinAxis(
854 bm
, mesh
, spinnedVerts
, parameters
, edge
.verts
[1].co
857 offsetVerts
= self
.offsetArcPerpendicular(
858 bm
, mesh
, rotatedVerts
, edge
, parameters
860 offsetVerts2
= self
.offsetArcParallel(
861 bm
, mesh
, offsetVerts
, edge
, parameters
863 ellipticVerts
= self
.makeElliptic(
864 bm
, mesh
, offsetVerts2
, parameters
866 self
.rotateArcAroundEdge(bm
, mesh
, ellipticVerts
, parameters
)
868 if parameters
["connectArcWithEdge"]:
869 self
.connectArcTogetherWithEdge(
870 edge
, offsetVerts2
, bm
, mesh
, parameters
874 def connectArcTogetherWithEdge(self
, edge
, arcVertices
, bm
, mesh
, parameters
):
875 lastVert
= len(arcVertices
) - 1
876 if parameters
["drawArcCenters"]:
877 lastVert
= lastVert
- 1 # center gets added as last vert of arc
878 edgeV1
= edge
.verts
[0].co
879 edgeV2
= edge
.verts
[1].co
880 arcV1
= arcVertices
[0].co
881 arcV2
= arcVertices
[lastVert
].co
883 bmv1
= bm
.verts
.new(edgeV1
)
884 bmv2
= bm
.verts
.new(arcV1
)
886 bmv3
= bm
.verts
.new(edgeV2
)
887 bmv4
= bm
.verts
.new(arcV2
)
889 if parameters
["connectArcWithEdgeFlip"] is False:
890 bme
= bm
.edges
.new([bmv1
, bmv2
])
891 bme2
= bm
.edges
.new([bmv3
, bmv4
])
893 bme
= bm
.edges
.new([bmv1
, bmv4
])
894 bme2
= bm
.edges
.new([bmv3
, bmv2
])
895 self
.sel
.refreshMesh(bm
, mesh
)
897 def connectScaledEdgesWithBaseEdge(self
, scaledEdges
, baseEdges
, bm
, mesh
):
898 for i
in range(0, len(scaledEdges
)):
899 scaledEdgeV1
= scaledEdges
[i
].verts
[0].co
900 baseEdgeV1
= baseEdges
[i
].verts
[0].co
901 scaledEdgeV2
= scaledEdges
[i
].verts
[1].co
902 baseEdgeV2
= baseEdges
[i
].verts
[1].co
904 bmv1
= bm
.verts
.new(baseEdgeV1
)
905 bmv2
= bm
.verts
.new(scaledEdgeV1
)
906 bme
= bm
.edges
.new([bmv1
, bmv2
])
908 bmv3
= bm
.verts
.new(scaledEdgeV2
)
909 bmv4
= bm
.verts
.new(baseEdgeV2
)
910 bme
= bm
.edges
.new([bmv3
, bmv4
])
911 self
.sel
.refreshMesh(bm
, mesh
)
913 def connectArcsTogether(self
, arcs
, bm
, mesh
, parameters
):
914 for i
in range(0, len(arcs
) - 1):
915 # in case on XZ or YZ there are no arcs drawn
916 if arcs
[i
] is None or arcs
[i
+ 1] is None:
919 lastVert
= len(arcs
[i
]) - 1
920 if parameters
["drawArcCenters"]:
921 lastVert
= lastVert
- 1 # center gets added as last vert of arc
922 # take last vert of arc i and first vert of arc i+1
924 V1
= arcs
[i
][lastVert
].co
925 V2
= arcs
[i
+ 1][0].co
927 if parameters
["connectArcsFlip"]:
929 V2
= arcs
[i
+ 1][lastVert
].co
931 bmv1
= bm
.verts
.new(V1
)
932 bmv2
= bm
.verts
.new(V2
)
933 bme
= bm
.edges
.new([bmv1
, bmv2
])
935 # connect last arc and first one
936 lastArcId
= len(arcs
) - 1
937 lastVertIdOfLastArc
= len(arcs
[lastArcId
]) - 1
938 if parameters
["drawArcCenters"]:
939 # center gets added as last vert of arc
940 lastVertIdOfLastArc
= lastVertIdOfLastArc
- 1
942 V1
= arcs
[lastArcId
][lastVertIdOfLastArc
].co
944 if parameters
["connectArcsFlip"]:
945 V1
= arcs
[lastArcId
][0].co
946 V2
= arcs
[0][lastVertIdOfLastArc
].co
948 bmv1
= bm
.verts
.new(V1
)
949 bmv2
= bm
.verts
.new(V2
)
950 bme
= bm
.edges
.new([bmv1
, bmv2
])
952 self
.sel
.refreshMesh(bm
, mesh
)
954 def offsetArcPerpendicular(self
, bm
, mesh
, Verts
, edge
, parameters
):
955 perpendicularVector
= self
.getEdgePerpendicularVector(edge
, parameters
["plane"])
956 offset
= parameters
["offset"]
957 translation
= offset
* perpendicularVector
960 bmesh
.ops
.translate(bm
, verts
=Verts
, vec
=translation
)
962 print("[Edge Roundifier]: Perpendicular translate value error - "
963 "multiple vertices in list - try unchecking 'Centers'")
965 indexes
= [v
.index
for v
in Verts
]
966 self
.sel
.refreshMesh(bm
, mesh
)
967 offsetVertices
= [bm
.verts
[i
] for i
in indexes
]
968 return offsetVertices
970 def offsetArcParallel(self
, bm
, mesh
, Verts
, edge
, parameters
):
971 edgeVector
= self
.getNormalizedEdgeVector(edge
)
972 offset
= parameters
["offset2"]
973 translation
= offset
* edgeVector
976 bmesh
.ops
.translate(bm
, verts
=Verts
, vec
=translation
)
978 print("[Edge Roundifier]: Parallel translate value error - "
979 "multiple vertices in list - try unchecking 'Centers'")
981 indexes
= [v
.index
for v
in Verts
]
982 self
.sel
.refreshMesh(bm
, mesh
)
983 offsetVertices
= [bm
.verts
[i
] for i
in indexes
]
984 return offsetVertices
986 def skipThisEdge(self
, V1
, V2
, plane
):
987 # Check If It is possible to spin selected verts on this plane if not exit roundifier
989 if (V1
[0] == V2
[0] and V1
[1] == V2
[1]):
992 if (V1
[1] == V2
[1] and V1
[2] == V2
[2]):
995 if (V1
[0] == V2
[0] and V1
[2] == V2
[2]):
999 def calculateRoundifyParams(self
, edge
, parameters
, bm
, mesh
):
1000 # Because all data from mesh is in local coordinates
1001 # and spin operator works on global coordinates
1002 # We first need to translate all input data by vector equal
1003 # to origin position and then perform calculations
1004 # At least that is my understanding :) <komi3D>
1006 # V1 V2 stores Local Coordinates
1007 V1
, V2
, edgeVector
, edgeLength
, edgeCenter
= self
.getEdgeInfo(edge
)
1009 debugPrintNew(d_Plane
, "PLANE: " + parameters
["plane"])
1010 lineAB
= self
.calc
.getLineCoefficientsPerpendicularToVectorInPoint(
1011 edgeCenter
, edgeVector
,
1015 circleMidPointOnPlane
= self
.calc
.getCircleMidPointOnPlane(
1016 V1
, parameters
["plane"]
1018 radius
= parameters
["radius"]
1021 if (parameters
["entryMode"] == 'Angle'):
1022 if (parameters
["angleEnum"] != 'Other'):
1023 radius
, angle
= self
.CalculateRadiusAndAngleForAnglePresets(
1024 parameters
["angleEnum"], radius
,
1028 radius
, angle
= self
.CalculateRadiusAndAngle(edgeLength
)
1029 debugPrintNew(d_Radius_Angle
, "RADIUS = " + str(radius
) + " ANGLE = " + str(angle
))
1031 if angle
!= pi
: # mode other than 180
1033 roots
= self
.calc
.getLineCircleIntersectionsWhenXPerpendicular(
1034 edgeCenter
, circleMidPointOnPlane
,
1035 radius
, parameters
["plane"]
1038 roots
= self
.calc
.getLineCircleIntersections(
1039 lineAB
, circleMidPointOnPlane
, radius
1044 "[Edge Roundifier]: No centers were found. Change radius to higher value")
1046 roots
= self
.addMissingCoordinate(roots
, V1
, parameters
["plane"]) # adds X, Y or Z coordinate
1048 roots
= [edgeCenter
, edgeCenter
]
1049 debugPrintNew(d_Roots
, "roots=" + str(roots
))
1051 refObjectLocation
= None
1052 objectLocation
= bpy
.context
.active_object
.location
# Origin Location
1054 if parameters
["refObject"] == "ORG":
1055 refObjectLocation
= [0, 0, 0]
1056 elif parameters
["refObject"] == "CUR":
1057 refObjectLocation
= bpy
.context
.scene
.cursor
.location
- objectLocation
1059 refObjectLocation
= self
.calc
.getEdgeReference(edge
, edgeCenter
, parameters
["plane"])
1061 debugPrintNew(d_RefObject
, parameters
["refObject"], refObjectLocation
)
1062 chosenSpinCenter
, otherSpinCenter
= self
.getSpinCenterClosestToRefCenter(
1063 refObjectLocation
, roots
1066 if (parameters
["entryMode"] == "Radius"):
1067 halfAngle
= self
.calc
.getAngle(edgeCenter
, chosenSpinCenter
, circleMidPoint
)
1068 angle
= 2 * halfAngle
[0] # in radians
1069 self
.a
= degrees(angle
) # in degrees
1071 spinAxis
= self
.getSpinAxis(parameters
["plane"])
1072 steps
= parameters
["segments"]
1073 angle
= -angle
# rotate clockwise by default
1075 return [chosenSpinCenter
, otherSpinCenter
, spinAxis
, angle
, steps
, refObjectLocation
]
1077 def drawSpin(self
, edge
, edgeCenter
, roundifyParams
, parameters
, bm
, mesh
):
1078 [chosenSpinCenter
, otherSpinCenter
, spinAxis
, angle
, steps
, refObjectLocation
] = roundifyParams
1080 v0org
, v1org
= (edge
.verts
[0], edge
.verts
[1])
1082 if parameters
["flip"]:
1084 spinCenterTemp
= chosenSpinCenter
1085 chosenSpinCenter
= otherSpinCenter
1086 otherSpinCenter
= spinCenterTemp
1088 if(parameters
["invertAngle"]):
1090 angle
= two_pi
+ angle
1092 angle
= -two_pi
+ angle
1096 if(parameters
["fullCircles"]):
1099 v0
= bm
.verts
.new(v0org
.co
)
1101 result
= bmesh
.ops
.spin(
1102 bm
, geom
=[v0
], cent
=chosenSpinCenter
, axis
=spinAxis
,
1103 angle
=angle
, steps
=steps
, use_duplicate
=False
1106 # it seems there is something wrong with last index of this spin
1107 # I need to calculate the last index manually here
1108 vertsLength
= len(bm
.verts
)
1109 bm
.verts
.ensure_lookup_table()
1110 lastVertIndex
= bm
.verts
[vertsLength
- 1].index
1111 lastSpinVertIndices
= self
.getLastSpinVertIndices(steps
, lastVertIndex
)
1113 self
.sel
.refreshMesh(bm
, mesh
)
1115 alternativeLastSpinVertIndices
= []
1116 bothSpinVertices
= []
1120 if ((angle
== pi
or angle
== -pi
) and not parameters
["bothSides"]):
1122 midVertexIndex
= lastVertIndex
- round(steps
/ 2)
1123 bm
.verts
.ensure_lookup_table()
1124 midVert
= bm
.verts
[midVertexIndex
].co
1126 midVertexDistance
= (Vector(refObjectLocation
) - Vector(midVert
)).length
1127 midEdgeDistance
= (Vector(refObjectLocation
) - Vector(edgeCenter
)).length
1129 if ((parameters
["invertAngle"]) or (parameters
["flip"])):
1130 if (midVertexDistance
> midEdgeDistance
):
1131 alternativeLastSpinVertIndices
= self
.alternateSpin(
1132 bm
, mesh
, angle
, chosenSpinCenter
,
1133 spinAxis
, steps
, v0
, v1org
, lastSpinVertIndices
1136 if (midVertexDistance
< midEdgeDistance
):
1137 alternativeLastSpinVertIndices
= self
.alternateSpin(
1138 bm
, mesh
, angle
, chosenSpinCenter
,
1139 spinAxis
, steps
, v0
, v1org
, lastSpinVertIndices
1141 elif (angle
!= two_pi
): # to allow full circles
1142 if (result
['geom_last'][0].co
- v1org
.co
).length
> SPIN_END_THRESHOLD
:
1143 alternativeLastSpinVertIndices
= self
.alternateSpin(
1144 bm
, mesh
, angle
, chosenSpinCenter
,
1145 spinAxis
, steps
, v0
, v1org
, lastSpinVertIndices
1149 self
.sel
.refreshMesh(bm
, mesh
)
1150 if alternativeLastSpinVertIndices
!= []:
1151 lastSpinVertIndices
= alternativeLastSpinVertIndices
1153 if lastSpinVertIndices
.stop
<= len(bm
.verts
): # make sure arc was added to bmesh
1154 spinVertices
= [bm
.verts
[i
] for i
in lastSpinVertIndices
]
1155 if alternativeLastSpinVertIndices
!= []:
1156 spinVertices
= spinVertices
+ [v0
]
1158 spinVertices
= [v0
] + spinVertices
1160 if (parameters
["bothSides"]):
1161 # do some more testing here!!!
1162 if (angle
== pi
or angle
== -pi
):
1163 alternativeLastSpinVertIndices
= self
.alternateSpinNoDelete(
1164 bm
, mesh
, -angle
, chosenSpinCenter
,
1165 spinAxis
, steps
, v0
, v1org
, []
1168 alternativeLastSpinVertIndices
= self
.alternateSpinNoDelete(
1169 bm
, mesh
, angle
, otherSpinCenter
,
1170 spinAxis
, steps
, v0
, v1org
, []
1173 alternativeLastSpinVertIndices
= self
.alternateSpinNoDelete(
1174 bm
, mesh
, -angle
, otherSpinCenter
,
1175 spinAxis
, steps
, v0
, v1org
, []
1177 bothSpinVertices
= [bm
.verts
[i
] for i
in lastSpinVertIndices
]
1178 alternativeSpinVertices
= [bm
.verts
[i
] for i
in alternativeLastSpinVertIndices
]
1179 bothSpinVertices
= [v0
] + bothSpinVertices
+ alternativeSpinVertices
1180 spinVertices
= bothSpinVertices
1182 if (parameters
["fullCircles"]):
1183 v1
= bm
.verts
.new(v1org
.co
)
1184 spinVertices
= spinVertices
+ [v1
]
1186 if (parameters
['drawArcCenters']):
1187 centerVert
= bm
.verts
.new(chosenSpinCenter
)
1188 spinVertices
.append(centerVert
)
1190 return spinVertices
, [chosenSpinCenter
, otherSpinCenter
, spinAxis
, angle
, steps
, refObjectLocation
]
1192 def deleteSpinVertices(self
, bm
, mesh
, lastSpinVertIndices
):
1193 verticesForDeletion
= []
1194 bm
.verts
.ensure_lookup_table()
1195 for i
in lastSpinVertIndices
:
1198 debugPrintNew(True, str(i
) + ") " + str(vi
))
1199 verticesForDeletion
.append(vi
)
1201 bmesh
.ops
.delete(bm
, geom
=verticesForDeletion
, context
= 'VERTS')
1202 bmesh
.update_edit_mesh(mesh
, loop_triangles
=True)
1203 bpy
.ops
.object.mode_set(mode
='OBJECT')
1204 bpy
.ops
.object.mode_set(mode
='EDIT')
1206 def alternateSpinNoDelete(self
, bm
, mesh
, angle
, chosenSpinCenter
,
1207 spinAxis
, steps
, v0
, v1org
, lastSpinVertIndices
):
1210 result2
= bmesh
.ops
.spin(bm
, geom
=[v0prim
], cent
=chosenSpinCenter
, axis
=spinAxis
,
1211 angle
=angle
, steps
=steps
, use_duplicate
=False)
1212 vertsLength
= len(bm
.verts
)
1213 bm
.verts
.ensure_lookup_table()
1214 lastVertIndex2
= bm
.verts
[vertsLength
- 1].index
1216 lastSpinVertIndices2
= self
.getLastSpinVertIndices(steps
, lastVertIndex2
)
1217 return lastSpinVertIndices2
1219 def alternateSpin(self
, bm
, mesh
, angle
, chosenSpinCenter
,
1220 spinAxis
, steps
, v0
, v1org
, lastSpinVertIndices
):
1222 self
.deleteSpinVertices(bm
, mesh
, lastSpinVertIndices
)
1225 result2
= bmesh
.ops
.spin(
1226 bm
, geom
=[v0prim
], cent
=chosenSpinCenter
, axis
=spinAxis
,
1227 angle
=-angle
, steps
=steps
, use_duplicate
=False
1229 # it seems there is something wrong with last index of this spin
1230 # I need to calculate the last index manually here
1231 vertsLength
= len(bm
.verts
)
1232 bm
.verts
.ensure_lookup_table()
1233 lastVertIndex2
= bm
.verts
[vertsLength
- 1].index
1235 lastSpinVertIndices2
= self
.getLastSpinVertIndices(steps
, lastVertIndex2
)
1236 # second spin also does not hit the v1org
1237 if (result2
['geom_last'][0].co
- v1org
.co
).length
> SPIN_END_THRESHOLD
:
1239 self
.deleteSpinVertices(bm
, mesh
, lastSpinVertIndices2
)
1240 self
.deleteSpinVertices(bm
, mesh
, range(v0
.index
, v0
.index
+ 1))
1243 return lastSpinVertIndices2
1245 def getLastSpinVertIndices(self
, steps
, lastVertIndex
):
1246 arcfirstVertexIndex
= lastVertIndex
- steps
+ 1
1247 lastSpinVertIndices
= range(arcfirstVertexIndex
, lastVertIndex
+ 1)
1248 return lastSpinVertIndices
1250 def rotateArcAroundSpinAxis(self
, bm
, mesh
, vertices
, parameters
, edgeCenter
):
1251 axisAngle
= parameters
["axisAngle"]
1252 plane
= parameters
["plane"]
1253 # compensate rotation center
1254 objectLocation
= bpy
.context
.active_object
.location
1255 center
= objectLocation
+ edgeCenter
1257 rot
= Euler((0.0, 0.0, radians(axisAngle
)), 'XYZ').to_matrix()
1259 rot
= Euler((radians(axisAngle
), 0.0, 0.0), 'XYZ').to_matrix()
1261 rot
= Euler((0.0, radians(axisAngle
), 0.0), 'XYZ').to_matrix()
1263 indexes
= [v
.index
for v
in vertices
]
1270 space
=bpy
.context
.edit_object
.matrix_world
1272 self
.sel
.refreshMesh(bm
, mesh
)
1273 bm
.verts
.ensure_lookup_table()
1274 rotatedVertices
= [bm
.verts
[i
] for i
in indexes
]
1276 return rotatedVertices
1278 def CalculateRadiusAndAngle(self
, edgeLength
):
1280 angle
= radians(degAngle
)
1281 self
.r
= radius
= edgeLength
/ (2 * sin(angle
/ 2))
1282 return radius
, angle
1284 def CalculateRadiusAndAngleForAnglePresets(self
, angleEnum
, initR
, initA
, edgeLength
):
1288 # Note - define an integer string in the angleEnum
1289 angle_convert
= int(angleEnum
)
1290 self
.a
= angle_convert
1292 self
.a
= 180 # fallback
1294 "CalculateRadiusAndAngleForAnglePresets problem with int conversion")
1296 return self
.CalculateRadiusAndAngle(edgeLength
)
1298 def getSpinCenterClosestToRefCenter(self
, objLocation
, roots
):
1299 root0Distance
= (Vector(objLocation
) - Vector(roots
[0])).length
1300 root1Distance
= (Vector(objLocation
) - Vector(roots
[1])).length
1304 if (root0Distance
> root1Distance
):
1307 return roots
[chosenId
], roots
[rejectedId
]
1309 def addMissingCoordinate(self
, roots
, startVertex
, plane
):
1310 if roots
is not None:
1314 roots
[0] = Vector((a
, b
, startVertex
[2]))
1315 roots
[1] = Vector((c
, d
, startVertex
[2]))
1317 roots
[0] = Vector((startVertex
[0], a
, b
))
1318 roots
[1] = Vector((startVertex
[0], c
, d
))
1320 roots
[0] = Vector((a
, startVertex
[1], b
))
1321 roots
[1] = Vector((c
, startVertex
[1], d
))
1324 def selectEdgesAfterRoundifier(self
, context
, edges
):
1325 bpy
.ops
.object.mode_set(mode
='OBJECT')
1326 bpy
.ops
.object.mode_set(mode
='EDIT')
1327 mesh
= context
.view_layer
.objects
.active
.data
1329 bmnew
.from_mesh(mesh
)
1331 self
.deselectEdges(bmnew
)
1332 for selectedEdge
in edges
:
1333 for e
in bmnew
.edges
:
1334 if (e
.verts
[0].co
- selectedEdge
.verts
[0].co
).length
<= self
.threshold \
1335 and (e
.verts
[1].co
- selectedEdge
.verts
[1].co
).length
<= self
.threshold
:
1338 bpy
.ops
.object.mode_set(mode
='OBJECT')
1341 bpy
.ops
.object.mode_set(mode
='EDIT')
1343 def deselectEdges(self
, bm
):
1344 for edge
in bm
.edges
:
1345 edge
.select_set(False)
1347 def getSpinAxis(self
, plane
):
1357 def poll(cls
, context
):
1358 return (context
.view_layer
.objects
.active
.type == 'MESH') and (context
.view_layer
.objects
.active
.mode
== 'EDIT')
1360 def draw_item(self
, context
):
1361 self
.layout
.operator_context
= 'INVOKE_DEFAULT'
1362 self
.layout
.operator('mesh.edge_roundifier')
1369 reg_cls
, unreg_cls
= bpy
.utils
.register_classes_factory(classes
)
1374 bpy
.types
.VIEW3D_MT_edit_mesh_edges
.append(draw_item
)
1379 bpy
.types
.VIEW3D_MT_edit_mesh_edges
.remove(draw_item
)
1381 if __name__
== "__main__":