1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and / or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
21 'author': 'Spivak Vladimir (cwolf3d)',
23 'blender': (2, 80, 0),
24 'location': 'Curve Tools addon. (N) Panel',
25 'description': 'PathFinder - quick search, selection, removal of splines',
26 'warning': '', # used for warning icon and text in addons panel
36 from gpu_extras
.batch
import batch_for_shader
39 from bpy
.props
import *
40 from bpy_extras
import object_utils
, view3d_utils
41 from mathutils
import *
44 from . import Properties
46 from . import CurveIntersections
48 from . import Surfaces
51 def get_bezier_points(spline
, matrix_world
):
53 len_bezier_points
= len(spline
.bezier_points
)
54 if len_bezier_points
> 1:
55 for i
in range(0, len_bezier_points
- 1):
56 point_list
.extend([matrix_world
@ spline
.bezier_points
[i
].co
])
57 for t
in range(0, 100, 2):
58 h
= Math
.subdivide_cubic_bezier(spline
.bezier_points
[i
].co
,
59 spline
.bezier_points
[i
].handle_right
,
60 spline
.bezier_points
[i
+ 1].handle_left
,
61 spline
.bezier_points
[i
+ 1].co
,
63 point_list
.extend([matrix_world
@ h
[2]])
64 if spline
.use_cyclic_u
and len_bezier_points
> 2:
65 point_list
.extend([matrix_world
@ spline
.bezier_points
[len_bezier_points
- 1].co
])
66 for t
in range(0, 100, 2):
67 h
= Math
.subdivide_cubic_bezier(spline
.bezier_points
[len_bezier_points
- 1].co
,
68 spline
.bezier_points
[len_bezier_points
- 1].handle_right
,
69 spline
.bezier_points
[0].handle_left
,
70 spline
.bezier_points
[0].co
,
72 point_list
.extend([matrix_world
@ h
[2]])
73 point_list
.extend([matrix_world
@ spline
.bezier_points
[0].co
])
77 def get_points(spline
, matrix_world
):
79 len_points
= len(spline
.points
)
81 for i
in range(0, len_points
- 1):
82 point_list
.extend([matrix_world
@ Vector((spline
.points
[i
].co
.x
, spline
.points
[i
].co
.y
, spline
.points
[i
].co
.z
))])
83 for t
in range(0, 100, 2):
84 x
= (spline
.points
[i
].co
.x
+ t
/ 100 * spline
.points
[i
+ 1].co
.x
) / (1 + t
/ 100)
85 y
= (spline
.points
[i
].co
.y
+ t
/ 100 * spline
.points
[i
+ 1].co
.y
) / (1 + t
/ 100)
86 z
= (spline
.points
[i
].co
.z
+ t
/ 100 * spline
.points
[i
+ 1].co
.z
) / (1 + t
/ 100)
87 point_list
.extend([matrix_world
@ Vector((x
, y
, z
))])
88 if spline
.use_cyclic_u
and len_points
> 2:
89 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
))])
90 for t
in range(0, 100, 2):
91 x
= (spline
.points
[len_points
- 1].co
.x
+ t
/ 100 * spline
.points
[0].co
.x
) / (1 + t
/ 100)
92 y
= (spline
.points
[len_points
- 1].co
.y
+ t
/ 100 * spline
.points
[0].co
.y
) / (1 + t
/ 100)
93 z
= (spline
.points
[len_points
- 1].co
.z
+ t
/ 100 * spline
.points
[0].co
.z
) / (1 + t
/ 100)
94 point_list
.extend([matrix_world
@ Vector((x
, y
, z
))])
95 point_list
.extend([matrix_world
@ Vector((spline
.points
[0].co
.x
, spline
.points
[0].co
.y
, spline
.points
[0].co
.z
))])
98 def draw_bezier_points(self
, context
, spline
, matrix_world
, path_color
, path_thickness
):
100 points
= get_bezier_points(spline
, matrix_world
)
102 shader
= gpu
.shader
.from_builtin('3D_UNIFORM_COLOR')
103 batch
= batch_for_shader(shader
, 'LINES', {"pos": points
})
106 shader
.uniform_float("color", path_color
)
107 bgl
.glEnable(bgl
.GL_BLEND
)
108 bgl
.glLineWidth(path_thickness
)
111 def draw_points(self
, context
, spline
, matrix_world
, path_color
, path_thickness
):
113 points
= get_points(spline
, matrix_world
)
115 shader
= gpu
.shader
.from_builtin('3D_UNIFORM_COLOR')
116 batch
= batch_for_shader(shader
, 'LINES', {"pos": points
})
119 shader
.uniform_float("color", path_color
)
120 bgl
.glEnable(bgl
.GL_BLEND
)
121 bgl
.glLineWidth(path_thickness
)
124 def near(location3D
, point
, radius
):
126 if point
.x
> (location3D
.x
- radius
):
128 if point
.x
< (location3D
.x
+ radius
):
130 if point
.y
> (location3D
.y
- radius
):
132 if point
.y
< (location3D
.y
+ radius
):
134 if point
.z
> (location3D
.z
- radius
):
136 if point
.z
< (location3D
.z
+ radius
):
141 def click(self
, context
, event
):
142 bpy
.ops
.object.mode_set(mode
= 'EDIT')
143 bpy
.context
.view_layer
.update()
144 for object in context
.selected_objects
:
145 matrix_world
= object.matrix_world
146 if object.type == 'CURVE':
147 curvedata
= object.data
149 radius
= bpy
.context
.scene
.curvetools
.PathFinderRadius
151 for spline
in curvedata
.splines
:
152 len_bezier_points
= len(spline
.bezier_points
)
154 for i
in range(0, len_bezier_points
):
156 co
= matrix_world
@ spline
.bezier_points
[i
].co
157 factor
= near(self
.location3D
, co
, radius
)
158 if factor
> factor_max
:
161 if i
< len_bezier_points
- 1:
162 for t
in range(0, 100, 2):
163 h
= Math
.subdivide_cubic_bezier(spline
.bezier_points
[i
].co
,
164 spline
.bezier_points
[i
].handle_right
,
165 spline
.bezier_points
[i
+ 1].handle_left
,
166 spline
.bezier_points
[i
+ 1].co
,
168 co
= matrix_world
@ h
[2]
169 factor
= near(self
.location3D
, co
, radius
)
170 if factor
> factor_max
:
173 if spline
.use_cyclic_u
and len_bezier_points
> 2:
174 for t
in range(0, 100, 2):
175 h
= Math
.subdivide_cubic_bezier(spline
.bezier_points
[len_bezier_points
- 1].co
,
176 spline
.bezier_points
[len_bezier_points
- 1].handle_right
,
177 spline
.bezier_points
[0].handle_left
,
178 spline
.bezier_points
[0].co
,
180 co
= matrix_world
@ h
[2]
181 factor
= near(self
.location3D
, co
, radius
)
182 if factor
> factor_max
:
186 args
= (self
, context
, spline
, matrix_world
, self
.path_color
, self
.path_thickness
)
187 self
.handlers
.append(bpy
.types
.SpaceView3D
.draw_handler_add(draw_bezier_points
, args
, 'WINDOW', 'POST_VIEW'))
189 for bezier_point
in spline
.bezier_points
:
190 bezier_point
.select_control_point
= True
191 bezier_point
.select_left_handle
= True
192 bezier_point
.select_right_handle
= True
194 for spline
in curvedata
.splines
:
195 len_points
= len(spline
.points
)
197 for i
in range(0, len_points
):
198 co
= matrix_world
@ Vector((spline
.points
[i
].co
.x
, spline
.points
[i
].co
.y
, spline
.points
[i
].co
.z
))
199 factor
= near(self
.location3D
, co
, radius
)
200 if factor
> factor_max
:
203 if i
< len_bezier_points
- 1:
204 for t
in range(0, 100, 2):
205 x
= (spline
.points
[i
].co
.x
+ t
/ 100 * spline
.points
[i
+ 1].co
.x
) / (1 + t
/ 100)
206 y
= (spline
.points
[i
].co
.y
+ t
/ 100 * spline
.points
[i
+ 1].co
.y
) / (1 + t
/ 100)
207 z
= (spline
.points
[i
].co
.z
+ t
/ 100 * spline
.points
[i
+ 1].co
.z
) / (1 + t
/ 100)
208 co
= matrix_world
@ Vector((x
, y
, z
))
209 factor
= near(self
.location3D
, co
, radius
)
210 if factor
> factor_max
:
213 if spline
.use_cyclic_u
and len_points
> 2:
214 for t
in range(0, 100, 2):
215 x
= (spline
.points
[len_points
- 1].co
.x
+ t
/ 100 * spline
.points
[0].co
.x
) / (1 + t
/ 100)
216 y
= (spline
.points
[len_points
- 1].co
.y
+ t
/ 100 * spline
.points
[0].co
.y
) / (1 + t
/ 100)
217 z
= (spline
.points
[len_points
- 1].co
.z
+ t
/ 100 * spline
.points
[0].co
.z
) / (1 + t
/ 100)
218 co
= matrix_world
@ Vector((x
, y
, z
))
219 factor
= near(self
.location3D
, co
, radius
)
220 if factor
> factor_max
:
224 args
= (self
, context
, spline
, matrix_world
, self
.path_color
, self
.path_thickness
)
225 self
.handlers
.append(bpy
.types
.SpaceView3D
.draw_handler_add(draw_points
, args
, 'WINDOW', 'POST_VIEW'))
227 for point
in spline
.points
:
230 def remove_handler(handlers
):
231 for handler
in handlers
:
233 bpy
.types
.SpaceView3D
.draw_handler_remove(handler
, 'WINDOW')
236 for handler
in handlers
:
237 handlers
.remove(handler
)
239 class PathFinder(bpy
.types
.Operator
):
240 bl_idname
= "curvetools.pathfinder"
241 bl_label
= "Path Finder"
242 bl_description
= "Path Finder"
243 bl_options
= {'REGISTER', 'UNDO'}
245 x
: IntProperty(name
="x", description
="x")
246 y
: IntProperty(name
="y", description
="y")
247 location3D
: FloatVectorProperty(name
= "",
248 description
= "Start location",
249 default
= (0.0, 0.0, 0.0),
254 def execute(self
, context
):
255 self
.report({'INFO'}, "ESC or TAB - cancel")
256 bpy
.ops
.object.mode_set(mode
= 'EDIT')
258 # color change in the panel
259 self
.path_color
= bpy
.context
.scene
.curvetools
.path_color
260 self
.path_thickness
= bpy
.context
.scene
.curvetools
.path_thickness
262 def modal(self
, context
, event
):
263 context
.area
.tag_redraw()
265 if event
.type in {'ESC', 'TAB'}: # Cancel
266 remove_handler(self
.handlers
)
269 if event
.type in {'X', 'DEL'}: # Cancel
270 remove_handler(self
.handlers
)
271 bpy
.ops
.curve
.delete(type='VERT')
272 return {'RUNNING_MODAL'}
274 elif event
.alt
and not event
.shift
and event
.type == 'LEFTMOUSE':
275 remove_handler(self
.handlers
)
276 bpy
.ops
.curve
.select_all(action
='DESELECT')
277 click(self
, context
, event
)
279 elif event
.alt
and event
.shift
and event
.type == 'LEFTMOUSE':
280 click(self
, context
, event
)
282 elif event
.alt
and event
.type == 'RIGHTMOUSE':
283 remove_handler(self
.handlers
)
284 bpy
.ops
.curve
.select_all(action
='DESELECT')
285 click(self
, context
, event
)
287 elif event
.alt
and not event
.shift
and event
.shift
and event
.type == 'RIGHTMOUSE':
288 click(self
, context
, event
)
290 elif event
.type == 'A':
291 remove_handler(self
.handlers
)
292 bpy
.ops
.curve
.select_all(action
='DESELECT')
294 elif event
.type == 'MOUSEMOVE': #
295 self
.x
= event
.mouse_x
296 self
.y
= event
.mouse_y
297 region
= bpy
.context
.region
298 rv3d
= bpy
.context
.space_data
.region_3d
299 self
.location3D
= view3d_utils
.region_2d_to_location_3d(
302 (event
.mouse_region_x
, event
.mouse_region_y
),
306 return {'PASS_THROUGH'}
308 def invoke(self
, context
, event
):
309 self
.execute(context
)
310 context
.window_manager
.modal_handler_add(self
)
311 return {'RUNNING_MODAL'}
314 def poll(cls
, context
):
315 return (context
.object is not None and
316 context
.object.type == 'CURVE')
319 bpy
.utils
.register_class(PathFinder
)
322 bpy
.utils
.unregister_class(PathFinder
)
324 if __name__
== "__main__":