Pose library: fix asset creation operator poll when no object active
[blender-addons.git] / curve_tools / path_finder.py
blob242ed27cd78438e3fb5dc7ac6a0606ce24c09c33
1 # SPDX-License-Identifier: GPL-2.0-or-later
3 bl_info = {
4 'name': 'PathFinder',
5 'author': 'Spivak Vladimir (cwolf3d)',
6 'version': (0, 5, 1),
7 'blender': (3, 0, 0),
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
11 'doc_url': '',
12 'tracker_url': '',
13 'category': 'Curve',
16 import time
17 import threading
19 import gpu
20 from gpu_extras.batch import batch_for_shader
22 import bpy
23 from bpy.props import *
24 from bpy_extras import object_utils, view3d_utils
25 from mathutils import *
26 from math import *
28 from . import mathematics
29 from . import util
31 def get_bezier_points(spline, matrix_world):
32 point_list = []
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,
42 t/100)
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,
51 t/100)
52 point_list.extend([matrix_world @ h[2]])
53 point_list.extend([matrix_world @ spline.bezier_points[0].co])
55 return point_list
57 def get_points(spline, matrix_world):
58 point_list = []
59 len_points = len(spline.points)
60 if len_points > 1:
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))])
76 return point_list
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})
85 shader.bind()
86 shader.uniform_float("color", path_color)
87 gpu.state.blend_set('ALPHA')
88 gpu.state.line_width_set(path_thickness)
89 batch.draw(shader)
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})
98 shader.bind()
99 shader.uniform_float("color", path_color)
100 gpu.state.blend_set('ALPHA')
101 gpu.state.line_width_set(path_thickness)
102 batch.draw(shader)
104 def near(location3D, point, radius):
105 factor = 0
106 if point.x > (location3D.x - radius):
107 factor += 1
108 if point.x < (location3D.x + radius):
109 factor += 1
110 if point.y > (location3D.y - radius):
111 factor += 1
112 if point.y < (location3D.y + radius):
113 factor += 1
114 if point.z > (location3D.z - radius):
115 factor += 1
116 if point.z < (location3D.z + radius):
117 factor += 1
119 return factor
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)
133 factor_max = 0
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:
139 factor_max = factor
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,
147 t/100)
148 co = matrix_world @ h[2]
149 factor = near(self.location3D, co, radius)
150 if factor > factor_max:
151 factor_max = factor
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,
159 t/100)
160 co = matrix_world @ h[2]
161 factor = near(self.location3D, co, radius)
162 if factor > factor_max:
163 factor_max = factor
165 if factor_max == 6:
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)
176 factor_max = 0
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:
181 factor_max = factor
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:
191 factor_max = factor
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:
201 factor_max = factor
203 if factor_max == 6:
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:
208 point.select = True
210 def remove_handler(handlers):
211 for handler in handlers:
212 try:
213 bpy.types.SpaceView3D.draw_handler_remove(handler, 'WINDOW')
214 except:
215 pass
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),
230 subtype = 'XYZ')
232 handlers = []
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)
247 return {'CANCELLED'}
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(
280 region,
281 rv3d,
282 (event.mouse_region_x, event.mouse_region_y),
283 (0.0, 0.0, 0.0)
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'}
293 @classmethod
294 def poll(cls, context):
295 return util.Selected1OrMoreCurves()
297 def register():
298 for cls in classes:
299 bpy.utils.register_class(operators)
301 def unregister():
302 for cls in classes:
303 bpy.utils.unregister_class(operators)
305 if __name__ == "__main__":
306 register()
309 operators = [PathFinder]