1 # SPDX-FileCopyrightText: 2019-2023 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
7 'author': 'Spivak Vladimir (cwolf3d)',
10 'location': 'Curve Tools addon. (N) Panel',
11 'description': 'PathFinder - quick search, selection, removal of splines',
12 'warning': '', # used for warning icon and text in addons panel
22 from gpu_extras
.batch
import batch_for_shader
25 from bpy
.props
import *
26 from bpy_extras
import object_utils
, view3d_utils
27 from mathutils
import *
30 from . import mathematics
33 def get_bezier_points(spline
, matrix_world
):
35 len_bezier_points
= len(spline
.bezier_points
)
36 if len_bezier_points
> 1:
37 for i
in range(0, len_bezier_points
- 1):
38 point_list
.extend([matrix_world
@ spline
.bezier_points
[i
].co
])
39 for t
in range(0, 100, 2):
40 h
= mathematics
.subdivide_cubic_bezier(spline
.bezier_points
[i
].co
,
41 spline
.bezier_points
[i
].handle_right
,
42 spline
.bezier_points
[i
+ 1].handle_left
,
43 spline
.bezier_points
[i
+ 1].co
,
45 point_list
.extend([matrix_world
@ h
[2]])
46 if spline
.use_cyclic_u
and len_bezier_points
> 2:
47 point_list
.extend([matrix_world
@ spline
.bezier_points
[len_bezier_points
- 1].co
])
48 for t
in range(0, 100, 2):
49 h
= mathematics
.subdivide_cubic_bezier(spline
.bezier_points
[len_bezier_points
- 1].co
,
50 spline
.bezier_points
[len_bezier_points
- 1].handle_right
,
51 spline
.bezier_points
[0].handle_left
,
52 spline
.bezier_points
[0].co
,
54 point_list
.extend([matrix_world
@ h
[2]])
55 point_list
.extend([matrix_world
@ spline
.bezier_points
[0].co
])
59 def get_points(spline
, matrix_world
):
61 len_points
= len(spline
.points
)
63 for i
in range(0, len_points
- 1):
64 point_list
.extend([matrix_world
@ Vector((spline
.points
[i
].co
.x
, spline
.points
[i
].co
.y
, spline
.points
[i
].co
.z
))])
65 for t
in range(0, 100, 2):
66 x
= (spline
.points
[i
].co
.x
+ t
/ 100 * spline
.points
[i
+ 1].co
.x
) / (1 + t
/ 100)
67 y
= (spline
.points
[i
].co
.y
+ t
/ 100 * spline
.points
[i
+ 1].co
.y
) / (1 + t
/ 100)
68 z
= (spline
.points
[i
].co
.z
+ t
/ 100 * spline
.points
[i
+ 1].co
.z
) / (1 + t
/ 100)
69 point_list
.extend([matrix_world
@ Vector((x
, y
, z
))])
70 if spline
.use_cyclic_u
and len_points
> 2:
71 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
))])
72 for t
in range(0, 100, 2):
73 x
= (spline
.points
[len_points
- 1].co
.x
+ t
/ 100 * spline
.points
[0].co
.x
) / (1 + t
/ 100)
74 y
= (spline
.points
[len_points
- 1].co
.y
+ t
/ 100 * spline
.points
[0].co
.y
) / (1 + t
/ 100)
75 z
= (spline
.points
[len_points
- 1].co
.z
+ t
/ 100 * spline
.points
[0].co
.z
) / (1 + t
/ 100)
76 point_list
.extend([matrix_world
@ Vector((x
, y
, z
))])
77 point_list
.extend([matrix_world
@ Vector((spline
.points
[0].co
.x
, spline
.points
[0].co
.y
, spline
.points
[0].co
.z
))])
80 def draw_bezier_points(self
, context
, spline
, matrix_world
, path_color
, path_thickness
):
82 points
= get_bezier_points(spline
, matrix_world
)
84 shader
= gpu
.shader
.from_builtin('UNIFORM_COLOR')
85 batch
= batch_for_shader(shader
, 'POINTS', {"pos": points
})
88 shader
.uniform_float("color", path_color
)
89 gpu
.state
.blend_set('ALPHA')
90 gpu
.state
.line_width_set(path_thickness
)
93 def draw_points(self
, context
, spline
, matrix_world
, path_color
, path_thickness
):
95 points
= get_points(spline
, matrix_world
)
97 shader
= gpu
.shader
.from_builtin('UNIFORM_COLOR')
98 batch
= batch_for_shader(shader
, 'POINTS', {"pos": points
})
101 shader
.uniform_float("color", path_color
)
102 gpu
.state
.blend_set('ALPHA')
103 gpu
.state
.line_width_set(path_thickness
)
106 def near(location3D
, point
, radius
):
108 if point
.x
> (location3D
.x
- radius
):
110 if point
.x
< (location3D
.x
+ radius
):
112 if point
.y
> (location3D
.y
- radius
):
114 if point
.y
< (location3D
.y
+ radius
):
116 if point
.z
> (location3D
.z
- radius
):
118 if point
.z
< (location3D
.z
+ radius
):
123 def click(self
, context
, event
):
124 bpy
.ops
.object.mode_set(mode
= 'EDIT')
125 bpy
.context
.view_layer
.update()
126 for object in context
.selected_objects
:
127 matrix_world
= object.matrix_world
128 if object.type == 'CURVE':
129 curvedata
= object.data
131 radius
= bpy
.context
.scene
.curvetools
.PathFinderRadius
133 for spline
in curvedata
.splines
:
134 len_bezier_points
= len(spline
.bezier_points
)
136 for i
in range(0, len_bezier_points
):
138 co
= matrix_world
@ spline
.bezier_points
[i
].co
139 factor
= near(self
.location3D
, co
, radius
)
140 if factor
> factor_max
:
143 if i
< len_bezier_points
- 1:
144 for t
in range(0, 100, 2):
145 h
= mathematics
.subdivide_cubic_bezier(spline
.bezier_points
[i
].co
,
146 spline
.bezier_points
[i
].handle_right
,
147 spline
.bezier_points
[i
+ 1].handle_left
,
148 spline
.bezier_points
[i
+ 1].co
,
150 co
= matrix_world
@ h
[2]
151 factor
= near(self
.location3D
, co
, radius
)
152 if factor
> factor_max
:
155 if spline
.use_cyclic_u
and len_bezier_points
> 2:
156 for t
in range(0, 100, 2):
157 h
= mathematics
.subdivide_cubic_bezier(spline
.bezier_points
[len_bezier_points
- 1].co
,
158 spline
.bezier_points
[len_bezier_points
- 1].handle_right
,
159 spline
.bezier_points
[0].handle_left
,
160 spline
.bezier_points
[0].co
,
162 co
= matrix_world
@ h
[2]
163 factor
= near(self
.location3D
, co
, radius
)
164 if factor
> factor_max
:
168 args
= (self
, context
, spline
, matrix_world
, self
.path_color
, self
.path_thickness
)
169 self
.handlers
.append(bpy
.types
.SpaceView3D
.draw_handler_add(draw_bezier_points
, args
, 'WINDOW', 'POST_VIEW'))
171 for bezier_point
in spline
.bezier_points
:
172 bezier_point
.select_control_point
= True
173 bezier_point
.select_left_handle
= True
174 bezier_point
.select_right_handle
= True
176 for spline
in curvedata
.splines
:
177 len_points
= len(spline
.points
)
179 for i
in range(0, len_points
):
180 co
= matrix_world
@ Vector((spline
.points
[i
].co
.x
, spline
.points
[i
].co
.y
, spline
.points
[i
].co
.z
))
181 factor
= near(self
.location3D
, co
, radius
)
182 if factor
> factor_max
:
185 if i
< len_bezier_points
- 1:
186 for t
in range(0, 100, 2):
187 x
= (spline
.points
[i
].co
.x
+ t
/ 100 * spline
.points
[i
+ 1].co
.x
) / (1 + t
/ 100)
188 y
= (spline
.points
[i
].co
.y
+ t
/ 100 * spline
.points
[i
+ 1].co
.y
) / (1 + t
/ 100)
189 z
= (spline
.points
[i
].co
.z
+ t
/ 100 * spline
.points
[i
+ 1].co
.z
) / (1 + t
/ 100)
190 co
= matrix_world
@ Vector((x
, y
, z
))
191 factor
= near(self
.location3D
, co
, radius
)
192 if factor
> factor_max
:
195 if spline
.use_cyclic_u
and len_points
> 2:
196 for t
in range(0, 100, 2):
197 x
= (spline
.points
[len_points
- 1].co
.x
+ t
/ 100 * spline
.points
[0].co
.x
) / (1 + t
/ 100)
198 y
= (spline
.points
[len_points
- 1].co
.y
+ t
/ 100 * spline
.points
[0].co
.y
) / (1 + t
/ 100)
199 z
= (spline
.points
[len_points
- 1].co
.z
+ t
/ 100 * spline
.points
[0].co
.z
) / (1 + t
/ 100)
200 co
= matrix_world
@ Vector((x
, y
, z
))
201 factor
= near(self
.location3D
, co
, radius
)
202 if factor
> factor_max
:
206 args
= (self
, context
, spline
, matrix_world
, self
.path_color
, self
.path_thickness
)
207 self
.handlers
.append(bpy
.types
.SpaceView3D
.draw_handler_add(draw_points
, args
, 'WINDOW', 'POST_VIEW'))
209 for point
in spline
.points
:
212 def remove_handler(handlers
):
213 for handler
in handlers
:
215 bpy
.types
.SpaceView3D
.draw_handler_remove(handler
, 'WINDOW')
218 for handler
in handlers
:
219 handlers
.remove(handler
)
221 class PathFinder(bpy
.types
.Operator
):
222 bl_idname
= "curvetools.pathfinder"
223 bl_label
= "Path Finder"
224 bl_description
= "Path Finder"
225 bl_options
= {'REGISTER', 'UNDO'}
227 x
: IntProperty(name
="x", description
="x")
228 y
: IntProperty(name
="y", description
="y")
229 location3D
: FloatVectorProperty(name
= "",
230 description
= "Start location",
231 default
= (0.0, 0.0, 0.0),
236 def execute(self
, context
):
237 self
.report({'INFO'}, "ESC or TAB - cancel")
238 bpy
.ops
.object.mode_set(mode
= 'EDIT')
240 # color change in the panel
241 self
.path_color
= bpy
.context
.scene
.curvetools
.path_color
242 self
.path_thickness
= bpy
.context
.scene
.curvetools
.path_thickness
244 def modal(self
, context
, event
):
245 context
.area
.tag_redraw()
247 if event
.type in {'ESC', 'TAB'}: # Cancel
248 remove_handler(self
.handlers
)
251 if event
.type in {'X', 'DEL'}: # Cancel
252 remove_handler(self
.handlers
)
253 bpy
.ops
.curve
.delete(type='VERT')
254 return {'RUNNING_MODAL'}
256 elif event
.alt
and event
.shift
and event
.type == 'LEFTMOUSE':
257 click(self
, context
, event
)
259 elif event
.alt
and not event
.shift
and event
.type == 'LEFTMOUSE':
260 remove_handler(self
.handlers
)
261 bpy
.ops
.curve
.select_all(action
='DESELECT')
262 click(self
, context
, event
)
264 elif event
.alt
and event
.type == 'RIGHTMOUSE':
265 remove_handler(self
.handlers
)
266 bpy
.ops
.curve
.select_all(action
='DESELECT')
267 click(self
, context
, event
)
269 elif event
.alt
and not event
.shift
and event
.shift
and event
.type == 'RIGHTMOUSE':
270 click(self
, context
, event
)
272 elif event
.type == 'A':
273 remove_handler(self
.handlers
)
274 bpy
.ops
.curve
.select_all(action
='DESELECT')
276 elif event
.type == 'MOUSEMOVE': #
277 self
.x
= event
.mouse_x
278 self
.y
= event
.mouse_y
279 region
= bpy
.context
.region
280 rv3d
= bpy
.context
.space_data
.region_3d
281 self
.location3D
= view3d_utils
.region_2d_to_location_3d(
284 (event
.mouse_region_x
, event
.mouse_region_y
),
288 return {'PASS_THROUGH'}
290 def invoke(self
, context
, event
):
291 self
.execute(context
)
292 context
.window_manager
.modal_handler_add(self
)
293 return {'RUNNING_MODAL'}
296 def poll(cls
, context
):
297 return util
.Selected1OrMoreCurves()
301 bpy
.utils
.register_class(operators
)
305 bpy
.utils
.unregister_class(operators
)
307 if __name__
== "__main__":
311 operators
= [PathFinder
]