Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVladimir Spivak(cwolf3d) <cwolf3d@gmail.com>2020-03-12 05:10:42 +0300
committerVladimir Spivak(cwolf3d) <cwolf3d@gmail.com>2020-03-12 05:10:42 +0300
commitbd54740ed08be078a870fcb3d83c7bd4ad304d43 (patch)
treee4fa6630cd56fd7c2cc066a8aba144d368b6ac63
parentcc237ba4df1bd0ebacbb80086f0cd2f522df1688 (diff)
Fix T74493 and D7045. Redesign.
-rw-r--r--curve_tools/__init__.py394
-rw-r--r--curve_tools/cad.py18
-rw-r--r--curve_tools/curves.py6
-rw-r--r--curve_tools/internal.py40
-rw-r--r--curve_tools/intersections.py6
-rw-r--r--curve_tools/operators.py149
-rw-r--r--curve_tools/toolpath.py125
7 files changed, 501 insertions, 237 deletions
diff --git a/curve_tools/__init__.py b/curve_tools/__init__.py
index 2fcec1d9..4a9d283c 100644
--- a/curve_tools/__init__.py
+++ b/curve_tools/__init__.py
@@ -25,7 +25,7 @@ bl_info = {
"name": "Curve Tools",
"description": "Adds some functionality for bezier/nurbs curve/surface modeling",
"author": "Mackraken",
- "version": (0, 4, 3),
+ "version": (0, 4, 4),
"blender": (2, 80, 0),
"location": "View3D > Tool Shelf > Edit Tab",
"warning": "WIP",
@@ -128,13 +128,13 @@ class curvetoolsSettings(PropertyGroup):
description="Only join splines at the starting point of one and the ending point of the other"
)
splineJoinModeItems = (
- ('At midpoint', 'At midpoint', 'Join splines at midpoint of neighbouring points'),
- ('Insert segment', 'Insert segment', 'Insert segment between neighbouring points')
+ ('At_midpoint', 'At midpoint', 'Join splines at midpoint of neighbouring points'),
+ ('Insert_segment', 'Insert segment', 'Insert segment between neighbouring points')
)
SplineJoinMode: EnumProperty(
items=splineJoinModeItems,
name="SplineJoinMode",
- default='At midpoint',
+ default='At_midpoint',
description="Determines how the splines will be joined"
)
# curve intersection
@@ -147,7 +147,7 @@ class curvetoolsSettings(PropertyGroup):
intAlgorithmItems = (
('3D', '3D', 'Detect where curves intersect in 3D'),
- ('From View', 'From View', 'Detect where curves intersect in the RegionView3D')
+ ('From_View', 'From View', 'Detect where curves intersect in the RegionView3D')
)
IntersectCurvesAlgorithm: EnumProperty(
items=intAlgorithmItems,
@@ -229,227 +229,223 @@ class curvetoolsSettings(PropertyGroup):
)
-class VIEW3D_PT_CurvePanel(Panel):
- bl_label = "Curve Tools"
+# Curve Info
+class VIEW3D_PT_curve_tools_info(Panel):
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
+ bl_category = "Curve Edit"
+ bl_label = "Curve Info"
bl_options = {'DEFAULT_CLOSED'}
- bl_category = "Edit"
- @classmethod
- def poll(cls, context):
- return context.scene is not None
+ def draw(self, context):
+ scene = context.scene
+ layout = self.layout
+
+ col = layout.column(align=True)
+ col.operator("curvetools.operatorcurveinfo", text="Curve")
+ row = col.row(align=True)
+ row.operator("curvetools.operatorsplinesinfo", text="Spline")
+ row.operator("curvetools.operatorsegmentsinfo", text="Segment")
+ row = col.row(align=True)
+ row.operator("curvetools.operatorcurvelength", icon = "DRIVER_DISTANCE", text="Length")
+ row.prop(context.scene.curvetools, "CurveLength", text="")
+
+# Curve Edit
+class VIEW3D_PT_curve_tools_edit(Panel):
+ bl_space_type = "VIEW_3D"
+ bl_region_type = "UI"
+ bl_category = "Curve Edit"
+ bl_label = "Curve Edit"
+
def draw(self, context):
scene = context.scene
- SINGLEDROP = scene.UTSingleDrop
- MOREDROP = scene.UTMOREDROP
- LOFTDROP = scene.UTLoftDrop
- ADVANCEDDROP = scene.UTAdvancedDrop
- EXTENDEDDROP = scene.UTExtendedDrop
- UTILSDROP = scene.UTUtilsDrop
layout = self.layout
- # Single Curve options
- box1 = self.layout.box()
- col = box1.column(align=True)
+ col = layout.column(align=True)
+ col.operator("curvetools.bezier_points_fillet", text='Fillet/Chamfer')
row = col.row(align=True)
- row.prop(scene, "UTSingleDrop", icon="TRIA_DOWN")
- if SINGLEDROP:
- # A. 1 curve
- row = col.row(align=True)
-
- # A.1 curve info/length
- row.operator("curvetools.operatorcurveinfo", text="Curve info")
- row = col.row(align=True)
- row.operator("curvetools.operatorcurvelength", text="Calc Length")
- row.prop(context.scene.curvetools, "CurveLength", text="")
-
- # A.2 splines info
- row = col.row(align=True)
- row.operator("curvetools.operatorsplinesinfo", text="Curve splines info")
-
- # A.3 segments info
- row = col.row(align=True)
- row.operator("curvetools.operatorsegmentsinfo", text="Curve segments info")
-
- # A.4 origin to spline0start
- row = col.row(align=True)
- row.operator("curvetools.operatororigintospline0start", text="Set origin to spline start")
-
- # Double Curve options
- box2 = self.layout.box()
- col = box2.column(align=True)
+ row.operator("curvetools.outline", text="Outline")
+ row.operator("curvetools.add_toolpath_offset_curve", text="Recursive Offset")
+ col.operator("curvetools.sep_outline", text="Separate Offset/Selected")
+ col.operator("curvetools.bezier_cad_handle_projection", text='Extend Handles')
+ col.operator("curvetools.bezier_cad_boolean", text="Boolean Splines")
row = col.row(align=True)
- row.prop(scene, "UTMOREDROP", icon="TRIA_DOWN")
+ row.operator("curvetools.bezier_spline_divide", text='Subdivide')
+ row.operator("curvetools.bezier_cad_subdivide", text="Multi Subdivide")
+
+ col.operator("curvetools.split", text='Split at Vertex')
+ col.operator("curvetools.add_toolpath_discretize_curve", text="Discretize Curve")
+ col.operator("curvetools.bezier_cad_array", text="Array Splines")
+
+# Curve Intersect
+class VIEW3D_PT_curve_tools_intersect(Panel):
+ bl_space_type = "VIEW_3D"
+ bl_region_type = "UI"
+ bl_category = "Curve Edit"
+ bl_label = "Intersect"
+ bl_options = {'DEFAULT_CLOSED'}
+
+ def draw(self, context):
+ scene = context.scene
+ layout = self.layout
+
+ col = layout.column(align=True)
+ col.operator("curvetools.bezier_curve_boolean", text="2D Curve Boolean")
+ col.operator("curvetools.operatorintersectcurves", text="Intersect Curves")
+ col.prop(context.scene.curvetools, "LimitDistance", text="Limit Distance")
+ col.prop(context.scene.curvetools, "IntersectCurvesAlgorithm", text="Algorithm")
+ col.prop(context.scene.curvetools, "IntersectCurvesMode", text="Mode")
+ col.prop(context.scene.curvetools, "IntersectCurvesAffect", text="Affect")
- if MOREDROP:
- # B. 2 curves
- row = col.row(align=True)
+# Curve Surfaces
+class VIEW3D_PT_curve_tools_surfaces(Panel):
+ bl_space_type = "VIEW_3D"
+ bl_region_type = "UI"
+ bl_category = "Curve Edit"
+ bl_label = "Surfaces"
+ bl_options = {'DEFAULT_CLOSED'}
+
+ def draw(self, context):
+ wm = context.window_manager
+ scene = context.scene
+ layout = self.layout
- # B.1 curve intersections
- row = col.row(align=True)
- row.operator("curvetools.operatorintersectcurves", text="Intersect curves")
+ col = layout.column(align=True)
+ col.operator("curvetools.operatorbirail", text="Birail")
+ col.operator("curvetools.convert_bezier_to_surface", text="Convert Bezier to Surface")
+ col.operator("curvetools.convert_selected_face_to_bezier", text="Convert Faces to Bezier")
- row = col.row(align=True)
- row.prop(context.scene.curvetools, "LimitDistance", text="LimitDistance")
+# Curve Path Finder
+class VIEW3D_PT_curve_tools_loft(Panel):
+ bl_space_type = "VIEW_3D"
+ bl_region_type = "UI"
+ bl_category = "Curve Edit"
+ bl_parent_id = "VIEW3D_PT_curve_tools_surfaces"
+ bl_label = "Loft"
+ bl_options = {'DEFAULT_CLOSED'}
- row = col.row(align=True)
- row.prop(context.scene.curvetools, "IntersectCurvesAlgorithm", text="Algorithm")
+ def draw(self, context):
+ wm = context.window_manager
+ scene = context.scene
+ layout = self.layout
- row = col.row(align=True)
- row.prop(context.scene.curvetools, "IntersectCurvesMode", text="Mode")
+ col = layout.column(align=True)
+ col.operator("curvetools.create_auto_loft")
+ lofters = [o for o in scene.objects if "autoloft" in o.keys()]
+ for o in lofters:
+ col.label(text=o.name)
+ # layout.prop(o, '["autoloft"]', toggle=True)
+ col.prop(wm, "auto_loft", toggle=True)
+ col.operator("curvetools.update_auto_loft_curves")
+ col = layout.column(align=True)
- row = col.row(align=True)
- row.prop(context.scene.curvetools, "IntersectCurvesAffect", text="Affect")
- # Loft options
- box1 = self.layout.box()
- col = box1.column(align=True)
+# Curve Sanitize
+class VIEW3D_PT_curve_tools_sanitize(Panel):
+ bl_space_type = "VIEW_3D"
+ bl_region_type = "UI"
+ bl_category = "Curve Edit"
+ bl_label = "Sanitize"
+ bl_options = {'DEFAULT_CLOSED'}
+
+ def draw(self, context):
+ scene = context.scene
+ layout = self.layout
+
+ col = layout.column(align=True)
+ col.operator("curvetools.operatororigintospline0start", icon = "OBJECT_ORIGIN", text="Set Origin to Spline Start")
+ col.operator("curvetools.scale_reset", text='Reset Scale')
+
+ col.label(text="Cleanup:")
+ col.operator("curvetools.remove_doubles", icon = "TRASH", text='Remove Doubles')
+ col.operator("curvetools.operatorsplinesremovezerosegment", icon = "TRASH", text="0-Segment Splines")
+ row = col.row(align=True)
+ row.operator("curvetools.operatorsplinesremoveshort", text="Short Splines")
+ row.prop(context.scene.curvetools, "SplineRemoveLength", text="Threshold remove")
+
+ col.label(text="Join Splines:")
+ col.operator("curvetools.operatorsplinesjoinneighbouring", text="Join Neighbouring Splines")
row = col.row(align=True)
- row.prop(scene, "UTLoftDrop", icon="TRIA_DOWN")
-
- if LOFTDROP:
- # B.2 surface generation
- wm = context.window_manager
- scene = context.scene
- layout = self.layout
- layout.operator("curvetools.create_auto_loft")
- lofters = [o for o in scene.objects if "autoloft" in o.keys()]
- for o in lofters:
- layout.label(text=o.name)
- # layout.prop(o, '["autoloft"]', toggle=True)
- layout.prop(wm, "auto_loft", toggle=True)
- layout.operator("curvetools.update_auto_loft_curves")
-
- # Advanced options
- box1 = self.layout.box()
- col = box1.column(align=True)
+ col.prop(context.scene.curvetools, "SplineJoinDistance", text="Threshold")
+ col.prop(context.scene.curvetools, "SplineJoinStartEnd", text="Only at Ends")
+ col.prop(context.scene.curvetools, "SplineJoinMode", text="Join Position")
+
+# Curve Utilities
+class VIEW3D_PT_curve_tools_utilities(Panel):
+ bl_space_type = "VIEW_3D"
+ bl_region_type = "UI"
+ bl_category = "Curve Edit"
+ bl_label = "Utilities"
+ bl_options = {'DEFAULT_CLOSED'}
+
+ def draw(self, context):
+ scene = context.scene
+ layout = self.layout
+
+ col = layout.column(align=True)
row = col.row(align=True)
- row.prop(scene, "UTAdvancedDrop", icon="TRIA_DOWN")
- if ADVANCEDDROP:
- # C. 3 curves
- row = col.row(align=True)
- row.operator("curvetools.outline", text="Curve Outline")
- row = col.row(align=True)
- row.operator("curvetools.sep_outline", text="Separate Outline or selected")
- row = col.row(align=True)
- row.operator("curvetools.bezier_curve_boolean", text="2D Curve Boolean")
- row = col.row(align=True)
- row.operator("curvetools.bezier_points_fillet", text='Fillet')
- row = col.row(align=True)
- row.operator("curvetools.bezier_cad_handle_projection", text='Handle Projection')
- row = col.row(align=True)
- row.operator("curvetools.bezier_spline_divide", text='Divide')
- row = col.row(align=True)
- row.operator("curvetools.scale_reset", text='Scale Reset')
- row = col.row(align=True)
- row.operator("curvetools.operatorbirail", text="Birail")
- row = col.row(align=True)
- row.operator("curvetools.convert_selected_face_to_bezier", text="Convert selected faces to Bezier")
- row = col.row(align=True)
- row.operator("curvetools.convert_bezier_to_surface", text="Convert Bezier to Surface")
-
- # Extended options
- box1 = self.layout.box()
- col = box1.column(align=True)
+ row.label(text="Curve Resolution:")
row = col.row(align=True)
- row.prop(scene, "UTExtendedDrop", icon="TRIA_DOWN")
- if EXTENDEDDROP:
- row = col.row(align=True)
- row.operator("curvetools.add_toolpath_offset_curve", text="Offset Curve")
- row = col.row(align=True)
- row.operator("curvetools.bezier_cad_boolean", text="Boolean 2 selected spline")
- row = col.row(align=True)
- row.operator("curvetools.bezier_cad_subdivide", text="Multi Subdivide")
- row = col.row(align=True)
- row.operator("curvetools.split", text='Split by selected points')
- row = col.row(align=True)
- row.operator("curvetools.remove_doubles", text='Remove Doubles')
- row = col.row(align=True)
- row.operator("curvetools.add_toolpath_discretize_curve", text="Discretize Curve")
- row = col.row(align=True)
- row.operator("curvetools.bezier_cad_array", text="Array selected spline")
-
- # Utils Curve options
- box1 = self.layout.box()
- col = box1.column(align=True)
+ row.operator("curvetools.show_resolution", icon="HIDE_OFF", text="Show [ESC]")
+ row.prop(context.scene.curvetools, "curve_vertcolor", text="")
row = col.row(align=True)
- row.prop(scene, "UTUtilsDrop", icon="TRIA_DOWN")
- if UTILSDROP:
- # D.1 set spline resolution
- row = col.row(align=True)
- row.label(text="Show point Resolution:")
- row = col.row(align=True)
- row.operator("curvetools.operatorsplinessetresolution", text="Set resolution")
- row.prop(context.scene.curvetools, "SplineResolution", text="")
- row = col.row(align=True)
- row.prop(context.scene.curvetools, "curve_vertcolor", text="")
- row = col.row(align=True)
- row.operator("curvetools.show_resolution", text="Run [ESC]")
-
- # D.1 set spline sequence
- row = col.row(align=True)
- row.label(text="Show and rearrange spline sequence:")
- row = col.row(align=True)
- row.prop(context.scene.curvetools, "sequence_color", text="")
- row.prop(context.scene.curvetools, "font_thickness", text="")
- row.prop(context.scene.curvetools, "font_size", text="")
- row = col.row(align=True)
- oper = row.operator("curvetools.rearrange_spline", text="<")
- oper.command = 'PREV'
- oper = row.operator("curvetools.rearrange_spline", text=">")
- oper.command = 'NEXT'
- row = col.row(align=True)
- row.operator("curvetools.show_splines_sequence", text="Run [ESC]")
-
- # D.2 remove splines
- row = col.row(align=True)
- row.label(text="Remove splines:")
- row = col.row(align=True)
- row.operator("curvetools.operatorsplinesremovezerosegment", text="Remove 0-segments splines")
- row = col.row(align=True)
- row.operator("curvetools.operatorsplinesremoveshort", text="Remove short splines")
- row = col.row(align=True)
- row.prop(context.scene.curvetools, "SplineRemoveLength", text="Threshold remove")
-
- # D.3 join splines
- row = col.row(align=True)
- row.label(text="Join splines:")
- row = col.row(align=True)
- row.operator("curvetools.operatorsplinesjoinneighbouring", text="Join neighbouring splines")
- row = col.row(align=True)
- row.prop(context.scene.curvetools, "SplineJoinDistance", text="Threshold join")
- row = col.row(align=True)
- row.prop(context.scene.curvetools, "SplineJoinStartEnd", text="Only at start & end")
- row = col.row(align=True)
- row.prop(context.scene.curvetools, "SplineJoinMode", text="Join mode")
-
- row = col.row(align=True)
- row.label(text="PathFinder:")
- row = col.row(align=True)
- row.prop(context.scene.curvetools, "PathFinderRadius", text="PathFinder Radius")
- row = col.row(align=True)
- row.prop(context.scene.curvetools, "path_color", text="")
- row.prop(context.scene.curvetools, "path_thickness", text="")
- row = col.row(align=True)
- row.operator("curvetools.pathfinder", text="Run Path Finder [ESC]")
- row = col.row(align=True)
- row.label(text="ESC or TAB - exit from PathFinder")
- row = col.row(align=True)
- row.label(text="X or DEL - delete")
- row = col.row(align=True)
- row.label(text="Alt + mouse click - select spline")
- row = col.row(align=True)
- row.label(text="Alt + Shift + mouse click - add spline to select")
- row = col.row(align=True)
- row.label(text="A - deselect all")
+ row.operator("curvetools.operatorsplinessetresolution", text="Set Resolution")
+ row.prop(context.scene.curvetools, "SplineResolution", text="")
+
+
+ row = col.row(align=True)
+ row.label(text="Spline Order:")
+ row = col.row(align=True)
+ row.operator("curvetools.show_splines_sequence", icon="HIDE_OFF", text="Show [ESC]")
+ row.prop(context.scene.curvetools, "sequence_color", text="")
+ row = col.row(align=True)
+ row.prop(context.scene.curvetools, "font_size", text="Font Size")
+ row.prop(context.scene.curvetools, "font_thickness", text="Font Thickness")
+ row = col.row(align=True)
+ oper = row.operator("curvetools.rearrange_spline", text = "<")
+ oper.command = 'PREV'
+ oper = row.operator("curvetools.rearrange_spline", text = ">")
+ oper.command = 'NEXT'
+ row = col.row(align=True)
+ row.operator("curve.switch_direction", text="Switch Direction")
+ row = col.row(align=True)
+ row.operator("curvetools.set_first_points", text="Set First Points")
+
+# Curve Path Finder
+class VIEW3D_PT_curve_tools_pathfinder(Panel):
+ bl_space_type = "VIEW_3D"
+ bl_region_type = "UI"
+ bl_category = "Curve Edit"
+ bl_parent_id = "VIEW3D_PT_curve_tools_utilities"
+ bl_label = "Path Finder"
+ bl_options = {'DEFAULT_CLOSED'}
+
+ def draw(self, context):
+ scene = context.scene
+ layout = self.layout
+
+ col = layout.column(align=True)
+ col.operator("curvetools.pathfinder", text="Path Finder [ESC]")
+ col.prop(context.scene.curvetools, "PathFinderRadius", text="PathFinder Radius")
+ col.prop(context.scene.curvetools, "path_color", text="")
+ col.prop(context.scene.curvetools, "path_thickness", text="Thickness")
+
+ col = layout.column(align=True)
+ col.label(text="ESC or TAB - Exit PathFinder")
+ col.label(text="X or DEL - Delete")
+ col.label(text="Alt + Mouse Click - Select Spline")
+ col.label(text="Alt + Shift + Mouse click - Add Spline to Selection")
+ col.label(text="A - Deselect All")
# Add-ons Preferences Update Panel
# Define Panel classes for updating
panels = (
- VIEW3D_PT_CurvePanel,
+ VIEW3D_PT_curve_tools_info, VIEW3D_PT_curve_tools_edit,
+ VIEW3D_PT_curve_tools_intersect, VIEW3D_PT_curve_tools_surfaces,
+ VIEW3D_PT_curve_tools_loft, VIEW3D_PT_curve_tools_sanitize,
+ VIEW3D_PT_curve_tools_utilities, VIEW3D_PT_curve_tools_pathfinder
)
diff --git a/curve_tools/cad.py b/curve_tools/cad.py
index 3d3f87bd..e46af8d3 100644
--- a/curve_tools/cad.py
+++ b/curve_tools/cad.py
@@ -156,10 +156,14 @@ class MergeEnds(bpy.types.Operator):
self.report({'WARNING'}, 'Invalid selection')
return {'CANCELLED'}
+ 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
@@ -169,13 +173,13 @@ class MergeEnds(bpy.types.Operator):
points[0].handle_left = handle
points[0].co = new_co
- bpy.ops.curve.select_all(action='DESELECT')
- points[1].select_control_point = True
- bpy.ops.curve.delete()
- selected_splines[0].bezier_points[-1 if is_last_point[0] else 0].select_control_point = True
- selected_splines[1].bezier_points[-1 if is_last_point[1] else 0].select_control_point = True
+ point_index = 0 if selected_splines[0] == selected_splines[1] else len(selected_splines[1].bezier_points)
bpy.ops.curve.make_segment()
- bpy.ops.curve.select_all(action='DESELECT')
+ 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):
diff --git a/curve_tools/curves.py b/curve_tools/curves.py
index da0b1398..202487de 100644
--- a/curve_tools/curves.py
+++ b/curve_tools/curves.py
@@ -357,12 +357,12 @@ class BezierSpline:
return [newSpline1, newSpline2]
- def Join(self, spline2, mode = 'At midpoint'):
- if mode == 'At midpoint':
+ def Join(self, spline2, mode = 'At_midpoint'):
+ if mode == 'At_midpoint':
self.JoinAtMidpoint(spline2)
return
- if mode == 'Insert segment':
+ if mode == 'Insert_segment':
self.JoinInsertSegment(spline2)
return
diff --git a/curve_tools/internal.py b/curve_tools/internal.py
index e967fc6e..96816189 100644
--- a/curve_tools/internal.py
+++ b/curve_tools/internal.py
@@ -103,11 +103,11 @@ def nearestPointOfLines(originA, dirA, originB, dirB, tollerance=0.0):
def lineSegmentLineSegmentIntersection(beginA, endA, beginB, endB, tollerance=0.001):
dirA = endA-beginA
dirB = endB-beginB
- intersection = nearestPointOfLines(beginA, dirA, beginB, dirB)
- if math.isnan(intersection[0]) or (intersection[2]-intersection[3]).length > tollerance or \
- intersection[0] < 0 or intersection[0] > 1 or intersection[1] < 0 or intersection[1] > 1:
+ paramA, paramB, pointA, pointB = nearestPointOfLines(beginA, dirA, beginB, dirB)
+ if math.isnan(paramA) or (pointA-pointB).length > tollerance or \
+ paramA < 0 or paramA > 1 or paramB < 0 or paramB > 1:
return None
- return intersection
+ return (paramA, paramB, pointA, pointB)
def aabbOfPoints(points):
min = Vector(points[0])
@@ -290,6 +290,16 @@ def isSegmentLinear(points, tollerance=0.0001):
def bezierSegmentPoints(begin, end):
return [begin.co, begin.handle_right, end.handle_left, end.co]
+def grab_cursor(context, event):
+ if event.mouse_region_x < 0:
+ context.window.cursor_warp(context.region.x+context.region.width, event.mouse_y)
+ elif event.mouse_region_x > context.region.width:
+ context.window.cursor_warp(context.region.x, event.mouse_y)
+ elif event.mouse_region_y < 0:
+ context.window.cursor_warp(event.mouse_x, context.region.y+context.region.height)
+ elif event.mouse_region_y > context.region.height:
+ context.window.cursor_warp(event.mouse_x, context.region.y)
+
def deleteFromArray(item, array):
for index, current in enumerate(array):
if current is item:
@@ -586,7 +596,6 @@ def getSelectedSplines(include_bezier, include_polygon, allow_partial_selection=
return result
def addObject(type, name):
- bpy.ops.object.select_all(action='DESELECT')
if type == 'CURVE':
data = bpy.data.curves.new(name=name, type='CURVE')
data.dimensions = '3D'
@@ -780,6 +789,27 @@ def filletSpline(spline, radius, chamfer_mode, limit_half_way, tollerance=0.0001
i = i+1
return addBezierSpline(bpy.context.object, spline.use_cyclic_u, vertices)
+def dogBone(spline, radius):
+ vertices = []
+ def handlePoint(prev_segment_points, next_segment_points, selected, prev_tangent, current_tangent, next_tangent, normal, angle, is_first, is_last):
+ if not selected or is_first or is_last or angle == 0 or normal[2] > 0.0 or \
+ (spline.type == 'BEZIER' and not (isSegmentLinear(prev_segment_points) and isSegmentLinear(next_segment_points))):
+ prev_handle = next_segment_points[0] if is_first else prev_segment_points[2] if spline.type == 'BEZIER' else prev_segment_points[0]
+ next_handle = next_segment_points[0] if is_last else next_segment_points[1] if spline.type == 'BEZIER' else next_segment_points[3]
+ vertices.append([prev_handle, next_segment_points[0], next_handle])
+ return
+ tan_factor = math.tan(angle*0.5)
+ corner = next_segment_points[0]+normal.cross(prev_tangent)*radius-prev_tangent*radius*tan_factor
+ direction = next_segment_points[0]-corner
+ distance = direction.length
+ corner = next_segment_points[0]+direction/distance*(distance-radius)
+ vertices.append([prev_segment_points[0], next_segment_points[0], corner])
+ 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):
vertices = []
def handlePoint(prev_segment_points, next_segment_points, selected, prev_tangent, current_tangent, next_tangent, normal, angle, is_first, is_last):
diff --git a/curve_tools/intersections.py b/curve_tools/intersections.py
index 77f19861..f0b8e96f 100644
--- a/curve_tools/intersections.py
+++ b/curve_tools/intersections.py
@@ -29,7 +29,7 @@ class BezierSegmentsIntersector:
if algorithm == '3D':
return self.CalcFirstRealIntersection3D(nrSamples1, nrSamples2)
- if algorithm == 'From View':
+ if algorithm == 'From_View':
global algoDIR
if algoDIR is not None:
return self.CalcFirstRealIntersectionFromViewDIR(nrSamples1, nrSamples2)
@@ -309,7 +309,7 @@ class BezierSegmentsIntersector:
if algorithm == '3D':
return self.CalcIntersections3D(nrSamples1, nrSamples2)
- if algorithm == 'From View':
+ if algorithm == 'From_View':
global algoDIR
if algoDIR is not None:
return self.CalcIntersectionsFromViewDIR(nrSamples1, nrSamples2)
@@ -527,7 +527,7 @@ class CurvesIntersector:
global algoDIR
algo = bpy.context.scene.curvetools.IntersectCurvesAlgorithm
- if algo == 'From View':
+ if algo == 'From_View':
regionView3D = util.GetFirstRegionView3D()
if regionView3D is None:
print("### ERROR: regionView3D is None. Stopping.")
diff --git a/curve_tools/operators.py b/curve_tools/operators.py
index aeb4672c..ea11aef3 100644
--- a/curve_tools/operators.py
+++ b/curve_tools/operators.py
@@ -1145,7 +1145,153 @@ class CurveBoolean(bpy.types.Operator):
j += 1
- bpy.ops.object.mode_set (mode = current_mode)
+ bpy.ops.object.mode_set(mode = 'EDIT')
+ bpy.ops.curve.select_all(action='SELECT')
+
+ return {'FINISHED'}
+
+# ----------------------------
+# Set first points operator
+class SetFirstPoints(bpy.types.Operator):
+ bl_idname = "curvetools.set_first_points"
+ bl_label = "Set first points"
+ bl_description = "Set the selected points as the first point of each spline"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ @classmethod
+ def poll(cls, context):
+ return util.Selected1OrMoreCurves()
+
+ def execute(self, context):
+ splines_to_invert = []
+
+ curve = bpy.context.object
+
+ bpy.ops.object.mode_set('INVOKE_REGION_WIN', mode='EDIT')
+
+ # Check non-cyclic splines to invert
+ for i in range(len(curve.data.splines)):
+ b_points = curve.data.splines[i].bezier_points
+
+ if i not in self.cyclic_splines: # Only for non-cyclic splines
+ if b_points[len(b_points) - 1].select_control_point:
+ splines_to_invert.append(i)
+
+ # Reorder points of cyclic splines, and set all handles to "Automatic"
+
+ # Check first selected point
+ cyclic_splines_new_first_pt = {}
+ for i in self.cyclic_splines:
+ sp = curve.data.splines[i]
+
+ for t in range(len(sp.bezier_points)):
+ bp = sp.bezier_points[t]
+ if bp.select_control_point or bp.select_right_handle or bp.select_left_handle:
+ cyclic_splines_new_first_pt[i] = t
+ break # To take only one if there are more
+
+ # Reorder
+ for spline_idx in cyclic_splines_new_first_pt:
+ sp = curve.data.splines[spline_idx]
+
+ spline_old_coords = []
+ for bp_old in sp.bezier_points:
+ coords = (bp_old.co[0], bp_old.co[1], bp_old.co[2])
+
+ left_handle_type = str(bp_old.handle_left_type)
+ left_handle_length = float(bp_old.handle_left.length)
+ left_handle_xyz = (
+ float(bp_old.handle_left.x),
+ float(bp_old.handle_left.y),
+ float(bp_old.handle_left.z)
+ )
+ right_handle_type = str(bp_old.handle_right_type)
+ right_handle_length = float(bp_old.handle_right.length)
+ right_handle_xyz = (
+ float(bp_old.handle_right.x),
+ float(bp_old.handle_right.y),
+ float(bp_old.handle_right.z)
+ )
+ spline_old_coords.append(
+ [coords, left_handle_type,
+ right_handle_type, left_handle_length,
+ right_handle_length, left_handle_xyz,
+ right_handle_xyz]
+ )
+
+ for t in range(len(sp.bezier_points)):
+ bp = sp.bezier_points
+
+ if t + cyclic_splines_new_first_pt[spline_idx] + 1 <= len(bp) - 1:
+ new_index = t + cyclic_splines_new_first_pt[spline_idx] + 1
+ else:
+ new_index = t + cyclic_splines_new_first_pt[spline_idx] + 1 - len(bp)
+
+ bp[t].co = Vector(spline_old_coords[new_index][0])
+
+ bp[t].handle_left.length = spline_old_coords[new_index][3]
+ bp[t].handle_right.length = spline_old_coords[new_index][4]
+
+ bp[t].handle_left_type = "FREE"
+ bp[t].handle_right_type = "FREE"
+
+ bp[t].handle_left.x = spline_old_coords[new_index][5][0]
+ bp[t].handle_left.y = spline_old_coords[new_index][5][1]
+ bp[t].handle_left.z = spline_old_coords[new_index][5][2]
+
+ bp[t].handle_right.x = spline_old_coords[new_index][6][0]
+ bp[t].handle_right.y = spline_old_coords[new_index][6][1]
+ bp[t].handle_right.z = spline_old_coords[new_index][6][2]
+
+ bp[t].handle_left_type = spline_old_coords[new_index][1]
+ bp[t].handle_right_type = spline_old_coords[new_index][2]
+
+ # Invert the non-cyclic splines designated above
+ for i in range(len(splines_to_invert)):
+ bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='DESELECT')
+
+ bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
+ curve.data.splines[splines_to_invert[i]].bezier_points[0].select_control_point = True
+ bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
+
+ bpy.ops.curve.switch_direction()
+
+ bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='DESELECT')
+
+ # Keep selected the first vert of each spline
+ bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
+ for i in range(len(curve.data.splines)):
+ if not curve.data.splines[i].use_cyclic_u:
+ bp = curve.data.splines[i].bezier_points[0]
+ else:
+ bp = curve.data.splines[i].bezier_points[
+ len(curve.data.splines[i].bezier_points) - 1
+ ]
+
+ bp.select_control_point = True
+ bp.select_right_handle = True
+ bp.select_left_handle = True
+
+ bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
+
+ return {'FINISHED'}
+
+ def invoke(self, context, event):
+ curve = bpy.context.object
+
+ # Check if all curves are Bezier, and detect which ones are cyclic
+ self.cyclic_splines = []
+ for i in range(len(curve.data.splines)):
+ if curve.data.splines[i].type != "BEZIER":
+ self.report({'WARNING'}, "All splines must be Bezier type")
+
+ return {'CANCELLED'}
+ else:
+ if curve.data.splines[i].use_cyclic_u:
+ self.cyclic_splines.append(i)
+
+ self.execute(context)
+ self.report({'INFO'}, "First points have been set")
return {'FINISHED'}
@@ -1182,4 +1328,5 @@ operators = [
Split,
SeparateOutline,
CurveBoolean,
+ SetFirstPoints,
]
diff --git a/curve_tools/toolpath.py b/curve_tools/toolpath.py
index 2b422280..fec6693a 100644
--- a/curve_tools/toolpath.py
+++ b/curve_tools/toolpath.py
@@ -17,6 +17,7 @@
# ***** GPL LICENSE BLOCK *****
import bpy, math, bmesh
+from bpy_extras import view3d_utils
from mathutils import Vector, Matrix
from . import internal
@@ -68,28 +69,19 @@ class SliceMesh(bpy.types.Operator):
bl_description = bl_label = 'Slice Mesh'
bl_options = {'REGISTER', 'UNDO'}
- pitch_axis: bpy.props.FloatVectorProperty(name='Pitch & Axis', unit='LENGTH', description='Vector between to slices', subtype='DIRECTION', default=(0.0, 0.0, 0.1), size=3)
- offset: bpy.props.FloatProperty(name='Offset', unit='LENGTH', description='Position of first slice along axis', default=-0.4)
- slice_count: bpy.props.IntProperty(name='Count', description='Number of slices', min=1, default=9)
+ pitch: bpy.props.FloatProperty(name='Pitch', unit='LENGTH', description='Distance between two slices', default=0.1)
+ offset: bpy.props.FloatProperty(name='Offset', unit='LENGTH', description='Position of first slice along the axis', default=0.0)
+ slice_count: bpy.props.IntProperty(name='Count', description='Number of slices', min=1, default=3)
@classmethod
def poll(cls, context):
return bpy.context.object != None and bpy.context.object.mode == 'OBJECT'
- def execute(self, context):
- if bpy.context.object.type != 'MESH':
- self.report({'WARNING'}, 'Active object must be a mesh')
- return {'CANCELLED'}
- depsgraph = context.evaluated_depsgraph_get()
- mesh = bmesh.new()
- mesh.from_object(bpy.context.object, depsgraph, deform=True, cage=False, face_normals=True)
- mesh.transform(bpy.context.object.matrix_world)
- toolpath = internal.addObject('CURVE', 'Slices Toolpath')
- pitch_axis = Vector(self.pitch_axis)
- axis = pitch_axis.normalized()
+ def perform(self, context):
+ axis = Vector((0.0, 0.0, 1.0))
for i in range(0, self.slice_count):
- aux_mesh = mesh.copy()
- cut_geometry = bmesh.ops.bisect_plane(aux_mesh, geom=aux_mesh.edges[:]+aux_mesh.faces[:], dist=0, plane_co=pitch_axis*i+axis*self.offset, plane_no=axis, clear_outer=False, clear_inner=False)['geom_cut']
+ aux_mesh = self.mesh.copy()
+ cut_geometry = bmesh.ops.bisect_plane(aux_mesh, geom=aux_mesh.edges[:]+aux_mesh.faces[:], dist=0, plane_co=axis*(i*self.pitch+self.offset), plane_no=axis, clear_outer=False, clear_inner=False)['geom_cut']
edge_pool = set([e for e in cut_geometry if isinstance(e, bmesh.types.BMEdge)])
while len(edge_pool) > 0:
current_edge = edge_pool.pop()
@@ -110,9 +102,104 @@ class SliceMesh(bpy.types.Operator):
break
current_vertex = current_edge.other_vert(current_vertex)
vertices.append(current_vertex.co)
- internal.addPolygonSpline(toolpath, False, vertices)
+ internal.addPolygonSpline(self.result, False, vertices)
aux_mesh.free()
- mesh.free()
+
+ def invoke(self, context, event):
+ if bpy.context.object.type != 'MESH':
+ self.report({'WARNING'}, 'Active object must be a mesh')
+ return {'CANCELLED'}
+ self.pitch = 0.1
+ self.offset = 0.0
+ self.slice_count = 3
+ self.mode = 'PITCH'
+ self.execute(context)
+ context.window_manager.modal_handler_add(self)
+ return {'RUNNING_MODAL'}
+
+ def modal(self, context, event):
+ if event.type == 'MOUSEMOVE':
+ mouse = (event.mouse_region_x, event.mouse_region_y)
+ input_value = internal.nearestPointOfLines(
+ bpy.context.scene.cursor.location,
+ bpy.context.scene.cursor.matrix.col[2].xyz,
+ view3d_utils.region_2d_to_origin_3d(context.region, context.region_data, mouse),
+ view3d_utils.region_2d_to_vector_3d(context.region, context.region_data, mouse)
+ )[0]
+ if self.mode == 'PITCH':
+ self.pitch = input_value/(self.slice_count-1) if self.slice_count > 2 else input_value
+ elif self.mode == 'OFFSET':
+ self.offset = input_value-self.pitch*0.5*((self.slice_count-1) if self.slice_count > 2 else 1.0)
+ elif event.type == 'WHEELUPMOUSE':
+ if self.slice_count > 2:
+ self.pitch *= (self.slice_count-1)
+ self.slice_count += 1
+ if self.slice_count > 2:
+ self.pitch /= (self.slice_count-1)
+ elif event.type == 'WHEELDOWNMOUSE':
+ if self.slice_count > 2:
+ self.pitch *= (self.slice_count-1)
+ if self.slice_count > 1:
+ self.slice_count -= 1
+ if self.slice_count > 2:
+ self.pitch /= (self.slice_count-1)
+ elif event.type == 'LEFTMOUSE' and event.value == 'RELEASE':
+ if self.mode == 'PITCH':
+ self.mode = 'OFFSET'
+ return {'RUNNING_MODAL'}
+ elif self.mode == 'OFFSET':
+ return {'FINISHED'}
+ elif event.type in {'RIGHTMOUSE', 'ESC'}:
+ bpy.context.scene.collection.objects.unlink(self.result)
+ return {'CANCELLED'}
+ else:
+ return {'PASS_THROUGH'}
+ self.result.data.splines.clear()
+ 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'
+ bl_options = {'REGISTER', 'UNDO'}
+
+ radius: bpy.props.FloatProperty(name='Radius', description='Tool radius to compensate for', unit='LENGTH', min=0.0, default=0.1)
+
+ @classmethod
+ def poll(cls, context):
+ return bpy.context.object != None and bpy.context.object.type == 'CURVE'
+
+ def execute(self, context):
+ if bpy.context.object.mode == 'EDIT':
+ splines = internal.getSelectedSplines(True, False)
+ else:
+ splines = bpy.context.object.data.splines
+
+ if len(splines) == 0:
+ self.report({'WARNING'}, 'Nothing selected')
+ return {'CANCELLED'}
+
+ if bpy.context.object.mode != 'EDIT':
+ internal.addObject('CURVE', 'Dog Bone')
+ origin = bpy.context.scene.cursor.location
+ else:
+ origin = Vector((0.0, 0.0, 0.0))
+
+ for spline in splines:
+ if spline.type != 'BEZIER':
+ continue
+ result = internal.dogBone(spline, self.radius)
+ internal.addBezierSpline(bpy.context.object, spline.use_cyclic_u, result) # [vertex-origin for vertex in result])
return {'FINISHED'}
class DiscretizeCurve(bpy.types.Operator):
@@ -295,4 +382,4 @@ def unregister():
if __name__ == "__main__":
register()
-operators = [OffsetCurve, SliceMesh, DiscretizeCurve, Truncate, RectMacro, DrillMacro]
+operators = [OffsetCurve, SliceMesh, DogBone, DiscretizeCurve, Truncate, RectMacro, DrillMacro]