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
37 from gpu_extras
.batch
import batch_for_shader
40 from bpy
.props
import *
41 from bpy_extras
import object_utils
, view3d_utils
42 from mathutils
import *
45 from . import mathematics
48 def get_bezier_points(spline
, matrix_world
):
50 len_bezier_points
= len(spline
.bezier_points
)
51 if len_bezier_points
> 1:
52 for i
in range(0, len_bezier_points
- 1):
53 point_list
.extend([matrix_world
@ spline
.bezier_points
[i
].co
])
54 for t
in range(0, 100, 2):
55 h
= mathematics
.subdivide_cubic_bezier(spline
.bezier_points
[i
].co
,
56 spline
.bezier_points
[i
].handle_right
,
57 spline
.bezier_points
[i
+ 1].handle_left
,
58 spline
.bezier_points
[i
+ 1].co
,
60 point_list
.extend([matrix_world
@ h
[2]])
61 if spline
.use_cyclic_u
and len_bezier_points
> 2:
62 point_list
.extend([matrix_world
@ spline
.bezier_points
[len_bezier_points
- 1].co
])
63 for t
in range(0, 100, 2):
64 h
= mathematics
.subdivide_cubic_bezier(spline
.bezier_points
[len_bezier_points
- 1].co
,
65 spline
.bezier_points
[len_bezier_points
- 1].handle_right
,
66 spline
.bezier_points
[0].handle_left
,
67 spline
.bezier_points
[0].co
,
69 point_list
.extend([matrix_world
@ h
[2]])
70 point_list
.extend([matrix_world
@ spline
.bezier_points
[0].co
])
74 def get_points(spline
, matrix_world
):
76 len_points
= len(spline
.points
)
78 for i
in range(0, len_points
- 1):
79 point_list
.extend([matrix_world
@ Vector((spline
.points
[i
].co
.x
, spline
.points
[i
].co
.y
, spline
.points
[i
].co
.z
))])
80 for t
in range(0, 100, 2):
81 x
= (spline
.points
[i
].co
.x
+ t
/ 100 * spline
.points
[i
+ 1].co
.x
) / (1 + t
/ 100)
82 y
= (spline
.points
[i
].co
.y
+ t
/ 100 * spline
.points
[i
+ 1].co
.y
) / (1 + t
/ 100)
83 z
= (spline
.points
[i
].co
.z
+ t
/ 100 * spline
.points
[i
+ 1].co
.z
) / (1 + t
/ 100)
84 point_list
.extend([matrix_world
@ Vector((x
, y
, z
))])
85 if spline
.use_cyclic_u
and len_points
> 2:
86 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
))])
87 for t
in range(0, 100, 2):
88 x
= (spline
.points
[len_points
- 1].co
.x
+ t
/ 100 * spline
.points
[0].co
.x
) / (1 + t
/ 100)
89 y
= (spline
.points
[len_points
- 1].co
.y
+ t
/ 100 * spline
.points
[0].co
.y
) / (1 + t
/ 100)
90 z
= (spline
.points
[len_points
- 1].co
.z
+ t
/ 100 * spline
.points
[0].co
.z
) / (1 + t
/ 100)
91 point_list
.extend([matrix_world
@ Vector((x
, y
, z
))])
92 point_list
.extend([matrix_world
@ Vector((spline
.points
[0].co
.x
, spline
.points
[0].co
.y
, spline
.points
[0].co
.z
))])
95 def draw_bezier_points(self
, context
, spline
, matrix_world
, path_color
, path_thickness
):
97 points
= get_bezier_points(spline
, matrix_world
)
99 shader
= gpu
.shader
.from_builtin('3D_UNIFORM_COLOR')
100 batch
= batch_for_shader(shader
, 'POINTS', {"pos": points
})
103 shader
.uniform_float("color", path_color
)
104 bgl
.glEnable(bgl
.GL_BLEND
)
105 bgl
.glLineWidth(path_thickness
)
108 def draw_points(self
, context
, spline
, matrix_world
, path_color
, path_thickness
):
110 points
= get_points(spline
, matrix_world
)
112 shader
= gpu
.shader
.from_builtin('3D_UNIFORM_COLOR')
113 batch
= batch_for_shader(shader
, 'POINTS', {"pos": points
})
116 shader
.uniform_float("color", path_color
)
117 bgl
.glEnable(bgl
.GL_BLEND
)
118 bgl
.glLineWidth(path_thickness
)
121 def near(location3D
, point
, radius
):
123 if point
.x
> (location3D
.x
- radius
):
125 if point
.x
< (location3D
.x
+ radius
):
127 if point
.y
> (location3D
.y
- radius
):
129 if point
.y
< (location3D
.y
+ radius
):
131 if point
.z
> (location3D
.z
- radius
):
133 if point
.z
< (location3D
.z
+ radius
):
138 def click(self
, context
, event
):
139 bpy
.ops
.object.mode_set(mode
= 'EDIT')
140 bpy
.context
.view_layer
.update()
141 for object in context
.selected_objects
:
142 matrix_world
= object.matrix_world
143 if object.type == 'CURVE':
144 curvedata
= object.data
146 radius
= bpy
.context
.scene
.curvetools
.PathFinderRadius
148 for spline
in curvedata
.splines
:
149 len_bezier_points
= len(spline
.bezier_points
)
151 for i
in range(0, len_bezier_points
):
153 co
= matrix_world
@ spline
.bezier_points
[i
].co
154 factor
= near(self
.location3D
, co
, radius
)
155 if factor
> factor_max
:
158 if i
< len_bezier_points
- 1:
159 for t
in range(0, 100, 2):
160 h
= mathematics
.subdivide_cubic_bezier(spline
.bezier_points
[i
].co
,
161 spline
.bezier_points
[i
].handle_right
,
162 spline
.bezier_points
[i
+ 1].handle_left
,
163 spline
.bezier_points
[i
+ 1].co
,
165 co
= matrix_world
@ h
[2]
166 factor
= near(self
.location3D
, co
, radius
)
167 if factor
> factor_max
:
170 if spline
.use_cyclic_u
and len_bezier_points
> 2:
171 for t
in range(0, 100, 2):
172 h
= mathematics
.subdivide_cubic_bezier(spline
.bezier_points
[len_bezier_points
- 1].co
,
173 spline
.bezier_points
[len_bezier_points
- 1].handle_right
,
174 spline
.bezier_points
[0].handle_left
,
175 spline
.bezier_points
[0].co
,
177 co
= matrix_world
@ h
[2]
178 factor
= near(self
.location3D
, co
, radius
)
179 if factor
> factor_max
:
183 args
= (self
, context
, spline
, matrix_world
, self
.path_color
, self
.path_thickness
)
184 self
.handlers
.append(bpy
.types
.SpaceView3D
.draw_handler_add(draw_bezier_points
, args
, 'WINDOW', 'POST_VIEW'))
186 for bezier_point
in spline
.bezier_points
:
187 bezier_point
.select_control_point
= True
188 bezier_point
.select_left_handle
= True
189 bezier_point
.select_right_handle
= True
191 for spline
in curvedata
.splines
:
192 len_points
= len(spline
.points
)
194 for i
in range(0, len_points
):
195 co
= matrix_world
@ Vector((spline
.points
[i
].co
.x
, spline
.points
[i
].co
.y
, spline
.points
[i
].co
.z
))
196 factor
= near(self
.location3D
, co
, radius
)
197 if factor
> factor_max
:
200 if i
< len_bezier_points
- 1:
201 for t
in range(0, 100, 2):
202 x
= (spline
.points
[i
].co
.x
+ t
/ 100 * spline
.points
[i
+ 1].co
.x
) / (1 + t
/ 100)
203 y
= (spline
.points
[i
].co
.y
+ t
/ 100 * spline
.points
[i
+ 1].co
.y
) / (1 + t
/ 100)
204 z
= (spline
.points
[i
].co
.z
+ t
/ 100 * spline
.points
[i
+ 1].co
.z
) / (1 + t
/ 100)
205 co
= matrix_world
@ Vector((x
, y
, z
))
206 factor
= near(self
.location3D
, co
, radius
)
207 if factor
> factor_max
:
210 if spline
.use_cyclic_u
and len_points
> 2:
211 for t
in range(0, 100, 2):
212 x
= (spline
.points
[len_points
- 1].co
.x
+ t
/ 100 * spline
.points
[0].co
.x
) / (1 + t
/ 100)
213 y
= (spline
.points
[len_points
- 1].co
.y
+ t
/ 100 * spline
.points
[0].co
.y
) / (1 + t
/ 100)
214 z
= (spline
.points
[len_points
- 1].co
.z
+ t
/ 100 * spline
.points
[0].co
.z
) / (1 + t
/ 100)
215 co
= matrix_world
@ Vector((x
, y
, z
))
216 factor
= near(self
.location3D
, co
, radius
)
217 if factor
> factor_max
:
221 args
= (self
, context
, spline
, matrix_world
, self
.path_color
, self
.path_thickness
)
222 self
.handlers
.append(bpy
.types
.SpaceView3D
.draw_handler_add(draw_points
, args
, 'WINDOW', 'POST_VIEW'))
224 for point
in spline
.points
:
227 def remove_handler(handlers
):
228 for handler
in handlers
:
230 bpy
.types
.SpaceView3D
.draw_handler_remove(handler
, 'WINDOW')
233 for handler
in handlers
:
234 handlers
.remove(handler
)
236 class PathFinder(bpy
.types
.Operator
):
237 bl_idname
= "curvetools.pathfinder"
238 bl_label
= "Path Finder"
239 bl_description
= "Path Finder"
240 bl_options
= {'REGISTER', 'UNDO'}
242 x
: IntProperty(name
="x", description
="x")
243 y
: IntProperty(name
="y", description
="y")
244 location3D
: FloatVectorProperty(name
= "",
245 description
= "Start location",
246 default
= (0.0, 0.0, 0.0),
251 def execute(self
, context
):
252 self
.report({'INFO'}, "ESC or TAB - cancel")
253 bpy
.ops
.object.mode_set(mode
= 'EDIT')
255 # color change in the panel
256 self
.path_color
= bpy
.context
.scene
.curvetools
.path_color
257 self
.path_thickness
= bpy
.context
.scene
.curvetools
.path_thickness
259 def modal(self
, context
, event
):
260 context
.area
.tag_redraw()
262 if event
.type in {'ESC', 'TAB'}: # Cancel
263 remove_handler(self
.handlers
)
266 if event
.type in {'X', 'DEL'}: # Cancel
267 remove_handler(self
.handlers
)
268 bpy
.ops
.curve
.delete(type='VERT')
269 return {'RUNNING_MODAL'}
271 elif event
.alt
and event
.shift
and event
.type == 'LEFTMOUSE':
272 click(self
, context
, event
)
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
.type == 'RIGHTMOUSE':
280 remove_handler(self
.handlers
)
281 bpy
.ops
.curve
.select_all(action
='DESELECT')
282 click(self
, context
, event
)
284 elif event
.alt
and not event
.shift
and event
.shift
and event
.type == 'RIGHTMOUSE':
285 click(self
, context
, event
)
287 elif event
.type == 'A':
288 remove_handler(self
.handlers
)
289 bpy
.ops
.curve
.select_all(action
='DESELECT')
291 elif event
.type == 'MOUSEMOVE': #
292 self
.x
= event
.mouse_x
293 self
.y
= event
.mouse_y
294 region
= bpy
.context
.region
295 rv3d
= bpy
.context
.space_data
.region_3d
296 self
.location3D
= view3d_utils
.region_2d_to_location_3d(
299 (event
.mouse_region_x
, event
.mouse_region_y
),
303 return {'PASS_THROUGH'}
305 def invoke(self
, context
, event
):
306 self
.execute(context
)
307 context
.window_manager
.modal_handler_add(self
)
308 return {'RUNNING_MODAL'}
311 def poll(cls
, context
):
312 return util
.Selected1OrMoreCurves()
316 bpy
.utils
.register_class(operators
)
320 bpy
.utils
.unregister_class(operators
)
322 if __name__
== "__main__":
326 operators
= [PathFinder
]