1 # SPDX-License-Identifier: GPL-2.0-or-later
5 'author': 'Spivak Vladimir (cwolf3d)',
8 'location': 'Curve Tools addon. (N) Panel',
9 'description': 'PathFinder - quick search, selection, removal of splines',
10 'warning': '', # used for warning icon and text in addons panel
20 from gpu_extras
.batch
import batch_for_shader
23 from bpy
.props
import *
24 from bpy_extras
import object_utils
, view3d_utils
25 from mathutils
import *
28 from . import mathematics
31 def get_bezier_points(spline
, matrix_world
):
33 len_bezier_points
= len(spline
.bezier_points
)
34 if len_bezier_points
> 1:
35 for i
in range(0, len_bezier_points
- 1):
36 point_list
.extend([matrix_world
@ spline
.bezier_points
[i
].co
])
37 for t
in range(0, 100, 2):
38 h
= mathematics
.subdivide_cubic_bezier(spline
.bezier_points
[i
].co
,
39 spline
.bezier_points
[i
].handle_right
,
40 spline
.bezier_points
[i
+ 1].handle_left
,
41 spline
.bezier_points
[i
+ 1].co
,
43 point_list
.extend([matrix_world
@ h
[2]])
44 if spline
.use_cyclic_u
and len_bezier_points
> 2:
45 point_list
.extend([matrix_world
@ spline
.bezier_points
[len_bezier_points
- 1].co
])
46 for t
in range(0, 100, 2):
47 h
= mathematics
.subdivide_cubic_bezier(spline
.bezier_points
[len_bezier_points
- 1].co
,
48 spline
.bezier_points
[len_bezier_points
- 1].handle_right
,
49 spline
.bezier_points
[0].handle_left
,
50 spline
.bezier_points
[0].co
,
52 point_list
.extend([matrix_world
@ h
[2]])
53 point_list
.extend([matrix_world
@ spline
.bezier_points
[0].co
])
57 def get_points(spline
, matrix_world
):
59 len_points
= len(spline
.points
)
61 for i
in range(0, len_points
- 1):
62 point_list
.extend([matrix_world
@ Vector((spline
.points
[i
].co
.x
, spline
.points
[i
].co
.y
, spline
.points
[i
].co
.z
))])
63 for t
in range(0, 100, 2):
64 x
= (spline
.points
[i
].co
.x
+ t
/ 100 * spline
.points
[i
+ 1].co
.x
) / (1 + t
/ 100)
65 y
= (spline
.points
[i
].co
.y
+ t
/ 100 * spline
.points
[i
+ 1].co
.y
) / (1 + t
/ 100)
66 z
= (spline
.points
[i
].co
.z
+ t
/ 100 * spline
.points
[i
+ 1].co
.z
) / (1 + t
/ 100)
67 point_list
.extend([matrix_world
@ Vector((x
, y
, z
))])
68 if spline
.use_cyclic_u
and len_points
> 2:
69 point_list
.extend([matrix_world
@ Vector((spline
.points
[len_points
- 1].co
.x
, spline
.points
[len_points
- 1].co
.y
, spline
.points
[len_points
- 1].co
.z
))])
70 for t
in range(0, 100, 2):
71 x
= (spline
.points
[len_points
- 1].co
.x
+ t
/ 100 * spline
.points
[0].co
.x
) / (1 + t
/ 100)
72 y
= (spline
.points
[len_points
- 1].co
.y
+ t
/ 100 * spline
.points
[0].co
.y
) / (1 + t
/ 100)
73 z
= (spline
.points
[len_points
- 1].co
.z
+ t
/ 100 * spline
.points
[0].co
.z
) / (1 + t
/ 100)
74 point_list
.extend([matrix_world
@ Vector((x
, y
, z
))])
75 point_list
.extend([matrix_world
@ Vector((spline
.points
[0].co
.x
, spline
.points
[0].co
.y
, spline
.points
[0].co
.z
))])
78 def draw_bezier_points(self
, context
, spline
, matrix_world
, path_color
, path_thickness
):
80 points
= get_bezier_points(spline
, matrix_world
)
82 shader
= gpu
.shader
.from_builtin('3D_UNIFORM_COLOR')
83 batch
= batch_for_shader(shader
, 'POINTS', {"pos": points
})
86 shader
.uniform_float("color", path_color
)
87 gpu
.state
.blend_set('ALPHA')
88 gpu
.state
.line_width_set(path_thickness
)
91 def draw_points(self
, context
, spline
, matrix_world
, path_color
, path_thickness
):
93 points
= get_points(spline
, matrix_world
)
95 shader
= gpu
.shader
.from_builtin('3D_UNIFORM_COLOR')
96 batch
= batch_for_shader(shader
, 'POINTS', {"pos": points
})
99 shader
.uniform_float("color", path_color
)
100 gpu
.state
.blend_set('ALPHA')
101 gpu
.state
.line_width_set(path_thickness
)
104 def near(location3D
, point
, radius
):
106 if point
.x
> (location3D
.x
- radius
):
108 if point
.x
< (location3D
.x
+ radius
):
110 if point
.y
> (location3D
.y
- radius
):
112 if point
.y
< (location3D
.y
+ radius
):
114 if point
.z
> (location3D
.z
- radius
):
116 if point
.z
< (location3D
.z
+ radius
):
121 def click(self
, context
, event
):
122 bpy
.ops
.object.mode_set(mode
= 'EDIT')
123 bpy
.context
.view_layer
.update()
124 for object in context
.selected_objects
:
125 matrix_world
= object.matrix_world
126 if object.type == 'CURVE':
127 curvedata
= object.data
129 radius
= bpy
.context
.scene
.curvetools
.PathFinderRadius
131 for spline
in curvedata
.splines
:
132 len_bezier_points
= len(spline
.bezier_points
)
134 for i
in range(0, len_bezier_points
):
136 co
= matrix_world
@ spline
.bezier_points
[i
].co
137 factor
= near(self
.location3D
, co
, radius
)
138 if factor
> factor_max
:
141 if i
< len_bezier_points
- 1:
142 for t
in range(0, 100, 2):
143 h
= mathematics
.subdivide_cubic_bezier(spline
.bezier_points
[i
].co
,
144 spline
.bezier_points
[i
].handle_right
,
145 spline
.bezier_points
[i
+ 1].handle_left
,
146 spline
.bezier_points
[i
+ 1].co
,
148 co
= matrix_world
@ h
[2]
149 factor
= near(self
.location3D
, co
, radius
)
150 if factor
> factor_max
:
153 if spline
.use_cyclic_u
and len_bezier_points
> 2:
154 for t
in range(0, 100, 2):
155 h
= mathematics
.subdivide_cubic_bezier(spline
.bezier_points
[len_bezier_points
- 1].co
,
156 spline
.bezier_points
[len_bezier_points
- 1].handle_right
,
157 spline
.bezier_points
[0].handle_left
,
158 spline
.bezier_points
[0].co
,
160 co
= matrix_world
@ h
[2]
161 factor
= near(self
.location3D
, co
, radius
)
162 if factor
> factor_max
:
166 args
= (self
, context
, spline
, matrix_world
, self
.path_color
, self
.path_thickness
)
167 self
.handlers
.append(bpy
.types
.SpaceView3D
.draw_handler_add(draw_bezier_points
, args
, 'WINDOW', 'POST_VIEW'))
169 for bezier_point
in spline
.bezier_points
:
170 bezier_point
.select_control_point
= True
171 bezier_point
.select_left_handle
= True
172 bezier_point
.select_right_handle
= True
174 for spline
in curvedata
.splines
:
175 len_points
= len(spline
.points
)
177 for i
in range(0, len_points
):
178 co
= matrix_world
@ Vector((spline
.points
[i
].co
.x
, spline
.points
[i
].co
.y
, spline
.points
[i
].co
.z
))
179 factor
= near(self
.location3D
, co
, radius
)
180 if factor
> factor_max
:
183 if i
< len_bezier_points
- 1:
184 for t
in range(0, 100, 2):
185 x
= (spline
.points
[i
].co
.x
+ t
/ 100 * spline
.points
[i
+ 1].co
.x
) / (1 + t
/ 100)
186 y
= (spline
.points
[i
].co
.y
+ t
/ 100 * spline
.points
[i
+ 1].co
.y
) / (1 + t
/ 100)
187 z
= (spline
.points
[i
].co
.z
+ t
/ 100 * spline
.points
[i
+ 1].co
.z
) / (1 + t
/ 100)
188 co
= matrix_world
@ Vector((x
, y
, z
))
189 factor
= near(self
.location3D
, co
, radius
)
190 if factor
> factor_max
:
193 if spline
.use_cyclic_u
and len_points
> 2:
194 for t
in range(0, 100, 2):
195 x
= (spline
.points
[len_points
- 1].co
.x
+ t
/ 100 * spline
.points
[0].co
.x
) / (1 + t
/ 100)
196 y
= (spline
.points
[len_points
- 1].co
.y
+ t
/ 100 * spline
.points
[0].co
.y
) / (1 + t
/ 100)
197 z
= (spline
.points
[len_points
- 1].co
.z
+ t
/ 100 * spline
.points
[0].co
.z
) / (1 + t
/ 100)
198 co
= matrix_world
@ Vector((x
, y
, z
))
199 factor
= near(self
.location3D
, co
, radius
)
200 if factor
> factor_max
:
204 args
= (self
, context
, spline
, matrix_world
, self
.path_color
, self
.path_thickness
)
205 self
.handlers
.append(bpy
.types
.SpaceView3D
.draw_handler_add(draw_points
, args
, 'WINDOW', 'POST_VIEW'))
207 for point
in spline
.points
:
210 def remove_handler(handlers
):
211 for handler
in handlers
:
213 bpy
.types
.SpaceView3D
.draw_handler_remove(handler
, 'WINDOW')
216 for handler
in handlers
:
217 handlers
.remove(handler
)
219 class PathFinder(bpy
.types
.Operator
):
220 bl_idname
= "curvetools.pathfinder"
221 bl_label
= "Path Finder"
222 bl_description
= "Path Finder"
223 bl_options
= {'REGISTER', 'UNDO'}
225 x
: IntProperty(name
="x", description
="x")
226 y
: IntProperty(name
="y", description
="y")
227 location3D
: FloatVectorProperty(name
= "",
228 description
= "Start location",
229 default
= (0.0, 0.0, 0.0),
234 def execute(self
, context
):
235 self
.report({'INFO'}, "ESC or TAB - cancel")
236 bpy
.ops
.object.mode_set(mode
= 'EDIT')
238 # color change in the panel
239 self
.path_color
= bpy
.context
.scene
.curvetools
.path_color
240 self
.path_thickness
= bpy
.context
.scene
.curvetools
.path_thickness
242 def modal(self
, context
, event
):
243 context
.area
.tag_redraw()
245 if event
.type in {'ESC', 'TAB'}: # Cancel
246 remove_handler(self
.handlers
)
249 if event
.type in {'X', 'DEL'}: # Cancel
250 remove_handler(self
.handlers
)
251 bpy
.ops
.curve
.delete(type='VERT')
252 return {'RUNNING_MODAL'}
254 elif event
.alt
and event
.shift
and event
.type == 'LEFTMOUSE':
255 click(self
, context
, event
)
257 elif event
.alt
and not event
.shift
and event
.type == 'LEFTMOUSE':
258 remove_handler(self
.handlers
)
259 bpy
.ops
.curve
.select_all(action
='DESELECT')
260 click(self
, context
, event
)
262 elif event
.alt
and event
.type == 'RIGHTMOUSE':
263 remove_handler(self
.handlers
)
264 bpy
.ops
.curve
.select_all(action
='DESELECT')
265 click(self
, context
, event
)
267 elif event
.alt
and not event
.shift
and event
.shift
and event
.type == 'RIGHTMOUSE':
268 click(self
, context
, event
)
270 elif event
.type == 'A':
271 remove_handler(self
.handlers
)
272 bpy
.ops
.curve
.select_all(action
='DESELECT')
274 elif event
.type == 'MOUSEMOVE': #
275 self
.x
= event
.mouse_x
276 self
.y
= event
.mouse_y
277 region
= bpy
.context
.region
278 rv3d
= bpy
.context
.space_data
.region_3d
279 self
.location3D
= view3d_utils
.region_2d_to_location_3d(
282 (event
.mouse_region_x
, event
.mouse_region_y
),
286 return {'PASS_THROUGH'}
288 def invoke(self
, context
, event
):
289 self
.execute(context
)
290 context
.window_manager
.modal_handler_add(self
)
291 return {'RUNNING_MODAL'}
294 def poll(cls
, context
):
295 return util
.Selected1OrMoreCurves()
299 bpy
.utils
.register_class(operators
)
303 bpy
.utils
.unregister_class(operators
)
305 if __name__
== "__main__":
309 operators
= [PathFinder
]