Cleanup: remove "Tweak" event type
[blender-addons.git] / curve_tools / path_finder.py
blobabf53a6cddf6570ccb3b6e589541b894740d01c5
1 # SPDX-License-Identifier: GPL-2.0-or-later
3 bl_info = {
4 'name': 'PathFinder',
5 'author': 'Spivak Vladimir (cwolf3d)',
6 'version': (0, 5, 0),
7 'blender': (2, 80, 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 import bgl
21 from gpu_extras.batch import batch_for_shader
23 import bpy
24 from bpy.props import *
25 from bpy_extras import object_utils, view3d_utils
26 from mathutils import *
27 from math import *
29 from . import mathematics
30 from . import util
32 def get_bezier_points(spline, matrix_world):
33 point_list = []
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,
43 t/100)
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,
52 t/100)
53 point_list.extend([matrix_world @ h[2]])
54 point_list.extend([matrix_world @ spline.bezier_points[0].co])
56 return point_list
58 def get_points(spline, matrix_world):
59 point_list = []
60 len_points = len(spline.points)
61 if len_points > 1:
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))])
77 return point_list
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})
86 shader.bind()
87 shader.uniform_float("color", path_color)
88 bgl.glEnable(bgl.GL_BLEND)
89 bgl.glLineWidth(path_thickness)
90 batch.draw(shader)
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})
99 shader.bind()
100 shader.uniform_float("color", path_color)
101 bgl.glEnable(bgl.GL_BLEND)
102 bgl.glLineWidth(path_thickness)
103 batch.draw(shader)
105 def near(location3D, point, radius):
106 factor = 0
107 if point.x > (location3D.x - radius):
108 factor += 1
109 if point.x < (location3D.x + radius):
110 factor += 1
111 if point.y > (location3D.y - radius):
112 factor += 1
113 if point.y < (location3D.y + radius):
114 factor += 1
115 if point.z > (location3D.z - radius):
116 factor += 1
117 if point.z < (location3D.z + radius):
118 factor += 1
120 return factor
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)
134 factor_max = 0
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:
140 factor_max = factor
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,
148 t/100)
149 co = matrix_world @ h[2]
150 factor = near(self.location3D, co, radius)
151 if factor > factor_max:
152 factor_max = factor
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,
160 t/100)
161 co = matrix_world @ h[2]
162 factor = near(self.location3D, co, radius)
163 if factor > factor_max:
164 factor_max = factor
166 if factor_max == 6:
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)
177 factor_max = 0
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:
182 factor_max = factor
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:
192 factor_max = factor
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:
202 factor_max = factor
204 if factor_max == 6:
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:
209 point.select = True
211 def remove_handler(handlers):
212 for handler in handlers:
213 try:
214 bpy.types.SpaceView3D.draw_handler_remove(handler, 'WINDOW')
215 except:
216 pass
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),
231 subtype = 'XYZ')
233 handlers = []
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)
248 return {'CANCELLED'}
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(
281 region,
282 rv3d,
283 (event.mouse_region_x, event.mouse_region_y),
284 (0.0, 0.0, 0.0)
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'}
294 @classmethod
295 def poll(cls, context):
296 return util.Selected1OrMoreCurves()
298 def register():
299 for cls in classes:
300 bpy.utils.register_class(operators)
302 def unregister():
303 for cls in classes:
304 bpy.utils.unregister_class(operators)
306 if __name__ == "__main__":
307 register()
310 operators = [PathFinder]