Merge branch 'blender-v2.92-release'
[blender-addons.git] / curve_tools / path_finder.py
blobe0b3fa983788476451daf89c8b8fbdb9158442b1
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 #####
19 bl_info = {
20 'name': 'PathFinder',
21 'author': 'Spivak Vladimir (cwolf3d)',
22 'version': (0, 5, 0),
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
27 'doc_url': '',
28 'tracker_url': '',
29 'category': 'Curve',
32 import time
33 import threading
35 import gpu
36 import bgl
37 from gpu_extras.batch import batch_for_shader
39 import bpy
40 from bpy.props import *
41 from bpy_extras import object_utils, view3d_utils
42 from mathutils import *
43 from math import *
45 from . import mathematics
46 from . import util
48 def get_bezier_points(spline, matrix_world):
49 point_list = []
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,
59 t/100)
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,
68 t/100)
69 point_list.extend([matrix_world @ h[2]])
70 point_list.extend([matrix_world @ spline.bezier_points[0].co])
72 return point_list
74 def get_points(spline, matrix_world):
75 point_list = []
76 len_points = len(spline.points)
77 if len_points > 1:
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))])
93 return point_list
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})
102 shader.bind()
103 shader.uniform_float("color", path_color)
104 bgl.glEnable(bgl.GL_BLEND)
105 bgl.glLineWidth(path_thickness)
106 batch.draw(shader)
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})
115 shader.bind()
116 shader.uniform_float("color", path_color)
117 bgl.glEnable(bgl.GL_BLEND)
118 bgl.glLineWidth(path_thickness)
119 batch.draw(shader)
121 def near(location3D, point, radius):
122 factor = 0
123 if point.x > (location3D.x - radius):
124 factor += 1
125 if point.x < (location3D.x + radius):
126 factor += 1
127 if point.y > (location3D.y - radius):
128 factor += 1
129 if point.y < (location3D.y + radius):
130 factor += 1
131 if point.z > (location3D.z - radius):
132 factor += 1
133 if point.z < (location3D.z + radius):
134 factor += 1
136 return factor
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)
150 factor_max = 0
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:
156 factor_max = factor
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,
164 t/100)
165 co = matrix_world @ h[2]
166 factor = near(self.location3D, co, radius)
167 if factor > factor_max:
168 factor_max = factor
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,
176 t/100)
177 co = matrix_world @ h[2]
178 factor = near(self.location3D, co, radius)
179 if factor > factor_max:
180 factor_max = factor
182 if factor_max == 6:
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)
193 factor_max = 0
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:
198 factor_max = factor
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:
208 factor_max = factor
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:
218 factor_max = factor
220 if factor_max == 6:
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:
225 point.select = True
227 def remove_handler(handlers):
228 for handler in handlers:
229 try:
230 bpy.types.SpaceView3D.draw_handler_remove(handler, 'WINDOW')
231 except:
232 pass
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),
247 subtype = 'XYZ')
249 handlers = []
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)
264 return {'CANCELLED'}
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(
297 region,
298 rv3d,
299 (event.mouse_region_x, event.mouse_region_y),
300 (0.0, 0.0, 0.0)
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'}
310 @classmethod
311 def poll(cls, context):
312 return util.Selected1OrMoreCurves()
314 def register():
315 for cls in classes:
316 bpy.utils.register_class(operators)
318 def unregister():
319 for cls in classes:
320 bpy.utils.unregister_class(operators)
322 if __name__ == "__main__":
323 register()
326 operators = [PathFinder]