Addon: Curve Tools: Refactoring. Added context menu.

This commit is contained in:
Vladimir Spivak 2019-10-11 02:52:59 +03:00
parent db2c65d9e7
commit 04f482c5aa
20 changed files with 760 additions and 434 deletions

View File

@ -1,4 +1,4 @@
from . import Math
from . import mathematics
import bpy
@ -390,7 +390,7 @@ class BezierSpline:
self.segments.append(BezierSegment(self.segments[-1].bezierPoint2, spline2.segments[0].bezierPoint1))
for seg2 in spline2.segments: self.segments.append(seg2)
self.resolution += spline2.resolution # extra segment will usually be short -- impact on resolution negligible
self.resolution += spline2.resolution # extra segment will usually be short -- impact on resolution negligable
self.isCyclic = False # is this ok?
@ -559,11 +559,11 @@ class Curve:
currEndPoint = currentSpline.segments[-1].bezierPoint2.co
nextStartPoint = nextSpline.segments[0].bezierPoint1.co
if Math.IsSamePoint(currEndPoint, nextStartPoint, threshold): return [currentSpline, nextSpline]
if mathematics.IsSamePoint(currEndPoint, nextStartPoint, threshold): return [currentSpline, nextSpline]
nextEndPoint = nextSpline.segments[-1].bezierPoint2.co
currStartPoint = currentSpline.segments[0].bezierPoint1.co
if Math.IsSamePoint(nextEndPoint, currStartPoint, threshold): return [nextSpline, currentSpline]
if mathematics.IsSamePoint(nextEndPoint, currStartPoint, threshold): return [nextSpline, currentSpline]
return None
else:
@ -575,18 +575,18 @@ class Curve:
currEndPoint = currentSpline.segments[-1].bezierPoint2.co
nextStartPoint = nextSpline.segments[0].bezierPoint1.co
if Math.IsSamePoint(currEndPoint, nextStartPoint, threshold): return [currentSpline, nextSpline]
if mathematics.IsSamePoint(currEndPoint, nextStartPoint, threshold): return [currentSpline, nextSpline]
nextEndPoint = nextSpline.segments[-1].bezierPoint2.co
currStartPoint = currentSpline.segments[0].bezierPoint1.co
if Math.IsSamePoint(nextEndPoint, currStartPoint, threshold): return [nextSpline, currentSpline]
if mathematics.IsSamePoint(nextEndPoint, currStartPoint, threshold): return [nextSpline, currentSpline]
if Math.IsSamePoint(currEndPoint, nextEndPoint, threshold):
if mathematics.IsSamePoint(currEndPoint, nextEndPoint, threshold):
nextSpline.Reverse()
#print("## ", "nextSpline.Reverse()")
return [currentSpline, nextSpline]
if Math.IsSamePoint(currStartPoint, nextStartPoint, threshold):
if mathematics.IsSamePoint(currStartPoint, nextStartPoint, threshold):
currentSpline.Reverse()
#print("## ", "currentSpline.Reverse()")
return [currentSpline, nextSpline]

View File

@ -7,12 +7,12 @@ from bpy_extras import object_utils, view3d_utils
from mathutils import *
from math import *
from . import Properties
from . import Curves
from . import CurveIntersections
from . import Util
from . import Surfaces
from . import Math
from . import properties
from . import curves
from . import intersections
from . import util
from . import surfaces
from . import mathematics
# 1 CURVE SELECTED
# ################
@ -24,11 +24,11 @@ class OperatorCurveInfo(bpy.types.Operator):
@classmethod
def poll(cls, context):
return Util.Selected1Curve()
return util.Selected1Curve()
def execute(self, context):
curve = Curves.Curve(context.active_object)
curve = curves.Curve(context.active_object)
nrSplines = len(curve.splines)
nrSegments = 0
@ -52,11 +52,11 @@ class OperatorCurveLength(bpy.types.Operator):
@classmethod
def poll(cls, context):
return Util.Selected1Curve()
return util.Selected1Curve()
def execute(self, context):
curve = Curves.Curve(context.active_object)
curve = curves.Curve(context.active_object)
context.scene.curvetools.CurveLength = curve.length
@ -72,11 +72,11 @@ class OperatorSplinesInfo(bpy.types.Operator):
@classmethod
def poll(cls, context):
return Util.Selected1Curve()
return util.Selected1Curve()
def execute(self, context):
curve = Curves.Curve(context.active_object)
curve = curves.Curve(context.active_object)
nrSplines = len(curve.splines)
print("")
@ -105,11 +105,11 @@ class OperatorSegmentsInfo(bpy.types.Operator):
@classmethod
def poll(cls, context):
return Util.Selected1Curve()
return util.Selected1Curve()
def execute(self, context):
curve = Curves.Curve(context.active_object)
curve = curves.Curve(context.active_object)
nrSplines = len(curve.splines)
nrSegments = 0
@ -146,7 +146,7 @@ class OperatorOriginToSpline0Start(bpy.types.Operator):
@classmethod
def poll(cls, context):
return Util.Selected1Curve()
return util.Selected1Curve()
def execute(self, context):
@ -183,11 +183,11 @@ class OperatorIntersectCurves(bpy.types.Operator):
@classmethod
def poll(cls, context):
return Util.Selected2OrMoreCurves()
return util.Selected2OrMoreCurves()
def execute(self, context):
print("### TODO: OperatorIntersectCurves.execute()")
print("### TODO: OperatorIntersectcurves.execute()")
algo = context.scene.curvetools.IntersectCurvesAlgorithm
print("-- algo:", algo)
@ -213,7 +213,7 @@ class OperatorIntersectCurves(bpy.types.Operator):
selected_objects[j].select_set(True)
if selected_objects[i].type == 'CURVE' and selected_objects[j].type == 'CURVE':
curveIntersector = CurveIntersections.CurvesIntersector.FromSelection()
curveIntersector = intersections.CurvesIntersector.FromSelection()
rvIntersectionNrs = curveIntersector.CalcAndApplyIntersections()
self.report({'INFO'}, "Active curve points: %d; other curve points: %d" % (rvIntersectionNrs[0], rvIntersectionNrs[1]))
@ -234,16 +234,16 @@ class OperatorLoftCurves(bpy.types.Operator):
@classmethod
def poll(cls, context):
return Util.Selected2Curves()
return util.Selected2Curves()
def execute(self, context):
#print("### TODO: OperatorLoftCurves.execute()")
#print("### TODO: OperatorLoftcurves.execute()")
loftedSurface = Surfaces.LoftedSurface.FromSelection()
loftedSurface = surfaces.LoftedSurface.FromSelection()
loftedSurface.AddToScene()
self.report({'INFO'}, "OperatorLoftCurves.execute()")
self.report({'INFO'}, "OperatorLoftcurves.execute()")
return {'FINISHED'}
@ -259,16 +259,16 @@ class OperatorSweepCurves(bpy.types.Operator):
@classmethod
def poll(cls, context):
return Util.Selected2Curves()
return util.Selected2Curves()
def execute(self, context):
#print("### TODO: OperatorSweepCurves.execute()")
#print("### TODO: OperatorSweepcurves.execute()")
sweptSurface = Surfaces.SweptSurface.FromSelection()
sweptSurface = surfaces.SweptSurface.FromSelection()
sweptSurface.AddToScene()
self.report({'INFO'}, "OperatorSweepCurves.execute()")
self.report({'INFO'}, "OperatorSweepcurves.execute()")
return {'FINISHED'}
@ -284,11 +284,11 @@ class OperatorBirail(bpy.types.Operator):
@classmethod
def poll(cls, context):
return Util.Selected3Curves()
return util.Selected3Curves()
def execute(self, context):
birailedSurface = Surfaces.BirailedSurface.FromSelection()
birailedSurface = surfaces.BirailedSurface.FromSelection()
birailedSurface.AddToScene()
self.report({'INFO'}, "OperatorBirail.execute()")
@ -307,12 +307,12 @@ class OperatorSplinesSetResolution(bpy.types.Operator):
@classmethod
def poll(cls, context):
return Util.Selected1OrMoreCurves()
return util.Selected1OrMoreCurves()
def execute(self, context):
splRes = context.scene.curvetools.SplineResolution
selCurves = Util.GetSelectedCurves()
selCurves = util.GetSelectedCurves()
for blCurve in selCurves:
for spline in blCurve.data.splines:
@ -331,14 +331,14 @@ class OperatorSplinesRemoveZeroSegment(bpy.types.Operator):
@classmethod
def poll(cls, context):
return Util.Selected1OrMoreCurves()
return util.Selected1OrMoreCurves()
def execute(self, context):
selCurves = Util.GetSelectedCurves()
selCurves = util.GetSelectedCurves()
for blCurve in selCurves:
curve = Curves.Curve(blCurve)
curve = curves.Curve(blCurve)
nrSplines = curve.nrSplines
splinesToRemove = []
@ -365,15 +365,15 @@ class OperatorSplinesRemoveShort(bpy.types.Operator):
@classmethod
def poll(cls, context):
return Util.Selected1OrMoreCurves()
return util.Selected1OrMoreCurves()
def execute(self, context):
threshold = context.scene.curvetools.SplineRemoveLength
selCurves = Util.GetSelectedCurves()
selCurves = util.GetSelectedCurves()
for blCurve in selCurves:
curve = Curves.Curve(blCurve)
curve = curves.Curve(blCurve)
nrSplines = curve.nrSplines
nrRemovedSplines = curve.RemoveShortSplines(threshold)
@ -394,14 +394,14 @@ class OperatorSplinesJoinNeighbouring(bpy.types.Operator):
@classmethod
def poll(cls, context):
return Util.Selected1OrMoreCurves()
return util.Selected1OrMoreCurves()
def execute(self, context):
selCurves = Util.GetSelectedCurves()
selCurves = util.GetSelectedCurves()
for blCurve in selCurves:
curve = Curves.Curve(blCurve)
curve = curves.Curve(blCurve)
nrSplines = curve.nrSplines
threshold = context.scene.curvetools.SplineJoinDistance
@ -423,7 +423,7 @@ def SurfaceFromBezier(surfacedata, points, center):
len_points = len(points) - 1
if len_points % 2 == 0:
h = Math.subdivide_cubic_bezier(
h = mathematics.subdivide_cubic_bezier(
points[len_points].co, points[len_points].handle_right,
points[0].handle_left, points[0].co, 0.5
)
@ -550,7 +550,7 @@ class ConvertSelectedFacesToBezier(bpy.types.Operator):
@classmethod
def poll(cls, context):
return Util.Selected1Mesh()
return util.Selected1Mesh()
def execute(self, context):
# main function
@ -620,7 +620,7 @@ class ConvertBezierToSurface(bpy.types.Operator):
@classmethod
def poll(cls, context):
return Util.Selected1OrMoreCurves()
return util.Selected1OrMoreCurves()
def execute(self, context):
# main function
@ -686,7 +686,7 @@ class BezierPointsFillet(bpy.types.Operator):
@classmethod
def poll(cls, context):
return Util.Selected1OrMoreCurves()
return util.Selected1OrMoreCurves()
def execute(self, context):
# main function
@ -798,7 +798,7 @@ class BezierDivide(bpy.types.Operator):
@classmethod
def poll(cls, context):
return Util.Selected1OrMoreCurves()
return util.Selected1OrMoreCurves()
def execute(self, context):
# main function
@ -834,7 +834,7 @@ class BezierDivide(bpy.types.Operator):
if (j in ii) and (j + 1 in ii):
bezier_points[j + jn].select_control_point = True
bezier_points[j + 1 + jn].select_control_point = True
h = Math.subdivide_cubic_bezier(
h = mathematics.subdivide_cubic_bezier(
bezier_points[j + jn].co, bezier_points[j + jn].handle_right,
bezier_points[j + 1 + jn].handle_left, bezier_points[j + 1 + jn].co, self.Bezier_t / 100
)
@ -853,7 +853,7 @@ class BezierDivide(bpy.types.Operator):
if j == n - 1 and (0 in ii) and spline.use_cyclic_u:
bezier_points[j + jn].select_control_point = True
bezier_points[0].select_control_point = True
h = Math.subdivide_cubic_bezier(
h = mathematics.subdivide_cubic_bezier(
bezier_points[j + jn].co, bezier_points[j + jn].handle_right,
bezier_points[0].handle_left, bezier_points[0].co, self.Bezier_t / 100
)
@ -922,10 +922,10 @@ class Split(bpy.types.Operator):
@classmethod
def poll(cls, context):
return Util.Selected1OrMoreCurves()
return util.Selected1OrMoreCurves()
def execute(self, context):
selected_Curves = Util.GetSelectedCurves()
selected_Curves = util.GetSelectedCurves()
for curve in selected_Curves:
spline_points = []
@ -1004,6 +1004,33 @@ class Split(bpy.types.Operator):
num=i
return {'FINISHED'}
class SeparateOutline(bpy.types.Operator):
bl_idname = "curvetools.sep_outline"
bl_label = "Separate Outline"
bl_options = {'REGISTER', 'UNDO'}
bl_description = "Makes 'Outline' separate mesh"
@classmethod
def poll(cls, context):
return util.Selected1OrMoreCurves()
def execute(self, context):
bpy.ops.object.mode_set(mode = 'EDIT')
bpy.ops.curve.separate()
return {'FINISHED'}
def register():
for cls in classes:
bpy.utils.register_class(operators)
def unregister():
for cls in classes:
bpy.utils.unregister_class(operators)
if __name__ == "__main__":
register()
operators = [
OperatorCurveInfo,
@ -1025,4 +1052,5 @@ operators = [
BezierDivide,
CurveScaleReset,
Split,
SeparateOutline,
]

View File

@ -87,3 +87,18 @@ class curvetoolsSelectedObject(bpy.types.PropertyGroup):
for blObject in blenderSelectedObjects: rvNames.append(blObject.name)
return rvNames
def register():
for cls in classes:
bpy.utils.register_class(operators)
def unregister():
for cls in classes:
bpy.utils.unregister_class(operators)
if __name__ == "__main__":
register()
operators = [
curvetoolsSelectedObject,
]

View File

@ -1,8 +1,8 @@
import bpy
import bmesh
from . import Math
from . import Curves
from . import mathematics
from . import curves
@ -62,8 +62,8 @@ class LoftedSurface:
blenderOtherCurve = selObjects[0]
if blenderActiveCurve == blenderOtherCurve: blenderOtherCurve = selObjects[1]
aCurve = Curves.Curve(blenderActiveCurve)
oCurve = Curves.Curve(blenderOtherCurve)
aCurve = curves.Curve(blenderActiveCurve)
oCurve = curves.Curve(blenderOtherCurve)
name = "TODO: autoname"
@ -162,7 +162,7 @@ class SweptSplineSurface:
prevDerivativeO = localDerivativesO[0]
for iO in range(self.resolutionO):
currDerivativeO = localDerivativesO[iO]
localRotMatO = Math.CalcRotationMatrix(prevDerivativeO, currDerivativeO)
localRotMatO = mathematics.CalcRotationMatrix(prevDerivativeO, currDerivativeO)
currLocalAToLocalO = worldMatrixOInv @ currWorldMatrixA
worldPointsA = []
@ -210,8 +210,8 @@ class SweptSurface:
blenderOtherCurve = selObjects[0]
if blenderActiveCurve == blenderOtherCurve: blenderOtherCurve = selObjects[1]
aCurve = Curves.Curve(blenderActiveCurve)
oCurve = Curves.Curve(blenderOtherCurve)
aCurve = curves.Curve(blenderActiveCurve)
oCurve = curves.Curve(blenderOtherCurve)
name = "TODO: autoname"
@ -315,7 +315,7 @@ class BirailedSplineSurface:
prevDerivativeRail1 = localDerivativesRail1[0]
for iRail in range(self.resolutionRails):
currDerivativeRail1 = localDerivativesRail1[iRail]
localRotMatRail1 = Math.CalcRotationMatrix(prevDerivativeRail1, currDerivativeRail1)
localRotMatRail1 = mathematics.CalcRotationMatrix(prevDerivativeRail1, currDerivativeRail1)
currLocalProfileToLocalRail1 = worldMatrixRail1Inv @ currWorldMatrixProfile
worldPointsProfileRail1 = []
@ -336,7 +336,7 @@ class BirailedSplineSurface:
scaleFactorRail2 = v3To.magnitude / v3From.magnitude
else:
scaleFactorRail2 = 1
rotMatRail2 = Math.CalcRotationMatrix(v3From, v3To)
rotMatRail2 = mathematics.CalcRotationMatrix(v3From, v3To)
worldOffsetsProfileRail2 = []
for iProfile in range(self.resolutionProfile):
@ -397,9 +397,9 @@ class BirailedSurface:
if profileBlenderCurve is None: raise Exception("profileBlenderCurve is None")
rail1Curve = Curves.Curve(rail1BlenderCurve)
rail2Curve = Curves.Curve(rail2BlenderCurve)
profileCurve = Curves.Curve(profileBlenderCurve)
rail1Curve = curves.Curve(rail1BlenderCurve)
rail2Curve = curves.Curve(rail2BlenderCurve)
profileCurve = curves.Curve(profileBlenderCurve)
name = "TODO: autoname"

View File

@ -25,13 +25,12 @@ bl_info = {
"name": "Curve Tools",
"description": "Adds some functionality for bezier/nurbs curve/surface modeling",
"author": "Mackraken",
"version": (0, 3, 3),
"version": (0, 4, 0),
"blender": (2, 80, 0),
"location": "View3D > Tool Shelf > Edit Tab",
"warning": "WIP",
"wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/"
"Scripts/Curve/Curve_Tools",
"tracker_url": "https://developer.blender.org/maniphest/task/edit/form/2/",
"wiki_url": "",
"tracker_url": "",
"category": "Add Curve"}
@ -50,22 +49,25 @@ from bpy.props import (
StringProperty,
FloatVectorProperty,
)
from . import Properties
from . import Operators
from . import auto_loft
from . import curve_outline
from . import PathFinder
from . import ShowCurveResolution
from . import SplinesSequence
from . import properties, operators, auto_loft, outline, remove_doubles
from . import path_finder, show_resolution, splines_sequence, fillet
from . import internal, cad, toolpath, exports
if 'internal' in locals():
if 'bpy' in locals():
importlib.reload(properties)
importlib.reload(operators)
importlib.reload(auto_loft)
importlib.reload(outline)
importlib.reload(remove_doubles)
importlib.reload(path_finder)
importlib.reload(show_resolution)
importlib.reload(splines_sequence)
importlib.reload(fillet)
importlib.reload(internal)
importlib.reload(cad)
importlib.reload(toolpath)
importlib.reload(exports)
from bpy.types import (
AddonPreferences,
)
@ -81,28 +83,10 @@ def UpdateDummy(object, context):
UTILSDROP = scene.UTUtilsDrop
class SeparateOutline(Operator):
bl_idname = "object.sep_outline"
bl_label = "Separate Outline"
bl_options = {'REGISTER', 'UNDO'}
bl_description = "Makes 'Outline' separate mesh"
@classmethod
def poll(cls, context):
return (context.object is not None and
context.object.type == 'CURVE')
def execute(self, context):
bpy.ops.object.mode_set(mode = 'EDIT')
bpy.ops.curve.separate()
return {'FINISHED'}
class curvetoolsSettings(PropertyGroup):
# selection
SelectedObjects: CollectionProperty(
type=Properties.curvetoolsSelectedObject
type=properties.curvetoolsSelectedObject
)
NrSelectedObjects: IntProperty(
name="NrSelectedObjects",
@ -308,7 +292,6 @@ class VIEW3D_PT_CurvePanel(Panel):
row = col.row(align=True)
row.prop(context.scene.curvetools, "LimitDistance", text="LimitDistance")
# row.active = (context.scene.curvetools.IntersectCurvesAlgorithm == '3D')
row = col.row(align=True)
row.prop(context.scene.curvetools, "IntersectCurvesAlgorithm", text="Algorithm")
@ -346,12 +329,14 @@ class VIEW3D_PT_CurvePanel(Panel):
if ADVANCEDDROP:
# C. 3 curves
row = col.row(align=True)
row.operator("object._curve_outline", text="Curve Outline")
row.operator("curvetools.outline", text="Curve Outline")
row = col.row(align=True)
row.operator("object.sep_outline", text="Separate Outline or selected")
row.operator("curvetools.sep_outline", text="Separate Outline or selected")
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')
@ -369,17 +354,19 @@ class VIEW3D_PT_CurvePanel(Panel):
row.prop(scene, "UTExtendedDrop", icon="TRIA_DOWN")
if EXTENDEDDROP:
row = col.row(align=True)
row.operator("curve.add_toolpath_offset_curve", text="Offset Curve")
row.operator("curvetools.add_toolpath_offset_curve", text="Offset Curve")
row = col.row(align=True)
row.operator("curve.bezier_cad_boolean", text="Boolean 2 selected spline")
row.operator("curvetools.bezier_cad_boolean", text="Boolean 2 selected spline")
row = col.row(align=True)
row.operator("curve.bezier_cad_subdivide", text="Multi Subdivide")
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("curve.add_toolpath_discretize_curve", text="Discretize Curve")
row.operator("curvetools.remove_doubles", text='Remove Doubles')
row = col.row(align=True)
row.operator("curve.bezier_cad_array", text="Array selected spline")
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()
@ -396,7 +383,7 @@ class VIEW3D_PT_CurvePanel(Panel):
row = col.row(align=True)
row.prop(context.scene.curvetools, "curve_vertcolor", text="")
row = col.row(align=True)
row.operator("curve.show_resolution", text="Run [ESC]")
row.operator("curvetools.show_resolution", text="Run [ESC]")
# D.1 set spline sequence
row = col.row(align=True)
@ -406,12 +393,12 @@ class VIEW3D_PT_CurvePanel(Panel):
row.prop(context.scene.curvetools, "font_thickness", text="")
row.prop(context.scene.curvetools, "font_size", text="")
row = col.row(align=True)
oper = row.operator("curve.rearrange_spline", text="<")
oper = row.operator("curvetools.rearrange_spline", text="<")
oper.command = 'PREV'
oper = row.operator("curve.rearrange_spline", text=">")
oper = row.operator("curvetools.rearrange_spline", text=">")
oper.command = 'NEXT'
row = col.row(align=True)
row.operator("curve.show_splines_sequence", text="Run [ESC]")
row.operator("curvetools.show_splines_sequence", text="Run [ESC]")
# D.2 remove splines
row = col.row(align=True)
@ -455,7 +442,6 @@ class VIEW3D_PT_CurvePanel(Panel):
row = col.row(align=True)
row.label(text="A - deselect all")
# Add-ons Preferences Update Panel
# Define Panel classes for updating
@ -500,6 +486,27 @@ class CurveAddonPreferences(AddonPreferences):
col.label(text="Tab Category:")
col.prop(self, "category", text="")
# Context MENU
def curve_tools_context_menu(self, context):
bl_label = 'Curve tools'
self.layout.operator("curvetools.bezier_points_fillet", text="Fillet")
self.layout.operator("curvetools.bezier_cad_handle_projection", text='Handle Projection')
self.layout.operator("curvetools.bezier_spline_divide", text="Divide")
self.layout.operator("curvetools.add_toolpath_offset_curve", text="Offset Curve")
self.layout.operator("curvetools.remove_doubles", text='Remove Doubles')
self.layout.separator()
def curve_tools_object_context_menu(self, context):
bl_label = 'Curve tools'
if context.active_object.type == "CURVE":
self.layout.operator("curvetools.scale_reset", text="Scale Reset")
self.layout.operator("curvetools.add_toolpath_offset_curve", text="Offset Curve")
self.layout.operator("curvetools.remove_doubles", text='Remove Doubles')
self.layout.separator()
# Import-export 2d svg
def menu_file_export(self, context):
for operator in exports.operators:
self.layout.operator(operator.bl_idname)
@ -509,16 +516,21 @@ def menu_file_import(self, context):
self.layout.operator(operator.bl_idname)
# REGISTER
classes = cad.operators + toolpath.operators + exports.operators + Operators.operators + [
Properties.curvetoolsSelectedObject,
CurveAddonPreferences,
curvetoolsSettings,
SeparateOutline,
PathFinder.PathFinder,
ShowCurveResolution.ShowCurveResolution,
SplinesSequence.ShowSplinesSequence,
SplinesSequence.RearrangeSpline,
]
classes = cad.operators + \
toolpath.operators + \
exports.operators + \
operators.operators + \
properties.operators + \
path_finder.operators + \
show_resolution.operators + \
splines_sequence.operators + \
outline.operators + \
fillet.operators + \
remove_doubles.operators + \
[
CurveAddonPreferences,
curvetoolsSettings,
]
def register():
bpy.types.Scene.UTSingleDrop = BoolProperty(
@ -560,13 +572,14 @@ def register():
auto_loft.register()
curve_outline.register()
bpy.types.TOPBAR_MT_file_export.append(menu_file_export)
bpy.types.Scene.curvetools = bpy.props.PointerProperty(type=curvetoolsSettings)
update_panel(None, bpy.context)
bpy.types.VIEW3D_MT_edit_curve_context_menu.prepend(curve_tools_context_menu)
bpy.types.VIEW3D_MT_object_context_menu.prepend(curve_tools_object_context_menu)
def unregister():
@ -579,10 +592,11 @@ def unregister():
auto_loft.unregister()
curve_outline.unregister()
bpy.types.TOPBAR_MT_file_export.remove(menu_file_export)
bpy.types.VIEW3D_MT_edit_curve_context_menu.remove(curve_tools_context_menu)
bpy.types.VIEW3D_MT_object_context_menu.remove(curve_tools_object_context_menu)
for panel in panels:
bpy.utils.unregister_class(panel)

View File

@ -1,9 +1,9 @@
import bpy
from bpy.props import BoolProperty
from bpy.types import Operator, Panel
from curve_tools.Surfaces import LoftedSurface
from curve_tools.Curves import Curve
from curve_tools import Util
from . import surfaces
from . import curves
from . import util
class OperatorAutoLoftCurves(Operator):
@ -13,16 +13,16 @@ class OperatorAutoLoftCurves(Operator):
@classmethod
def poll(cls, context):
return Util.Selected2Curves()
return util.Selected2Curves()
def execute(self, context):
#print("### TODO: OperatorLoftCurves.execute()")
#print("### TODO: OperatorLoftcurves.execute()")
mesh = bpy.data.meshes.new("LoftMesh")
curve0 = context.selected_objects[0]
curve1 = context.selected_objects[1]
ls = LoftedSurface(Curve(curve0), Curve(curve1), "AutoLoft")
ls = surfaces.LoftedSurface(curves.Curve(curve0), curves.Curve(curve1), "AutoLoft")
ls.bMesh.to_mesh(mesh)
@ -37,8 +37,6 @@ class OperatorAutoLoftCurves(Operator):
"description": "Auto loft from %s to %s" % (curve0.name, curve1.name),
"curve0": curve0.name,
"curve1": curve1.name}
#print(loftobj['_RNA_UI'].to_dict())
#self.report({'INFO'}, "OperatorAutoLoftCurves.execute()")
return {'FINISHED'}
@ -61,12 +59,11 @@ class AutoLoftModalOperator(Operator):
#print("TIMER", lofters)
for loftmesh in lofters:
#loftmesh.hide_select = True
rna = loftmesh['_RNA_UI']["autoloft"].to_dict()
curve0 = scene.objects.get(rna["curve0"])
curve1 = scene.objects.get(rna["curve1"])
if curve0 and curve1:
ls = LoftedSurface(Curve(curve0), Curve(curve1), loftmesh.name)
ls = surfaces.LoftedSurface(curves.Curve(curve0), curves.Curve(curve1), loftmesh.name)
ls.bMesh.to_mesh(loftmesh.data)
return {'FINISHED'}

View File

@ -28,20 +28,20 @@ bl_info = {
import bpy
from . import internal
from . import Util
from . import util
class Fillet(bpy.types.Operator):
bl_idname = 'curve.bezier_cad_fillet'
bl_idname = 'curvetools.bezier_cad_fillet'
bl_description = bl_label = 'Fillet'
bl_options = {'REGISTER', 'UNDO'}
radius: bpy.props.FloatProperty(name='Radius', description='Radius of the rounded corners', unit='LENGTH', min=0.0, default=0.1)
chamfer_mode: bpy.props.BoolProperty(name='Chamfer', description='Cut off sharp without rounding', default=False)
limit_half_way: bpy.props.BoolProperty(name='Limit Half Way', description='Limits the segments to half their length in order to prevent collisions', default=False)
limit_half_way: bpy.props.BoolProperty(name='Limit Half Way', description='Limits the segements to half their length in order to prevent collisions', default=False)
@classmethod
def poll(cls, context):
return Util.Selected1OrMoreCurves()
return util.Selected1OrMoreCurves()
def execute(self, context):
splines = internal.getSelectedSplines(True, True, True)
@ -54,7 +54,7 @@ class Fillet(bpy.types.Operator):
return {'FINISHED'}
class Boolean(bpy.types.Operator):
bl_idname = 'curve.bezier_cad_boolean'
bl_idname = 'curvetools.bezier_cad_boolean'
bl_description = bl_label = 'Boolean'
bl_options = {'REGISTER', 'UNDO'}
@ -66,7 +66,7 @@ class Boolean(bpy.types.Operator):
@classmethod
def poll(cls, context):
return Util.Selected1OrMoreCurves()
return util.Selected1OrMoreCurves()
def execute(self, context):
current_mode = bpy.context.object.mode
@ -80,7 +80,7 @@ class Boolean(bpy.types.Operator):
if len(splines) != 2:
self.report({'WARNING'}, 'Invalid selection. Only work to selected two spline.')
return {'CANCELLED'}
bpy.ops.curve.spline_type_set(type='BEZIER')
bpy.ops.curvetools.spline_type_set(type='BEZIER')
splineA = bpy.context.object.data.splines.active
splineB = splines[0] if (splines[1] == splineA) else splines[1]
if not internal.bezierBooleanGeometry(splineA, splineB, self.operation):
@ -92,13 +92,13 @@ class Boolean(bpy.types.Operator):
return {'FINISHED'}
class Intersection(bpy.types.Operator):
bl_idname = 'curve.bezier_cad_intersection'
bl_idname = 'curvetools.bezier_cad_intersection'
bl_description = bl_label = 'Intersection'
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
return Util.Selected1OrMoreCurves()
return util.Selected1OrMoreCurves()
def execute(self, context):
segments = internal.bezierSegments(bpy.context.object.data.splines, True)
@ -108,15 +108,33 @@ class Intersection(bpy.types.Operator):
internal.bezierMultiIntersection(segments)
return {'FINISHED'}
class HandleProjection(bpy.types.Operator):
bl_idname = 'curvetools.bezier_cad_handle_projection'
bl_description = bl_label = 'Handle Projection'
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
return internal.curveObject()
def execute(self, context):
segments = internal.bezierSegments(bpy.context.object.data.splines, True)
if len(segments) < 1:
self.report({'WARNING'}, 'Nothing selected')
return {'CANCELLED'}
internal.bezierProjectHandles(segments)
return {'FINISHED'}
class MergeEnds(bpy.types.Operator):
bl_idname = 'curve.bezier_cad_merge_ends'
bl_idname = 'curvetools.bezier_cad_merge_ends'
bl_description = bl_label = 'Merge Ends'
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
return Util.Selected1OrMoreCurves()
return util.Selected1OrMoreCurves()
def execute(self, context):
points = []
@ -151,17 +169,17 @@ class MergeEnds(bpy.types.Operator):
points[0].handle_left = handle
points[0].co = new_co
bpy.ops.curve.select_all(action='DESELECT')
bpy.ops.curvetools.select_all(action='DESELECT')
points[1].select_control_point = True
bpy.ops.curve.delete()
bpy.ops.curvetools.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
bpy.ops.curve.make_segment()
bpy.ops.curve.select_all(action='DESELECT')
bpy.ops.curvetools.make_segment()
bpy.ops.curvetools.select_all(action='DESELECT')
return {'FINISHED'}
class Subdivide(bpy.types.Operator):
bl_idname = 'curve.bezier_cad_subdivide'
bl_idname = 'curvetools.bezier_cad_subdivide'
bl_description = bl_label = 'Subdivide'
bl_options = {'REGISTER', 'UNDO'}
@ -169,7 +187,7 @@ class Subdivide(bpy.types.Operator):
@classmethod
def poll(cls, context):
return Util.Selected1OrMoreCurves()
return util.Selected1OrMoreCurves()
def execute(self, context):
current_mode = bpy.context.object.mode
@ -193,7 +211,7 @@ class Subdivide(bpy.types.Operator):
return {'FINISHED'}
class Array(bpy.types.Operator):
bl_idname = 'curve.bezier_cad_array'
bl_idname = 'curvetools.bezier_cad_array'
bl_description = bl_label = 'Array'
bl_options = {'REGISTER', 'UNDO'}
@ -204,7 +222,7 @@ class Array(bpy.types.Operator):
@classmethod
def poll(cls, context):
return Util.Selected1OrMoreCurves()
return util.Selected1OrMoreCurves()
def execute(self, context):
splines = internal.getSelectedSplines(True, True)
@ -215,13 +233,13 @@ class Array(bpy.types.Operator):
return {'FINISHED'}
class Circle(bpy.types.Operator):
bl_idname = 'curve.bezier_cad_circle'
bl_idname = 'curvetools.bezier_cad_circle'
bl_description = bl_label = 'Circle'
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
return Util.Selected1OrMoreCurves()
return util.Selected1OrMoreCurves()
def execute(self, context):
segments = internal.bezierSegments(bpy.context.object.data.splines, True)
@ -240,12 +258,12 @@ class Circle(bpy.types.Operator):
return {'FINISHED'}
class Length(bpy.types.Operator):
bl_idname = 'curve.bezier_cad_length'
bl_idname = 'curvetools.bezier_cad_length'
bl_description = bl_label = 'Length'
@classmethod
def poll(cls, context):
return Util.Selected1OrMoreCurves()
return util.Selected1OrMoreCurves()
def execute(self, context):
segments = internal.bezierSegments(bpy.context.object.data.splines, True)
@ -259,4 +277,15 @@ class Length(bpy.types.Operator):
self.report({'INFO'}, bpy.utils.units.to_string(bpy.context.scene.unit_settings.system, 'LENGTH', length))
return {'FINISHED'}
operators = [Fillet, Boolean, Intersection, MergeEnds, Subdivide, Array, Circle, Length]
def register():
for cls in classes:
bpy.utils.register_class(operators)
def unregister():
for cls in classes:
bpy.utils.unregister_class(operators)
if __name__ == "__main__":
register()
operators = [Fillet, Boolean, Intersection, HandleProjection, MergeEnds, Subdivide, Array, Circle, Length]

View File

@ -1,112 +0,0 @@
'''
by Yann Bertrand, january 2014.
BEGIN GPL LICENSE BLOCK
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
END GPL LICENCE BLOCK
'''
bl_info = {
"name": "Curve Outline",
"description": "creates an Outline",
"category": "Object",
"author": "Yann Bertrand (jimflim)",
"version": (0, 4),
"blender": (2, 69, 0),
}
import bpy
from mathutils import Vector
from mathutils.geometry import intersect_line_line
def createOutline(curve, outline):
for spline in curve.data.splines[:]:
p = spline.bezier_points
out = []
n = ((p[0].handle_right-p[0].co).normalized()-(p[0].handle_left-p[0].co).normalized()).normalized()
n = Vector((-n[1], n[0], n[2]))
o = p[0].co+outline*n
out.append(o)
for i in range(1,len(p)):
n = ((p[i].handle_right-p[i].co).normalized()-(p[i].handle_left-p[i].co).normalized()).normalized()
n = Vector((-n[1], n[0], n[2]))
o = intersect_line_line(out[-1], (out[-1]+p[i].co-p[i-1].co), p[i].co, p[i].co+n)[0]
out.append(o)
curve.data.splines.new('BEZIER')
if spline.use_cyclic_u:
curve.data.splines[-1].use_cyclic_u = True
p_out = curve.data.splines[-1].bezier_points
p_out.add(len(out)-1)
for i in range(len(out)):
p_out[i].handle_left_type = 'FREE'
p_out[i].handle_right_type = 'FREE'
p_out[i].co = out[i]
if i<len(out)-1:
l = (p[i+1].co-p[i].co).length
l2 = (out[i]-out[i+1]).length
if i==0:
p_out[i].handle_left = out[i] + ((p[i].handle_left-p[i].co)*l2/l)
if i<len(out)-1:
p_out[i+1].handle_left = out[i+1] + ((p[i+1].handle_left-p[i+1].co)*l2/l)
p_out[i].handle_right = out[i] + ((p[i].handle_right-p[i].co)*l2/l)
for i in range(len(p)):
p_out[i].handle_left_type = p[i].handle_left_type
p_out[i].handle_right_type = p[i].handle_right_type
return
class CurveOutline(bpy.types.Operator):
"""Curve Outliner"""
bl_idname = "object._curve_outline"
bl_label = "Create Outline"
bl_options = {'REGISTER', 'UNDO'}
outline: bpy.props.FloatProperty(name="Amount", default=0.1)
@classmethod
def poll(cls, context):
return (context.object is not None and
context.object.type == 'CURVE')
def execute(self, context):
createOutline(context.object, self.outline)
return {'FINISHED'}
def invoke(self, context, event):
return context.window_manager.invoke_props_popup(self, event)
def menu_func(self, context):
self.layout.operator(CurveOutline.bl_idname)
def register():
bpy.utils.register_class(CurveOutline)
def unregister():
bpy.utils.unregister_class(CurveOutline)
if __name__ == "__main__":
register()

View File

@ -1,110 +0,0 @@
import bpy, mathutils
bl_info = {
'name': 'Curve Remove Doubles',
'author': 'Michael Soluyanov',
'version': (1, 1),
'blender': (2, 80, 0),
'location': 'View3D > Context menu (W/RMB) > Remove Doubles',
'description': 'Adds command "Remove Doubles" for curves',
'category': 'Object'
}
def main(context, distance = 0.01):
obj = context.active_object
dellist = []
if bpy.ops.object.mode_set.poll():
bpy.ops.object.mode_set(mode='EDIT')
for spline in obj.data.splines:
if len(spline.bezier_points) > 1:
for i in range(0, len(spline.bezier_points)):
if i == 0:
ii = len(spline.bezier_points) - 1
else:
ii = i - 1
dot = spline.bezier_points[i];
dot1 = spline.bezier_points[ii];
while dot1 in dellist and i != ii:
ii -= 1
if ii < 0:
ii = len(spline.bezier_points)-1
dot1 = spline.bezier_points[ii]
if dot.select_control_point and dot1.select_control_point and (i!=0 or spline.use_cyclic_u):
if (dot.co-dot1.co).length < distance:
# remove points and recreate hangles
dot1.handle_right_type = "FREE"
dot1.handle_right = dot.handle_right
dot1.co = (dot.co + dot1.co) / 2
dellist.append(dot)
else:
# Handles that are on main point position converts to vector,
# if next handle are also vector
if dot.handle_left_type == 'VECTOR' and (dot1.handle_right - dot1.co).length < distance:
dot1.handle_right_type = "VECTOR"
if dot1.handle_right_type == 'VECTOR' and (dot.handle_left - dot.co).length < distance:
dot.handle_left_type = "VECTOR"
bpy.ops.curve.select_all(action = 'DESELECT')
for dot in dellist:
dot.select_control_point = True
count = len(dellist)
bpy.ops.curve.delete(type = 'VERT')
bpy.ops.curve.select_all(action = 'SELECT')
return count
class CurveRemvDbs(bpy.types.Operator):
"""Merge consecutive points that are near to each other"""
bl_idname = 'curve.remove_doubles'
bl_label = 'Remove Doubles'
bl_options = {'REGISTER', 'UNDO'}
distance: bpy.props.FloatProperty(name = 'Distance', default = 0.01)
@classmethod
def poll(cls, context):
obj = context.active_object
return (obj and obj.type == 'CURVE')
def execute(self, context):
removed=main(context, self.distance)
self.report({'INFO'}, "Removed %d bezier points" % removed)
return {'FINISHED'}
def menu_func(self, context):
self.layout.operator(CurveRemvDbs.bl_idname, text='Remove Doubles')
def register():
bpy.utils.register_class(CurveRemvDbs)
bpy.types.VIEW3D_MT_edit_curve_context_menu.append(menu_func)
def unregister():
bpy.utils.unregister_class(CurveRemvDbs)
bpy.types.VIEW3D_MT_edit_curve_context_menu.remove(menu_func)
if __name__ == "__main__":
register()

View File

@ -224,4 +224,15 @@ class GCodeExport(bpy.types.Operator, ExportHelper):
f.write(speed_code+' X{:.3f} Y{:.3f} Z{:.3f}\n'.format(position[0], position[1], position[2]))
return {'FINISHED'}
def register():
for cls in classes:
bpy.utils.register_class(operators)
def unregister():
for cls in classes:
bpy.utils.unregister_class(operators)
if __name__ == "__main__":
register()
operators = [SvgExport, GCodeExport]

143
curve_tools/fillet.py Normal file
View File

@ -0,0 +1,143 @@
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and / or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
bl_info = {
'name': 'Curve Fillet',
'author': 'Spivak Vladimir (cwolf3d)',
'version': (0, 0, 1),
'blender': (2, 80, 0),
'location': 'Curve Tools addon. (N) Panel',
'description': 'Various types of fillet (chamfering)',
'warning': '', # used for warning icon and text in addons panel
'wiki_url': '',
'tracker_url': '',
'category': 'Curve'}
import bpy
from bpy.props import *
from bpy_extras import object_utils, view3d_utils
from mathutils import *
from math import *
def click(self, context, event):
bpy.ops.object.mode_set(mode = 'EDIT')
bpy.context.view_layer.update()
def remove_handler(handlers):
for handler in handlers:
try:
bpy.types.SpaceView3D.draw_handler_remove(handler, 'WINDOW')
except:
pass
for handler in handlers:
handlers.remove(handler)
class Fillet(bpy.types.Operator):
bl_idname = "curvetools.fillet"
bl_label = "Curve Fillet"
bl_description = "Curve Fillet"
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')
handlers = []
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)
return {'CANCELLED'}
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')
elif event.type == 'MOUSEMOVE': #
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 (context.object is not None and
context.object.type == 'CURVE')
def register():
for cls in classes:
bpy.utils.register_class(operators)
def unregister():
for cls in classes:
bpy.utils.unregister_class(operators)
if __name__ == "__main__":
register()
operators = [Fillet]

View File

@ -408,6 +408,25 @@ def bezierMultiIntersection(segments):
prepareSegmentIntersections(segments)
subdivideBezierSegments(segments)
def bezierProjectHandles(segments):
insertions = []
index_offset = 0
for segment in segments:
if len(insertions) > 0 and insertions[-1][0] != segment['spline']:
index_offset = 0
points = bezierSegmentPoints(segment['beginPoint'], segment['endPoint'])
paramA, paramB, pointA, pointB = nearestPointOfLines(points[0], points[1]-points[0], points[3], points[2]-points[3])
if pointA and pointB:
segment['cuts'].append({'param': 0.5})
insertions.append((segment['spline'], segment['beginIndex']+1+index_offset, (pointA+pointB)*0.5))
index_offset += 1
subdivideBezierSegments(segments)
for insertion in insertions:
bezier_point = insertion[0].bezier_points[insertion[1]]
bezier_point.co = insertion[2]
bezier_point.handle_left_type = 'VECTOR'
bezier_point.handle_right_type = 'VECTOR'
def bezierSubivideAt(points, params):
if len(params) == 0:
return []

View File

@ -1,7 +1,7 @@
import bpy
from . import Math
from . import Curves
from . import Util
from . import mathematics
from . import curves
from . import util
from mathutils import Vector
@ -58,7 +58,7 @@ class BezierSegmentsIntersector:
Q0 = self.worldMatrix2 @ self.segment2.CalcPoint(parameter=segPar20)
Q1 = self.worldMatrix2 @ self.segment2.CalcPoint(parameter=segPar21)
intersectionPointData = Math.CalcIntersectionPointLineSegments(P0, P1, Q0, Q1, limitDistance)
intersectionPointData = mathematics.CalcIntersectionPointLineSegments(P0, P1, Q0, Q1, limitDistance)
if intersectionPointData is None:
continue
@ -94,15 +94,15 @@ class BezierSegmentsIntersector:
Q0 = self.worldMatrix2 @ self.segment2.CalcPoint(parameter=segPar20)
Q1 = self.worldMatrix2 @ self.segment2.CalcPoint(parameter=segPar21)
intersectionPointData = Math.CalcIntersectionPointLineSegments(P0, P1, Q0, Q1, limitDistance)
intersectionPointData = mathematics.CalcIntersectionPointLineSegments(P0, P1, Q0, Q1, limitDistance)
if intersectionPointData is None:
continue
# intersection point can't be an existing point
intersectionSegment1Parameter = segPar10 + (intersectionPointData[0] / (fltNrSamples1))
worldPoint1 = self.worldMatrix1 @ self.segment1.CalcPoint(parameter=intersectionSegment1Parameter)
if (Math.IsSamePoint(P0, worldPoint1, limitDistance)) or \
(Math.IsSamePoint(P1, worldPoint1, limitDistance)):
if (mathematics.IsSamePoint(P0, worldPoint1, limitDistance)) or \
(mathematics.IsSamePoint(P1, worldPoint1, limitDistance)):
intersectionPoint1 = None
else:
@ -112,8 +112,8 @@ class BezierSegmentsIntersector:
intersectionSegment2Parameter = segPar20 + (intersectionPointData[1] / (fltNrSamples2))
worldPoint2 = self.worldMatrix2 @ self.segment2.CalcPoint(parameter=intersectionSegment2Parameter)
if (Math.IsSamePoint(Q0, worldPoint2, limitDistance)) or \
(Math.IsSamePoint(Q1, worldPoint2, limitDistance)):
if (mathematics.IsSamePoint(Q0, worldPoint2, limitDistance)) or \
(mathematics.IsSamePoint(Q1, worldPoint2, limitDistance)):
intersectionPoint2 = None
else:
@ -143,7 +143,7 @@ class BezierSegmentsIntersector:
Q0 = self.worldMatrix2 @ self.segment2.CalcPoint(parameter=segPar20)
Q1 = self.worldMatrix2 @ self.segment2.CalcPoint(parameter=segPar21)
intersectionPointData = Math.CalcIntersectionPointsLineSegmentsDIR(P0, P1, Q0, Q1, algoDIR)
intersectionPointData = mathematics.CalcIntersectionPointsLineSegmentsDIR(P0, P1, Q0, Q1, algoDIR)
if intersectionPointData is None:
continue
@ -183,15 +183,15 @@ class BezierSegmentsIntersector:
Q0 = self.worldMatrix2 @ self.segment2.CalcPoint(parameter=segPar20)
Q1 = self.worldMatrix2 @ self.segment2.CalcPoint(parameter=segPar21)
intersectionPointData = Math.CalcIntersectionPointsLineSegmentsDIR(P0, P1, Q0, Q1, algoDIR)
intersectionPointData = mathematics.CalcIntersectionPointsLineSegmentsDIR(P0, P1, Q0, Q1, algoDIR)
if intersectionPointData is None:
continue
# intersection point can't be an existing point
intersectionSegment1Parameter = segPar10 + (intersectionPointData[0] / (fltNrSamples1))
worldPoint1 = self.worldMatrix1 @ self.segment1.CalcPoint(parameter=intersectionSegment1Parameter)
if (Math.IsSamePoint(P0, worldPoint1, limitDistance)) or \
(Math.IsSamePoint(P1, worldPoint1, limitDistance)):
if (mathematics.IsSamePoint(P0, worldPoint1, limitDistance)) or \
(mathematics.IsSamePoint(P1, worldPoint1, limitDistance)):
intersectionPoint1 = None
else:
@ -201,8 +201,8 @@ class BezierSegmentsIntersector:
intersectionSegment2Parameter = segPar20 + (intersectionPointData[1] / (fltNrSamples2))
worldPoint2 = self.worldMatrix2 @ self.segment2.CalcPoint(parameter=intersectionSegment2Parameter)
if (Math.IsSamePoint(Q0, worldPoint2, limitDistance)) or \
(Math.IsSamePoint(Q1, worldPoint2, limitDistance)):
if (mathematics.IsSamePoint(Q0, worldPoint2, limitDistance)) or \
(mathematics.IsSamePoint(Q1, worldPoint2, limitDistance)):
intersectionPoint2 = None
else:
@ -232,7 +232,7 @@ class BezierSegmentsIntersector:
Q0 = self.worldMatrix2 @ self.segment2.CalcPoint(parameter=segPar20)
Q1 = self.worldMatrix2 @ self.segment2.CalcPoint(parameter=segPar21)
intersectionPointData = Math.CalcIntersectionPointsLineSegmentsPOV(P0, P1, Q0, Q1, algoPOV)
intersectionPointData = mathematics.CalcIntersectionPointsLineSegmentsPOV(P0, P1, Q0, Q1, algoPOV)
if intersectionPointData is None:
continue
@ -272,15 +272,15 @@ class BezierSegmentsIntersector:
Q0 = self.worldMatrix2 @ self.segment2.CalcPoint(parameter=segPar20)
Q1 = self.worldMatrix2 @ self.segment2.CalcPoint(parameter=segPar21)
intersectionPointData = Math.CalcIntersectionPointsLineSegmentsPOV(P0, P1, Q0, Q1, algoPOV)
intersectionPointData = mathematics.CalcIntersectionPointsLineSegmentsPOV(P0, P1, Q0, Q1, algoPOV)
if intersectionPointData is None:
continue
# intersection point can't be an existing point
intersectionSegment1Parameter = segPar10 + (intersectionPointData[0] / fltNrSamples1)
worldPoint1 = self.worldMatrix1 @ self.segment1.CalcPoint(parameter=intersectionSegment1Parameter)
if (Math.IsSamePoint(P0, worldPoint1, limitDistance)) or \
(Math.IsSamePoint(P1, worldPoint1, limitDistance)):
if (mathematics.IsSamePoint(P0, worldPoint1, limitDistance)) or \
(mathematics.IsSamePoint(P1, worldPoint1, limitDistance)):
intersectionPoint1 = None
else:
@ -290,8 +290,8 @@ class BezierSegmentsIntersector:
intersectionSegment2Parameter = segPar20 + (intersectionPointData[1] / fltNrSamples2)
worldPoint2 = self.worldMatrix2 @ self.segment2.CalcPoint(parameter=intersectionSegment2Parameter)
if (Math.IsSamePoint(Q0, worldPoint2, limitDistance)) or \
(Math.IsSamePoint(Q1, worldPoint2, limitDistance)):
if (mathematics.IsSamePoint(Q0, worldPoint2, limitDistance)) or \
(mathematics.IsSamePoint(Q1, worldPoint2, limitDistance)):
intersectionPoint2 = None
else:
@ -341,7 +341,7 @@ class BezierSegmentsIntersector:
Q0 = self.worldMatrix2 @ self.segment2.CalcPoint(parameter=segPar20)
Q1 = self.worldMatrix2 @ self.segment2.CalcPoint(parameter=segPar21)
intersectionPointData = Math.CalcIntersectionPointLineSegments(P0, P1, Q0, Q1, limitDistance)
intersectionPointData = mathematics.CalcIntersectionPointLineSegments(P0, P1, Q0, Q1, limitDistance)
if intersectionPointData is None:
continue
@ -382,7 +382,7 @@ class BezierSegmentsIntersector:
Q0 = self.worldMatrix2 @ self.segment2.CalcPoint(parameter=segPar20)
Q1 = self.worldMatrix2 @ self.segment2.CalcPoint(parameter=segPar21)
intersectionPointData = Math.CalcIntersectionPointsLineSegmentsDIR(P0, P1, Q0, Q1, algoDIR)
intersectionPointData = mathematics.CalcIntersectionPointsLineSegmentsDIR(P0, P1, Q0, Q1, algoDIR)
if intersectionPointData is None:
continue
@ -423,7 +423,7 @@ class BezierSegmentsIntersector:
Q0 = self.worldMatrix2 @ self.segment2.CalcPoint(parameter=segPar20)
Q1 = self.worldMatrix2 @ self.segment2.CalcPoint(parameter=segPar21)
intersectionPointData = Math.CalcIntersectionPointsLineSegmentsPOV(P0, P1, Q0, Q1, algoPOV)
intersectionPointData = mathematics.CalcIntersectionPointsLineSegmentsPOV(P0, P1, Q0, Q1, algoPOV)
if intersectionPointData is None:
continue
@ -508,8 +508,8 @@ class CurvesIntersector:
if blenderActiveCurve == blenderOtherCurve:
blenderOtherCurve = selObjects[1]
aCurve = Curves.Curve(blenderActiveCurve)
oCurve = Curves.Curve(blenderOtherCurve)
aCurve = curves.Curve(blenderActiveCurve)
oCurve = curves.Curve(blenderOtherCurve)
return CurvesIntersector(aCurve, oCurve)
@ -528,7 +528,7 @@ class CurvesIntersector:
algo = bpy.context.scene.curvetools.IntersectCurvesAlgorithm
if algo == 'From View':
regionView3D = Util.GetFirstRegionView3D()
regionView3D = util.GetFirstRegionView3D()
if regionView3D is None:
print("### ERROR: regionView3D is None. Stopping.")
return

118
curve_tools/outline.py Normal file
View File

@ -0,0 +1,118 @@
'''
by Yann Bertrand, january 2014.
BEGIN GPL LICENSE BLOCK
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
END GPL LICENCE BLOCK
'''
bl_info = {
"name": "Curve Outline",
"description": "creates an Outline",
"category": "Object",
"author": "Yann Bertrand (jimflim)",
"version": (0, 4),
"blender": (2, 69, 0),
}
import bpy
from mathutils import Vector
from mathutils.geometry import intersect_line_line
from . import util
def createOutline(curve, outline):
for spline in curve.data.splines[:]:
if spline.type == 'BEZIER':
p = spline.bezier_points
out = []
n = ((p[0].handle_right-p[0].co).normalized()-(p[0].handle_left-p[0].co).normalized()).normalized()
n = Vector((-n[1], n[0], n[2]))
o = p[0].co+outline*n
out.append(o)
for i in range(1,len(p)):
n = ((p[i].handle_right-p[i].co).normalized()-(p[i].handle_left-p[i].co).normalized()).normalized()
n = Vector((-n[1], n[0], n[2]))
o = intersect_line_line(out[-1], (out[-1]+p[i].co-p[i-1].co), p[i].co, p[i].co+n)[0]
out.append(o)
curve.data.splines.new('BEZIER')
if spline.use_cyclic_u:
curve.data.splines[-1].use_cyclic_u = True
p_out = curve.data.splines[-1].bezier_points
p_out.add(len(out)-1)
for i in range(len(out)):
p_out[i].handle_left_type = 'FREE'
p_out[i].handle_right_type = 'FREE'
p_out[i].co = out[i]
if i<len(out)-1:
l = (p[i+1].co-p[i].co).length
l2 = (out[i]-out[i+1]).length
if i==0:
p_out[i].handle_left = out[i] + ((p[i].handle_left-p[i].co)*l2/l)
if i<len(out)-1:
p_out[i+1].handle_left = out[i+1] + ((p[i+1].handle_left-p[i+1].co)*l2/l)
p_out[i].handle_right = out[i] + ((p[i].handle_right-p[i].co)*l2/l)
for i in range(len(p)):
p_out[i].handle_left_type = p[i].handle_left_type
p_out[i].handle_right_type = p[i].handle_right_type
return
class CurveOutline(bpy.types.Operator):
"""Curve Outliner"""
bl_idname = "curvetools.outline"
bl_label = "Create Outline"
bl_options = {'REGISTER', 'UNDO'}
outline: bpy.props.FloatProperty(name="Amount", default=0.1)
@classmethod
def poll(cls, context):
return util.Selected1OrMoreCurves()
def execute(self, context):
createOutline(context.object, self.outline)
return {'FINISHED'}
def invoke(self, context, event):
return context.window_manager.invoke_props_popup(self, event)
def menu_func(self, context):
self.layout.operator(CurveOutline.bl_idname)
def register():
for cls in classes:
bpy.utils.register_class(operators)
def unregister():
for cls in classes:
bpy.utils.unregister_class(operators)
if __name__ == "__main__":
register()
operators = [CurveOutline]

View File

@ -41,12 +41,8 @@ from bpy_extras import object_utils, view3d_utils
from mathutils import *
from math import *
from . import Properties
from . import Curves
from . import CurveIntersections
from . import Util
from . import Surfaces
from . import Math
from . import mathematics
from . import util
def get_bezier_points(spline, matrix_world):
point_list = []
@ -55,7 +51,7 @@ def get_bezier_points(spline, matrix_world):
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 = Math.subdivide_cubic_bezier(spline.bezier_points[i].co,
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,
@ -64,7 +60,7 @@ def get_bezier_points(spline, matrix_world):
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 = Math.subdivide_cubic_bezier(spline.bezier_points[len_bezier_points - 1].co,
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,
@ -160,7 +156,7 @@ def click(self, context, event):
if i < len_bezier_points - 1:
for t in range(0, 100, 2):
h = Math.subdivide_cubic_bezier(spline.bezier_points[i].co,
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,
@ -172,7 +168,7 @@ def click(self, context, event):
if spline.use_cyclic_u and len_bezier_points > 2:
for t in range(0, 100, 2):
h = Math.subdivide_cubic_bezier(spline.bezier_points[len_bezier_points - 1].co,
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,
@ -312,14 +308,18 @@ class PathFinder(bpy.types.Operator):
@classmethod
def poll(cls, context):
return (context.object is not None and
context.object.type == 'CURVE')
return util.Selected1OrMoreCurves()
def register():
bpy.utils.register_class(PathFinder)
for cls in classes:
bpy.utils.register_class(operators)
def unregister():
bpy.utils.unregister_class(PathFinder)
for cls in classes:
bpy.utils.unregister_class(operators)
if __name__ == "__main__":
register()
operators = [PathFinder]

View File

@ -0,0 +1,135 @@
import bpy, mathutils
from . import util
bl_info = {
'name': 'Curve Remove Doubles',
'author': 'Michael Soluyanov',
'version': (1, 1),
'blender': (2, 80, 0),
'location': 'View3D > Context menu (W/RMB) > Remove Doubles',
'description': 'Adds comand "Remove Doubles" for curves',
'category': 'Add Curve'
}
def main(context, distance = 0.01):
selected_Curves = util.GetSelectedCurves()
if bpy.ops.object.mode_set.poll():
bpy.ops.object.mode_set(mode='EDIT')
for curve in selected_Curves:
bezier_dellist = []
dellist = []
for spline in curve.data.splines:
if spline.type == 'BEZIER':
if len(spline.bezier_points) > 1:
for i in range(0, len(spline.bezier_points)):
if i == 0:
ii = len(spline.bezier_points) - 1
else:
ii = i - 1
dot = spline.bezier_points[i];
dot1 = spline.bezier_points[ii];
while dot1 in bezier_dellist and i != ii:
ii -= 1
if ii < 0:
ii = len(spline.bezier_points)-1
dot1 = spline.bezier_points[ii]
if dot.select_control_point and dot1.select_control_point and (i!=0 or spline.use_cyclic_u):
if (dot.co-dot1.co).length < distance:
# remove points and recreate hangles
dot1.handle_right_type = "FREE"
dot1.handle_right = dot.handle_right
dot1.co = (dot.co + dot1.co) / 2
bezier_dellist.append(dot)
else:
# Handles that are on main point position converts to vector,
# if next handle are also vector
if dot.handle_left_type == 'VECTOR' and (dot1.handle_right - dot1.co).length < distance:
dot1.handle_right_type = "VECTOR"
if dot1.handle_right_type == 'VECTOR' and (dot.handle_left - dot.co).length < distance:
dot.handle_left_type = "VECTOR"
else:
if len(spline.points) > 1:
for i in range(0, len(spline.points)):
if i == 0:
ii = len(spline.points) - 1
else:
ii = i - 1
dot = spline.points[i];
dot1 = spline.points[ii];
while dot1 in dellist and i != ii:
ii -= 1
if ii < 0:
ii = len(spline.points)-1
dot1 = spline.points[ii]
if dot.select and dot1.select and (i!=0 or spline.use_cyclic_u):
if (dot.co-dot1.co).length < distance:
dot1.co = (dot.co + dot1.co) / 2
dellist.append(dot)
bpy.ops.curve.select_all(action = 'DESELECT')
for dot in bezier_dellist:
dot.select_control_point = True
for dot in dellist:
dot.select = True
bezier_count = len(bezier_dellist)
count = len(dellist)
bpy.ops.curve.delete(type = 'VERT')
bpy.ops.curve.select_all(action = 'DESELECT')
return bezier_count + count
class CurveRemvDbs(bpy.types.Operator):
"""Merge consecutive points that are near to each other"""
bl_idname = 'curvetools.remove_doubles'
bl_label = 'Remove Doubles'
bl_options = {'REGISTER', 'UNDO'}
distance: bpy.props.FloatProperty(name = 'Distance', default = 0.01)
@classmethod
def poll(cls, context):
return util.Selected1Curve()
def execute(self, context):
removed=main(context, self.distance)
self.report({'INFO'}, "Removed %d bezier points" % removed)
return {'FINISHED'}
def menu_func(self, context):
self.layout.operator(CurveRemvDbs.bl_idname, text='Remove Doubles')
def register():
bpy.utils.register_class(CurveRemvDbs)
bpy.types.VIEW3D_MT_edit_curve_context_menu.append(menu_func)
def unregister():
bpy.utils.unregister_class(CurveRemvDbs)
bpy.types.VIEW3D_MT_edit_curve_context_menu.remove(menu_func)
if __name__ == "__main__":
register()
operators = [CurveRemvDbs]

View File

@ -18,7 +18,7 @@
#
# LOAD MODULE #
# LOAD MODUL #
import bpy
from bpy import *
from bpy.props import *
@ -79,7 +79,7 @@ def draw(self, context, splines, curve_vertcolor, matrix_world):
class ShowCurveResolution(bpy.types.Operator):
bl_idname = "curve.show_resolution"
bl_idname = "curvetools.show_resolution"
bl_label = "Show Curve Resolution"
bl_description = "Show curve Resolution / [ESC] - remove"
@ -129,3 +129,18 @@ class ShowCurveResolution(bpy.types.Operator):
def poll(cls, context):
return (context.object is not None and
context.object.type == 'CURVE')
def register():
for cls in classes:
bpy.utils.register_class(operators)
def unregister():
for cls in classes:
bpy.utils.unregister_class(operators)
if __name__ == "__main__":
register()
operators = [
ShowCurveResolution,
]

View File

@ -89,7 +89,7 @@ def draw(self, context, splines, sequence_color, font_thickness, font_size, matr
i += font_size + font_size * 0.5
class ShowSplinesSequence(bpy.types.Operator):
bl_idname = "curve.show_splines_sequence"
bl_idname = "curvetools.show_splines_sequence"
bl_label = "Show Splines Sequence"
bl_description = "Show Splines Sequence / [ESC] - remove"
@ -229,7 +229,7 @@ def rearrange(dataCurve, select_spline, command):
rearrangesplines(dataCurve, select_spline, select_spline - 1)
class RearrangeSpline(bpy.types.Operator):
bl_idname = "curve.rearrange_spline"
bl_idname = "curvetools.rearrange_spline"
bl_label = "Rearrange Spline"
bl_description = "Rearrange Spline"
@ -273,3 +273,16 @@ class RearrangeSpline(bpy.types.Operator):
def poll(cls, context):
return (context.object is not None and
context.object.type == 'CURVE')
def register():
for cls in classes:
bpy.utils.register_class(operators)
def unregister():
for cls in classes:
bpy.utils.unregister_class(operators)
if __name__ == "__main__":
register()
operators = [ShowSplinesSequence, RearrangeSpline]

View File

@ -21,7 +21,7 @@ from mathutils import Vector, Matrix
from . import internal
class OffsetCurve(bpy.types.Operator):
bl_idname = 'curve.add_toolpath_offset_curve'
bl_idname = 'curvetools.add_toolpath_offset_curve'
bl_description = bl_label = 'Offset Curve'
bl_options = {'REGISTER', 'UNDO'}
@ -64,7 +64,7 @@ class OffsetCurve(bpy.types.Operator):
return {'FINISHED'}
class SliceMesh(bpy.types.Operator):
bl_idname = 'curve.add_toolpath_slice_mesh'
bl_idname = 'curvetools.add_toolpath_slice_mesh'
bl_description = bl_label = 'Slice Mesh'
bl_options = {'REGISTER', 'UNDO'}
@ -116,7 +116,7 @@ class SliceMesh(bpy.types.Operator):
return {'FINISHED'}
class DiscretizeCurve(bpy.types.Operator):
bl_idname = 'curve.add_toolpath_discretize_curve'
bl_idname = 'curvetools.add_toolpath_discretize_curve'
bl_description = bl_label = 'Discretize Curve'
bl_options = {'REGISTER', 'UNDO'}
@ -151,7 +151,7 @@ class DiscretizeCurve(bpy.types.Operator):
return {'FINISHED'}
class Truncate(bpy.types.Operator):
bl_idname = 'curve.add_toolpath_truncate'
bl_idname = 'curvetools.add_toolpath_truncate'
bl_description = bl_label = 'Truncate'
bl_options = {'REGISTER', 'UNDO'}
@ -197,7 +197,7 @@ class Truncate(bpy.types.Operator):
return {'FINISHED'}
class RectMacro(bpy.types.Operator):
bl_idname = 'curve.add_toolpath_rect_macro'
bl_idname = 'curvetools.add_toolpath_rect_macro'
bl_description = bl_label = 'Rect Macro'
bl_options = {'REGISTER', 'UNDO'}
@ -233,7 +233,7 @@ class RectMacro(bpy.types.Operator):
return {'FINISHED'}
class DrillMacro(bpy.types.Operator):
bl_idname = 'curve.add_toolpath_drill_macro'
bl_idname = 'curvetools.add_toolpath_drill_macro'
bl_description = bl_label = 'Drill Macro'
bl_options = {'REGISTER', 'UNDO'}
@ -284,4 +284,15 @@ class DrillMacro(bpy.types.Operator):
internal.addPolygonSpline(bpy.context.object, False, vertices, weights)
return {'FINISHED'}
def register():
for cls in classes:
bpy.utils.register_class(operators)
def unregister():
for cls in classes:
bpy.utils.unregister_class(operators)
if __name__ == "__main__":
register()
operators = [OffsetCurve, SliceMesh, DiscretizeCurve, Truncate, RectMacro, DrillMacro]