1 # ***** BEGIN GPL LICENSE BLOCK *****
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software Foundation,
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 # ***** END GPL LICENCE BLOCK *****
21 "name": "Offset Edges",
22 "author": "Hidesato Ikeya, Veezen fix 2.8 (temporary)",
23 #i tried edit newest version, but got some errors, works only on 0,2,6
25 "blender": (2, 80, 0),
26 "location": "VIEW3D > Edge menu(CTRL-E) > Offset Edges",
27 "description": "Offset Edges",
29 "doc_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/Modeling/offset_edges",
35 from math
import sin
, cos
, pi
, copysign
, radians
37 from bpy_extras
import view3d_utils
39 from mathutils
import Vector
40 from time
import perf_counter
42 X_UP
= Vector((1.0, .0, .0))
43 Y_UP
= Vector((.0, 1.0, .0))
44 Z_UP
= Vector((.0, .0, 1.0))
45 ZERO_VEC
= Vector((.0, .0, .0))
51 def calc_loop_normal(verts
, fallback
=Z_UP
):
52 # Calculate normal from verts using Newell's method.
53 normal
= ZERO_VEC
.copy()
55 if verts
[0] is verts
[-1]:
57 range_verts
= range(1, len(verts
))
60 range_verts
= range(0, len(verts
))
63 v1co
, v2co
= verts
[i
-1].co
, verts
[i
].co
64 normal
.x
+= (v1co
.y
- v2co
.y
) * (v1co
.z
+ v2co
.z
)
65 normal
.y
+= (v1co
.z
- v2co
.z
) * (v1co
.x
+ v2co
.x
)
66 normal
.z
+= (v1co
.x
- v2co
.x
) * (v1co
.y
+ v2co
.y
)
68 if normal
!= ZERO_VEC
:
75 def collect_edges(bm
):
76 set_edges_orig
= set()
80 for f
in e
.link_faces
:
82 co_faces_selected
+= 1
83 if co_faces_selected
== 2:
88 if not set_edges_orig
:
93 def collect_loops(set_edges_orig
):
94 set_edges_copy
= set_edges_orig
.copy()
96 loops
= [] # [v, e, v, e, ... , e, v]
98 edge_start
= set_edges_copy
.pop()
99 v_left
, v_right
= edge_start
.verts
100 lp
= [v_left
, edge_start
, v_right
]
104 for e
in v_right
.link_edges
:
105 if e
in set_edges_copy
:
110 set_edges_copy
.remove(e
)
112 v_right
= edge
.other_vert(v_right
)
113 lp
.extend((edge
, v_right
))
116 if v_right
is v_left
:
120 elif reverse
is False:
121 # Right side of half loop.
122 # Reversing the loop to operate same procedure on the left side.
124 v_right
, v_left
= v_left
, v_right
128 # Half loop, completed.
133 def get_adj_ix(ix_start
, vec_edges
, half_loop
):
134 # Get adjacent edge index, skipping zero length edges
135 len_edges
= len(vec_edges
)
137 range_right
= range(ix_start
, len_edges
)
138 range_left
= range(ix_start
-1, -1, -1)
140 range_right
= range(ix_start
, ix_start
+len_edges
)
141 range_left
= range(ix_start
-1, ix_start
-1-len_edges
, -1)
143 ix_right
= ix_left
= None
144 for i
in range_right
:
147 if vec_edges
[i
] != ZERO_VEC
:
153 if vec_edges
[i
] != ZERO_VEC
:
157 # If index of one side is None, assign another index.
163 return ix_right
, ix_left
165 def get_adj_faces(edges
):
170 for f
in e
.link_faces
:
171 # Search an adjacent face.
172 # Selected face has precedance.
173 if not f
.hide
and f
.normal
!= ZERO_VEC
:
178 adj_faces
.append(adj_f
)
182 adj_faces
.append(adj_f
)
184 adj_faces
.append(None)
188 def get_edge_rail(vert
, set_edges_orig
):
189 co_edges
= co_edges_selected
= 0
191 for e
in vert
.link_edges
:
192 if (e
not in set_edges_orig
and
193 (e
.select
or (co_edges_selected
== 0 and not e
.hide
))):
194 v_other
= e
.other_vert(vert
)
195 vec
= v_other
.co
- vert
.co
199 co_edges_selected
+= 1
200 if co_edges_selected
== 2:
204 if co_edges_selected
== 1:
205 vec_inner
.normalize()
208 # No selected edges, one unselected edge.
209 vec_inner
.normalize()
214 def get_cross_rail(vec_tan
, vec_edge_r
, vec_edge_l
, normal_r
, normal_l
):
215 # Cross rail is a cross vector between normal_r and normal_l.
217 vec_cross
= normal_r
.cross(normal_l
)
218 if vec_cross
.dot(vec_tan
) < .0:
220 cos_min
= min(vec_tan
.dot(vec_edge_r
), vec_tan
.dot(-vec_edge_l
))
221 cos
= vec_tan
.dot(vec_cross
)
223 vec_cross
.normalize()
228 def move_verts(width
, depth
, verts
, directions
, geom_ex
):
230 geom_s
= geom_ex
['side']
233 for e
in v
.link_edges
:
235 verts_ex
.append(e
.other_vert(v
))
237 #assert len(verts) == len(verts_ex)
240 for v
, (vec_width
, vec_depth
) in zip(verts
, directions
):
241 v
.co
+= width
* vec_width
+ depth
* vec_depth
243 def extrude_edges(bm
, edges_orig
):
244 extruded
= bmesh
.ops
.extrude_edge_only(bm
, edges
=edges_orig
)['geom']
245 n_edges
= n_faces
= len(edges_orig
)
246 n_verts
= len(extruded
) - n_edges
- n_faces
249 geom
['verts'] = verts
= set(extruded
[:n_verts
])
250 geom
['edges'] = edges
= set(extruded
[n_verts
:n_verts
+ n_edges
])
251 geom
['faces'] = set(extruded
[n_verts
+ n_edges
:])
252 geom
['side'] = set(e
for v
in verts
for e
in v
.link_edges
if e
not in edges
)
256 def clean(bm
, mode
, edges_orig
, geom_ex
=None):
260 for e
in geom_ex
['edges']:
263 lis_geom
= list(geom_ex
['side']) + list(geom_ex
['faces'])
264 bmesh
.ops
.delete(bm
, geom
=lis_geom
, context
='EDGES')
269 def collect_mirror_planes(edit_object
):
271 eob_mat_inv
= edit_object
.matrix_world
.inverted()
274 for m
in edit_object
.modifiers
:
275 if (m
.type == 'MIRROR' and m
.use_mirror_merge
):
276 merge_limit
= m
.merge_threshold
277 if not m
.mirror_object
:
279 norm_x
, norm_y
, norm_z
= X_UP
, Y_UP
, Z_UP
281 mirror_mat_local
= eob_mat_inv
@ m
.mirror_object
.matrix_world
282 loc
= mirror_mat_local
.to_translation()
283 norm_x
, norm_y
, norm_z
, _
= mirror_mat_local
.adjugated()
284 norm_x
= norm_x
.to_3d().normalized()
285 norm_y
= norm_y
.to_3d().normalized()
286 norm_z
= norm_z
.to_3d().normalized()
288 mirror_planes
.append((loc
, norm_x
, merge_limit
))
290 mirror_planes
.append((loc
, norm_y
, merge_limit
))
292 mirror_planes
.append((loc
, norm_z
, merge_limit
))
295 def get_vert_mirror_pairs(set_edges_orig
, mirror_planes
):
297 set_edges_copy
= set_edges_orig
.copy()
298 vert_mirror_pairs
= dict()
299 for e
in set_edges_orig
:
301 for mp
in mirror_planes
:
302 p_co
, p_norm
, mlimit
= mp
303 v1_dist
= abs(p_norm
.dot(v1
.co
- p_co
))
304 v2_dist
= abs(p_norm
.dot(v2
.co
- p_co
))
305 if v1_dist
<= mlimit
:
306 # v1 is on a mirror plane.
307 vert_mirror_pairs
[v1
] = mp
308 if v2_dist
<= mlimit
:
309 # v2 is on a mirror plane.
310 vert_mirror_pairs
[v2
] = mp
311 if v1_dist
<= mlimit
and v2_dist
<= mlimit
:
312 # This edge is on a mirror_plane, so should not be offsetted.
313 set_edges_copy
.remove(e
)
314 return vert_mirror_pairs
, set_edges_copy
316 return None, set_edges_orig
318 def get_mirror_rail(mirror_plane
, vec_up
):
319 p_norm
= mirror_plane
[1]
320 mirror_rail
= vec_up
.cross(p_norm
)
321 if mirror_rail
!= ZERO_VEC
:
322 mirror_rail
.normalize()
323 # Project vec_up to mirror_plane
324 vec_up
= vec_up
- vec_up
.project(p_norm
)
326 return mirror_rail
, vec_up
330 def reorder_loop(verts
, edges
, lp_normal
, adj_faces
):
331 for i
, adj_f
in enumerate(adj_faces
):
334 v1
, v2
= verts
[i
], verts
[i
+1]
336 fv
= tuple(adj_f
.verts
)
337 if fv
[fv
.index(v1
)-1] is v2
:
338 # Align loop direction
342 if lp_normal
.dot(adj_f
.normal
) < .0:
346 # All elements in adj_faces are None
348 if v
.normal
!= ZERO_VEC
:
349 if lp_normal
.dot(v
.normal
) < .0:
355 return verts
, edges
, lp_normal
, adj_faces
357 def get_directions(lp
, vec_upward
, normal_fallback
, vert_mirror_pairs
, **options
):
358 opt_follow_face
= options
['follow_face']
359 opt_edge_rail
= options
['edge_rail']
360 opt_er_only_end
= options
['edge_rail_only_end']
361 opt_threshold
= options
['threshold']
363 verts
, edges
= lp
[::2], lp
[1::2]
364 set_edges
= set(edges
)
365 lp_normal
= calc_loop_normal(verts
, fallback
=normal_fallback
)
367 ##### Loop order might be changed below.
368 if lp_normal
.dot(vec_upward
) < .0:
369 # Make this loop's normal towards vec_upward.
375 adj_faces
= get_adj_faces(edges
)
376 verts
, edges
, lp_normal
, adj_faces
= \
377 reorder_loop(verts
, edges
, lp_normal
, adj_faces
)
379 adj_faces
= (None, ) * len(edges
)
380 ##### Loop order might be changed above.
382 vec_edges
= tuple((e
.other_vert(v
).co
- v
.co
).normalized()
383 for v
, e
in zip(verts
, edges
))
385 if verts
[0] is verts
[-1]:
386 # Real loop. Popping last vertex.
393 len_verts
= len(verts
)
395 for i
in range(len_verts
):
397 ix_right
, ix_left
= i
, i
-1
405 elif i
== len_verts
- 1:
410 edge_right
, edge_left
= vec_edges
[ix_right
], vec_edges
[ix_left
]
411 face_right
, face_left
= adj_faces
[ix_right
], adj_faces
[ix_left
]
413 norm_right
= face_right
.normal
if face_right
else lp_normal
414 norm_left
= face_left
.normal
if face_left
else lp_normal
415 if norm_right
.angle(norm_left
) > opt_threshold
:
416 # Two faces are not flat.
421 tan_right
= edge_right
.cross(norm_right
).normalized()
422 tan_left
= edge_left
.cross(norm_left
).normalized()
423 tan_avr
= (tan_right
+ tan_left
).normalized()
424 norm_avr
= (norm_right
+ norm_left
).normalized()
427 if two_normals
or opt_edge_rail
:
429 # edge rail is a vector of an inner edge.
430 if two_normals
or (not opt_er_only_end
) or VERT_END
:
431 rail
= get_edge_rail(vert
, set_edges
)
432 if vert_mirror_pairs
and VERT_END
:
433 if vert
in vert_mirror_pairs
:
435 get_mirror_rail(vert_mirror_pairs
[vert
], norm_avr
)
436 if (not rail
) and two_normals
:
438 # Cross rail is a cross vector between norm_right and norm_left.
439 rail
= get_cross_rail(
440 tan_avr
, edge_right
, edge_left
, norm_right
, norm_left
)
442 dot
= tan_avr
.dot(rail
)
448 vec_plane
= norm_avr
.cross(tan_avr
)
449 e_dot_p_r
= edge_right
.dot(vec_plane
)
450 e_dot_p_l
= edge_left
.dot(vec_plane
)
451 if e_dot_p_r
or e_dot_p_l
:
452 if e_dot_p_r
> e_dot_p_l
:
453 vec_edge
, e_dot_p
= edge_right
, e_dot_p_r
455 vec_edge
, e_dot_p
= edge_left
, e_dot_p_l
457 vec_tan
= (tan_avr
- tan_avr
.project(vec_edge
)).normalized()
458 # Make vec_tan perpendicular to vec_edge
459 vec_up
= vec_tan
.cross(vec_edge
)
461 vec_width
= vec_tan
- (vec_tan
.dot(vec_plane
) / e_dot_p
) * vec_edge
462 vec_depth
= vec_up
- (vec_up
.dot(vec_plane
) / e_dot_p
) * vec_edge
467 directions
.append((vec_width
, vec_depth
))
469 return verts
, directions
471 def use_cashes(self
, context
):
472 self
.caches_valid
= True
474 angle_presets
= {'0°': 0,
480 '90°': radians(90),}
481 def assign_angle_presets(self
, context
):
482 use_cashes(self
, context
)
483 self
.angle
= angle_presets
[self
.angle_presets
]
485 class OffsetEdges(bpy
.types
.Operator
):
487 bl_idname
= "mesh.offset_edges"
488 bl_label
= "Offset Edges"
489 bl_options
= {'REGISTER', 'UNDO'}
491 geometry_mode
: bpy
.props
.EnumProperty(
492 items
=[('offset', "Offset", "Offset edges"),
493 ('extrude', "Extrude", "Extrude edges"),
494 ('move', "Move", "Move selected edges")],
495 name
="Geometory mode", default
='offset',
497 width
: bpy
.props
.FloatProperty(
498 name
="Width", default
=.2, precision
=4, step
=1, update
=use_cashes
)
499 flip_width
: bpy
.props
.BoolProperty(
500 name
="Flip Width", default
=False,
501 description
="Flip width direction", update
=use_cashes
)
502 depth
: bpy
.props
.FloatProperty(
503 name
="Depth", default
=.0, precision
=4, step
=1, update
=use_cashes
)
504 flip_depth
: bpy
.props
.BoolProperty(
505 name
="Flip Depth", default
=False,
506 description
="Flip depth direction", update
=use_cashes
)
507 depth_mode
: bpy
.props
.EnumProperty(
508 items
=[('angle', "Angle", "Angle"),
509 ('depth', "Depth", "Depth")],
510 name
="Depth mode", default
='angle', update
=use_cashes
)
511 angle
: bpy
.props
.FloatProperty(
512 name
="Angle", default
=0, precision
=3, step
=.1,
513 min=-2*pi
, max=2*pi
, subtype
='ANGLE',
514 description
="Angle", update
=use_cashes
)
515 flip_angle
: bpy
.props
.BoolProperty(
516 name
="Flip Angle", default
=False,
517 description
="Flip Angle", update
=use_cashes
)
518 follow_face
: bpy
.props
.BoolProperty(
519 name
="Follow Face", default
=False,
520 description
="Offset along faces around")
521 mirror_modifier
: bpy
.props
.BoolProperty(
522 name
="Mirror Modifier", default
=False,
523 description
="Take into account of Mirror modifier")
524 edge_rail
: bpy
.props
.BoolProperty(
525 name
="Edge Rail", default
=False,
526 description
="Align vertices along inner edges")
527 edge_rail_only_end
: bpy
.props
.BoolProperty(
528 name
="Edge Rail Only End", default
=False,
529 description
="Apply edge rail to end verts only")
530 threshold
: bpy
.props
.FloatProperty(
531 name
="Flat Face Threshold", default
=radians(0.05), precision
=5,
532 step
=1.0e-4, subtype
='ANGLE',
533 description
="If difference of angle between two adjacent faces is "
534 "below this value, those faces are regarded as flat.",
536 caches_valid
: bpy
.props
.BoolProperty(
537 name
="Caches Valid", default
=False,
539 angle_presets
: bpy
.props
.EnumProperty(
540 items
=[('0°', "0°", "0°"),
541 ('15°', "15°", "15°"),
542 ('30°', "30°", "30°"),
543 ('45°', "45°", "45°"),
544 ('60°', "60°", "60°"),
545 ('75°', "75°", "75°"),
546 ('90°', "90°", "90°"), ],
547 name
="Angle Presets", default
='0°',
548 update
=assign_angle_presets
)
550 _cache_offset_infos
= None
551 _cache_edges_orig_ixs
= None
554 def poll(self
, context
):
555 return context
.mode
== 'EDIT_MESH'
557 def draw(self
, context
):
559 layout
.prop(self
, 'geometry_mode', text
="")
560 #layout.prop(self, 'geometry_mode', expand=True)
562 row
= layout
.row(align
=True)
563 row
.prop(self
, 'width')
564 row
.prop(self
, 'flip_width', icon
='ARROW_LEFTRIGHT', icon_only
=True)
566 layout
.prop(self
, 'depth_mode', expand
=True)
567 if self
.depth_mode
== 'angle':
573 row
= layout
.row(align
=True)
574 row
.prop(self
, d_mode
)
575 row
.prop(self
, flip
, icon
='ARROW_LEFTRIGHT', icon_only
=True)
576 if self
.depth_mode
== 'angle':
577 layout
.prop(self
, 'angle_presets', text
="Presets", expand
=True)
581 layout
.prop(self
, 'follow_face')
584 row
.prop(self
, 'edge_rail')
586 row
.prop(self
, 'edge_rail_only_end', text
="OnlyEnd", toggle
=True)
588 layout
.prop(self
, 'mirror_modifier')
590 #layout.operator('mesh.offset_edges', text='Repeat')
594 layout
.prop(self
, 'threshold', text
='Threshold')
597 def get_offset_infos(self
, bm
, edit_object
):
598 if self
.caches_valid
and self
._cache
_offset
_infos
is not None:
599 # Return None, indicating to use cache.
602 time
= perf_counter()
604 set_edges_orig
= collect_edges(bm
)
605 if set_edges_orig
is None:
606 self
.report({'WARNING'},
607 "No edges selected.")
610 if self
.mirror_modifier
:
611 mirror_planes
= collect_mirror_planes(edit_object
)
612 vert_mirror_pairs
, set_edges
= \
613 get_vert_mirror_pairs(set_edges_orig
, mirror_planes
)
616 set_edges_orig
= set_edges
618 #self.report({'WARNING'},
619 # "All selected edges are on mirror planes.")
620 vert_mirror_pairs
= None
622 vert_mirror_pairs
= None
624 loops
= collect_loops(set_edges_orig
)
626 self
.report({'WARNING'},
627 "Overlap detected. Select non-overlap edge loops")
630 vec_upward
= (X_UP
+ Y_UP
+ Z_UP
).normalized()
631 # vec_upward is used to unify loop normals when follow_face is off.
632 normal_fallback
= Z_UP
633 #normal_fallback = Vector(context.region_data.view_matrix[2][:3])
634 # normal_fallback is used when loop normal cannot be calculated.
636 follow_face
= self
.follow_face
637 edge_rail
= self
.edge_rail
638 er_only_end
= self
.edge_rail_only_end
639 threshold
= self
.threshold
643 verts
, directions
= get_directions(
644 lp
, vec_upward
, normal_fallback
, vert_mirror_pairs
,
645 follow_face
=follow_face
, edge_rail
=edge_rail
,
646 edge_rail_only_end
=er_only_end
,
649 offset_infos
.append((verts
, directions
))
652 self
._cache
_offset
_infos
= _cache_offset_infos
= []
653 for verts
, directions
in offset_infos
:
654 v_ixs
= tuple(v
.index
for v
in verts
)
655 _cache_offset_infos
.append((v_ixs
, directions
))
656 self
._cache
_edges
_orig
_ixs
= tuple(e
.index
for e
in set_edges_orig
)
658 print("Preparing OffsetEdges: ", perf_counter() - time
)
660 return offset_infos
, set_edges_orig
662 def do_offset_and_free(self
, bm
, me
, offset_infos
=None, set_edges_orig
=None):
663 # If offset_infos is None, use caches.
664 # Makes caches invalid after offset.
666 #time = perf_counter()
668 if offset_infos
is None:
670 bmverts
= tuple(bm
.verts
)
671 bmedges
= tuple(bm
.edges
)
672 edges_orig
= [bmedges
[ix
] for ix
in self
._cache
_edges
_orig
_ixs
]
673 verts_directions
= []
674 for ix_vs
, directions
in self
._cache
_offset
_infos
:
675 verts
= tuple(bmverts
[ix
] for ix
in ix_vs
)
676 verts_directions
.append((verts
, directions
))
678 verts_directions
= offset_infos
679 edges_orig
= list(set_edges_orig
)
681 if self
.depth_mode
== 'angle':
682 w
= self
.width
if not self
.flip_width
else -self
.width
683 angle
= self
.angle
if not self
.flip_angle
else -self
.angle
684 width
= w
* cos(angle
)
685 depth
= w
* sin(angle
)
687 width
= self
.width
if not self
.flip_width
else -self
.width
688 depth
= self
.depth
if not self
.flip_depth
else -self
.depth
691 if self
.geometry_mode
== 'move':
694 geom_ex
= extrude_edges(bm
, edges_orig
)
696 for verts
, directions
in verts_directions
:
697 move_verts(width
, depth
, verts
, directions
, geom_ex
)
699 clean(bm
, self
.geometry_mode
, edges_orig
, geom_ex
)
701 bpy
.ops
.object.mode_set(mode
="OBJECT")
703 bpy
.ops
.object.mode_set(mode
="EDIT")
705 self
.caches_valid
= False # Make caches invalid.
707 #print("OffsetEdges offset: ", perf_counter() - time)
709 def execute(self
, context
):
711 edit_object
= context
.edit_object
712 bpy
.ops
.object.mode_set(mode
="OBJECT")
714 me
= edit_object
.data
718 offset_infos
, edges_orig
= self
.get_offset_infos(bm
, edit_object
)
719 if offset_infos
is False:
720 bpy
.ops
.object.mode_set(mode
="EDIT")
723 self
.do_offset_and_free(bm
, me
, offset_infos
, edges_orig
)
727 def restore_original_and_free(self
, context
):
728 self
.caches_valid
= False # Make caches invalid.
729 context
.area
.header_text_set()
731 me
= context
.edit_object
.data
732 bpy
.ops
.object.mode_set(mode
="OBJECT")
733 self
._bm
_orig
.to_mesh(me
)
734 bpy
.ops
.object.mode_set(mode
="EDIT")
737 context
.area
.header_text_set()
739 def invoke(self
, context
, event
):
741 edit_object
= context
.edit_object
742 me
= edit_object
.data
743 bpy
.ops
.object.mode_set(mode
="OBJECT")
744 for p
in me
.polygons
:
746 self
.follow_face
= True
749 self
.caches_valid
= False
750 bpy
.ops
.object.mode_set(mode
="EDIT")
751 return self
.execute(context
)
753 class OffsetEdgesMenu(bpy
.types
.Menu
):
754 bl_idname
= "VIEW3D_MT_edit_mesh_offset_edges"
755 bl_label
= "Offset Edges"
757 def draw(self
, context
):
759 layout
.operator_context
= 'INVOKE_DEFAULT'
761 off
= layout
.operator('mesh.offset_edges', text
='Offset')
762 off
.geometry_mode
= 'offset'
764 ext
= layout
.operator('mesh.offset_edges', text
='Extrude')
765 ext
.geometry_mode
= 'extrude'
767 mov
= layout
.operator('mesh.offset_edges', text
='Move')
768 mov
.geometry_mode
= 'move'
775 def draw_item(self
, context
):
776 self
.layout
.menu("VIEW3D_MT_edit_mesh_offset_edges")
781 bpy
.utils
.register_class(cls
)
782 bpy
.types
.VIEW3D_MT_edit_mesh_edges
.prepend(draw_item
)
786 for cls
in reversed(classes
):
787 bpy
.utils
.unregister_class(cls
)
788 bpy
.types
.VIEW3D_MT_edit_mesh_edges
.remove(draw_item
)
791 if __name__
== '__main__':