1 # SPDX-License-Identifier: GPL-2.0-or-later
7 'name': 'Curve Remove Doubles',
8 'author': 'Michael Soluyanov',
10 'blender': (2, 80, 0),
11 'location': 'View3D > Context menu (W/RMB) > Remove Doubles',
12 'description': 'Adds command "Remove Doubles" for curves',
13 'category': 'Add Curve'
16 def main(context
, distance
= 0.01):
18 selected_Curves
= util
.GetSelectedCurves()
19 if bpy
.ops
.object.mode_set
.poll():
20 bpy
.ops
.object.mode_set(mode
='EDIT')
22 for curve
in selected_Curves
:
26 for spline
in curve
.data
.splines
:
27 if spline
.type == 'BEZIER':
28 if len(spline
.bezier_points
) > 1:
29 for i
in range(0, len(spline
.bezier_points
)):
32 ii
= len(spline
.bezier_points
) - 1
36 dot
= spline
.bezier_points
[i
];
37 dot1
= spline
.bezier_points
[ii
];
39 while dot1
in bezier_dellist
and i
!= ii
:
42 ii
= len(spline
.bezier_points
)-1
43 dot1
= spline
.bezier_points
[ii
]
45 if dot
.select_control_point
and dot1
.select_control_point
and (i
!=0 or spline
.use_cyclic_u
):
47 if (dot
.co
-dot1
.co
).length
< distance
:
48 # remove points and recreate hangles
49 dot1
.handle_right_type
= "FREE"
50 dot1
.handle_right
= dot
.handle_right
51 dot1
.co
= (dot
.co
+ dot1
.co
) / 2
52 bezier_dellist
.append(dot
)
55 # Handles that are on main point position converts to vector,
56 # if next handle are also vector
57 if dot
.handle_left_type
== 'VECTOR' and (dot1
.handle_right
- dot1
.co
).length
< distance
:
58 dot1
.handle_right_type
= "VECTOR"
59 if dot1
.handle_right_type
== 'VECTOR' and (dot
.handle_left
- dot
.co
).length
< distance
:
60 dot
.handle_left_type
= "VECTOR"
62 if len(spline
.points
) > 1:
63 for i
in range(0, len(spline
.points
)):
66 ii
= len(spline
.points
) - 1
70 dot
= spline
.points
[i
];
71 dot1
= spline
.points
[ii
];
73 while dot1
in dellist
and i
!= ii
:
76 ii
= len(spline
.points
)-1
77 dot1
= spline
.points
[ii
]
79 if dot
.select
and dot1
.select
and (i
!=0 or spline
.use_cyclic_u
):
81 if (dot
.co
-dot1
.co
).length
< distance
:
82 dot1
.co
= (dot
.co
+ dot1
.co
) / 2
85 bpy
.ops
.curve
.select_all(action
= 'DESELECT')
87 for dot
in bezier_dellist
:
88 dot
.select_control_point
= True
93 bezier_count
= len(bezier_dellist
)
96 bpy
.ops
.curve
.delete(type = 'VERT')
98 bpy
.ops
.curve
.select_all(action
= 'DESELECT')
100 return bezier_count
+ count
104 class CurveRemvDbs(bpy
.types
.Operator
):
105 """Merge consecutive points that are near to each other"""
106 bl_idname
= 'curvetools.remove_doubles'
107 bl_label
= 'Remove Doubles'
108 bl_options
= {'REGISTER', 'UNDO'}
110 distance
: bpy
.props
.FloatProperty(name
= 'Distance', default
= 0.01)
113 def poll(cls
, context
):
114 return util
.Selected1OrMoreCurves()
116 def execute(self
, context
):
117 removed
=main(context
, self
.distance
)
118 self
.report({'INFO'}, "Removed %d bezier points" % removed
)
123 def menu_func(self
, context
):
124 self
.layout
.operator(CurveRemvDbs
.bl_idname
, text
='Remove Doubles')
127 bpy
.utils
.register_class(CurveRemvDbs
)
128 bpy
.types
.VIEW3D_MT_edit_curve_context_menu
.append(menu_func
)
131 bpy
.utils
.unregister_class(CurveRemvDbs
)
132 bpy
.types
.VIEW3D_MT_edit_curve_context_menu
.remove(menu_func
)
134 if __name__
== "__main__":
137 operators
= [CurveRemvDbs
]