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
21 from gpu_extras
.batch
import batch_for_shader
24 from bpy
.props
import *
25 from bpy_extras
import object_utils
, view3d_utils
26 from mathutils
import *
29 from . import mathematics
32 def get_bezier_points(spline
, matrix_world
):
34 len_bezier_points
= len(spline
.bezier_points
)
35 if len_bezier_points
> 1:
36 for i
in range(0, len_bezier_points
- 1):
37 point_list
.extend([matrix_world
@ spline
.bezier_points
[i
].co
])
38 for t
in range(0, 100, 2):
39 h
= mathematics
.subdivide_cubic_bezier(spline
.bezier_points
[i
].co
,
40 spline
.bezier_points
[i
].handle_right
,
41 spline
.bezier_points
[i
+ 1].handle_left
,
42 spline
.bezier_points
[i
+ 1].co
,
44 point_list
.extend([matrix_world
@ h
[2]])
45 if spline
.use_cyclic_u
and len_bezier_points
> 2:
46 point_list
.extend([matrix_world
@ spline
.bezier_points
[len_bezier_points
- 1].co
])
47 for t
in range(0, 100, 2):
48 h
= mathematics
.subdivide_cubic_bezier(spline
.bezier_points
[len_bezier_points
- 1].co
,
49 spline
.bezier_points
[len_bezier_points
- 1].handle_right
,
50 spline
.bezier_points
[0].handle_left
,
51 spline
.bezier_points
[0].co
,
53 point_list
.extend([matrix_world
@ h
[2]])
54 point_list
.extend([matrix_world
@ spline
.bezier_points
[0].co
])
58 def get_points(spline
, matrix_world
):
60 len_points
= len(spline
.points
)
62 for i
in range(0, len_points
- 1):
63 point_list
.extend([matrix_world
@ Vector((spline
.points
[i
].co
.x
, spline
.points
[i
].co
.y
, spline
.points
[i
].co
.z
))])
64 for t
in range(0, 100, 2):
65 x
= (spline
.points
[i
].co
.x
+ t
/ 100 * spline
.points
[i
+ 1].co
.x
) / (1 + t
/ 100)
66 y
= (spline
.points
[i
].co
.y
+ t
/ 100 * spline
.points
[i
+ 1].co
.y
) / (1 + t
/ 100)
67 z
= (spline
.points
[i
].co
.z
+ t
/ 100 * spline
.points
[i
+ 1].co
.z
) / (1 + t
/ 100)
68 point_list
.extend([matrix_world
@ Vector((x
, y
, z
))])
69 if spline
.use_cyclic_u
and len_points
> 2:
70 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
))])
71 for t
in range(0, 100, 2):
72 x
= (spline
.points
[len_points
- 1].co
.x
+ t
/ 100 * spline
.points
[0].co
.x
) / (1 + t
/ 100)
73 y
= (spline
.points
[len_points
- 1].co
.y
+ t
/ 100 * spline
.points
[0].co
.y
) / (1 + t
/ 100)
74 z
= (spline
.points
[len_points
- 1].co
.z
+ t
/ 100 * spline
.points
[0].co
.z
) / (1 + t
/ 100)
75 point_list
.extend([matrix_world
@ Vector((x
, y
, z
))])
76 point_list
.extend([matrix_world
@ Vector((spline
.points
[0].co
.x
, spline
.points
[0].co
.y
, spline
.points
[0].co
.z
))])
79 def draw_bezier_points(self
, context
, spline
, matrix_world
, path_color
, path_thickness
):
81 points
= get_bezier_points(spline
, matrix_world
)
83 shader
= gpu
.shader
.from_builtin('3D_UNIFORM_COLOR')
84 batch
= batch_for_shader(shader
, 'POINTS', {"pos": points
})
87 shader
.uniform_float("color", path_color
)
88 bgl
.glEnable(bgl
.GL_BLEND
)
89 bgl
.glLineWidth(path_thickness
)
92 def draw_points(self
, context
, spline
, matrix_world
, path_color
, path_thickness
):
94 points
= get_points(spline
, matrix_world
)
96 shader
= gpu
.shader
.from_builtin('3D_UNIFORM_COLOR')
97 batch
= batch_for_shader(shader
, 'POINTS', {"pos": points
})
100 shader
.uniform_float("color", path_color
)
101 bgl
.glEnable(bgl
.GL_BLEND
)
102 bgl
.glLineWidth(path_thickness
)
105 def near(location3D
, point
, radius
):
107 if point
.x
> (location3D
.x
- radius
):
109 if point
.x
< (location3D
.x
+ radius
):
111 if point
.y
> (location3D
.y
- radius
):
113 if point
.y
< (location3D
.y
+ radius
):
115 if point
.z
> (location3D
.z
- radius
):
117 if point
.z
< (location3D
.z
+ radius
):
122 def click(self
, context
, event
):
123 bpy
.ops
.object.mode_set(mode
= 'EDIT')
124 bpy
.context
.view_layer
.update()
125 for object in context
.selected_objects
:
126 matrix_world
= object.matrix_world
127 if object.type == 'CURVE':
128 curvedata
= object.data
130 radius
= bpy
.context
.scene
.curvetools
.PathFinderRadius
132 for spline
in curvedata
.splines
:
133 len_bezier_points
= len(spline
.bezier_points
)
135 for i
in range(0, len_bezier_points
):
137 co
= matrix_world
@ spline
.bezier_points
[i
].co
138 factor
= near(self
.location3D
, co
, radius
)
139 if factor
> factor_max
:
142 if i
< len_bezier_points
- 1:
143 for t
in range(0, 100, 2):
144 h
= mathematics
.subdivide_cubic_bezier(spline
.bezier_points
[i
].co
,
145 spline
.bezier_points
[i
].handle_right
,
146 spline
.bezier_points
[i
+ 1].handle_left
,
147 spline
.bezier_points
[i
+ 1].co
,
149 co
= matrix_world
@ h
[2]
150 factor
= near(self
.location3D
, co
, radius
)
151 if factor
> factor_max
:
154 if spline
.use_cyclic_u
and len_bezier_points
> 2:
155 for t
in range(0, 100, 2):
156 h
= mathematics
.subdivide_cubic_bezier(spline
.bezier_points
[len_bezier_points
- 1].co
,
157 spline
.bezier_points
[len_bezier_points
- 1].handle_right
,
158 spline
.bezier_points
[0].handle_left
,
159 spline
.bezier_points
[0].co
,
161 co
= matrix_world
@ h
[2]
162 factor
= near(self
.location3D
, co
, radius
)
163 if factor
> factor_max
:
167 args
= (self
, context
, spline
, matrix_world
, self
.path_color
, self
.path_thickness
)
168 self
.handlers
.append(bpy
.types
.SpaceView3D
.draw_handler_add(draw_bezier_points
, args
, 'WINDOW', 'POST_VIEW'))
170 for bezier_point
in spline
.bezier_points
:
171 bezier_point
.select_control_point
= True
172 bezier_point
.select_left_handle
= True
173 bezier_point
.select_right_handle
= True
175 for spline
in curvedata
.splines
:
176 len_points
= len(spline
.points
)
178 for i
in range(0, len_points
):
179 co
= matrix_world
@ Vector((spline
.points
[i
].co
.x
, spline
.points
[i
].co
.y
, spline
.points
[i
].co
.z
))
180 factor
= near(self
.location3D
, co
, radius
)
181 if factor
> factor_max
:
184 if i
< len_bezier_points
- 1:
185 for t
in range(0, 100, 2):
186 x
= (spline
.points
[i
].co
.x
+ t
/ 100 * spline
.points
[i
+ 1].co
.x
) / (1 + t
/ 100)
187 y
= (spline
.points
[i
].co
.y
+ t
/ 100 * spline
.points
[i
+ 1].co
.y
) / (1 + t
/ 100)
188 z
= (spline
.points
[i
].co
.z
+ t
/ 100 * spline
.points
[i
+ 1].co
.z
) / (1 + t
/ 100)
189 co
= matrix_world
@ Vector((x
, y
, z
))
190 factor
= near(self
.location3D
, co
, radius
)
191 if factor
> factor_max
:
194 if spline
.use_cyclic_u
and len_points
> 2:
195 for t
in range(0, 100, 2):
196 x
= (spline
.points
[len_points
- 1].co
.x
+ t
/ 100 * spline
.points
[0].co
.x
) / (1 + t
/ 100)
197 y
= (spline
.points
[len_points
- 1].co
.y
+ t
/ 100 * spline
.points
[0].co
.y
) / (1 + t
/ 100)
198 z
= (spline
.points
[len_points
- 1].co
.z
+ t
/ 100 * spline
.points
[0].co
.z
) / (1 + t
/ 100)
199 co
= matrix_world
@ Vector((x
, y
, z
))
200 factor
= near(self
.location3D
, co
, radius
)
201 if factor
> factor_max
:
205 args
= (self
, context
, spline
, matrix_world
, self
.path_color
, self
.path_thickness
)
206 self
.handlers
.append(bpy
.types
.SpaceView3D
.draw_handler_add(draw_points
, args
, 'WINDOW', 'POST_VIEW'))
208 for point
in spline
.points
:
211 def remove_handler(handlers
):
212 for handler
in handlers
:
214 bpy
.types
.SpaceView3D
.draw_handler_remove(handler
, 'WINDOW')
217 for handler
in handlers
:
218 handlers
.remove(handler
)
220 class PathFinder(bpy
.types
.Operator
):
221 bl_idname
= "curvetools.pathfinder"
222 bl_label
= "Path Finder"
223 bl_description
= "Path Finder"
224 bl_options
= {'REGISTER', 'UNDO'}
226 x
: IntProperty(name
="x", description
="x")
227 y
: IntProperty(name
="y", description
="y")
228 location3D
: FloatVectorProperty(name
= "",
229 description
= "Start location",
230 default
= (0.0, 0.0, 0.0),
235 def execute(self
, context
):
236 self
.report({'INFO'}, "ESC or TAB - cancel")
237 bpy
.ops
.object.mode_set(mode
= 'EDIT')
239 # color change in the panel
240 self
.path_color
= bpy
.context
.scene
.curvetools
.path_color
241 self
.path_thickness
= bpy
.context
.scene
.curvetools
.path_thickness
243 def modal(self
, context
, event
):
244 context
.area
.tag_redraw()
246 if event
.type in {'ESC', 'TAB'}: # Cancel
247 remove_handler(self
.handlers
)
250 if event
.type in {'X', 'DEL'}: # Cancel
251 remove_handler(self
.handlers
)
252 bpy
.ops
.curve
.delete(type='VERT')
253 return {'RUNNING_MODAL'}
255 elif event
.alt
and event
.shift
and event
.type == 'LEFTMOUSE':
256 click(self
, context
, event
)
258 elif event
.alt
and not event
.shift
and event
.type == 'LEFTMOUSE':
259 remove_handler(self
.handlers
)
260 bpy
.ops
.curve
.select_all(action
='DESELECT')
261 click(self
, context
, event
)
263 elif event
.alt
and event
.type == 'RIGHTMOUSE':
264 remove_handler(self
.handlers
)
265 bpy
.ops
.curve
.select_all(action
='DESELECT')
266 click(self
, context
, event
)
268 elif event
.alt
and not event
.shift
and event
.shift
and event
.type == 'RIGHTMOUSE':
269 click(self
, context
, event
)
271 elif event
.type == 'A':
272 remove_handler(self
.handlers
)
273 bpy
.ops
.curve
.select_all(action
='DESELECT')
275 elif event
.type == 'MOUSEMOVE': #
276 self
.x
= event
.mouse_x
277 self
.y
= event
.mouse_y
278 region
= bpy
.context
.region
279 rv3d
= bpy
.context
.space_data
.region_3d
280 self
.location3D
= view3d_utils
.region_2d_to_location_3d(
283 (event
.mouse_region_x
, event
.mouse_region_y
),
287 return {'PASS_THROUGH'}
289 def invoke(self
, context
, event
):
290 self
.execute(context
)
291 context
.window_manager
.modal_handler_add(self
)
292 return {'RUNNING_MODAL'}
295 def poll(cls
, context
):
296 return util
.Selected1OrMoreCurves()
300 bpy
.utils
.register_class(operators
)
304 bpy
.utils
.unregister_class(operators
)
306 if __name__
== "__main__":
310 operators
= [PathFinder
]