Newer
Older
# SPDX-License-Identifier: GPL-2.0-or-later
bl_info = {
'name': 'PathFinder',
'author': 'Spivak Vladimir (cwolf3d)',
'version': (0, 5, 0),
'blender': (2, 80, 0),
'location': 'Curve Tools addon. (N) Panel',
'description': 'PathFinder - quick search, selection, removal of splines',
'warning': '', # used for warning icon and text in addons panel
'category': 'Curve',
}
import time
import threading
import gpu
import bgl
from gpu_extras.batch import batch_for_shader
import bpy
from bpy.props import *
from bpy_extras import object_utils, view3d_utils
from mathutils import *
from math import *
from . import mathematics
from . import util
def get_bezier_points(spline, matrix_world):
point_list = []
len_bezier_points = len(spline.bezier_points)
if len_bezier_points > 1:
for i in range(0, len_bezier_points - 1):
point_list.extend([matrix_world @ spline.bezier_points[i].co])
for t in range(0, 100, 2):
h = mathematics.subdivide_cubic_bezier(spline.bezier_points[i].co,
spline.bezier_points[i].handle_right,
spline.bezier_points[i + 1].handle_left,
spline.bezier_points[i + 1].co,
t/100)
point_list.extend([matrix_world @ h[2]])
if spline.use_cyclic_u and len_bezier_points > 2:
point_list.extend([matrix_world @ spline.bezier_points[len_bezier_points - 1].co])
for t in range(0, 100, 2):
h = mathematics.subdivide_cubic_bezier(spline.bezier_points[len_bezier_points - 1].co,
spline.bezier_points[len_bezier_points - 1].handle_right,
spline.bezier_points[0].handle_left,
spline.bezier_points[0].co,
t/100)
point_list.extend([matrix_world @ h[2]])
point_list.extend([matrix_world @ spline.bezier_points[0].co])
return point_list
def get_points(spline, matrix_world):
point_list = []
len_points = len(spline.points)
if len_points > 1:
for i in range(0, len_points - 1):
point_list.extend([matrix_world @ Vector((spline.points[i].co.x, spline.points[i].co.y, spline.points[i].co.z))])
for t in range(0, 100, 2):
x = (spline.points[i].co.x + t / 100 * spline.points[i + 1].co.x) / (1 + t / 100)
y = (spline.points[i].co.y + t / 100 * spline.points[i + 1].co.y) / (1 + t / 100)
z = (spline.points[i].co.z + t / 100 * spline.points[i + 1].co.z) / (1 + t / 100)
point_list.extend([matrix_world @ Vector((x, y, z))])
if spline.use_cyclic_u and len_points > 2:
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))])
for t in range(0, 100, 2):
x = (spline.points[len_points - 1].co.x + t / 100 * spline.points[0].co.x) / (1 + t / 100)
y = (spline.points[len_points - 1].co.y + t / 100 * spline.points[0].co.y) / (1 + t / 100)
z = (spline.points[len_points - 1].co.z + t / 100 * spline.points[0].co.z) / (1 + t / 100)
point_list.extend([matrix_world @ Vector((x, y, z))])
point_list.extend([matrix_world @ Vector((spline.points[0].co.x, spline.points[0].co.y, spline.points[0].co.z))])
return point_list
def draw_bezier_points(self, context, spline, matrix_world, path_color, path_thickness):
points = get_bezier_points(spline, matrix_world)
shader = gpu.shader.from_builtin('3D_UNIFORM_COLOR')
batch = batch_for_shader(shader, 'POINTS', {"pos": points})
shader.bind()
shader.uniform_float("color", path_color)
bgl.glEnable(bgl.GL_BLEND)
bgl.glLineWidth(path_thickness)
batch.draw(shader)
def draw_points(self, context, spline, matrix_world, path_color, path_thickness):
points = get_points(spline, matrix_world)
shader = gpu.shader.from_builtin('3D_UNIFORM_COLOR')
batch = batch_for_shader(shader, 'POINTS', {"pos": points})
shader.bind()
shader.uniform_float("color", path_color)
bgl.glEnable(bgl.GL_BLEND)
bgl.glLineWidth(path_thickness)
batch.draw(shader)
def near(location3D, point, radius):
factor = 0
if point.x > (location3D.x - radius):
factor += 1
if point.x < (location3D.x + radius):
factor += 1
if point.y > (location3D.y - radius):
factor += 1
if point.y < (location3D.y + radius):
factor += 1
if point.z > (location3D.z - radius):
factor += 1
if point.z < (location3D.z + radius):
factor += 1
return factor
def click(self, context, event):
bpy.ops.object.mode_set(mode = 'EDIT')
bpy.context.view_layer.update()
for object in context.selected_objects:
matrix_world = object.matrix_world
if object.type == 'CURVE':
curvedata = object.data
radius = bpy.context.scene.curvetools.PathFinderRadius
for spline in curvedata.splines:
len_bezier_points = len(spline.bezier_points)
factor_max = 0
for i in range(0, len_bezier_points):
co = matrix_world @ spline.bezier_points[i].co
factor = near(self.location3D, co, radius)
if factor > factor_max:
factor_max = factor
if i < len_bezier_points - 1:
for t in range(0, 100, 2):
h = mathematics.subdivide_cubic_bezier(spline.bezier_points[i].co,
spline.bezier_points[i].handle_right,
spline.bezier_points[i + 1].handle_left,
spline.bezier_points[i + 1].co,
t/100)
co = matrix_world @ h[2]
factor = near(self.location3D, co, radius)
if factor > factor_max:
factor_max = factor
if spline.use_cyclic_u and len_bezier_points > 2:
for t in range(0, 100, 2):
h = mathematics.subdivide_cubic_bezier(spline.bezier_points[len_bezier_points - 1].co,
spline.bezier_points[len_bezier_points - 1].handle_right,
spline.bezier_points[0].handle_left,
spline.bezier_points[0].co,
t/100)
co = matrix_world @ h[2]
factor = near(self.location3D, co, radius)
if factor > factor_max:
factor_max = factor
if factor_max == 6:
args = (self, context, spline, matrix_world, self.path_color, self.path_thickness)
self.handlers.append(bpy.types.SpaceView3D.draw_handler_add(draw_bezier_points, args, 'WINDOW', 'POST_VIEW'))
for bezier_point in spline.bezier_points:
bezier_point.select_control_point = True
bezier_point.select_left_handle = True
bezier_point.select_right_handle = True
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
for spline in curvedata.splines:
len_points = len(spline.points)
factor_max = 0
for i in range(0, len_points):
co = matrix_world @ Vector((spline.points[i].co.x, spline.points[i].co.y, spline.points[i].co.z))
factor = near(self.location3D, co, radius)
if factor > factor_max:
factor_max = factor
if i < len_bezier_points - 1:
for t in range(0, 100, 2):
x = (spline.points[i].co.x + t / 100 * spline.points[i + 1].co.x) / (1 + t / 100)
y = (spline.points[i].co.y + t / 100 * spline.points[i + 1].co.y) / (1 + t / 100)
z = (spline.points[i].co.z + t / 100 * spline.points[i + 1].co.z) / (1 + t / 100)
co = matrix_world @ Vector((x, y, z))
factor = near(self.location3D, co, radius)
if factor > factor_max:
factor_max = factor
if spline.use_cyclic_u and len_points > 2:
for t in range(0, 100, 2):
x = (spline.points[len_points - 1].co.x + t / 100 * spline.points[0].co.x) / (1 + t / 100)
y = (spline.points[len_points - 1].co.y + t / 100 * spline.points[0].co.y) / (1 + t / 100)
z = (spline.points[len_points - 1].co.z + t / 100 * spline.points[0].co.z) / (1 + t / 100)
co = matrix_world @ Vector((x, y, z))
factor = near(self.location3D, co, radius)
if factor > factor_max:
factor_max = factor
if factor_max == 6:
args = (self, context, spline, matrix_world, self.path_color, self.path_thickness)
self.handlers.append(bpy.types.SpaceView3D.draw_handler_add(draw_points, args, 'WINDOW', 'POST_VIEW'))
for point in spline.points:
point.select = True
def remove_handler(handlers):
for handler in handlers:
try:
bpy.types.SpaceView3D.draw_handler_remove(handler, 'WINDOW')
except:
pass
for handler in handlers:
class PathFinder(bpy.types.Operator):
bl_idname = "curvetools.pathfinder"
bl_label = "Path Finder"
bl_description = "Path Finder"
bl_options = {'REGISTER', 'UNDO'}
x: IntProperty(name="x", description="x")
y: IntProperty(name="y", description="y")
location3D: FloatVectorProperty(name = "",
description = "Start location",
default = (0.0, 0.0, 0.0),
subtype = 'XYZ')
def execute(self, context):
self.report({'INFO'}, "ESC or TAB - cancel")
bpy.ops.object.mode_set(mode = 'EDIT')
# color change in the panel
self.path_color = bpy.context.scene.curvetools.path_color
self.path_thickness = bpy.context.scene.curvetools.path_thickness
def modal(self, context, event):
context.area.tag_redraw()
if event.type in {'ESC', 'TAB'}: # Cancel
remove_handler(self.handlers)
if event.type in {'X', 'DEL'}: # Cancel
remove_handler(self.handlers)
bpy.ops.curve.delete(type='VERT')
return {'RUNNING_MODAL'}
elif event.alt and event.shift and event.type == 'LEFTMOUSE':
click(self, context, event)
elif event.alt and not event.shift and event.type == 'LEFTMOUSE':
remove_handler(self.handlers)
bpy.ops.curve.select_all(action='DESELECT')
click(self, context, event)
elif event.alt and event.type == 'RIGHTMOUSE':
remove_handler(self.handlers)
bpy.ops.curve.select_all(action='DESELECT')
click(self, context, event)
elif event.alt and not event.shift and event.shift and event.type == 'RIGHTMOUSE':
click(self, context, event)
elif event.type == 'A':
remove_handler(self.handlers)
bpy.ops.curve.select_all(action='DESELECT')
self.x = event.mouse_x
self.y = event.mouse_y
region = bpy.context.region
rv3d = bpy.context.space_data.region_3d
self.location3D = view3d_utils.region_2d_to_location_3d(
region,
rv3d,
(event.mouse_region_x, event.mouse_region_y),
(0.0, 0.0, 0.0)
return {'PASS_THROUGH'}
def invoke(self, context, event):
self.execute(context)
context.window_manager.modal_handler_add(self)
return {'RUNNING_MODAL'}
@classmethod
def poll(cls, context):
return util.Selected1OrMoreCurves()
for cls in classes:
bpy.utils.register_class(operators)
for cls in classes:
bpy.utils.unregister_class(operators)
if __name__ == "__main__":
operators = [PathFinder]