1import bpy, mathutils
2from . import util
3
4bl_info = {
5    'name': 'Curve Remove Doubles',
6    'author': 'Michael Soluyanov',
7    'version': (1, 1),
8    'blender': (2, 80, 0),
9    'location': 'View3D > Context menu (W/RMB) > Remove Doubles',
10    'description': 'Adds comand "Remove Doubles" for curves',
11    'category': 'Add Curve'
12}
13
14def main(context, distance = 0.01):
15
16    selected_Curves = util.GetSelectedCurves()
17    if bpy.ops.object.mode_set.poll():
18        bpy.ops.object.mode_set(mode='EDIT')
19
20    for curve in selected_Curves:
21        bezier_dellist = []
22        dellist = []
23
24        for spline in curve.data.splines:
25            if spline.type == 'BEZIER':
26                if len(spline.bezier_points) > 1:
27                    for i in range(0, len(spline.bezier_points)):
28
29                        if i == 0:
30                            ii = len(spline.bezier_points) - 1
31                        else:
32                            ii = i - 1
33
34                        dot = spline.bezier_points[i];
35                        dot1 = spline.bezier_points[ii];
36
37                        while dot1 in bezier_dellist and i != ii:
38                            ii -= 1
39                            if ii < 0:
40                                ii = len(spline.bezier_points)-1
41                            dot1 = spline.bezier_points[ii]
42
43                        if dot.select_control_point and dot1.select_control_point and (i!=0 or spline.use_cyclic_u):
44
45                            if (dot.co-dot1.co).length < distance:
46                                # remove points and recreate hangles
47                                dot1.handle_right_type = "FREE"
48                                dot1.handle_right = dot.handle_right
49                                dot1.co = (dot.co + dot1.co) / 2
50                                bezier_dellist.append(dot)
51
52                            else:
53                                # Handles that are on main point position converts to vector,
54                                # if next handle are also vector
55                                if dot.handle_left_type == 'VECTOR' and (dot1.handle_right - dot1.co).length < distance:
56                                    dot1.handle_right_type = "VECTOR"
57                                if dot1.handle_right_type == 'VECTOR' and (dot.handle_left - dot.co).length < distance:
58                                    dot.handle_left_type = "VECTOR"
59            else:
60                if len(spline.points) > 1:
61                    for i in range(0, len(spline.points)):
62
63                        if i == 0:
64                            ii = len(spline.points) - 1
65                        else:
66                            ii = i - 1
67
68                        dot = spline.points[i];
69                        dot1 = spline.points[ii];
70
71                        while dot1 in dellist and i != ii:
72                            ii -= 1
73                            if ii < 0:
74                                ii = len(spline.points)-1
75                            dot1 = spline.points[ii]
76
77                        if dot.select and dot1.select and (i!=0 or spline.use_cyclic_u):
78
79                            if (dot.co-dot1.co).length < distance:
80                                dot1.co = (dot.co + dot1.co) / 2
81                                dellist.append(dot)
82
83    bpy.ops.curve.select_all(action = 'DESELECT')
84
85    for dot in bezier_dellist:
86        dot.select_control_point = True
87
88    for dot in dellist:
89        dot.select = True
90
91    bezier_count = len(bezier_dellist)
92    count = len(dellist)
93
94    bpy.ops.curve.delete(type = 'VERT')
95
96    bpy.ops.curve.select_all(action = 'DESELECT')
97
98    return bezier_count + count
99
100
101
102class CurveRemvDbs(bpy.types.Operator):
103    """Merge consecutive points that are near to each other"""
104    bl_idname = 'curvetools.remove_doubles'
105    bl_label = 'Remove Doubles'
106    bl_options = {'REGISTER', 'UNDO'}
107
108    distance: bpy.props.FloatProperty(name = 'Distance', default = 0.01)
109
110    @classmethod
111    def poll(cls, context):
112        return util.Selected1OrMoreCurves()
113
114    def execute(self, context):
115        removed=main(context, self.distance)
116        self.report({'INFO'}, "Removed %d bezier points" % removed)
117        return {'FINISHED'}
118
119
120
121def menu_func(self, context):
122    self.layout.operator(CurveRemvDbs.bl_idname, text='Remove Doubles')
123
124def register():
125    bpy.utils.register_class(CurveRemvDbs)
126    bpy.types.VIEW3D_MT_edit_curve_context_menu.append(menu_func)
127
128def unregister():
129    bpy.utils.unregister_class(CurveRemvDbs)
130    bpy.types.VIEW3D_MT_edit_curve_context_menu.remove(menu_func)
131
132if __name__ == "__main__":
133    register()
134
135operators = [CurveRemvDbs]
136