From d2f6cf924a622dd28ab4bee4e3a74792bce1eebc Mon Sep 17 00:00:00 2001 From: "Vladimir Spivak(cwolf3d)" Date: Thu, 7 Jan 2021 19:05:54 +0200 Subject: Addon: Curve Tools: Made changes from Lichtso github --- curve_tools/__init__.py | 3 +-- curve_tools/cad.py | 67 +++++++++++++++++-------------------------------- curve_tools/internal.py | 22 +++++++++++++++- curve_tools/toolpath.py | 22 ++++++++-------- 4 files changed, 56 insertions(+), 58 deletions(-) diff --git a/curve_tools/__init__.py b/curve_tools/__init__.py index 4a9d283c..d7ce3736 100644 --- a/curve_tools/__init__.py +++ b/curve_tools/__init__.py @@ -25,11 +25,10 @@ bl_info = { "name": "Curve Tools", "description": "Adds some functionality for bezier/nurbs curve/surface modeling", "author": "Mackraken", - "version": (0, 4, 4), + "version": (0, 4, 5), "blender": (2, 80, 0), "location": "View3D > Tool Shelf > Edit Tab", "warning": "WIP", - "doc_url": "", "doc_url": "{BLENDER_MANUAL_URL}/addons/add_curve/curve_tools.html", "category": "Add Curve", } diff --git a/curve_tools/cad.py b/curve_tools/cad.py index e46af8d3..dd11b4c7 100644 --- a/curve_tools/cad.py +++ b/curve_tools/cad.py @@ -72,7 +72,6 @@ class Boolean(bpy.types.Operator): current_mode = bpy.context.object.mode bpy.ops.object.mode_set(mode = 'EDIT') - if bpy.context.object.data.dimensions != '2D': self.report({'WARNING'}, 'Can only be applied in 2D') return {'CANCELLED'} @@ -116,7 +115,7 @@ class HandleProjection(bpy.types.Operator): @classmethod def poll(cls, context): - return internal.curveObject() + return util.Selected1OrMoreCurves() def execute(self, context): segments = internal.bezierSegments(bpy.context.object.data.splines, True) @@ -132,54 +131,34 @@ class MergeEnds(bpy.types.Operator): bl_description = bl_label = 'Merge Ends' bl_options = {'REGISTER', 'UNDO'} + max_dist: bpy.props.FloatProperty(name='Distance', description='Threshold of the maximum distance at which two control points are merged', unit='LENGTH', min=0.0, default=0.1) + @classmethod def poll(cls, context): return util.Selected1OrMoreCurves() def execute(self, context): - points = [] - selected_splines = [] - is_last_point = [] - for spline in bpy.context.object.data.splines: - if spline.type != 'BEZIER' or spline.use_cyclic_u: + splines = [spline for spline in internal.getSelectedSplines(True, False) if spline.use_cyclic_u == False] + + while len(splines) > 0: + spline = splines.pop() + closest_pair = ([spline, spline], [spline.bezier_points[0], spline.bezier_points[-1]], [False, True]) + min_dist = (spline.bezier_points[0].co-spline.bezier_points[-1].co).length + for other_spline in splines: + for j in range(-1, 1): + for i in range(-1, 1): + dist = (spline.bezier_points[i].co-other_spline.bezier_points[j].co).length + if min_dist > dist: + min_dist = dist + closest_pair = ([spline, other_spline], [spline.bezier_points[i], other_spline.bezier_points[j]], [i == -1, j == -1]) + if min_dist > self.max_dist: continue - if spline.bezier_points[0].select_control_point: - points.append(spline.bezier_points[0]) - selected_splines.append(spline) - is_last_point.append(False) - if spline.bezier_points[-1].select_control_point: - points.append(spline.bezier_points[-1]) - selected_splines.append(spline) - is_last_point.append(True) - - if len(points) != 2: - self.report({'WARNING'}, 'Invalid selection') - return {'CANCELLED'} + if closest_pair[0][0] != closest_pair[0][1]: + splines.remove(closest_pair[0][1]) + spline = internal.mergeEnds(closest_pair[0], closest_pair[1], closest_pair[2]) + if spline.use_cyclic_u == False: + splines.append(spline) - if is_last_point[0]: - points[1], points[0] = points[0], points[1] - selected_splines[1], selected_splines[0] = selected_splines[0], selected_splines[1] - is_last_point[1], is_last_point[0] = is_last_point[0], is_last_point[1] - - points[0].handle_left_type = 'FREE' - points[0].handle_right_type = 'FREE' - new_co = (points[0].co+points[1].co)*0.5 - handle = (points[1].handle_left if is_last_point[1] else points[1].handle_right)+new_co-points[1].co - if is_last_point[0]: - points[0].handle_left += new_co-points[0].co - points[0].handle_right = handle - else: - points[0].handle_right += new_co-points[0].co - points[0].handle_left = handle - points[0].co = new_co - - point_index = 0 if selected_splines[0] == selected_splines[1] else len(selected_splines[1].bezier_points) - bpy.ops.curve.make_segment() - point = selected_splines[0].bezier_points[point_index] - point.select_control_point = False - point.select_left_handle = False - point.select_right_handle = False - bpy.ops.curve.delete() return {'FINISHED'} class Subdivide(bpy.types.Operator): @@ -219,7 +198,7 @@ class Array(bpy.types.Operator): bl_description = bl_label = 'Array' bl_options = {'REGISTER', 'UNDO'} - offset: bpy.props.FloatVectorProperty(name='Offset', unit='LENGTH', description='Vector between to copies', subtype='DIRECTION', default=(1.0, 0.0, 0.0), size=3) + offset: bpy.props.FloatVectorProperty(name='Offset', unit='LENGTH', description='Vector between to copies', subtype='DIRECTION', default=(0.0, 0.0, -1.0), size=3) count: bpy.props.IntProperty(name='Count', description='Number of copies', min=1, default=2) connect: bpy.props.BoolProperty(name='Connect', description='Concatenate individual copies', default=False) serpentine: bpy.props.BoolProperty(name='Serpentine', description='Switch direction of every second copy', default=False) diff --git a/curve_tools/internal.py b/curve_tools/internal.py index 96816189..149c31d9 100644 --- a/curve_tools/internal.py +++ b/curve_tools/internal.py @@ -637,6 +637,27 @@ def addBezierSpline(obj, cyclic, vertices, weights=None, select=False): point.handle_left_type = 'VECTOR' return spline +def mergeEnds(splines, points, is_last_point): + bpy.ops.curve.select_all(action='DESELECT') + points[0].handle_left_type = points[0].handle_right_type = 'FREE' + new_co = (points[0].co+points[1].co)*0.5 + handle = (points[1].handle_left if is_last_point[1] else points[1].handle_right)+new_co-points[1].co + points[0].select_left_handle = points[0].select_right_handle = True + if is_last_point[0]: + points[0].handle_left += new_co-points[0].co + points[0].handle_right = handle + else: + points[0].handle_right += new_co-points[0].co + points[0].handle_left = handle + points[0].co = new_co + points[0].select_control_point = points[1].select_control_point = True + bpy.ops.curve.make_segment() + spline = splines[0] if splines[0] in bpy.context.object.data.splines.values() else splines[1] + point = next(point for point in spline.bezier_points if point.select_left_handle) + point.select_left_handle = point.select_right_handle = point.select_control_point = False + bpy.ops.curve.delete() + return spline + def polygonArcAt(center, radius, begin_angle, angle, step_angle, include_ends): vertices = [] circle_samples = math.ceil(abs(angle)/step_angle) @@ -807,7 +828,6 @@ def dogBone(spline, radius): vertices.append([next_segment_points[0], corner, next_segment_points[0]]) vertices.append([corner, next_segment_points[0], next_segment_points[3]]) iterateSpline(spline, handlePoint) - print(vertices) return vertices def discretizeCurve(spline, step_angle, samples): diff --git a/curve_tools/toolpath.py b/curve_tools/toolpath.py index fec6693a..2c409f1a 100644 --- a/curve_tools/toolpath.py +++ b/curve_tools/toolpath.py @@ -113,7 +113,14 @@ class SliceMesh(bpy.types.Operator): self.offset = 0.0 self.slice_count = 3 self.mode = 'PITCH' - self.execute(context) + self.input_obj = bpy.context.object + depsgraph = context.evaluated_depsgraph_get() + self.mesh = bmesh.new() + self.mesh.from_object(self.input_obj, depsgraph, deform=True, cage=False, face_normals=True) + self.mesh.transform(bpy.context.scene.cursor.matrix.inverted()@self.input_obj.matrix_world) + self.result = internal.addObject('CURVE', 'Slices') + self.result.matrix_world = bpy.context.scene.cursor.matrix + self.perform(context) context.window_manager.modal_handler_add(self) return {'RUNNING_MODAL'} @@ -148,9 +155,12 @@ class SliceMesh(bpy.types.Operator): self.mode = 'OFFSET' return {'RUNNING_MODAL'} elif self.mode == 'OFFSET': + self.mesh.free() return {'FINISHED'} elif event.type in {'RIGHTMOUSE', 'ESC'}: + self.mesh.free() bpy.context.scene.collection.objects.unlink(self.result) + bpy.context.view_layer.objects.active = self.input_obj return {'CANCELLED'} else: return {'PASS_THROUGH'} @@ -158,16 +168,6 @@ class SliceMesh(bpy.types.Operator): self.perform(context) return {'RUNNING_MODAL'} - def execute(self, context): - depsgraph = context.evaluated_depsgraph_get() - self.mesh = bmesh.new() - self.mesh.from_object(bpy.context.object, depsgraph, deform=True, cage=False, face_normals=True) - self.mesh.transform(bpy.context.scene.cursor.matrix.inverted()@bpy.context.object.matrix_world) - self.result = internal.addObject('CURVE', 'Slices') - self.result.matrix_world = bpy.context.scene.cursor.matrix - self.perform(context) - return {'FINISHED'} - class DogBone(bpy.types.Operator): bl_idname = 'curvetools.add_toolpath_dogbone' bl_description = bl_label = 'Dog Bone' -- cgit v1.2.3