From 99fd99981a4ef80cf53a8d8a1fdd605a2d275b05 Mon Sep 17 00:00:00 2001 From: "Spivak Vladimir (cwolf3d)" Date: Fri, 11 Oct 2019 03:49:52 +0300 Subject: Addon: Curve Tools: Switch uppercase file name to lowercase --- curve_tools/Curves.py | 594 ------------------------- curve_tools/Operators.py | 1056 -------------------------------------------- curve_tools/Properties.py | 104 ----- curve_tools/Surfaces.py | 462 ------------------- curve_tools/Util.py | 178 -------- curve_tools/curvess.py | 594 +++++++++++++++++++++++++ curve_tools/operatorss.py | 1056 ++++++++++++++++++++++++++++++++++++++++++++ curve_tools/propertiess.py | 104 +++++ curve_tools/surfacess.py | 462 +++++++++++++++++++ curve_tools/utils.py | 178 ++++++++ 10 files changed, 2394 insertions(+), 2394 deletions(-) delete mode 100644 curve_tools/Curves.py delete mode 100644 curve_tools/Operators.py delete mode 100644 curve_tools/Properties.py delete mode 100644 curve_tools/Surfaces.py delete mode 100644 curve_tools/Util.py create mode 100644 curve_tools/curvess.py create mode 100644 curve_tools/operatorss.py create mode 100644 curve_tools/propertiess.py create mode 100644 curve_tools/surfacess.py create mode 100644 curve_tools/utils.py diff --git a/curve_tools/Curves.py b/curve_tools/Curves.py deleted file mode 100644 index da0b1398..00000000 --- a/curve_tools/Curves.py +++ /dev/null @@ -1,594 +0,0 @@ -from . import mathematics - -import bpy - - -class BezierPoint: - @staticmethod - def FromBlenderBezierPoint(blenderBezierPoint): - return BezierPoint(blenderBezierPoint.handle_left, blenderBezierPoint.co, blenderBezierPoint.handle_right) - - - def __init__(self, handle_left, co, handle_right): - self.handle_left = handle_left - self.co = co - self.handle_right = handle_right - - - def Copy(self): - return BezierPoint(self.handle_left.copy(), self.co.copy(), self.handle_right.copy()) - - def Reversed(self): - return BezierPoint(self.handle_right, self.co, self.handle_left) - - def Reverse(self): - tmp = self.handle_left - self.handle_left = self.handle_right - self.handle_right = tmp - - -class BezierSegment: - @staticmethod - def FromBlenderBezierPoints(blenderBezierPoint1, blenderBezierPoint2): - bp1 = BezierPoint.FromBlenderBezierPoint(blenderBezierPoint1) - bp2 = BezierPoint.FromBlenderBezierPoint(blenderBezierPoint2) - - return BezierSegment(bp1, bp2) - - - def Copy(self): - return BezierSegment(self.bezierPoint1.Copy(), self.bezierPoint2.Copy()) - - def Reversed(self): - return BezierSegment(self.bezierPoint2.Reversed(), self.bezierPoint1.Reversed()) - - def Reverse(self): - # make a copy, otherwise neighboring segment may be affected - tmp = self.bezierPoint1.Copy() - self.bezierPoint1 = self.bezierPoint2.Copy() - self.bezierPoint2 = tmp - self.bezierPoint1.Reverse() - self.bezierPoint2.Reverse() - - - def __init__(self, bezierPoint1, bezierPoint2): - # bpy.types.BezierSplinePoint - # ## NOTE/TIP: copy() helps with repeated (intersection) action -- ?? - self.bezierPoint1 = bezierPoint1.Copy() - self.bezierPoint2 = bezierPoint2.Copy() - - self.ctrlPnt0 = self.bezierPoint1.co - self.ctrlPnt1 = self.bezierPoint1.handle_right - self.ctrlPnt2 = self.bezierPoint2.handle_left - self.ctrlPnt3 = self.bezierPoint2.co - - self.coeff0 = self.ctrlPnt0 - self.coeff1 = self.ctrlPnt0 * (-3.0) + self.ctrlPnt1 * (+3.0) - self.coeff2 = self.ctrlPnt0 * (+3.0) + self.ctrlPnt1 * (-6.0) + self.ctrlPnt2 * (+3.0) - self.coeff3 = self.ctrlPnt0 * (-1.0) + self.ctrlPnt1 * (+3.0) + self.ctrlPnt2 * (-3.0) + self.ctrlPnt3 - - - def CalcPoint(self, parameter = 0.5): - parameter2 = parameter * parameter - parameter3 = parameter * parameter2 - - rvPoint = self.coeff0 + self.coeff1 * parameter + self.coeff2 * parameter2 + self.coeff3 * parameter3 - - return rvPoint - - - def CalcDerivative(self, parameter = 0.5): - parameter2 = parameter * parameter - - rvPoint = self.coeff1 + self.coeff2 * parameter * 2.0 + self.coeff3 * parameter2 * 3.0 - - return rvPoint - - - def CalcLength(self, nrSamples = 2): - nrSamplesFloat = float(nrSamples) - rvLength = 0.0 - for iSample in range(nrSamples): - par1 = float(iSample) / nrSamplesFloat - par2 = float(iSample + 1) / nrSamplesFloat - - point1 = self.CalcPoint(parameter = par1) - point2 = self.CalcPoint(parameter = par2) - diff12 = point1 - point2 - - rvLength += diff12.magnitude - - return rvLength - - - #http://en.wikipedia.org/wiki/De_Casteljau's_algorithm - def CalcSplitPoint(self, parameter = 0.5): - par1min = 1.0 - parameter - - bez00 = self.ctrlPnt0 - bez01 = self.ctrlPnt1 - bez02 = self.ctrlPnt2 - bez03 = self.ctrlPnt3 - - bez10 = bez00 * par1min + bez01 * parameter - bez11 = bez01 * par1min + bez02 * parameter - bez12 = bez02 * par1min + bez03 * parameter - - bez20 = bez10 * par1min + bez11 * parameter - bez21 = bez11 * par1min + bez12 * parameter - - bez30 = bez20 * par1min + bez21 * parameter - - bezPoint1 = BezierPoint(self.bezierPoint1.handle_left, bez00, bez10) - bezPointNew = BezierPoint(bez20, bez30, bez21) - bezPoint2 = BezierPoint(bez12, bez03, self.bezierPoint2.handle_right) - - return [bezPoint1, bezPointNew, bezPoint2] - - -class BezierSpline: - @staticmethod - def FromSegments(listSegments): - rvSpline = BezierSpline(None) - - rvSpline.segments = listSegments - - return rvSpline - - - def __init__(self, blenderBezierSpline): - if not blenderBezierSpline is None: - if blenderBezierSpline.type != 'BEZIER': - print("## ERROR:", "blenderBezierSpline.type != 'BEZIER'") - raise Exception("blenderBezierSpline.type != 'BEZIER'") - if len(blenderBezierSpline.bezier_points) < 1: - if not blenderBezierSpline.use_cyclic_u: - print("## ERROR:", "len(blenderBezierSpline.bezier_points) < 1") - raise Exception("len(blenderBezierSpline.bezier_points) < 1") - - self.bezierSpline = blenderBezierSpline - - self.resolution = 12 - self.isCyclic = False - if not self.bezierSpline is None: - self.resolution = self.bezierSpline.resolution_u - self.isCyclic = self.bezierSpline.use_cyclic_u - - self.segments = self.SetupSegments() - - - def __getattr__(self, attrName): - if attrName == "nrSegments": - return len(self.segments) - - if attrName == "bezierPoints": - rvList = [] - - for seg in self.segments: rvList.append(seg.bezierPoint1) - if not self.isCyclic: rvList.append(self.segments[-1].bezierPoint2) - - return rvList - - if attrName == "resolutionPerSegment": - try: rvResPS = int(self.resolution / self.nrSegments) - except: rvResPS = 2 - if rvResPS < 2: rvResPS = 2 - - return rvResPS - - if attrName == "length": - return self.CalcLength() - - return None - - - def SetupSegments(self): - rvSegments = [] - if self.bezierSpline is None: return rvSegments - - nrBezierPoints = len(self.bezierSpline.bezier_points) - for iBezierPoint in range(nrBezierPoints - 1): - bezierPoint1 = self.bezierSpline.bezier_points[iBezierPoint] - bezierPoint2 = self.bezierSpline.bezier_points[iBezierPoint + 1] - rvSegments.append(BezierSegment.FromBlenderBezierPoints(bezierPoint1, bezierPoint2)) - if self.isCyclic: - bezierPoint1 = self.bezierSpline.bezier_points[-1] - bezierPoint2 = self.bezierSpline.bezier_points[0] - rvSegments.append(BezierSegment.FromBlenderBezierPoints(bezierPoint1, bezierPoint2)) - - return rvSegments - - - def UpdateSegments(self, newSegments): - prevNrSegments = len(self.segments) - diffNrSegments = len(newSegments) - prevNrSegments - if diffNrSegments > 0: - newBezierPoints = [] - for segment in newSegments: newBezierPoints.append(segment.bezierPoint1) - if not self.isCyclic: newBezierPoints.append(newSegments[-1].bezierPoint2) - - self.bezierSpline.bezier_points.add(diffNrSegments) - - for i, bezPoint in enumerate(newBezierPoints): - blBezPoint = self.bezierSpline.bezier_points[i] - - blBezPoint.tilt = 0 - blBezPoint.radius = 1.0 - - blBezPoint.handle_left_type = 'FREE' - blBezPoint.handle_left = bezPoint.handle_left - blBezPoint.co = bezPoint.co - blBezPoint.handle_right_type = 'FREE' - blBezPoint.handle_right = bezPoint.handle_right - - self.segments = newSegments - else: - print("### WARNING: UpdateSegments(): not diffNrSegments > 0") - - - def Reversed(self): - revSegments = [] - - for iSeg in reversed(range(self.nrSegments)): revSegments.append(self.segments[iSeg].Reversed()) - - rvSpline = BezierSpline.FromSegments(revSegments) - rvSpline.resolution = self.resolution - rvSpline.isCyclic = self.isCyclic - - return rvSpline - - - def Reverse(self): - revSegments = [] - - for iSeg in reversed(range(self.nrSegments)): - self.segments[iSeg].Reverse() - revSegments.append(self.segments[iSeg]) - - self.segments = revSegments - - - def CalcDivideResolution(self, segment, parameter): - if not segment in self.segments: - print("### WARNING: InsertPoint(): not segment in self.segments") - return None - - iSeg = self.segments.index(segment) - dPar = 1.0 / self.nrSegments - splinePar = dPar * (parameter + float(iSeg)) - - res1 = int(splinePar * self.resolution) - if res1 < 2: - print("### WARNING: CalcDivideResolution(): res1 < 2 -- res1: %d" % res1, "-- setting it to 2") - res1 = 2 - - res2 = int((1.0 - splinePar) * self.resolution) - if res2 < 2: - print("### WARNING: CalcDivideResolution(): res2 < 2 -- res2: %d" % res2, "-- setting it to 2") - res2 = 2 - - return [res1, res2] - # return [self.resolution, self.resolution] - - - def CalcPoint(self, parameter): - nrSegs = self.nrSegments - - segmentIndex = int(nrSegs * parameter) - if segmentIndex < 0: segmentIndex = 0 - if segmentIndex > (nrSegs - 1): segmentIndex = nrSegs - 1 - - segmentParameter = nrSegs * parameter - segmentIndex - if segmentParameter < 0.0: segmentParameter = 0.0 - if segmentParameter > 1.0: segmentParameter = 1.0 - - return self.segments[segmentIndex].CalcPoint(parameter = segmentParameter) - - - def CalcDerivative(self, parameter): - nrSegs = self.nrSegments - - segmentIndex = int(nrSegs * parameter) - if segmentIndex < 0: segmentIndex = 0 - if segmentIndex > (nrSegs - 1): segmentIndex = nrSegs - 1 - - segmentParameter = nrSegs * parameter - segmentIndex - if segmentParameter < 0.0: segmentParameter = 0.0 - if segmentParameter > 1.0: segmentParameter = 1.0 - - return self.segments[segmentIndex].CalcDerivative(parameter = segmentParameter) - - - def InsertPoint(self, segment, parameter): - if not segment in self.segments: - print("### WARNING: InsertPoint(): not segment in self.segments") - return - iSeg = self.segments.index(segment) - nrSegments = len(self.segments) - - splitPoints = segment.CalcSplitPoint(parameter = parameter) - bezPoint1 = splitPoints[0] - bezPointNew = splitPoints[1] - bezPoint2 = splitPoints[2] - - segment.bezierPoint1.handle_right = bezPoint1.handle_right - segment.bezierPoint2 = bezPointNew - - if iSeg < (nrSegments - 1): - nextSeg = self.segments[iSeg + 1] - nextSeg.bezierPoint1.handle_left = bezPoint2.handle_left - else: - if self.isCyclic: - nextSeg = self.segments[0] - nextSeg.bezierPoint1.handle_left = bezPoint2.handle_left - - - newSeg = BezierSegment(bezPointNew, bezPoint2) - self.segments.insert(iSeg + 1, newSeg) - - - def Split(self, segment, parameter): - if not segment in self.segments: - print("### WARNING: InsertPoint(): not segment in self.segments") - return None - iSeg = self.segments.index(segment) - nrSegments = len(self.segments) - - splitPoints = segment.CalcSplitPoint(parameter = parameter) - bezPoint1 = splitPoints[0] - bezPointNew = splitPoints[1] - bezPoint2 = splitPoints[2] - - - newSpline1Segments = [] - for iSeg1 in range(iSeg): newSpline1Segments.append(self.segments[iSeg1]) - if len(newSpline1Segments) > 0: newSpline1Segments[-1].bezierPoint2.handle_right = bezPoint1.handle_right - newSpline1Segments.append(BezierSegment(bezPoint1, bezPointNew)) - - newSpline2Segments = [] - newSpline2Segments.append(BezierSegment(bezPointNew, bezPoint2)) - for iSeg2 in range(iSeg + 1, nrSegments): newSpline2Segments.append(self.segments[iSeg2]) - if len(newSpline2Segments) > 1: newSpline2Segments[1].bezierPoint1.handle_left = newSpline2Segments[0].bezierPoint2.handle_left - - - newSpline1 = BezierSpline.FromSegments(newSpline1Segments) - newSpline2 = BezierSpline.FromSegments(newSpline2Segments) - - return [newSpline1, newSpline2] - - - def Join(self, spline2, mode = 'At midpoint'): - if mode == 'At midpoint': - self.JoinAtMidpoint(spline2) - return - - if mode == 'Insert segment': - self.JoinInsertSegment(spline2) - return - - print("### ERROR: Join(): unknown mode:", mode) - - - def JoinAtMidpoint(self, spline2): - bezPoint1 = self.segments[-1].bezierPoint2 - bezPoint2 = spline2.segments[0].bezierPoint1 - - mpHandleLeft = bezPoint1.handle_left.copy() - mpCo = (bezPoint1.co + bezPoint2.co) * 0.5 - mpHandleRight = bezPoint2.handle_right.copy() - mpBezPoint = BezierPoint(mpHandleLeft, mpCo, mpHandleRight) - - self.segments[-1].bezierPoint2 = mpBezPoint - spline2.segments[0].bezierPoint1 = mpBezPoint - for seg2 in spline2.segments: self.segments.append(seg2) - - self.resolution += spline2.resolution - self.isCyclic = False # is this ok? - - - def JoinInsertSegment(self, spline2): - 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 negligable - - self.isCyclic = False # is this ok? - - - def RefreshInScene(self): - bezierPoints = self.bezierPoints - - currNrBezierPoints = len(self.bezierSpline.bezier_points) - diffNrBezierPoints = len(bezierPoints) - currNrBezierPoints - if diffNrBezierPoints > 0: self.bezierSpline.bezier_points.add(diffNrBezierPoints) - - for i, bezPoint in enumerate(bezierPoints): - blBezPoint = self.bezierSpline.bezier_points[i] - - blBezPoint.tilt = 0 - blBezPoint.radius = 1.0 - - blBezPoint.handle_left_type = 'FREE' - blBezPoint.handle_left = bezPoint.handle_left - blBezPoint.co = bezPoint.co - blBezPoint.handle_right_type = 'FREE' - blBezPoint.handle_right = bezPoint.handle_right - - self.bezierSpline.use_cyclic_u = self.isCyclic - self.bezierSpline.resolution_u = self.resolution - - - def CalcLength(self): - try: nrSamplesPerSegment = int(self.resolution / self.nrSegments) - except: nrSamplesPerSegment = 2 - if nrSamplesPerSegment < 2: nrSamplesPerSegment = 2 - - rvLength = 0.0 - for segment in self.segments: - rvLength += segment.CalcLength(nrSamples = nrSamplesPerSegment) - - return rvLength - - - def GetLengthIsSmallerThan(self, threshold): - try: nrSamplesPerSegment = int(self.resolution / self.nrSegments) - except: nrSamplesPerSegment = 2 - if nrSamplesPerSegment < 2: nrSamplesPerSegment = 2 - - length = 0.0 - for segment in self.segments: - length += segment.CalcLength(nrSamples = nrSamplesPerSegment) - if not length < threshold: return False - - return True - - -class Curve: - def __init__(self, blenderCurve): - self.curve = blenderCurve - self.curveData = blenderCurve.data - - self.splines = self.SetupSplines() - - - def __getattr__(self, attrName): - if attrName == "nrSplines": - return len(self.splines) - - if attrName == "length": - return self.CalcLength() - - if attrName == "worldMatrix": - return self.curve.matrix_world - - if attrName == "location": - return self.curve.location - - return None - - - def SetupSplines(self): - rvSplines = [] - for spline in self.curveData.splines: - if spline.type != 'BEZIER': - print("## WARNING: only bezier splines are supported, atm; other types are ignored") - continue - - try: newSpline = BezierSpline(spline) - except: - print("## EXCEPTION: newSpline = BezierSpline(spline)") - continue - - rvSplines.append(newSpline) - - return rvSplines - - - def RebuildInScene(self): - self.curveData.splines.clear() - - for spline in self.splines: - blSpline = self.curveData.splines.new('BEZIER') - blSpline.use_cyclic_u = spline.isCyclic - blSpline.resolution_u = spline.resolution - - bezierPoints = [] - for segment in spline.segments: bezierPoints.append(segment.bezierPoint1) - if not spline.isCyclic: bezierPoints.append(spline.segments[-1].bezierPoint2) - #else: print("????", "spline.isCyclic") - - nrBezierPoints = len(bezierPoints) - blSpline.bezier_points.add(nrBezierPoints - 1) - - for i, blBezPoint in enumerate(blSpline.bezier_points): - bezPoint = bezierPoints[i] - - blBezPoint.tilt = 0 - blBezPoint.radius = 1.0 - - blBezPoint.handle_left_type = 'FREE' - blBezPoint.handle_left = bezPoint.handle_left - blBezPoint.co = bezPoint.co - blBezPoint.handle_right_type = 'FREE' - blBezPoint.handle_right = bezPoint.handle_right - - - def CalcLength(self): - rvLength = 0.0 - for spline in self.splines: - rvLength += spline.length - - return rvLength - - - def RemoveShortSplines(self, threshold): - splinesToRemove = [] - - for spline in self.splines: - if spline.GetLengthIsSmallerThan(threshold): splinesToRemove.append(spline) - - for spline in splinesToRemove: self.splines.remove(spline) - - return len(splinesToRemove) - - - def JoinNeighbouringSplines(self, startEnd, threshold, mode): - nrJoins = 0 - - while True: - firstPair = self.JoinGetFirstPair(startEnd, threshold) - if firstPair is None: break - - firstPair[0].Join(firstPair[1], mode) - self.splines.remove(firstPair[1]) - - nrJoins += 1 - - return nrJoins - - - def JoinGetFirstPair(self, startEnd, threshold): - nrSplines = len(self.splines) - - if startEnd: - for iCurrentSpline in range(nrSplines): - currentSpline = self.splines[iCurrentSpline] - - for iNextSpline in range(iCurrentSpline + 1, nrSplines): - nextSpline = self.splines[iNextSpline] - - currEndPoint = currentSpline.segments[-1].bezierPoint2.co - nextStartPoint = nextSpline.segments[0].bezierPoint1.co - if mathematics.IsSamePoint(currEndPoint, nextStartPoint, threshold): return [currentSpline, nextSpline] - - nextEndPoint = nextSpline.segments[-1].bezierPoint2.co - currStartPoint = currentSpline.segments[0].bezierPoint1.co - if mathematics.IsSamePoint(nextEndPoint, currStartPoint, threshold): return [nextSpline, currentSpline] - - return None - else: - for iCurrentSpline in range(nrSplines): - currentSpline = self.splines[iCurrentSpline] - - for iNextSpline in range(iCurrentSpline + 1, nrSplines): - nextSpline = self.splines[iNextSpline] - - currEndPoint = currentSpline.segments[-1].bezierPoint2.co - nextStartPoint = nextSpline.segments[0].bezierPoint1.co - if mathematics.IsSamePoint(currEndPoint, nextStartPoint, threshold): return [currentSpline, nextSpline] - - nextEndPoint = nextSpline.segments[-1].bezierPoint2.co - currStartPoint = currentSpline.segments[0].bezierPoint1.co - if mathematics.IsSamePoint(nextEndPoint, currStartPoint, threshold): return [nextSpline, currentSpline] - - if mathematics.IsSamePoint(currEndPoint, nextEndPoint, threshold): - nextSpline.Reverse() - #print("## ", "nextSpline.Reverse()") - return [currentSpline, nextSpline] - - if mathematics.IsSamePoint(currStartPoint, nextStartPoint, threshold): - currentSpline.Reverse() - #print("## ", "currentSpline.Reverse()") - return [currentSpline, nextSpline] - - return None diff --git a/curve_tools/Operators.py b/curve_tools/Operators.py deleted file mode 100644 index e4fe24dd..00000000 --- a/curve_tools/Operators.py +++ /dev/null @@ -1,1056 +0,0 @@ -import time -import threading - -import bpy -from bpy.props import * -from bpy_extras import object_utils, view3d_utils -from mathutils import * -from math import * - -from . import properties -from . import curves -from . import intersections -from . import util -from . import surfaces -from . import mathematics - -# 1 CURVE SELECTED -# ################ -class OperatorCurveInfo(bpy.types.Operator): - bl_idname = "curvetools.operatorcurveinfo" - bl_label = "Info" - bl_description = "Displays general info about the active/selected curve" - - - @classmethod - def poll(cls, context): - return util.Selected1Curve() - - - def execute(self, context): - curve = curves.Curve(context.active_object) - - nrSplines = len(curve.splines) - nrSegments = 0 - nrEmptySplines = 0 - for spline in curve.splines: - nrSegments += spline.nrSegments - if spline.nrSegments < 1: nrEmptySplines += 1 - - - self.report({'INFO'}, "nrSplines: %d; nrSegments: %d; nrEmptySplines: %d" % (nrSplines, nrSegments, nrEmptySplines)) - - return {'FINISHED'} - - - -class OperatorCurveLength(bpy.types.Operator): - bl_idname = "curvetools.operatorcurvelength" - bl_label = "Length" - bl_description = "Calculates the length of the active/selected curve" - - - @classmethod - def poll(cls, context): - return util.Selected1Curve() - - - def execute(self, context): - curve = curves.Curve(context.active_object) - - context.scene.curvetools.CurveLength = curve.length - - return {'FINISHED'} - - - -class OperatorSplinesInfo(bpy.types.Operator): - bl_idname = "curvetools.operatorsplinesinfo" - bl_label = "Info" - bl_description = "Displays general info about the splines of the active/selected curve" - - - @classmethod - def poll(cls, context): - return util.Selected1Curve() - - - def execute(self, context): - curve = curves.Curve(context.active_object) - nrSplines = len(curve.splines) - - print("") - print("OperatorSplinesInfo:", "nrSplines:", nrSplines) - - nrEmptySplines = 0 - for iSpline, spline in enumerate(curve.splines): - print("--", "spline %d of %d: nrSegments: %d" % (iSpline + 1, nrSplines, spline.nrSegments)) - - if spline.nrSegments < 1: - nrEmptySplines += 1 - print("--", "--", "## WARNING: spline has no segments and will therefor be ignored in any further calculations") - - - self.report({'INFO'}, "nrSplines: %d; nrEmptySplines: %d" % (nrSplines, nrEmptySplines) + " -- more info: see console") - - return {'FINISHED'} - - - -class OperatorSegmentsInfo(bpy.types.Operator): - bl_idname = "curvetools.operatorsegmentsinfo" - bl_label = "Info" - bl_description = "Displays general info about the segments of the active/selected curve" - - - @classmethod - def poll(cls, context): - return util.Selected1Curve() - - - def execute(self, context): - curve = curves.Curve(context.active_object) - nrSplines = len(curve.splines) - nrSegments = 0 - - print("") - print("OperatorSegmentsInfo:", "nrSplines:", nrSplines) - - nrEmptySplines = 0 - for iSpline, spline in enumerate(curve.splines): - nrSegmentsSpline = spline.nrSegments - print("--", "spline %d of %d: nrSegments: %d" % (iSpline + 1, nrSplines, nrSegmentsSpline)) - - if nrSegmentsSpline < 1: - nrEmptySplines += 1 - print("--", "--", "## WARNING: spline has no segments and will therefor be ignored in any further calculations") - continue - - for iSegment, segment in enumerate(spline.segments): - print("--", "--", "segment %d of %d coefficients:" % (iSegment + 1, nrSegmentsSpline)) - print("--", "--", "--", "C0: %.6f, %.6f, %.6f" % (segment.coeff0.x, segment.coeff0.y, segment.coeff0.z)) - - nrSegments += nrSegmentsSpline - - self.report({'INFO'}, "nrSplines: %d; nrSegments: %d; nrEmptySplines: %d" % (nrSplines, nrSegments, nrEmptySplines)) - - return {'FINISHED'} - - - -class OperatorOriginToSpline0Start(bpy.types.Operator): - bl_idname = "curvetools.operatororigintospline0start" - bl_label = "OriginToSpline0Start" - bl_description = "Sets the origin of the active/selected curve to the starting point of the (first) spline. Nice for curve modifiers." - - - @classmethod - def poll(cls, context): - return util.Selected1Curve() - - - def execute(self, context): - - - blCurve = context.active_object - blSpline = blCurve.data.splines[0] - newOrigin = blCurve.matrix_world @ blSpline.bezier_points[0].co - - origOrigin = bpy.context.scene.cursor.location.copy() - self.report({'INFO'}, "origOrigin: %.6f, %.6f, %.6f" % (origOrigin.x, origOrigin.y, origOrigin.z)) - self.report({'INFO'}, "newOrigin: %.6f, %.6f, %.6f" % (newOrigin.x, newOrigin.y, newOrigin.z)) - - current_mode = bpy.context.object.mode - - bpy.ops.object.mode_set(mode = 'OBJECT') - bpy.context.scene.cursor.location = newOrigin - bpy.ops.object.origin_set(type='ORIGIN_CURSOR') - bpy.context.scene.cursor.location = origOrigin - - bpy.ops.object.mode_set (mode = current_mode) - - return {'FINISHED'} - - - -# 2 CURVES SELECTED -# ################# -class OperatorIntersectCurves(bpy.types.Operator): - bl_idname = "curvetools.operatorintersectcurves" - bl_label = "Intersect" - bl_description = "Intersects selected curves" - - - @classmethod - def poll(cls, context): - return util.Selected2OrMoreCurves() - - - def execute(self, context): - print("### TODO: OperatorIntersectcurves.execute()") - - algo = context.scene.curvetools.IntersectCurvesAlgorithm - print("-- algo:", algo) - - - mode = context.scene.curvetools.IntersectCurvesMode - print("-- mode:", mode) - # if mode == 'Split': - # self.report({'WARNING'}, "'Split' mode is not implemented yet -- <>") - # return {'CANCELLED'} - - affect = context.scene.curvetools.IntersectCurvesAffect - print("-- affect:", affect) - - selected_objects = context.selected_objects - lenodjs = len(selected_objects) - print('lenodjs:', lenodjs) - for i in range(0, lenodjs): - for j in range(0, lenodjs): - if j != i: - bpy.ops.object.select_all(action='DESELECT') - selected_objects[i].select_set(True) - selected_objects[j].select_set(True) - - if selected_objects[i].type == 'CURVE' and selected_objects[j].type == 'CURVE': - curveIntersector = intersections.CurvesIntersector.FromSelection() - rvIntersectionNrs = curveIntersector.CalcAndApplyIntersections() - - self.report({'INFO'}, "Active curve points: %d; other curve points: %d" % (rvIntersectionNrs[0], rvIntersectionNrs[1])) - - for obj in selected_objects: - obj.select_set(True) - - return {'FINISHED'} - -# ------------------------------------------------------------ -# OperatorLoftCurves - -class OperatorLoftCurves(bpy.types.Operator): - bl_idname = "curvetools.operatorloftcurves" - bl_label = "Loft" - bl_description = "Lofts selected curves" - - - @classmethod - def poll(cls, context): - return util.Selected2Curves() - - - def execute(self, context): - #print("### TODO: OperatorLoftcurves.execute()") - - loftedSurface = surfaces.LoftedSurface.FromSelection() - loftedSurface.AddToScene() - - self.report({'INFO'}, "OperatorLoftcurves.execute()") - - return {'FINISHED'} - - -# ------------------------------------------------------------ -# OperatorSweepCurves - -class OperatorSweepCurves(bpy.types.Operator): - bl_idname = "curvetools.operatorsweepcurves" - bl_label = "Sweep" - bl_description = "Sweeps the active curve along to other curve (rail)" - - - @classmethod - def poll(cls, context): - return util.Selected2Curves() - - - def execute(self, context): - #print("### TODO: OperatorSweepcurves.execute()") - - sweptSurface = surfaces.SweptSurface.FromSelection() - sweptSurface.AddToScene() - - self.report({'INFO'}, "OperatorSweepcurves.execute()") - - return {'FINISHED'} - - - -# 3 CURVES SELECTED -# ################# -class OperatorBirail(bpy.types.Operator): - bl_idname = "curvetools.operatorbirail" - bl_label = "Birail" - bl_description = "Generates a birailed surface from 3 selected curves -- in order: rail1, rail2 and profile" - - - @classmethod - def poll(cls, context): - return util.Selected3Curves() - - - def execute(self, context): - birailedSurface = surfaces.BirailedSurface.FromSelection() - birailedSurface.AddToScene() - - self.report({'INFO'}, "OperatorBirail.execute()") - - return {'FINISHED'} - - - -# 1 OR MORE CURVES SELECTED -# ######################### -class OperatorSplinesSetResolution(bpy.types.Operator): - bl_idname = "curvetools.operatorsplinessetresolution" - bl_label = "SplinesSetResolution" - bl_description = "Sets the resolution of all splines" - - - @classmethod - def poll(cls, context): - return util.Selected1OrMoreCurves() - - - def execute(self, context): - splRes = context.scene.curvetools.SplineResolution - selCurves = util.GetSelectedCurves() - - for blCurve in selCurves: - for spline in blCurve.data.splines: - spline.resolution_u = splRes - - return {'FINISHED'} - -# ------------------------------------------------------------ -# OperatorSplinesRemoveZeroSegment - -class OperatorSplinesRemoveZeroSegment(bpy.types.Operator): - bl_idname = "curvetools.operatorsplinesremovezerosegment" - bl_label = "SplinesRemoveZeroSegment" - bl_description = "Removes splines with no segments -- they seem to creep up, sometimes.." - - - @classmethod - def poll(cls, context): - return util.Selected1OrMoreCurves() - - - def execute(self, context): - selCurves = util.GetSelectedCurves() - - for blCurve in selCurves: - curve = curves.Curve(blCurve) - nrSplines = curve.nrSplines - - splinesToRemove = [] - for spline in curve.splines: - if len(spline.segments) < 1: splinesToRemove.append(spline) - nrRemovedSplines = len(splinesToRemove) - - for spline in splinesToRemove: curve.splines.remove(spline) - - if nrRemovedSplines > 0: curve.RebuildInScene() - - self.report({'INFO'}, "Removed %d of %d splines" % (nrRemovedSplines, nrSplines)) - - return {'FINISHED'} - -# ------------------------------------------------------------ -# OperatorSplinesRemoveShort - -class OperatorSplinesRemoveShort(bpy.types.Operator): - bl_idname = "curvetools.operatorsplinesremoveshort" - bl_label = "SplinesRemoveShort" - bl_description = "Removes splines with a length smaller than the threshold" - - - @classmethod - def poll(cls, context): - return util.Selected1OrMoreCurves() - - - def execute(self, context): - threshold = context.scene.curvetools.SplineRemoveLength - selCurves = util.GetSelectedCurves() - - for blCurve in selCurves: - curve = curves.Curve(blCurve) - nrSplines = curve.nrSplines - - nrRemovedSplines = curve.RemoveShortSplines(threshold) - if nrRemovedSplines > 0: curve.RebuildInScene() - - self.report({'INFO'}, "Removed %d of %d splines" % (nrRemovedSplines, nrSplines)) - - return {'FINISHED'} - -# ------------------------------------------------------------ -# OperatorSplinesJoinNeighbouring - -class OperatorSplinesJoinNeighbouring(bpy.types.Operator): - bl_idname = "curvetools.operatorsplinesjoinneighbouring" - bl_label = "SplinesJoinNeighbouring" - bl_description = "Joins neighbouring splines within a distance smaller than the threshold" - - - @classmethod - def poll(cls, context): - return util.Selected1OrMoreCurves() - - - def execute(self, context): - selCurves = util.GetSelectedCurves() - - for blCurve in selCurves: - curve = curves.Curve(blCurve) - nrSplines = curve.nrSplines - - threshold = context.scene.curvetools.SplineJoinDistance - startEnd = context.scene.curvetools.SplineJoinStartEnd - mode = context.scene.curvetools.SplineJoinMode - - nrJoins = curve.JoinNeighbouringSplines(startEnd, threshold, mode) - if nrJoins > 0: curve.RebuildInScene() - - self.report({'INFO'}, "Applied %d joins on %d splines; resulting nrSplines: %d" % (nrJoins, nrSplines, curve.nrSplines)) - - return {'FINISHED'} - -# ------------------------------------------------------------ -# SurfaceFromBezier - -def SurfaceFromBezier(surfacedata, points, center): - - len_points = len(points) - 1 - - if len_points % 2 == 0: - h = mathematics.subdivide_cubic_bezier( - points[len_points].co, points[len_points].handle_right, - points[0].handle_left, points[0].co, 0.5 - ) - points.add(1) - len_points = len(points) - 1 - points[len_points - 1].handle_right = h[0] - points[len_points].handle_left = h[1] - points[len_points].co = h[2] - points[len_points].handle_right = h[3] - points[0].handle_left = h[4] - - half = round((len_points + 1)/2) - 1 - # 1 - surfacespline1 = surfacedata.splines.new(type='NURBS') - surfacespline1.points.add(3) - surfacespline1.points[0].co = [points[0].co.x, points[0].co.y, points[0].co.z, 1] - surfacespline1.points[1].co = [points[0].handle_left.x, points[0].handle_left.y, points[0].handle_left.z, 1] - surfacespline1.points[2].co = [points[len_points].handle_right.x,points[len_points].handle_right.y, points[len_points].handle_right.z, 1] - surfacespline1.points[3].co = [points[len_points].co.x, points[len_points].co.y, points[len_points].co.z, 1] - for p in surfacespline1.points: - p.select = True - surfacespline1.use_endpoint_u = True - surfacespline1.use_endpoint_v = True - - for i in range(0, half): - - if center: - # 2 - surfacespline2 = surfacedata.splines.new(type='NURBS') - surfacespline2.points.add(3) - surfacespline2.points[0].co = [points[i].co.x, points[i].co.y, points[i].co.z, 1] - surfacespline2.points[1].co = [(points[i].co.x + points[len_points - i].co.x)/2, - (points[i].co.y + points[len_points - i].co.y)/2, - (points[i].co.z + points[len_points - i].co.z)/2, 1] - surfacespline2.points[2].co = [(points[len_points - i].co.x + points[i].co.x)/2, - (points[len_points - i].co.y + points[i].co.y)/2, - (points[len_points - i].co.z + points[i].co.z)/2, 1] - surfacespline2.points[3].co = [points[len_points - i].co.x, points[len_points - i].co.y, points[len_points - i].co.z, 1] - for p in surfacespline2.points: - p.select = True - surfacespline2.use_endpoint_u = True - surfacespline2.use_endpoint_v = True - - # 3 - surfacespline3 = surfacedata.splines.new(type='NURBS') - surfacespline3.points.add(3) - surfacespline3.points[0].co = [points[i].handle_right.x, points[i].handle_right.y, points[i].handle_right.z, 1] - surfacespline3.points[1].co = [(points[i].handle_right.x + points[len_points - i].handle_left.x)/2, - (points[i].handle_right.y + points[len_points - i].handle_left.y)/2, - (points[i].handle_right.z + points[len_points - i].handle_left.z)/2, 1] - surfacespline3.points[2].co = [(points[len_points - i].handle_left.x + points[i].handle_right.x)/2, - (points[len_points - i].handle_left.y + points[i].handle_right.y)/2, - (points[len_points - i].handle_left.z + points[i].handle_right.z)/2, 1] - surfacespline3.points[3].co = [points[len_points - i].handle_left.x, points[len_points - i].handle_left.y, points[len_points - i].handle_left.z, 1] - for p in surfacespline3.points: - p.select = True - surfacespline3.use_endpoint_u = True - surfacespline3.use_endpoint_v = True - - # 4 - surfacespline4 = surfacedata.splines.new(type='NURBS') - surfacespline4.points.add(3) - surfacespline4.points[0].co = [points[i + 1].handle_left.x, points[i + 1].handle_left.y, points[i + 1].handle_left.z, 1] - surfacespline4.points[1].co = [(points[i + 1].handle_left.x + points[len_points - i - 1].handle_right.x)/2, - (points[i + 1].handle_left.y + points[len_points - i - 1].handle_right.y)/2, - (points[i + 1].handle_left.z + points[len_points - i - 1].handle_right.z)/2, 1] - surfacespline4.points[2].co = [(points[len_points - i - 1].handle_right.x + points[i + 1].handle_left.x)/2, - (points[len_points - i - 1].handle_right.y + points[i + 1].handle_left.y)/2, - (points[len_points - i - 1].handle_right.z + points[i + 1].handle_left.z)/2, 1] - surfacespline4.points[3].co = [points[len_points - i - 1].handle_right.x, points[len_points - i - 1].handle_right.y, points[len_points - i - 1].handle_right.z, 1] - for p in surfacespline4.points: - p.select = True - surfacespline4.use_endpoint_u = True - surfacespline4.use_endpoint_v = True - - if center: - # 5 - surfacespline5 = surfacedata.splines.new(type='NURBS') - surfacespline5.points.add(3) - surfacespline5.points[0].co = [points[i + 1].co.x, points[i + 1].co.y, points[i + 1].co.z, 1] - surfacespline5.points[1].co = [(points[i + 1].co.x + points[len_points - i - 1].co.x)/2, - (points[i + 1].co.y + points[len_points - i - 1].co.y)/2, - (points[i + 1].co.z + points[len_points - i - 1].co.z)/2, 1] - surfacespline5.points[2].co = [(points[len_points - i - 1].co.x + points[i + 1].co.x)/2, - (points[len_points - i - 1].co.y + points[i + 1].co.y)/2, - (points[len_points - i - 1].co.z + points[i + 1].co.z)/2, 1] - surfacespline5.points[3].co = [points[len_points - i - 1].co.x, points[len_points - i - 1].co.y, points[len_points - i - 1].co.z, 1] - for p in surfacespline5.points: - p.select = True - surfacespline5.use_endpoint_u = True - surfacespline5.use_endpoint_v = True - - # 6 - surfacespline6 = surfacedata.splines.new(type='NURBS') - surfacespline6.points.add(3) - surfacespline6.points[0].co = [points[half].co.x, points[half].co.y, points[half].co.z, 1] - surfacespline6.points[1].co = [points[half].handle_right.x, points[half].handle_right.y, points[half].handle_right.z, 1] - surfacespline6.points[2].co = [points[half+1].handle_left.x, points[half+1].handle_left.y, points[half+1].handle_left.z, 1] - surfacespline6.points[3].co = [points[half+1].co.x, points[half+1].co.y, points[half+1].co.z, 1] - for p in surfacespline6.points: - p.select = True - surfacespline6.use_endpoint_u = True - surfacespline6.use_endpoint_v = True - - bpy.ops.object.mode_set(mode = 'EDIT') - bpy.ops.curve.make_segment() - - for s in surfacedata.splines: - s.resolution_u = 4 - s.resolution_v = 4 - s.order_u = 4 - s.order_v = 4 - for p in s.points: - p.select = False - -# ------------------------------------------------------------ -# Convert selected faces to Bezier - -class ConvertSelectedFacesToBezier(bpy.types.Operator): - bl_idname = "curvetools.convert_selected_face_to_bezier" - bl_label = "Convert selected faces to Bezier" - bl_description = "Convert selected faces to Bezier" - bl_options = {'REGISTER', 'UNDO'} - - @classmethod - def poll(cls, context): - return util.Selected1Mesh() - - def execute(self, context): - # main function - bpy.ops.object.mode_set(mode = 'OBJECT') - active_object = context.active_object - meshdata = active_object.data - curvedata = bpy.data.curves.new('Curve' + active_object.name, type='CURVE') - curveobject = object_utils.object_data_add(context, curvedata) - curvedata.dimensions = '3D' - - for poly in meshdata.polygons: - if poly.select: - newSpline = curvedata.splines.new(type='BEZIER') - newSpline.use_cyclic_u = True - newSpline.bezier_points.add(poly.loop_total - 1) - npoint = 0 - for loop_index in range(poly.loop_start, poly.loop_start + poly.loop_total): - newSpline.bezier_points[npoint].co = meshdata.vertices[meshdata.loops[loop_index].vertex_index].co - newSpline.bezier_points[npoint].handle_left_type = 'VECTOR' - newSpline.bezier_points[npoint].handle_right_type = 'VECTOR' - newSpline.bezier_points[npoint].select_control_point = True - newSpline.bezier_points[npoint].select_left_handle = True - newSpline.bezier_points[npoint].select_right_handle = True - npoint += 1 - - return {'FINISHED'} - -# ------------------------------------------------------------ -# Convert Bezier to Surface - -class ConvertBezierToSurface(bpy.types.Operator): - bl_idname = "curvetools.convert_bezier_to_surface" - bl_label = "Convert Bezier to Surface" - bl_description = "Convert Bezier to Surface" - bl_options = {'REGISTER', 'UNDO'} - - Center : BoolProperty( - name="Center", - default=False, - description="Consider center points" - ) - - Resolution_U: IntProperty( - name="Resolution_U", - default=4, - min=1, max=64, - soft_min=1, - description="Surface resolution U" - ) - - Resolution_V: IntProperty( - name="Resolution_V", - default=4, - min=1, max=64, - soft_min=1, - description="Surface resolution V" - ) - - def draw(self, context): - layout = self.layout - - # general options - col = layout.column() - col.prop(self, 'Center') - col.prop(self, 'Resolution_U') - col.prop(self, 'Resolution_V') - - @classmethod - def poll(cls, context): - return util.Selected1OrMoreCurves() - - def execute(self, context): - # main function - bpy.ops.object.mode_set(mode = 'OBJECT') - active_object = context.active_object - curvedata = active_object.data - - surfacedata = bpy.data.curves.new('Surface', type='SURFACE') - surfaceobject = object_utils.object_data_add(context, surfacedata) - surfaceobject.matrix_world = active_object.matrix_world - surfaceobject.rotation_euler = active_object.rotation_euler - surfacedata.dimensions = '3D' - surfaceobject.show_wire = True - surfaceobject.show_in_front = True - - for spline in curvedata.splines: - SurfaceFromBezier(surfacedata, spline.bezier_points, self.Center) - - for spline in surfacedata.splines: - len_p = len(spline.points) - len_devide_4 = round(len_p / 4) + 1 - len_devide_2 = round(len_p / 2) - bpy.ops.object.mode_set(mode = 'EDIT') - for point_index in range(len_devide_4, len_p - len_devide_4): - if point_index != len_devide_2 and point_index != len_devide_2 - 1: - spline.points[point_index].select = True - - surfacedata.resolution_u = self.Resolution_U - surfacedata.resolution_v = self.Resolution_V - - return {'FINISHED'} - -# ------------------------------------------------------------ -# Fillet - -class BezierPointsFillet(bpy.types.Operator): - bl_idname = "curvetools.bezier_points_fillet" - bl_label = "Bezier points Fillet" - bl_description = "Bezier points Fillet" - bl_options = {'REGISTER', 'UNDO', 'PRESET'} - - Fillet_radius : FloatProperty( - name="Radius", - default=0.25, - unit='LENGTH', - description="Radius" - ) - Types = [('Round', "Round", "Round"), - ('Chamfer', "Chamfer", "Chamfer")] - Fillet_Type : EnumProperty( - name="Type", - description="Fillet type", - items=Types - ) - - def draw(self, context): - layout = self.layout - - # general options - col = layout.column() - col.prop(self, "Fillet_radius") - col.prop(self, "Fillet_Type", expand=True) - - @classmethod - def poll(cls, context): - return util.Selected1OrMoreCurves() - - def execute(self, context): - # main function - if bpy.ops.object.mode_set.poll(): - bpy.ops.object.mode_set(mode='EDIT') - - splines = bpy.context.object.data.splines - bpy.ops.curve.spline_type_set(type='BEZIER') - - bpy.ops.curve.handle_type_set(type='VECTOR') - s = [] - for spline in splines: - n = 0 - ii = [] - for p in spline.bezier_points: - if p.select_control_point: - ii.append(n) - n += 1 - else: - n += 1 - s.append(ii) - - sn = 0 - for spline in splines: - ii = s[sn] - bezier_points = spline.bezier_points - n = len(bezier_points) - if n > 2: - jn = 0 - for j in ii: - j += jn - - bpy.ops.curve.select_all(action='DESELECT') - - if j != 0 and j != n - 1: - bezier_points[j].select_control_point = True - bezier_points[j + 1].select_control_point = True - bpy.ops.curve.subdivide() - selected4 = [bezier_points[j - 1], bezier_points[j], - bezier_points[j + 1], bezier_points[j + 2]] - jn += 1 - n += 1 - - elif j == 0: - bezier_points[j].select_control_point = True - bezier_points[j + 1].select_control_point = True - bpy.ops.curve.subdivide() - selected4 = [bezier_points[n], bezier_points[0], - bezier_points[1], bezier_points[2]] - jn += 1 - n += 1 - - elif j == n - 1: - bezier_points[j].select_control_point = True - bezier_points[j - 1].select_control_point = True - bpy.ops.curve.subdivide() - selected4 = [bezier_points[0], bezier_points[n], - bezier_points[n - 1], bezier_points[n - 2]] - - selected4[2].co = selected4[1].co - s1 = Vector(selected4[0].co) - Vector(selected4[1].co) - s2 = Vector(selected4[3].co) - Vector(selected4[2].co) - s1.normalize() - s11 = Vector(selected4[1].co) + s1 * self.Fillet_radius - selected4[1].co = s11 - s2.normalize() - s22 = Vector(selected4[2].co) + s2 * self.Fillet_radius - selected4[2].co = s22 - - if self.Fillet_Type == 'Round': - if j != n - 1: - selected4[2].handle_right_type = 'VECTOR' - selected4[1].handle_left_type = 'VECTOR' - selected4[1].handle_right_type = 'ALIGNED' - selected4[2].handle_left_type = 'ALIGNED' - else: - selected4[1].handle_right_type = 'VECTOR' - selected4[2].handle_left_type = 'VECTOR' - selected4[2].handle_right_type = 'ALIGNED' - selected4[1].handle_left_type = 'ALIGNED' - if self.Fillet_Type == 'Chamfer': - selected4[2].handle_right_type = 'VECTOR' - selected4[1].handle_left_type = 'VECTOR' - selected4[1].handle_right_type = 'VECTOR' - selected4[2].handle_left_type = 'VECTOR' - sn += 1 - - return {'FINISHED'} - -# ------------------------------------------------------------ -# BezierDivide Operator - -class BezierDivide(bpy.types.Operator): - bl_idname = "curvetools.bezier_spline_divide" - bl_label = "Bezier Spline Divide" - bl_description = "Bezier Divide (enters edit mode) for Fillet Curves" - bl_options = {'REGISTER', 'UNDO'} - - # align_matrix for the invoke - align_matrix : Matrix() - - Bezier_t : FloatProperty( - name="t (0% - 100%)", - default=50.0, - min=0.0, soft_min=0.0, - max=100.0, soft_max=100.0, - description="t (0% - 100%)" - ) - - @classmethod - def poll(cls, context): - return util.Selected1OrMoreCurves() - - def execute(self, context): - # main function - if bpy.ops.object.mode_set.poll(): - bpy.ops.object.mode_set(mode='EDIT') - - splines = bpy.context.object.data.splines - s = [] - for spline in splines: - bpy.ops.curve.spline_type_set(type='BEZIER') - - n = 0 - ii = [] - for p in spline.bezier_points: - if p.select_control_point: - ii.append(n) - n += 1 - else: - n += 1 - s.append(ii) - - sn = 0 - for spline in splines: - ii = s[sn] - bezier_points = spline.bezier_points - n = len(bezier_points) - if n > 2: - jn = 0 - for j in ii: - - bpy.ops.curve.select_all(action='DESELECT') - - 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 = 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 - ) - bpy.ops.curve.subdivide(1) - bezier_points[j + jn].handle_right_type = 'FREE' - bezier_points[j + jn].handle_right = h[0] - bezier_points[j + 1 + jn].co = h[2] - bezier_points[j + 1 + jn].handle_left_type = 'FREE' - bezier_points[j + 1 + jn].handle_left = h[1] - bezier_points[j + 1 + jn].handle_right_type = 'FREE' - bezier_points[j + 1 + jn].handle_right = h[3] - bezier_points[j + 2 + jn].handle_left_type = 'FREE' - bezier_points[j + 2 + jn].handle_left = h[4] - jn += 1 - - 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 = 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 - ) - bpy.ops.curve.subdivide(1) - bezier_points[j + jn].handle_right_type = 'FREE' - bezier_points[j + jn].handle_right = h[0] - bezier_points[j + 1 + jn].co = h[2] - bezier_points[j + 1 + jn].handle_left_type = 'FREE' - bezier_points[j + 1 + jn].handle_left = h[1] - bezier_points[j + 1 + jn].handle_right_type = 'FREE' - bezier_points[j + 1 + jn].handle_right = h[3] - bezier_points[0].handle_left_type = 'FREE' - bezier_points[0].handle_left = h[4] - - sn += 1 - - return {'FINISHED'} - -# ------------------------------------------------------------ -# CurveScaleReset Operator - -class CurveScaleReset(bpy.types.Operator): - bl_idname = "curvetools.scale_reset" - bl_label = "Curve Scale Reset" - bl_description = "Curve Scale Reset" - bl_options = {'REGISTER', 'UNDO'} - - @classmethod - def poll(cls, context): - return (context.object is not None and - context.object.type == 'CURVE') - - def execute(self, context): - # main function - current_mode = bpy.context.object.mode - - bpy.ops.object.mode_set(mode = 'OBJECT') - - oldCurve = context.active_object - oldCurveName = oldCurve.name - - bpy.ops.object.duplicate_move(OBJECT_OT_duplicate=None, TRANSFORM_OT_translate=None) - newCurve = context.active_object - newCurve.data.splines.clear() - newCurve.scale = (1.0, 1.0, 1.0) - - oldCurve.select_set(True) - newCurve.select_set(True) - bpy.context.view_layer.objects.active = newCurve - bpy.ops.object.join() - - joinCurve = context.active_object - joinCurve.name = oldCurveName - - bpy.ops.object.mode_set (mode = current_mode) - - return {'FINISHED'} - -# ------------------------------------------------------------ -# Split Operator - -class Split(bpy.types.Operator): - bl_idname = "curvetools.split" - bl_label = "Split" - bl_options = {'REGISTER', 'UNDO'} - - @classmethod - def poll(cls, context): - return util.Selected1OrMoreCurves() - - def execute(self, context): - selected_Curves = util.GetSelectedCurves() - - for curve in selected_Curves: - spline_points = [] - select_points = {} - bezier_spline_points = [] - select_bezier_points = {} - i_bp = 0 - i_p = 0 - for spline in curve.data.splines: - if spline.type == 'BEZIER': - points = {} - select_bezier_points[i_bp] = [len(spline.bezier_points)] - for i in range(len(spline.bezier_points)): - bezier_point = spline.bezier_points[i] - points[i]=[bezier_point.co[:], bezier_point.handle_left[:], bezier_point.handle_right[:]] - - if spline.bezier_points[i].select_control_point: - select_bezier_points[i_bp].append(i) - i_bp+=1 - bezier_spline_points.append(points) - else: - points = {} - select_points[i_p] = [len(spline.points)] - for i in range(len(spline.points)): - point = spline.points[i] - points[i]=[point.co[:], spline.type] - if spline.points[i].select: - select_points[i_p].append(i) - i_p+=1 - spline_points.append(points) - - curve.data.splines.clear() - - for key in select_bezier_points: - - num=0 - - if select_bezier_points[key][-1] == select_bezier_points[key][0]-1: - select_bezier_points[key].pop() - - for i in select_bezier_points[key][1:]+[select_bezier_points[key][0]-1]: - if i != 0: - spline = curve.data.splines.new('BEZIER') - spline.bezier_points.add(i-num) - - for j in range(num, i): - bezier_point = spline.bezier_points[j-num] - - bezier_point.co = bezier_spline_points[key][j][0] - bezier_point.handle_left = bezier_spline_points[key][j][1] - bezier_point.handle_right = bezier_spline_points[key][j][2] - bezier_point = spline.bezier_points[-1] - bezier_point.co = bezier_spline_points[key][i][0] - bezier_point.handle_left = bezier_spline_points[key][i][1] - bezier_point.handle_right = bezier_spline_points[key][i][2] - num=i - - for key in select_points: - - num=0 - - if select_points[key][-1] == select_points[key][0]-1: - select_points[key].pop() - - for i in select_points[key][1:]+[select_points[key][0]-1]: - if i != 0: - spline = curve.data.splines.new(spline_points[key][i][1]) - spline.points.add(i-num) - - for j in range(num, i): - point = spline.points[j-num] - - point.co = spline_points[key][j][0] - point = spline.points[-1] - point.co = spline_points[key][i][0] - 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, - OperatorCurveLength, - OperatorSplinesInfo, - OperatorSegmentsInfo, - OperatorOriginToSpline0Start, - OperatorIntersectCurves, - OperatorLoftCurves, - OperatorSweepCurves, - OperatorBirail, - OperatorSplinesSetResolution, - OperatorSplinesRemoveZeroSegment, - OperatorSplinesRemoveShort, - OperatorSplinesJoinNeighbouring, - ConvertSelectedFacesToBezier, - ConvertBezierToSurface, - BezierPointsFillet, - BezierDivide, - CurveScaleReset, - Split, - SeparateOutline, - ] diff --git a/curve_tools/Properties.py b/curve_tools/Properties.py deleted file mode 100644 index d6fe9e0f..00000000 --- a/curve_tools/Properties.py +++ /dev/null @@ -1,104 +0,0 @@ -import time - -import bpy -from bpy.props import * - - - -class curvetoolsSelectedObjectHeader(bpy.types.Header): - bl_label = "Selection" - bl_space_type = "VIEW_3D" - - def __init__(self): - self.update() - - - def update(self): - blenderSelectedObjects = bpy.context.selected_objects - selectedObjects = bpy.context.scene.curvetools.SelectedObjects - - selectedObjectsToRemove = [] - for selectedObject in selectedObjects: - if not selectedObject.IsElementOf(blenderSelectedObjects): selectedObjectsToRemove.append(selectedObject) - for selectedObject in selectedObjectsToRemove: selectedObjects.remove(selectedObject) - - blenderObjectsToAdd = [] - for blenderObject in blenderSelectedObjects: - if not curvetoolsSelectedObject.ListContains(selectedObjects, blenderObject): blenderObjectsToAdd.append(blenderObject) - for blenderObject in blenderObjectsToAdd: - newSelectedObject = curvetoolsSelectedObject(blenderObject) - selectedObjects.append(newSelectedObject) - - - def draw(self, context): - selectedObjects = bpy.context.scene.curvetools.SelectedObjects - nrSelectedObjects = len(selectedObjects) - - layout = self.layout - row = layout.row() - row.label(text="Sel: " + str(nrSelectedObjects)) - - -class curvetoolsSelectedObject(bpy.types.PropertyGroup): - name: StringProperty(name = "name", default = "??") - - - @staticmethod - def UpdateThreadTarget(lock, sleepTime, selectedObjectNames, selectedBlenderObjectNames): - time.sleep(sleepTime) - - newSelectedObjectNames = [] - - for name in selectedObjectNames: - if name in selectedBlenderObjectNames: newSelectedObjectNames.append(name) - - for name in selectedBlenderObjectNames: - if not (name in selectedObjectNames): newSelectedObjectNames.append(name) - - # sometimes it still complains about the context - try: - nrNewSelectedObjects = len(newSelectedObjectNames) - bpy.context.scene.curvetools.NrSelectedObjects = nrNewSelectedObjects - - selectedObjects = bpy.context.scene.curvetools.SelectedObjects - selectedObjects.clear() - for i in range(nrNewSelectedObjects): selectedObjects.add() - for i, newSelectedObjectName in enumerate(newSelectedObjectNames): - selectedObjects[i].name = newSelectedObjectName - except: pass - - - @staticmethod - def GetSelectedObjectNames(): - selectedObjects = bpy.context.scene.curvetools.SelectedObjects - - rvNames = [] - selectedObjectValues = selectedObjects.values() - for selectedObject in selectedObjectValues: rvNames.append(selectedObject.name) - - return rvNames - - - @staticmethod - def GetSelectedBlenderObjectNames(): - blenderSelectedObjects = bpy.context.selected_objects - - rvNames = [] - 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, - ] diff --git a/curve_tools/Surfaces.py b/curve_tools/Surfaces.py deleted file mode 100644 index cfae7040..00000000 --- a/curve_tools/Surfaces.py +++ /dev/null @@ -1,462 +0,0 @@ -import bpy -import bmesh - -from . import mathematics -from . import curves - - - -class LoftedSplineSurface: - def __init__(self, activeSpline, otherSpline, bMesh, vert0Index, resolution): - self.splineA = activeSpline - self.splineO = otherSpline - - self.bMesh = bMesh - self.vert0Index = vert0Index - self.resolution = resolution - - - def Apply(self, worldMatrixA, worldMatrixO): - #deltaPar = 1.0 / float(self.resolution - 1) - - par = 0.0 - pointA = worldMatrixA @ self.splineA.CalcPoint(par) - pointO = worldMatrixO @ self.splineO.CalcPoint(par) - self.bMesh.verts[self.vert0Index].co = pointA - self.bMesh.verts[self.vert0Index + 1].co = pointO - - fltResm1 = float(self.resolution - 1) - for i in range(1, self.resolution): - par = float(i) / fltResm1 - - pointA = worldMatrixA @ self.splineA.CalcPoint(par) - pointO = worldMatrixO @ self.splineO.CalcPoint(par) - self.bMesh.verts[self.vert0Index + 2 * i].co = pointA - self.bMesh.verts[self.vert0Index + 2 * i + 1].co = pointO - - - def AddFaces(self): - currIndexA = self.vert0Index - currIndexO = self.vert0Index + 1 - - bmVerts = self.bMesh.verts - bmVerts.ensure_lookup_table() - - for i in range(1, self.resolution): - nextIndexA = self.vert0Index + 2 * i - nextIndexO = nextIndexA + 1 - - self.bMesh.faces.new([bmVerts[currIndexA], bmVerts[currIndexO], bmVerts[nextIndexO], bmVerts[nextIndexA]]) - - currIndexA = nextIndexA - currIndexO = nextIndexO - - -class LoftedSurface: - @staticmethod - def FromSelection(): - selObjects = bpy.context.selected_objects - if len(selObjects) != 2: raise Exception("len(selObjects) != 2") # shouldn't be possible - - blenderActiveCurve = bpy.context.active_object - blenderOtherCurve = selObjects[0] - if blenderActiveCurve == blenderOtherCurve: blenderOtherCurve = selObjects[1] - - aCurve = curves.Curve(blenderActiveCurve) - oCurve = curves.Curve(blenderOtherCurve) - - name = "TODO: autoname" - - return LoftedSurface(aCurve, oCurve, name) - - - def __init__(self, activeCurve, otherCurve, name = "LoftedSurface"): - self.curveA = activeCurve - self.curveO = otherCurve - self.name = name - - self.nrSplines = self.curveA.nrSplines - if self.curveO.nrSplines < self.nrSplines: self.nrSplines = self.curveO.nrSplines - - self.bMesh = bmesh.new() - - self.splineSurfaces = self.SetupSplineSurfaces() - - self.Apply() - - - def SetupSplineSurfaces(self): - rvSplineSurfaces = [] - - currV0Index = 0 - for i in range(self.nrSplines): - splineA = self.curveA.splines[i] - splineO = self.curveO.splines[i] - - res = splineA.resolution - if splineO.resolution < res: res = splineO.resolution - - for iv in range(2 * res): self.bMesh.verts.new() - - splSurf = LoftedSplineSurface(splineA, splineO, self.bMesh, currV0Index, res) - splSurf.AddFaces() - rvSplineSurfaces.append(splSurf) - - currV0Index += 2 * res - - return rvSplineSurfaces - - - def Apply(self): - for splineSurface in self.splineSurfaces: splineSurface.Apply(self.curveA.worldMatrix, self.curveO.worldMatrix) - - - def AddToScene(self): - mesh = bpy.data.meshes.new("Mesh" + self.name) - - self.bMesh.to_mesh(mesh) - mesh.update() - - meshObject = bpy.data.objects.new(self.name, mesh) - - bpy.context.collection.objects.link(meshObject) - - - -# active spline is swept over other spline (rail) -class SweptSplineSurface: - def __init__(self, activeSpline, otherSpline, bMesh, vert0Index, resolutionA, resolutionO): - self.splineA = activeSpline - self.splineO = otherSpline - - self.bMesh = bMesh - self.vert0Index = vert0Index - self.resolutionA = resolutionA - self.resolutionO = resolutionO - - - def Apply(self, worldMatrixA, worldMatrixO): - localPointsA = [] - fltResAm1 = float(self.resolutionA - 1) - for i in range(self.resolutionA): - par = float(i) / fltResAm1 - pointA = self.splineA.CalcPoint(par) - localPointsA.append(pointA) - - - worldPointsO = [] - localDerivativesO = [] - fltResOm1 = float(self.resolutionO - 1) - for i in range(self.resolutionO): - par = float(i) / fltResOm1 - - pointO = self.splineO.CalcPoint(par) - worldPointsO.append(worldMatrixO @ pointO) - - derivativeO = self.splineO.CalcDerivative(par) - localDerivativesO.append(derivativeO) - - - currWorldMatrixA = worldMatrixA - worldMatrixOInv = worldMatrixO.inverted() - prevDerivativeO = localDerivativesO[0] - for iO in range(self.resolutionO): - currDerivativeO = localDerivativesO[iO] - localRotMatO = mathematics.CalcRotationMatrix(prevDerivativeO, currDerivativeO) - - currLocalAToLocalO = worldMatrixOInv @ currWorldMatrixA - worldPointsA = [] - for iA in range(self.resolutionA): - pointALocalToO = currLocalAToLocalO @ localPointsA[iA] - rotatedPointA = localRotMatO @ pointALocalToO - worldPointsA.append(worldMatrixO @ rotatedPointA) - - worldOffsetsA = [] - worldPoint0A = worldPointsA[0] - for i in range(self.resolutionA): worldOffsetsA.append(worldPointsA[i] - worldPoint0A) - - - for iA in range(self.resolutionA): - iVert = self.vert0Index + (self.resolutionA * iO) + iA - currVert = worldPointsO[iO] + worldOffsetsA[iA] - self.bMesh.verts[iVert].co = currVert - - prevDerivativeO = currDerivativeO - currWorldMatrixA = worldMatrixO @ localRotMatO @ currLocalAToLocalO - - - def AddFaces(self): - bmVerts = self.bMesh.verts - bmVerts.ensure_lookup_table() - - for iO in range(self.resolutionO - 1): - for iA in range(self.resolutionA - 1): - currIndexA1 = self.vert0Index + (self.resolutionA * iO) + iA - currIndexA2 = currIndexA1 + 1 - nextIndexA1 = self.vert0Index + (self.resolutionA * (iO + 1)) + iA - nextIndexA2 = nextIndexA1 + 1 - - self.bMesh.faces.new([bmVerts[currIndexA1], bmVerts[currIndexA2], bmVerts[nextIndexA2], bmVerts[nextIndexA1]]) - - - -class SweptSurface: - @staticmethod - def FromSelection(): - selObjects = bpy.context.selected_objects - if len(selObjects) != 2: raise Exception("len(selObjects) != 2") # shouldn't be possible - - blenderActiveCurve = bpy.context.active_object - blenderOtherCurve = selObjects[0] - if blenderActiveCurve == blenderOtherCurve: blenderOtherCurve = selObjects[1] - - aCurve = curves.Curve(blenderActiveCurve) - oCurve = curves.Curve(blenderOtherCurve) - - name = "TODO: autoname" - - return SweptSurface(aCurve, oCurve, name) - - - def __init__(self, activeCurve, otherCurve, name = "SweptSurface"): - self.curveA = activeCurve - self.curveO = otherCurve - self.name = name - - self.nrSplines = self.curveA.nrSplines - if self.curveO.nrSplines < self.nrSplines: self.nrSplines = self.curveO.nrSplines - - self.bMesh = bmesh.new() - - self.splineSurfaces = self.SetupSplineSurfaces() - - self.Apply() - - - def SetupSplineSurfaces(self): - rvSplineSurfaces = [] - - currV0Index = 0 - for i in range(self.nrSplines): - splineA = self.curveA.splines[i] - splineO = self.curveO.splines[i] - - resA = splineA.resolution - resO = splineO.resolution - - for iv in range(resA * resO): self.bMesh.verts.new() - - splSurf = SweptSplineSurface(splineA, splineO, self.bMesh, currV0Index, resA, resO) - splSurf.AddFaces() - rvSplineSurfaces.append(splSurf) - - currV0Index += resA * resO - - return rvSplineSurfaces - - - def Apply(self): - for splineSurface in self.splineSurfaces: splineSurface.Apply(self.curveA.worldMatrix, self.curveO.worldMatrix) - - - def AddToScene(self): - mesh = bpy.data.meshes.new("Mesh" + self.name) - - self.bMesh.to_mesh(mesh) - mesh.update() - - meshObject = bpy.data.objects.new(self.name, mesh) - - bpy.context.collection.objects.link(meshObject) - - - -# profileSpline is swept over rail1Spline and scaled/rotated to have its endpoint on rail2Spline -class BirailedSplineSurface: - def __init__(self, rail1Spline, rail2Spline, profileSpline, bMesh, vert0Index, resolutionRails, resolutionProfile): - self.rail1Spline = rail1Spline - self.rail2Spline = rail2Spline - self.profileSpline = profileSpline - - self.bMesh = bMesh - self.vert0Index = vert0Index - self.resolutionRails = resolutionRails - self.resolutionProfile = resolutionProfile - - - def Apply(self, worldMatrixRail1, worldMatrixRail2, worldMatrixProfile): - localPointsProfile = [] - fltResProfilem1 = float(self.resolutionProfile - 1) - for i in range(self.resolutionProfile): - par = float(i) / fltResProfilem1 - pointProfile = self.profileSpline.CalcPoint(par) - localPointsProfile.append(pointProfile) - - - worldPointsRail1 = [] - localDerivativesRail1 = [] - worldPointsRail2 = [] - fltResRailsm1 = float(self.resolutionRails - 1) - for i in range(self.resolutionRails): - par = float(i) / fltResRailsm1 - - pointRail1 = self.rail1Spline.CalcPoint(par) - worldPointsRail1.append(worldMatrixRail1 @ pointRail1) - - derivativeRail1 = self.rail1Spline.CalcDerivative(par) - localDerivativesRail1.append(derivativeRail1) - - pointRail2 = self.rail2Spline.CalcPoint(par) - worldPointsRail2.append(worldMatrixRail2 @ pointRail2) - - - currWorldMatrixProfile = worldMatrixProfile - worldMatrixRail1Inv = worldMatrixRail1.inverted() - prevDerivativeRail1 = localDerivativesRail1[0] - for iRail in range(self.resolutionRails): - currDerivativeRail1 = localDerivativesRail1[iRail] - localRotMatRail1 = mathematics.CalcRotationMatrix(prevDerivativeRail1, currDerivativeRail1) - - currLocalProfileToLocalRail1 = worldMatrixRail1Inv @ currWorldMatrixProfile - worldPointsProfileRail1 = [] - for iProfile in range(self.resolutionProfile): - pointProfileLocalToRail1 = currLocalProfileToLocalRail1 @ localPointsProfile[iProfile] - rotatedPointProfile = localRotMatRail1 @ pointProfileLocalToRail1 - worldPointsProfileRail1.append(worldMatrixRail1 @ rotatedPointProfile) - - worldOffsetsProfileRail1 = [] - worldPoint0ProfileRail1 = worldPointsProfileRail1[0] - for iProfile in range(self.resolutionProfile): worldOffsetsProfileRail1.append(worldPointsProfileRail1[iProfile] - worldPoint0ProfileRail1) - - worldStartPointProfileRail1 = worldPointsRail1[iRail] - worldEndPointProfileRail1 = worldStartPointProfileRail1 + worldOffsetsProfileRail1[-1] - v3From = worldEndPointProfileRail1 - worldStartPointProfileRail1 - v3To = worldPointsRail2[iRail] - worldStartPointProfileRail1 - if not v3From.magnitude == 0: - scaleFactorRail2 = v3To.magnitude / v3From.magnitude - else: - scaleFactorRail2 = 1 - rotMatRail2 = mathematics.CalcRotationMatrix(v3From, v3To) - - worldOffsetsProfileRail2 = [] - for iProfile in range(self.resolutionProfile): - offsetProfileRail1 = worldOffsetsProfileRail1[iProfile] - worldOffsetsProfileRail2.append(rotMatRail2 @ (offsetProfileRail1 * scaleFactorRail2)) - - - for iProfile in range(self.resolutionProfile): - iVert = self.vert0Index + (self.resolutionProfile * iRail) + iProfile - currVert = worldPointsRail1[iRail] + worldOffsetsProfileRail2[iProfile] - self.bMesh.verts[iVert].co = currVert - - prevDerivativeRail1 = currDerivativeRail1 - currWorldMatrixProfile = worldMatrixRail1 @ localRotMatRail1 @ currLocalProfileToLocalRail1 - - - def AddFaces(self): - bmVerts = self.bMesh.verts - bmVerts.ensure_lookup_table() - - for iRail in range(self.resolutionRails - 1): - for iProfile in range(self.resolutionProfile - 1): - currIndex1 = self.vert0Index + (self.resolutionProfile * iRail) + iProfile - currIndex2 = currIndex1 + 1 - nextIndex1 = self.vert0Index + (self.resolutionProfile * (iRail + 1)) + iProfile - nextIndex2 = nextIndex1 + 1 - - self.bMesh.faces.new([bmVerts[currIndex1], bmVerts[currIndex2], bmVerts[nextIndex2], bmVerts[nextIndex1]]) - - - -class BirailedSurface: - @staticmethod - def FromSelection(): - nrSelectedObjects = bpy.context.scene.curvetools.NrSelectedObjects - if nrSelectedObjects != 3: raise Exception("nrSelectedObjects != 3") # shouldn't be possible - - - selectedObjects = bpy.context.scene.curvetools.SelectedObjects - selectedObjectValues = selectedObjects.values() - - curveName = selectedObjectValues[0].name - rail1BlenderCurve = None - try: rail1BlenderCurve = bpy.data.objects[curveName] - except: rail1BlenderCurve = None - if rail1BlenderCurve is None: raise Exception("rail1BlenderCurve is None") - - curveName = selectedObjectValues[1].name - rail2BlenderCurve = None - try: rail2BlenderCurve = bpy.data.objects[curveName] - except: rail2BlenderCurve = None - if rail2BlenderCurve is None: raise Exception("rail2BlenderCurve is None") - - curveName = selectedObjectValues[2].name - profileBlenderCurve = None - try: profileBlenderCurve = bpy.data.objects[curveName] - except: profileBlenderCurve = None - if profileBlenderCurve is None: raise Exception("profileBlenderCurve is None") - - - rail1Curve = curves.Curve(rail1BlenderCurve) - rail2Curve = curves.Curve(rail2BlenderCurve) - profileCurve = curves.Curve(profileBlenderCurve) - - name = "TODO: autoname" - - return BirailedSurface(rail1Curve, rail2Curve, profileCurve, name) - - - def __init__(self, rail1Curve, rail2Curve, profileCurve, name = "BirailedSurface"): - self.rail1Curve = rail1Curve - self.rail2Curve = rail2Curve - self.profileCurve = profileCurve - self.name = name - - self.nrSplines = self.rail1Curve.nrSplines - if self.rail2Curve.nrSplines < self.nrSplines: self.nrSplines = self.rail2Curve.nrSplines - if self.profileCurve.nrSplines < self.nrSplines: self.nrSplines = self.profileCurve.nrSplines - - self.bMesh = bmesh.new() - - self.splineSurfaces = self.SetupSplineSurfaces() - - self.Apply() - - - def SetupSplineSurfaces(self): - rvSplineSurfaces = [] - - currV0Index = 0 - for i in range(self.nrSplines): - splineRail1 = self.rail1Curve.splines[i] - splineRail2 = self.rail2Curve.splines[i] - splineProfile = self.profileCurve.splines[i] - - resProfile = splineProfile.resolution - resRails = splineRail1.resolution - if splineRail2.resolution < resRails: resRails = splineRail2.resolution - - for iv in range(resProfile * resRails): self.bMesh.verts.new() - - splSurf = BirailedSplineSurface(splineRail1, splineRail2, splineProfile, self.bMesh, currV0Index, resRails, resProfile) - splSurf.AddFaces() - rvSplineSurfaces.append(splSurf) - - currV0Index += resProfile * resRails - - return rvSplineSurfaces - - - def Apply(self): - for splineSurface in self.splineSurfaces: splineSurface.Apply(self.rail1Curve.worldMatrix, self.rail2Curve.worldMatrix, self.profileCurve.worldMatrix) - - - def AddToScene(self): - mesh = bpy.data.meshes.new("Mesh" + self.name) - - self.bMesh.to_mesh(mesh) - mesh.update() - - meshObject = bpy.data.objects.new(self.name, mesh) - - bpy.context.collection.objects.link(meshObject) diff --git a/curve_tools/Util.py b/curve_tools/Util.py deleted file mode 100644 index 30596697..00000000 --- a/curve_tools/Util.py +++ /dev/null @@ -1,178 +0,0 @@ -import bpy -from mathutils import * - - -def GetSelectedCurves(): - rvList = [] - - for obj in bpy.context.selected_objects: - try: - if obj.type == "CURVE": rvList.append(obj) - except: - pass - - return rvList - - -def GetSelectedMeshes(): - rvList = [] - - for obj in bpy.context.selected_objects: - try: - if obj.type == "MESH": rvList.append(obj) - except: - pass - - return rvList - - -def Selected1Curve(): - try: - if len(GetSelectedCurves()) == 1: - return (bpy.context.active_object.type == "CURVE") - except: - pass - - return False - - -def Selected1Mesh(): - try: - if len(GetSelectedMeshes()) == 1: - return (bpy.context.active_object.type == "MESH") - except: - pass - - return False - - -def Selected1SingleSplineCurve(): - try: - if Selected1Curve(): - return (len(bpy.context.active_object.data.splines) == 1) - except: - pass - - return False - - -def Selected2Curves(): - try: - if len(GetSelectedCurves()) == 2: - return (bpy.context.active_object.type == "CURVE") - except: - pass - - return False - - -def Selected3Curves(): - try: - if len(GetSelectedCurves()) == 3: - return (bpy.context.active_object.type == "CURVE") - except: - pass - - return False - - -def Selected1OrMoreCurves(): - try: - if len(GetSelectedCurves()) > 0: - return (bpy.context.active_object.type == "CURVE") - except: - pass - - return False - -def Selected2OrMoreCurves(): - try: - if len(GetSelectedCurves()) > 1: - return (bpy.context.active_object.type == "CURVE") - except: - pass - - return False - - -def Selected1OrMoreMesh(): - try: - if len(GetSelectedMeshes()) > 0: - return (bpy.context.active_object.type == "MESH") - except: - pass - - return False - - -def GetToolsRegion(): - for area in bpy.context.screen.areas: - if area.type == 'VIEW_3D': - for region in area.regions: - if region.type == 'TOOLS': return region - - return None - - -def GetFirstRegionView3D(): - for area in bpy.context.screen.areas: - if area.type == 'VIEW_3D': - return area.spaces[0].region_3d - - return None - - -def LogFirstRegionView3D(): - print("LogFirstRegionView3D()") - regionView3D = GetFirstRegionView3D() - if regionView3D is None: - print("--", "ERROR:", "regionView3D is None") - return - - print("--", "view_matrix:") - print("--", "--", regionView3D.view_matrix) - print("--", "view_location:") - print("--", "--", regionView3D.view_location) - - -class Intersection: - # listIP: list of BezierSplineIntersectionPoint - # return: list of splines - @staticmethod - def GetBezierSplines(listIP): - rvList = [] - - for ip in listIP: - if not (ip.spline in rvList): rvList.append(ip.spline) - - return rvList - - - # listIP: list of BezierSplineIntersectionPoint - # return: list of segments - @staticmethod - def GetBezierSegments(listIP, spline): - rvList = [] - - for ip in listIP: - if not ip.spline is spline: continue - - segIP = ip.bezierSegmentIntersectionPoint - if not (segIP.segment in rvList): rvList.append(segIP.segment) - - return rvList - - - # listIP: list of BezierSplineIntersectionPoint - # return: list of floats (not necessarily ordered) - @staticmethod - def GetBezierSegmentParameters(listIP, segment): - rvList = [] - - for ip in listIP: - segIP = ip.bezierSegmentIntersectionPoint - if not segIP.segment is segment: continue - - rvList.append(segIP.parameter) - - return rvList diff --git a/curve_tools/curvess.py b/curve_tools/curvess.py new file mode 100644 index 00000000..da0b1398 --- /dev/null +++ b/curve_tools/curvess.py @@ -0,0 +1,594 @@ +from . import mathematics + +import bpy + + +class BezierPoint: + @staticmethod + def FromBlenderBezierPoint(blenderBezierPoint): + return BezierPoint(blenderBezierPoint.handle_left, blenderBezierPoint.co, blenderBezierPoint.handle_right) + + + def __init__(self, handle_left, co, handle_right): + self.handle_left = handle_left + self.co = co + self.handle_right = handle_right + + + def Copy(self): + return BezierPoint(self.handle_left.copy(), self.co.copy(), self.handle_right.copy()) + + def Reversed(self): + return BezierPoint(self.handle_right, self.co, self.handle_left) + + def Reverse(self): + tmp = self.handle_left + self.handle_left = self.handle_right + self.handle_right = tmp + + +class BezierSegment: + @staticmethod + def FromBlenderBezierPoints(blenderBezierPoint1, blenderBezierPoint2): + bp1 = BezierPoint.FromBlenderBezierPoint(blenderBezierPoint1) + bp2 = BezierPoint.FromBlenderBezierPoint(blenderBezierPoint2) + + return BezierSegment(bp1, bp2) + + + def Copy(self): + return BezierSegment(self.bezierPoint1.Copy(), self.bezierPoint2.Copy()) + + def Reversed(self): + return BezierSegment(self.bezierPoint2.Reversed(), self.bezierPoint1.Reversed()) + + def Reverse(self): + # make a copy, otherwise neighboring segment may be affected + tmp = self.bezierPoint1.Copy() + self.bezierPoint1 = self.bezierPoint2.Copy() + self.bezierPoint2 = tmp + self.bezierPoint1.Reverse() + self.bezierPoint2.Reverse() + + + def __init__(self, bezierPoint1, bezierPoint2): + # bpy.types.BezierSplinePoint + # ## NOTE/TIP: copy() helps with repeated (intersection) action -- ?? + self.bezierPoint1 = bezierPoint1.Copy() + self.bezierPoint2 = bezierPoint2.Copy() + + self.ctrlPnt0 = self.bezierPoint1.co + self.ctrlPnt1 = self.bezierPoint1.handle_right + self.ctrlPnt2 = self.bezierPoint2.handle_left + self.ctrlPnt3 = self.bezierPoint2.co + + self.coeff0 = self.ctrlPnt0 + self.coeff1 = self.ctrlPnt0 * (-3.0) + self.ctrlPnt1 * (+3.0) + self.coeff2 = self.ctrlPnt0 * (+3.0) + self.ctrlPnt1 * (-6.0) + self.ctrlPnt2 * (+3.0) + self.coeff3 = self.ctrlPnt0 * (-1.0) + self.ctrlPnt1 * (+3.0) + self.ctrlPnt2 * (-3.0) + self.ctrlPnt3 + + + def CalcPoint(self, parameter = 0.5): + parameter2 = parameter * parameter + parameter3 = parameter * parameter2 + + rvPoint = self.coeff0 + self.coeff1 * parameter + self.coeff2 * parameter2 + self.coeff3 * parameter3 + + return rvPoint + + + def CalcDerivative(self, parameter = 0.5): + parameter2 = parameter * parameter + + rvPoint = self.coeff1 + self.coeff2 * parameter * 2.0 + self.coeff3 * parameter2 * 3.0 + + return rvPoint + + + def CalcLength(self, nrSamples = 2): + nrSamplesFloat = float(nrSamples) + rvLength = 0.0 + for iSample in range(nrSamples): + par1 = float(iSample) / nrSamplesFloat + par2 = float(iSample + 1) / nrSamplesFloat + + point1 = self.CalcPoint(parameter = par1) + point2 = self.CalcPoint(parameter = par2) + diff12 = point1 - point2 + + rvLength += diff12.magnitude + + return rvLength + + + #http://en.wikipedia.org/wiki/De_Casteljau's_algorithm + def CalcSplitPoint(self, parameter = 0.5): + par1min = 1.0 - parameter + + bez00 = self.ctrlPnt0 + bez01 = self.ctrlPnt1 + bez02 = self.ctrlPnt2 + bez03 = self.ctrlPnt3 + + bez10 = bez00 * par1min + bez01 * parameter + bez11 = bez01 * par1min + bez02 * parameter + bez12 = bez02 * par1min + bez03 * parameter + + bez20 = bez10 * par1min + bez11 * parameter + bez21 = bez11 * par1min + bez12 * parameter + + bez30 = bez20 * par1min + bez21 * parameter + + bezPoint1 = BezierPoint(self.bezierPoint1.handle_left, bez00, bez10) + bezPointNew = BezierPoint(bez20, bez30, bez21) + bezPoint2 = BezierPoint(bez12, bez03, self.bezierPoint2.handle_right) + + return [bezPoint1, bezPointNew, bezPoint2] + + +class BezierSpline: + @staticmethod + def FromSegments(listSegments): + rvSpline = BezierSpline(None) + + rvSpline.segments = listSegments + + return rvSpline + + + def __init__(self, blenderBezierSpline): + if not blenderBezierSpline is None: + if blenderBezierSpline.type != 'BEZIER': + print("## ERROR:", "blenderBezierSpline.type != 'BEZIER'") + raise Exception("blenderBezierSpline.type != 'BEZIER'") + if len(blenderBezierSpline.bezier_points) < 1: + if not blenderBezierSpline.use_cyclic_u: + print("## ERROR:", "len(blenderBezierSpline.bezier_points) < 1") + raise Exception("len(blenderBezierSpline.bezier_points) < 1") + + self.bezierSpline = blenderBezierSpline + + self.resolution = 12 + self.isCyclic = False + if not self.bezierSpline is None: + self.resolution = self.bezierSpline.resolution_u + self.isCyclic = self.bezierSpline.use_cyclic_u + + self.segments = self.SetupSegments() + + + def __getattr__(self, attrName): + if attrName == "nrSegments": + return len(self.segments) + + if attrName == "bezierPoints": + rvList = [] + + for seg in self.segments: rvList.append(seg.bezierPoint1) + if not self.isCyclic: rvList.append(self.segments[-1].bezierPoint2) + + return rvList + + if attrName == "resolutionPerSegment": + try: rvResPS = int(self.resolution / self.nrSegments) + except: rvResPS = 2 + if rvResPS < 2: rvResPS = 2 + + return rvResPS + + if attrName == "length": + return self.CalcLength() + + return None + + + def SetupSegments(self): + rvSegments = [] + if self.bezierSpline is None: return rvSegments + + nrBezierPoints = len(self.bezierSpline.bezier_points) + for iBezierPoint in range(nrBezierPoints - 1): + bezierPoint1 = self.bezierSpline.bezier_points[iBezierPoint] + bezierPoint2 = self.bezierSpline.bezier_points[iBezierPoint + 1] + rvSegments.append(BezierSegment.FromBlenderBezierPoints(bezierPoint1, bezierPoint2)) + if self.isCyclic: + bezierPoint1 = self.bezierSpline.bezier_points[-1] + bezierPoint2 = self.bezierSpline.bezier_points[0] + rvSegments.append(BezierSegment.FromBlenderBezierPoints(bezierPoint1, bezierPoint2)) + + return rvSegments + + + def UpdateSegments(self, newSegments): + prevNrSegments = len(self.segments) + diffNrSegments = len(newSegments) - prevNrSegments + if diffNrSegments > 0: + newBezierPoints = [] + for segment in newSegments: newBezierPoints.append(segment.bezierPoint1) + if not self.isCyclic: newBezierPoints.append(newSegments[-1].bezierPoint2) + + self.bezierSpline.bezier_points.add(diffNrSegments) + + for i, bezPoint in enumerate(newBezierPoints): + blBezPoint = self.bezierSpline.bezier_points[i] + + blBezPoint.tilt = 0 + blBezPoint.radius = 1.0 + + blBezPoint.handle_left_type = 'FREE' + blBezPoint.handle_left = bezPoint.handle_left + blBezPoint.co = bezPoint.co + blBezPoint.handle_right_type = 'FREE' + blBezPoint.handle_right = bezPoint.handle_right + + self.segments = newSegments + else: + print("### WARNING: UpdateSegments(): not diffNrSegments > 0") + + + def Reversed(self): + revSegments = [] + + for iSeg in reversed(range(self.nrSegments)): revSegments.append(self.segments[iSeg].Reversed()) + + rvSpline = BezierSpline.FromSegments(revSegments) + rvSpline.resolution = self.resolution + rvSpline.isCyclic = self.isCyclic + + return rvSpline + + + def Reverse(self): + revSegments = [] + + for iSeg in reversed(range(self.nrSegments)): + self.segments[iSeg].Reverse() + revSegments.append(self.segments[iSeg]) + + self.segments = revSegments + + + def CalcDivideResolution(self, segment, parameter): + if not segment in self.segments: + print("### WARNING: InsertPoint(): not segment in self.segments") + return None + + iSeg = self.segments.index(segment) + dPar = 1.0 / self.nrSegments + splinePar = dPar * (parameter + float(iSeg)) + + res1 = int(splinePar * self.resolution) + if res1 < 2: + print("### WARNING: CalcDivideResolution(): res1 < 2 -- res1: %d" % res1, "-- setting it to 2") + res1 = 2 + + res2 = int((1.0 - splinePar) * self.resolution) + if res2 < 2: + print("### WARNING: CalcDivideResolution(): res2 < 2 -- res2: %d" % res2, "-- setting it to 2") + res2 = 2 + + return [res1, res2] + # return [self.resolution, self.resolution] + + + def CalcPoint(self, parameter): + nrSegs = self.nrSegments + + segmentIndex = int(nrSegs * parameter) + if segmentIndex < 0: segmentIndex = 0 + if segmentIndex > (nrSegs - 1): segmentIndex = nrSegs - 1 + + segmentParameter = nrSegs * parameter - segmentIndex + if segmentParameter < 0.0: segmentParameter = 0.0 + if segmentParameter > 1.0: segmentParameter = 1.0 + + return self.segments[segmentIndex].CalcPoint(parameter = segmentParameter) + + + def CalcDerivative(self, parameter): + nrSegs = self.nrSegments + + segmentIndex = int(nrSegs * parameter) + if segmentIndex < 0: segmentIndex = 0 + if segmentIndex > (nrSegs - 1): segmentIndex = nrSegs - 1 + + segmentParameter = nrSegs * parameter - segmentIndex + if segmentParameter < 0.0: segmentParameter = 0.0 + if segmentParameter > 1.0: segmentParameter = 1.0 + + return self.segments[segmentIndex].CalcDerivative(parameter = segmentParameter) + + + def InsertPoint(self, segment, parameter): + if not segment in self.segments: + print("### WARNING: InsertPoint(): not segment in self.segments") + return + iSeg = self.segments.index(segment) + nrSegments = len(self.segments) + + splitPoints = segment.CalcSplitPoint(parameter = parameter) + bezPoint1 = splitPoints[0] + bezPointNew = splitPoints[1] + bezPoint2 = splitPoints[2] + + segment.bezierPoint1.handle_right = bezPoint1.handle_right + segment.bezierPoint2 = bezPointNew + + if iSeg < (nrSegments - 1): + nextSeg = self.segments[iSeg + 1] + nextSeg.bezierPoint1.handle_left = bezPoint2.handle_left + else: + if self.isCyclic: + nextSeg = self.segments[0] + nextSeg.bezierPoint1.handle_left = bezPoint2.handle_left + + + newSeg = BezierSegment(bezPointNew, bezPoint2) + self.segments.insert(iSeg + 1, newSeg) + + + def Split(self, segment, parameter): + if not segment in self.segments: + print("### WARNING: InsertPoint(): not segment in self.segments") + return None + iSeg = self.segments.index(segment) + nrSegments = len(self.segments) + + splitPoints = segment.CalcSplitPoint(parameter = parameter) + bezPoint1 = splitPoints[0] + bezPointNew = splitPoints[1] + bezPoint2 = splitPoints[2] + + + newSpline1Segments = [] + for iSeg1 in range(iSeg): newSpline1Segments.append(self.segments[iSeg1]) + if len(newSpline1Segments) > 0: newSpline1Segments[-1].bezierPoint2.handle_right = bezPoint1.handle_right + newSpline1Segments.append(BezierSegment(bezPoint1, bezPointNew)) + + newSpline2Segments = [] + newSpline2Segments.append(BezierSegment(bezPointNew, bezPoint2)) + for iSeg2 in range(iSeg + 1, nrSegments): newSpline2Segments.append(self.segments[iSeg2]) + if len(newSpline2Segments) > 1: newSpline2Segments[1].bezierPoint1.handle_left = newSpline2Segments[0].bezierPoint2.handle_left + + + newSpline1 = BezierSpline.FromSegments(newSpline1Segments) + newSpline2 = BezierSpline.FromSegments(newSpline2Segments) + + return [newSpline1, newSpline2] + + + def Join(self, spline2, mode = 'At midpoint'): + if mode == 'At midpoint': + self.JoinAtMidpoint(spline2) + return + + if mode == 'Insert segment': + self.JoinInsertSegment(spline2) + return + + print("### ERROR: Join(): unknown mode:", mode) + + + def JoinAtMidpoint(self, spline2): + bezPoint1 = self.segments[-1].bezierPoint2 + bezPoint2 = spline2.segments[0].bezierPoint1 + + mpHandleLeft = bezPoint1.handle_left.copy() + mpCo = (bezPoint1.co + bezPoint2.co) * 0.5 + mpHandleRight = bezPoint2.handle_right.copy() + mpBezPoint = BezierPoint(mpHandleLeft, mpCo, mpHandleRight) + + self.segments[-1].bezierPoint2 = mpBezPoint + spline2.segments[0].bezierPoint1 = mpBezPoint + for seg2 in spline2.segments: self.segments.append(seg2) + + self.resolution += spline2.resolution + self.isCyclic = False # is this ok? + + + def JoinInsertSegment(self, spline2): + 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 negligable + + self.isCyclic = False # is this ok? + + + def RefreshInScene(self): + bezierPoints = self.bezierPoints + + currNrBezierPoints = len(self.bezierSpline.bezier_points) + diffNrBezierPoints = len(bezierPoints) - currNrBezierPoints + if diffNrBezierPoints > 0: self.bezierSpline.bezier_points.add(diffNrBezierPoints) + + for i, bezPoint in enumerate(bezierPoints): + blBezPoint = self.bezierSpline.bezier_points[i] + + blBezPoint.tilt = 0 + blBezPoint.radius = 1.0 + + blBezPoint.handle_left_type = 'FREE' + blBezPoint.handle_left = bezPoint.handle_left + blBezPoint.co = bezPoint.co + blBezPoint.handle_right_type = 'FREE' + blBezPoint.handle_right = bezPoint.handle_right + + self.bezierSpline.use_cyclic_u = self.isCyclic + self.bezierSpline.resolution_u = self.resolution + + + def CalcLength(self): + try: nrSamplesPerSegment = int(self.resolution / self.nrSegments) + except: nrSamplesPerSegment = 2 + if nrSamplesPerSegment < 2: nrSamplesPerSegment = 2 + + rvLength = 0.0 + for segment in self.segments: + rvLength += segment.CalcLength(nrSamples = nrSamplesPerSegment) + + return rvLength + + + def GetLengthIsSmallerThan(self, threshold): + try: nrSamplesPerSegment = int(self.resolution / self.nrSegments) + except: nrSamplesPerSegment = 2 + if nrSamplesPerSegment < 2: nrSamplesPerSegment = 2 + + length = 0.0 + for segment in self.segments: + length += segment.CalcLength(nrSamples = nrSamplesPerSegment) + if not length < threshold: return False + + return True + + +class Curve: + def __init__(self, blenderCurve): + self.curve = blenderCurve + self.curveData = blenderCurve.data + + self.splines = self.SetupSplines() + + + def __getattr__(self, attrName): + if attrName == "nrSplines": + return len(self.splines) + + if attrName == "length": + return self.CalcLength() + + if attrName == "worldMatrix": + return self.curve.matrix_world + + if attrName == "location": + return self.curve.location + + return None + + + def SetupSplines(self): + rvSplines = [] + for spline in self.curveData.splines: + if spline.type != 'BEZIER': + print("## WARNING: only bezier splines are supported, atm; other types are ignored") + continue + + try: newSpline = BezierSpline(spline) + except: + print("## EXCEPTION: newSpline = BezierSpline(spline)") + continue + + rvSplines.append(newSpline) + + return rvSplines + + + def RebuildInScene(self): + self.curveData.splines.clear() + + for spline in self.splines: + blSpline = self.curveData.splines.new('BEZIER') + blSpline.use_cyclic_u = spline.isCyclic + blSpline.resolution_u = spline.resolution + + bezierPoints = [] + for segment in spline.segments: bezierPoints.append(segment.bezierPoint1) + if not spline.isCyclic: bezierPoints.append(spline.segments[-1].bezierPoint2) + #else: print("????", "spline.isCyclic") + + nrBezierPoints = len(bezierPoints) + blSpline.bezier_points.add(nrBezierPoints - 1) + + for i, blBezPoint in enumerate(blSpline.bezier_points): + bezPoint = bezierPoints[i] + + blBezPoint.tilt = 0 + blBezPoint.radius = 1.0 + + blBezPoint.handle_left_type = 'FREE' + blBezPoint.handle_left = bezPoint.handle_left + blBezPoint.co = bezPoint.co + blBezPoint.handle_right_type = 'FREE' + blBezPoint.handle_right = bezPoint.handle_right + + + def CalcLength(self): + rvLength = 0.0 + for spline in self.splines: + rvLength += spline.length + + return rvLength + + + def RemoveShortSplines(self, threshold): + splinesToRemove = [] + + for spline in self.splines: + if spline.GetLengthIsSmallerThan(threshold): splinesToRemove.append(spline) + + for spline in splinesToRemove: self.splines.remove(spline) + + return len(splinesToRemove) + + + def JoinNeighbouringSplines(self, startEnd, threshold, mode): + nrJoins = 0 + + while True: + firstPair = self.JoinGetFirstPair(startEnd, threshold) + if firstPair is None: break + + firstPair[0].Join(firstPair[1], mode) + self.splines.remove(firstPair[1]) + + nrJoins += 1 + + return nrJoins + + + def JoinGetFirstPair(self, startEnd, threshold): + nrSplines = len(self.splines) + + if startEnd: + for iCurrentSpline in range(nrSplines): + currentSpline = self.splines[iCurrentSpline] + + for iNextSpline in range(iCurrentSpline + 1, nrSplines): + nextSpline = self.splines[iNextSpline] + + currEndPoint = currentSpline.segments[-1].bezierPoint2.co + nextStartPoint = nextSpline.segments[0].bezierPoint1.co + if mathematics.IsSamePoint(currEndPoint, nextStartPoint, threshold): return [currentSpline, nextSpline] + + nextEndPoint = nextSpline.segments[-1].bezierPoint2.co + currStartPoint = currentSpline.segments[0].bezierPoint1.co + if mathematics.IsSamePoint(nextEndPoint, currStartPoint, threshold): return [nextSpline, currentSpline] + + return None + else: + for iCurrentSpline in range(nrSplines): + currentSpline = self.splines[iCurrentSpline] + + for iNextSpline in range(iCurrentSpline + 1, nrSplines): + nextSpline = self.splines[iNextSpline] + + currEndPoint = currentSpline.segments[-1].bezierPoint2.co + nextStartPoint = nextSpline.segments[0].bezierPoint1.co + if mathematics.IsSamePoint(currEndPoint, nextStartPoint, threshold): return [currentSpline, nextSpline] + + nextEndPoint = nextSpline.segments[-1].bezierPoint2.co + currStartPoint = currentSpline.segments[0].bezierPoint1.co + if mathematics.IsSamePoint(nextEndPoint, currStartPoint, threshold): return [nextSpline, currentSpline] + + if mathematics.IsSamePoint(currEndPoint, nextEndPoint, threshold): + nextSpline.Reverse() + #print("## ", "nextSpline.Reverse()") + return [currentSpline, nextSpline] + + if mathematics.IsSamePoint(currStartPoint, nextStartPoint, threshold): + currentSpline.Reverse() + #print("## ", "currentSpline.Reverse()") + return [currentSpline, nextSpline] + + return None diff --git a/curve_tools/operatorss.py b/curve_tools/operatorss.py new file mode 100644 index 00000000..e4fe24dd --- /dev/null +++ b/curve_tools/operatorss.py @@ -0,0 +1,1056 @@ +import time +import threading + +import bpy +from bpy.props import * +from bpy_extras import object_utils, view3d_utils +from mathutils import * +from math import * + +from . import properties +from . import curves +from . import intersections +from . import util +from . import surfaces +from . import mathematics + +# 1 CURVE SELECTED +# ################ +class OperatorCurveInfo(bpy.types.Operator): + bl_idname = "curvetools.operatorcurveinfo" + bl_label = "Info" + bl_description = "Displays general info about the active/selected curve" + + + @classmethod + def poll(cls, context): + return util.Selected1Curve() + + + def execute(self, context): + curve = curves.Curve(context.active_object) + + nrSplines = len(curve.splines) + nrSegments = 0 + nrEmptySplines = 0 + for spline in curve.splines: + nrSegments += spline.nrSegments + if spline.nrSegments < 1: nrEmptySplines += 1 + + + self.report({'INFO'}, "nrSplines: %d; nrSegments: %d; nrEmptySplines: %d" % (nrSplines, nrSegments, nrEmptySplines)) + + return {'FINISHED'} + + + +class OperatorCurveLength(bpy.types.Operator): + bl_idname = "curvetools.operatorcurvelength" + bl_label = "Length" + bl_description = "Calculates the length of the active/selected curve" + + + @classmethod + def poll(cls, context): + return util.Selected1Curve() + + + def execute(self, context): + curve = curves.Curve(context.active_object) + + context.scene.curvetools.CurveLength = curve.length + + return {'FINISHED'} + + + +class OperatorSplinesInfo(bpy.types.Operator): + bl_idname = "curvetools.operatorsplinesinfo" + bl_label = "Info" + bl_description = "Displays general info about the splines of the active/selected curve" + + + @classmethod + def poll(cls, context): + return util.Selected1Curve() + + + def execute(self, context): + curve = curves.Curve(context.active_object) + nrSplines = len(curve.splines) + + print("") + print("OperatorSplinesInfo:", "nrSplines:", nrSplines) + + nrEmptySplines = 0 + for iSpline, spline in enumerate(curve.splines): + print("--", "spline %d of %d: nrSegments: %d" % (iSpline + 1, nrSplines, spline.nrSegments)) + + if spline.nrSegments < 1: + nrEmptySplines += 1 + print("--", "--", "## WARNING: spline has no segments and will therefor be ignored in any further calculations") + + + self.report({'INFO'}, "nrSplines: %d; nrEmptySplines: %d" % (nrSplines, nrEmptySplines) + " -- more info: see console") + + return {'FINISHED'} + + + +class OperatorSegmentsInfo(bpy.types.Operator): + bl_idname = "curvetools.operatorsegmentsinfo" + bl_label = "Info" + bl_description = "Displays general info about the segments of the active/selected curve" + + + @classmethod + def poll(cls, context): + return util.Selected1Curve() + + + def execute(self, context): + curve = curves.Curve(context.active_object) + nrSplines = len(curve.splines) + nrSegments = 0 + + print("") + print("OperatorSegmentsInfo:", "nrSplines:", nrSplines) + + nrEmptySplines = 0 + for iSpline, spline in enumerate(curve.splines): + nrSegmentsSpline = spline.nrSegments + print("--", "spline %d of %d: nrSegments: %d" % (iSpline + 1, nrSplines, nrSegmentsSpline)) + + if nrSegmentsSpline < 1: + nrEmptySplines += 1 + print("--", "--", "## WARNING: spline has no segments and will therefor be ignored in any further calculations") + continue + + for iSegment, segment in enumerate(spline.segments): + print("--", "--", "segment %d of %d coefficients:" % (iSegment + 1, nrSegmentsSpline)) + print("--", "--", "--", "C0: %.6f, %.6f, %.6f" % (segment.coeff0.x, segment.coeff0.y, segment.coeff0.z)) + + nrSegments += nrSegmentsSpline + + self.report({'INFO'}, "nrSplines: %d; nrSegments: %d; nrEmptySplines: %d" % (nrSplines, nrSegments, nrEmptySplines)) + + return {'FINISHED'} + + + +class OperatorOriginToSpline0Start(bpy.types.Operator): + bl_idname = "curvetools.operatororigintospline0start" + bl_label = "OriginToSpline0Start" + bl_description = "Sets the origin of the active/selected curve to the starting point of the (first) spline. Nice for curve modifiers." + + + @classmethod + def poll(cls, context): + return util.Selected1Curve() + + + def execute(self, context): + + + blCurve = context.active_object + blSpline = blCurve.data.splines[0] + newOrigin = blCurve.matrix_world @ blSpline.bezier_points[0].co + + origOrigin = bpy.context.scene.cursor.location.copy() + self.report({'INFO'}, "origOrigin: %.6f, %.6f, %.6f" % (origOrigin.x, origOrigin.y, origOrigin.z)) + self.report({'INFO'}, "newOrigin: %.6f, %.6f, %.6f" % (newOrigin.x, newOrigin.y, newOrigin.z)) + + current_mode = bpy.context.object.mode + + bpy.ops.object.mode_set(mode = 'OBJECT') + bpy.context.scene.cursor.location = newOrigin + bpy.ops.object.origin_set(type='ORIGIN_CURSOR') + bpy.context.scene.cursor.location = origOrigin + + bpy.ops.object.mode_set (mode = current_mode) + + return {'FINISHED'} + + + +# 2 CURVES SELECTED +# ################# +class OperatorIntersectCurves(bpy.types.Operator): + bl_idname = "curvetools.operatorintersectcurves" + bl_label = "Intersect" + bl_description = "Intersects selected curves" + + + @classmethod + def poll(cls, context): + return util.Selected2OrMoreCurves() + + + def execute(self, context): + print("### TODO: OperatorIntersectcurves.execute()") + + algo = context.scene.curvetools.IntersectCurvesAlgorithm + print("-- algo:", algo) + + + mode = context.scene.curvetools.IntersectCurvesMode + print("-- mode:", mode) + # if mode == 'Split': + # self.report({'WARNING'}, "'Split' mode is not implemented yet -- <>") + # return {'CANCELLED'} + + affect = context.scene.curvetools.IntersectCurvesAffect + print("-- affect:", affect) + + selected_objects = context.selected_objects + lenodjs = len(selected_objects) + print('lenodjs:', lenodjs) + for i in range(0, lenodjs): + for j in range(0, lenodjs): + if j != i: + bpy.ops.object.select_all(action='DESELECT') + selected_objects[i].select_set(True) + selected_objects[j].select_set(True) + + if selected_objects[i].type == 'CURVE' and selected_objects[j].type == 'CURVE': + curveIntersector = intersections.CurvesIntersector.FromSelection() + rvIntersectionNrs = curveIntersector.CalcAndApplyIntersections() + + self.report({'INFO'}, "Active curve points: %d; other curve points: %d" % (rvIntersectionNrs[0], rvIntersectionNrs[1])) + + for obj in selected_objects: + obj.select_set(True) + + return {'FINISHED'} + +# ------------------------------------------------------------ +# OperatorLoftCurves + +class OperatorLoftCurves(bpy.types.Operator): + bl_idname = "curvetools.operatorloftcurves" + bl_label = "Loft" + bl_description = "Lofts selected curves" + + + @classmethod + def poll(cls, context): + return util.Selected2Curves() + + + def execute(self, context): + #print("### TODO: OperatorLoftcurves.execute()") + + loftedSurface = surfaces.LoftedSurface.FromSelection() + loftedSurface.AddToScene() + + self.report({'INFO'}, "OperatorLoftcurves.execute()") + + return {'FINISHED'} + + +# ------------------------------------------------------------ +# OperatorSweepCurves + +class OperatorSweepCurves(bpy.types.Operator): + bl_idname = "curvetools.operatorsweepcurves" + bl_label = "Sweep" + bl_description = "Sweeps the active curve along to other curve (rail)" + + + @classmethod + def poll(cls, context): + return util.Selected2Curves() + + + def execute(self, context): + #print("### TODO: OperatorSweepcurves.execute()") + + sweptSurface = surfaces.SweptSurface.FromSelection() + sweptSurface.AddToScene() + + self.report({'INFO'}, "OperatorSweepcurves.execute()") + + return {'FINISHED'} + + + +# 3 CURVES SELECTED +# ################# +class OperatorBirail(bpy.types.Operator): + bl_idname = "curvetools.operatorbirail" + bl_label = "Birail" + bl_description = "Generates a birailed surface from 3 selected curves -- in order: rail1, rail2 and profile" + + + @classmethod + def poll(cls, context): + return util.Selected3Curves() + + + def execute(self, context): + birailedSurface = surfaces.BirailedSurface.FromSelection() + birailedSurface.AddToScene() + + self.report({'INFO'}, "OperatorBirail.execute()") + + return {'FINISHED'} + + + +# 1 OR MORE CURVES SELECTED +# ######################### +class OperatorSplinesSetResolution(bpy.types.Operator): + bl_idname = "curvetools.operatorsplinessetresolution" + bl_label = "SplinesSetResolution" + bl_description = "Sets the resolution of all splines" + + + @classmethod + def poll(cls, context): + return util.Selected1OrMoreCurves() + + + def execute(self, context): + splRes = context.scene.curvetools.SplineResolution + selCurves = util.GetSelectedCurves() + + for blCurve in selCurves: + for spline in blCurve.data.splines: + spline.resolution_u = splRes + + return {'FINISHED'} + +# ------------------------------------------------------------ +# OperatorSplinesRemoveZeroSegment + +class OperatorSplinesRemoveZeroSegment(bpy.types.Operator): + bl_idname = "curvetools.operatorsplinesremovezerosegment" + bl_label = "SplinesRemoveZeroSegment" + bl_description = "Removes splines with no segments -- they seem to creep up, sometimes.." + + + @classmethod + def poll(cls, context): + return util.Selected1OrMoreCurves() + + + def execute(self, context): + selCurves = util.GetSelectedCurves() + + for blCurve in selCurves: + curve = curves.Curve(blCurve) + nrSplines = curve.nrSplines + + splinesToRemove = [] + for spline in curve.splines: + if len(spline.segments) < 1: splinesToRemove.append(spline) + nrRemovedSplines = len(splinesToRemove) + + for spline in splinesToRemove: curve.splines.remove(spline) + + if nrRemovedSplines > 0: curve.RebuildInScene() + + self.report({'INFO'}, "Removed %d of %d splines" % (nrRemovedSplines, nrSplines)) + + return {'FINISHED'} + +# ------------------------------------------------------------ +# OperatorSplinesRemoveShort + +class OperatorSplinesRemoveShort(bpy.types.Operator): + bl_idname = "curvetools.operatorsplinesremoveshort" + bl_label = "SplinesRemoveShort" + bl_description = "Removes splines with a length smaller than the threshold" + + + @classmethod + def poll(cls, context): + return util.Selected1OrMoreCurves() + + + def execute(self, context): + threshold = context.scene.curvetools.SplineRemoveLength + selCurves = util.GetSelectedCurves() + + for blCurve in selCurves: + curve = curves.Curve(blCurve) + nrSplines = curve.nrSplines + + nrRemovedSplines = curve.RemoveShortSplines(threshold) + if nrRemovedSplines > 0: curve.RebuildInScene() + + self.report({'INFO'}, "Removed %d of %d splines" % (nrRemovedSplines, nrSplines)) + + return {'FINISHED'} + +# ------------------------------------------------------------ +# OperatorSplinesJoinNeighbouring + +class OperatorSplinesJoinNeighbouring(bpy.types.Operator): + bl_idname = "curvetools.operatorsplinesjoinneighbouring" + bl_label = "SplinesJoinNeighbouring" + bl_description = "Joins neighbouring splines within a distance smaller than the threshold" + + + @classmethod + def poll(cls, context): + return util.Selected1OrMoreCurves() + + + def execute(self, context): + selCurves = util.GetSelectedCurves() + + for blCurve in selCurves: + curve = curves.Curve(blCurve) + nrSplines = curve.nrSplines + + threshold = context.scene.curvetools.SplineJoinDistance + startEnd = context.scene.curvetools.SplineJoinStartEnd + mode = context.scene.curvetools.SplineJoinMode + + nrJoins = curve.JoinNeighbouringSplines(startEnd, threshold, mode) + if nrJoins > 0: curve.RebuildInScene() + + self.report({'INFO'}, "Applied %d joins on %d splines; resulting nrSplines: %d" % (nrJoins, nrSplines, curve.nrSplines)) + + return {'FINISHED'} + +# ------------------------------------------------------------ +# SurfaceFromBezier + +def SurfaceFromBezier(surfacedata, points, center): + + len_points = len(points) - 1 + + if len_points % 2 == 0: + h = mathematics.subdivide_cubic_bezier( + points[len_points].co, points[len_points].handle_right, + points[0].handle_left, points[0].co, 0.5 + ) + points.add(1) + len_points = len(points) - 1 + points[len_points - 1].handle_right = h[0] + points[len_points].handle_left = h[1] + points[len_points].co = h[2] + points[len_points].handle_right = h[3] + points[0].handle_left = h[4] + + half = round((len_points + 1)/2) - 1 + # 1 + surfacespline1 = surfacedata.splines.new(type='NURBS') + surfacespline1.points.add(3) + surfacespline1.points[0].co = [points[0].co.x, points[0].co.y, points[0].co.z, 1] + surfacespline1.points[1].co = [points[0].handle_left.x, points[0].handle_left.y, points[0].handle_left.z, 1] + surfacespline1.points[2].co = [points[len_points].handle_right.x,points[len_points].handle_right.y, points[len_points].handle_right.z, 1] + surfacespline1.points[3].co = [points[len_points].co.x, points[len_points].co.y, points[len_points].co.z, 1] + for p in surfacespline1.points: + p.select = True + surfacespline1.use_endpoint_u = True + surfacespline1.use_endpoint_v = True + + for i in range(0, half): + + if center: + # 2 + surfacespline2 = surfacedata.splines.new(type='NURBS') + surfacespline2.points.add(3) + surfacespline2.points[0].co = [points[i].co.x, points[i].co.y, points[i].co.z, 1] + surfacespline2.points[1].co = [(points[i].co.x + points[len_points - i].co.x)/2, + (points[i].co.y + points[len_points - i].co.y)/2, + (points[i].co.z + points[len_points - i].co.z)/2, 1] + surfacespline2.points[2].co = [(points[len_points - i].co.x + points[i].co.x)/2, + (points[len_points - i].co.y + points[i].co.y)/2, + (points[len_points - i].co.z + points[i].co.z)/2, 1] + surfacespline2.points[3].co = [points[len_points - i].co.x, points[len_points - i].co.y, points[len_points - i].co.z, 1] + for p in surfacespline2.points: + p.select = True + surfacespline2.use_endpoint_u = True + surfacespline2.use_endpoint_v = True + + # 3 + surfacespline3 = surfacedata.splines.new(type='NURBS') + surfacespline3.points.add(3) + surfacespline3.points[0].co = [points[i].handle_right.x, points[i].handle_right.y, points[i].handle_right.z, 1] + surfacespline3.points[1].co = [(points[i].handle_right.x + points[len_points - i].handle_left.x)/2, + (points[i].handle_right.y + points[len_points - i].handle_left.y)/2, + (points[i].handle_right.z + points[len_points - i].handle_left.z)/2, 1] + surfacespline3.points[2].co = [(points[len_points - i].handle_left.x + points[i].handle_right.x)/2, + (points[len_points - i].handle_left.y + points[i].handle_right.y)/2, + (points[len_points - i].handle_left.z + points[i].handle_right.z)/2, 1] + surfacespline3.points[3].co = [points[len_points - i].handle_left.x, points[len_points - i].handle_left.y, points[len_points - i].handle_left.z, 1] + for p in surfacespline3.points: + p.select = True + surfacespline3.use_endpoint_u = True + surfacespline3.use_endpoint_v = True + + # 4 + surfacespline4 = surfacedata.splines.new(type='NURBS') + surfacespline4.points.add(3) + surfacespline4.points[0].co = [points[i + 1].handle_left.x, points[i + 1].handle_left.y, points[i + 1].handle_left.z, 1] + surfacespline4.points[1].co = [(points[i + 1].handle_left.x + points[len_points - i - 1].handle_right.x)/2, + (points[i + 1].handle_left.y + points[len_points - i - 1].handle_right.y)/2, + (points[i + 1].handle_left.z + points[len_points - i - 1].handle_right.z)/2, 1] + surfacespline4.points[2].co = [(points[len_points - i - 1].handle_right.x + points[i + 1].handle_left.x)/2, + (points[len_points - i - 1].handle_right.y + points[i + 1].handle_left.y)/2, + (points[len_points - i - 1].handle_right.z + points[i + 1].handle_left.z)/2, 1] + surfacespline4.points[3].co = [points[len_points - i - 1].handle_right.x, points[len_points - i - 1].handle_right.y, points[len_points - i - 1].handle_right.z, 1] + for p in surfacespline4.points: + p.select = True + surfacespline4.use_endpoint_u = True + surfacespline4.use_endpoint_v = True + + if center: + # 5 + surfacespline5 = surfacedata.splines.new(type='NURBS') + surfacespline5.points.add(3) + surfacespline5.points[0].co = [points[i + 1].co.x, points[i + 1].co.y, points[i + 1].co.z, 1] + surfacespline5.points[1].co = [(points[i + 1].co.x + points[len_points - i - 1].co.x)/2, + (points[i + 1].co.y + points[len_points - i - 1].co.y)/2, + (points[i + 1].co.z + points[len_points - i - 1].co.z)/2, 1] + surfacespline5.points[2].co = [(points[len_points - i - 1].co.x + points[i + 1].co.x)/2, + (points[len_points - i - 1].co.y + points[i + 1].co.y)/2, + (points[len_points - i - 1].co.z + points[i + 1].co.z)/2, 1] + surfacespline5.points[3].co = [points[len_points - i - 1].co.x, points[len_points - i - 1].co.y, points[len_points - i - 1].co.z, 1] + for p in surfacespline5.points: + p.select = True + surfacespline5.use_endpoint_u = True + surfacespline5.use_endpoint_v = True + + # 6 + surfacespline6 = surfacedata.splines.new(type='NURBS') + surfacespline6.points.add(3) + surfacespline6.points[0].co = [points[half].co.x, points[half].co.y, points[half].co.z, 1] + surfacespline6.points[1].co = [points[half].handle_right.x, points[half].handle_right.y, points[half].handle_right.z, 1] + surfacespline6.points[2].co = [points[half+1].handle_left.x, points[half+1].handle_left.y, points[half+1].handle_left.z, 1] + surfacespline6.points[3].co = [points[half+1].co.x, points[half+1].co.y, points[half+1].co.z, 1] + for p in surfacespline6.points: + p.select = True + surfacespline6.use_endpoint_u = True + surfacespline6.use_endpoint_v = True + + bpy.ops.object.mode_set(mode = 'EDIT') + bpy.ops.curve.make_segment() + + for s in surfacedata.splines: + s.resolution_u = 4 + s.resolution_v = 4 + s.order_u = 4 + s.order_v = 4 + for p in s.points: + p.select = False + +# ------------------------------------------------------------ +# Convert selected faces to Bezier + +class ConvertSelectedFacesToBezier(bpy.types.Operator): + bl_idname = "curvetools.convert_selected_face_to_bezier" + bl_label = "Convert selected faces to Bezier" + bl_description = "Convert selected faces to Bezier" + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(cls, context): + return util.Selected1Mesh() + + def execute(self, context): + # main function + bpy.ops.object.mode_set(mode = 'OBJECT') + active_object = context.active_object + meshdata = active_object.data + curvedata = bpy.data.curves.new('Curve' + active_object.name, type='CURVE') + curveobject = object_utils.object_data_add(context, curvedata) + curvedata.dimensions = '3D' + + for poly in meshdata.polygons: + if poly.select: + newSpline = curvedata.splines.new(type='BEZIER') + newSpline.use_cyclic_u = True + newSpline.bezier_points.add(poly.loop_total - 1) + npoint = 0 + for loop_index in range(poly.loop_start, poly.loop_start + poly.loop_total): + newSpline.bezier_points[npoint].co = meshdata.vertices[meshdata.loops[loop_index].vertex_index].co + newSpline.bezier_points[npoint].handle_left_type = 'VECTOR' + newSpline.bezier_points[npoint].handle_right_type = 'VECTOR' + newSpline.bezier_points[npoint].select_control_point = True + newSpline.bezier_points[npoint].select_left_handle = True + newSpline.bezier_points[npoint].select_right_handle = True + npoint += 1 + + return {'FINISHED'} + +# ------------------------------------------------------------ +# Convert Bezier to Surface + +class ConvertBezierToSurface(bpy.types.Operator): + bl_idname = "curvetools.convert_bezier_to_surface" + bl_label = "Convert Bezier to Surface" + bl_description = "Convert Bezier to Surface" + bl_options = {'REGISTER', 'UNDO'} + + Center : BoolProperty( + name="Center", + default=False, + description="Consider center points" + ) + + Resolution_U: IntProperty( + name="Resolution_U", + default=4, + min=1, max=64, + soft_min=1, + description="Surface resolution U" + ) + + Resolution_V: IntProperty( + name="Resolution_V", + default=4, + min=1, max=64, + soft_min=1, + description="Surface resolution V" + ) + + def draw(self, context): + layout = self.layout + + # general options + col = layout.column() + col.prop(self, 'Center') + col.prop(self, 'Resolution_U') + col.prop(self, 'Resolution_V') + + @classmethod + def poll(cls, context): + return util.Selected1OrMoreCurves() + + def execute(self, context): + # main function + bpy.ops.object.mode_set(mode = 'OBJECT') + active_object = context.active_object + curvedata = active_object.data + + surfacedata = bpy.data.curves.new('Surface', type='SURFACE') + surfaceobject = object_utils.object_data_add(context, surfacedata) + surfaceobject.matrix_world = active_object.matrix_world + surfaceobject.rotation_euler = active_object.rotation_euler + surfacedata.dimensions = '3D' + surfaceobject.show_wire = True + surfaceobject.show_in_front = True + + for spline in curvedata.splines: + SurfaceFromBezier(surfacedata, spline.bezier_points, self.Center) + + for spline in surfacedata.splines: + len_p = len(spline.points) + len_devide_4 = round(len_p / 4) + 1 + len_devide_2 = round(len_p / 2) + bpy.ops.object.mode_set(mode = 'EDIT') + for point_index in range(len_devide_4, len_p - len_devide_4): + if point_index != len_devide_2 and point_index != len_devide_2 - 1: + spline.points[point_index].select = True + + surfacedata.resolution_u = self.Resolution_U + surfacedata.resolution_v = self.Resolution_V + + return {'FINISHED'} + +# ------------------------------------------------------------ +# Fillet + +class BezierPointsFillet(bpy.types.Operator): + bl_idname = "curvetools.bezier_points_fillet" + bl_label = "Bezier points Fillet" + bl_description = "Bezier points Fillet" + bl_options = {'REGISTER', 'UNDO', 'PRESET'} + + Fillet_radius : FloatProperty( + name="Radius", + default=0.25, + unit='LENGTH', + description="Radius" + ) + Types = [('Round', "Round", "Round"), + ('Chamfer', "Chamfer", "Chamfer")] + Fillet_Type : EnumProperty( + name="Type", + description="Fillet type", + items=Types + ) + + def draw(self, context): + layout = self.layout + + # general options + col = layout.column() + col.prop(self, "Fillet_radius") + col.prop(self, "Fillet_Type", expand=True) + + @classmethod + def poll(cls, context): + return util.Selected1OrMoreCurves() + + def execute(self, context): + # main function + if bpy.ops.object.mode_set.poll(): + bpy.ops.object.mode_set(mode='EDIT') + + splines = bpy.context.object.data.splines + bpy.ops.curve.spline_type_set(type='BEZIER') + + bpy.ops.curve.handle_type_set(type='VECTOR') + s = [] + for spline in splines: + n = 0 + ii = [] + for p in spline.bezier_points: + if p.select_control_point: + ii.append(n) + n += 1 + else: + n += 1 + s.append(ii) + + sn = 0 + for spline in splines: + ii = s[sn] + bezier_points = spline.bezier_points + n = len(bezier_points) + if n > 2: + jn = 0 + for j in ii: + j += jn + + bpy.ops.curve.select_all(action='DESELECT') + + if j != 0 and j != n - 1: + bezier_points[j].select_control_point = True + bezier_points[j + 1].select_control_point = True + bpy.ops.curve.subdivide() + selected4 = [bezier_points[j - 1], bezier_points[j], + bezier_points[j + 1], bezier_points[j + 2]] + jn += 1 + n += 1 + + elif j == 0: + bezier_points[j].select_control_point = True + bezier_points[j + 1].select_control_point = True + bpy.ops.curve.subdivide() + selected4 = [bezier_points[n], bezier_points[0], + bezier_points[1], bezier_points[2]] + jn += 1 + n += 1 + + elif j == n - 1: + bezier_points[j].select_control_point = True + bezier_points[j - 1].select_control_point = True + bpy.ops.curve.subdivide() + selected4 = [bezier_points[0], bezier_points[n], + bezier_points[n - 1], bezier_points[n - 2]] + + selected4[2].co = selected4[1].co + s1 = Vector(selected4[0].co) - Vector(selected4[1].co) + s2 = Vector(selected4[3].co) - Vector(selected4[2].co) + s1.normalize() + s11 = Vector(selected4[1].co) + s1 * self.Fillet_radius + selected4[1].co = s11 + s2.normalize() + s22 = Vector(selected4[2].co) + s2 * self.Fillet_radius + selected4[2].co = s22 + + if self.Fillet_Type == 'Round': + if j != n - 1: + selected4[2].handle_right_type = 'VECTOR' + selected4[1].handle_left_type = 'VECTOR' + selected4[1].handle_right_type = 'ALIGNED' + selected4[2].handle_left_type = 'ALIGNED' + else: + selected4[1].handle_right_type = 'VECTOR' + selected4[2].handle_left_type = 'VECTOR' + selected4[2].handle_right_type = 'ALIGNED' + selected4[1].handle_left_type = 'ALIGNED' + if self.Fillet_Type == 'Chamfer': + selected4[2].handle_right_type = 'VECTOR' + selected4[1].handle_left_type = 'VECTOR' + selected4[1].handle_right_type = 'VECTOR' + selected4[2].handle_left_type = 'VECTOR' + sn += 1 + + return {'FINISHED'} + +# ------------------------------------------------------------ +# BezierDivide Operator + +class BezierDivide(bpy.types.Operator): + bl_idname = "curvetools.bezier_spline_divide" + bl_label = "Bezier Spline Divide" + bl_description = "Bezier Divide (enters edit mode) for Fillet Curves" + bl_options = {'REGISTER', 'UNDO'} + + # align_matrix for the invoke + align_matrix : Matrix() + + Bezier_t : FloatProperty( + name="t (0% - 100%)", + default=50.0, + min=0.0, soft_min=0.0, + max=100.0, soft_max=100.0, + description="t (0% - 100%)" + ) + + @classmethod + def poll(cls, context): + return util.Selected1OrMoreCurves() + + def execute(self, context): + # main function + if bpy.ops.object.mode_set.poll(): + bpy.ops.object.mode_set(mode='EDIT') + + splines = bpy.context.object.data.splines + s = [] + for spline in splines: + bpy.ops.curve.spline_type_set(type='BEZIER') + + n = 0 + ii = [] + for p in spline.bezier_points: + if p.select_control_point: + ii.append(n) + n += 1 + else: + n += 1 + s.append(ii) + + sn = 0 + for spline in splines: + ii = s[sn] + bezier_points = spline.bezier_points + n = len(bezier_points) + if n > 2: + jn = 0 + for j in ii: + + bpy.ops.curve.select_all(action='DESELECT') + + 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 = 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 + ) + bpy.ops.curve.subdivide(1) + bezier_points[j + jn].handle_right_type = 'FREE' + bezier_points[j + jn].handle_right = h[0] + bezier_points[j + 1 + jn].co = h[2] + bezier_points[j + 1 + jn].handle_left_type = 'FREE' + bezier_points[j + 1 + jn].handle_left = h[1] + bezier_points[j + 1 + jn].handle_right_type = 'FREE' + bezier_points[j + 1 + jn].handle_right = h[3] + bezier_points[j + 2 + jn].handle_left_type = 'FREE' + bezier_points[j + 2 + jn].handle_left = h[4] + jn += 1 + + 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 = 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 + ) + bpy.ops.curve.subdivide(1) + bezier_points[j + jn].handle_right_type = 'FREE' + bezier_points[j + jn].handle_right = h[0] + bezier_points[j + 1 + jn].co = h[2] + bezier_points[j + 1 + jn].handle_left_type = 'FREE' + bezier_points[j + 1 + jn].handle_left = h[1] + bezier_points[j + 1 + jn].handle_right_type = 'FREE' + bezier_points[j + 1 + jn].handle_right = h[3] + bezier_points[0].handle_left_type = 'FREE' + bezier_points[0].handle_left = h[4] + + sn += 1 + + return {'FINISHED'} + +# ------------------------------------------------------------ +# CurveScaleReset Operator + +class CurveScaleReset(bpy.types.Operator): + bl_idname = "curvetools.scale_reset" + bl_label = "Curve Scale Reset" + bl_description = "Curve Scale Reset" + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(cls, context): + return (context.object is not None and + context.object.type == 'CURVE') + + def execute(self, context): + # main function + current_mode = bpy.context.object.mode + + bpy.ops.object.mode_set(mode = 'OBJECT') + + oldCurve = context.active_object + oldCurveName = oldCurve.name + + bpy.ops.object.duplicate_move(OBJECT_OT_duplicate=None, TRANSFORM_OT_translate=None) + newCurve = context.active_object + newCurve.data.splines.clear() + newCurve.scale = (1.0, 1.0, 1.0) + + oldCurve.select_set(True) + newCurve.select_set(True) + bpy.context.view_layer.objects.active = newCurve + bpy.ops.object.join() + + joinCurve = context.active_object + joinCurve.name = oldCurveName + + bpy.ops.object.mode_set (mode = current_mode) + + return {'FINISHED'} + +# ------------------------------------------------------------ +# Split Operator + +class Split(bpy.types.Operator): + bl_idname = "curvetools.split" + bl_label = "Split" + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(cls, context): + return util.Selected1OrMoreCurves() + + def execute(self, context): + selected_Curves = util.GetSelectedCurves() + + for curve in selected_Curves: + spline_points = [] + select_points = {} + bezier_spline_points = [] + select_bezier_points = {} + i_bp = 0 + i_p = 0 + for spline in curve.data.splines: + if spline.type == 'BEZIER': + points = {} + select_bezier_points[i_bp] = [len(spline.bezier_points)] + for i in range(len(spline.bezier_points)): + bezier_point = spline.bezier_points[i] + points[i]=[bezier_point.co[:], bezier_point.handle_left[:], bezier_point.handle_right[:]] + + if spline.bezier_points[i].select_control_point: + select_bezier_points[i_bp].append(i) + i_bp+=1 + bezier_spline_points.append(points) + else: + points = {} + select_points[i_p] = [len(spline.points)] + for i in range(len(spline.points)): + point = spline.points[i] + points[i]=[point.co[:], spline.type] + if spline.points[i].select: + select_points[i_p].append(i) + i_p+=1 + spline_points.append(points) + + curve.data.splines.clear() + + for key in select_bezier_points: + + num=0 + + if select_bezier_points[key][-1] == select_bezier_points[key][0]-1: + select_bezier_points[key].pop() + + for i in select_bezier_points[key][1:]+[select_bezier_points[key][0]-1]: + if i != 0: + spline = curve.data.splines.new('BEZIER') + spline.bezier_points.add(i-num) + + for j in range(num, i): + bezier_point = spline.bezier_points[j-num] + + bezier_point.co = bezier_spline_points[key][j][0] + bezier_point.handle_left = bezier_spline_points[key][j][1] + bezier_point.handle_right = bezier_spline_points[key][j][2] + bezier_point = spline.bezier_points[-1] + bezier_point.co = bezier_spline_points[key][i][0] + bezier_point.handle_left = bezier_spline_points[key][i][1] + bezier_point.handle_right = bezier_spline_points[key][i][2] + num=i + + for key in select_points: + + num=0 + + if select_points[key][-1] == select_points[key][0]-1: + select_points[key].pop() + + for i in select_points[key][1:]+[select_points[key][0]-1]: + if i != 0: + spline = curve.data.splines.new(spline_points[key][i][1]) + spline.points.add(i-num) + + for j in range(num, i): + point = spline.points[j-num] + + point.co = spline_points[key][j][0] + point = spline.points[-1] + point.co = spline_points[key][i][0] + 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, + OperatorCurveLength, + OperatorSplinesInfo, + OperatorSegmentsInfo, + OperatorOriginToSpline0Start, + OperatorIntersectCurves, + OperatorLoftCurves, + OperatorSweepCurves, + OperatorBirail, + OperatorSplinesSetResolution, + OperatorSplinesRemoveZeroSegment, + OperatorSplinesRemoveShort, + OperatorSplinesJoinNeighbouring, + ConvertSelectedFacesToBezier, + ConvertBezierToSurface, + BezierPointsFillet, + BezierDivide, + CurveScaleReset, + Split, + SeparateOutline, + ] diff --git a/curve_tools/propertiess.py b/curve_tools/propertiess.py new file mode 100644 index 00000000..d6fe9e0f --- /dev/null +++ b/curve_tools/propertiess.py @@ -0,0 +1,104 @@ +import time + +import bpy +from bpy.props import * + + + +class curvetoolsSelectedObjectHeader(bpy.types.Header): + bl_label = "Selection" + bl_space_type = "VIEW_3D" + + def __init__(self): + self.update() + + + def update(self): + blenderSelectedObjects = bpy.context.selected_objects + selectedObjects = bpy.context.scene.curvetools.SelectedObjects + + selectedObjectsToRemove = [] + for selectedObject in selectedObjects: + if not selectedObject.IsElementOf(blenderSelectedObjects): selectedObjectsToRemove.append(selectedObject) + for selectedObject in selectedObjectsToRemove: selectedObjects.remove(selectedObject) + + blenderObjectsToAdd = [] + for blenderObject in blenderSelectedObjects: + if not curvetoolsSelectedObject.ListContains(selectedObjects, blenderObject): blenderObjectsToAdd.append(blenderObject) + for blenderObject in blenderObjectsToAdd: + newSelectedObject = curvetoolsSelectedObject(blenderObject) + selectedObjects.append(newSelectedObject) + + + def draw(self, context): + selectedObjects = bpy.context.scene.curvetools.SelectedObjects + nrSelectedObjects = len(selectedObjects) + + layout = self.layout + row = layout.row() + row.label(text="Sel: " + str(nrSelectedObjects)) + + +class curvetoolsSelectedObject(bpy.types.PropertyGroup): + name: StringProperty(name = "name", default = "??") + + + @staticmethod + def UpdateThreadTarget(lock, sleepTime, selectedObjectNames, selectedBlenderObjectNames): + time.sleep(sleepTime) + + newSelectedObjectNames = [] + + for name in selectedObjectNames: + if name in selectedBlenderObjectNames: newSelectedObjectNames.append(name) + + for name in selectedBlenderObjectNames: + if not (name in selectedObjectNames): newSelectedObjectNames.append(name) + + # sometimes it still complains about the context + try: + nrNewSelectedObjects = len(newSelectedObjectNames) + bpy.context.scene.curvetools.NrSelectedObjects = nrNewSelectedObjects + + selectedObjects = bpy.context.scene.curvetools.SelectedObjects + selectedObjects.clear() + for i in range(nrNewSelectedObjects): selectedObjects.add() + for i, newSelectedObjectName in enumerate(newSelectedObjectNames): + selectedObjects[i].name = newSelectedObjectName + except: pass + + + @staticmethod + def GetSelectedObjectNames(): + selectedObjects = bpy.context.scene.curvetools.SelectedObjects + + rvNames = [] + selectedObjectValues = selectedObjects.values() + for selectedObject in selectedObjectValues: rvNames.append(selectedObject.name) + + return rvNames + + + @staticmethod + def GetSelectedBlenderObjectNames(): + blenderSelectedObjects = bpy.context.selected_objects + + rvNames = [] + 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, + ] diff --git a/curve_tools/surfacess.py b/curve_tools/surfacess.py new file mode 100644 index 00000000..cfae7040 --- /dev/null +++ b/curve_tools/surfacess.py @@ -0,0 +1,462 @@ +import bpy +import bmesh + +from . import mathematics +from . import curves + + + +class LoftedSplineSurface: + def __init__(self, activeSpline, otherSpline, bMesh, vert0Index, resolution): + self.splineA = activeSpline + self.splineO = otherSpline + + self.bMesh = bMesh + self.vert0Index = vert0Index + self.resolution = resolution + + + def Apply(self, worldMatrixA, worldMatrixO): + #deltaPar = 1.0 / float(self.resolution - 1) + + par = 0.0 + pointA = worldMatrixA @ self.splineA.CalcPoint(par) + pointO = worldMatrixO @ self.splineO.CalcPoint(par) + self.bMesh.verts[self.vert0Index].co = pointA + self.bMesh.verts[self.vert0Index + 1].co = pointO + + fltResm1 = float(self.resolution - 1) + for i in range(1, self.resolution): + par = float(i) / fltResm1 + + pointA = worldMatrixA @ self.splineA.CalcPoint(par) + pointO = worldMatrixO @ self.splineO.CalcPoint(par) + self.bMesh.verts[self.vert0Index + 2 * i].co = pointA + self.bMesh.verts[self.vert0Index + 2 * i + 1].co = pointO + + + def AddFaces(self): + currIndexA = self.vert0Index + currIndexO = self.vert0Index + 1 + + bmVerts = self.bMesh.verts + bmVerts.ensure_lookup_table() + + for i in range(1, self.resolution): + nextIndexA = self.vert0Index + 2 * i + nextIndexO = nextIndexA + 1 + + self.bMesh.faces.new([bmVerts[currIndexA], bmVerts[currIndexO], bmVerts[nextIndexO], bmVerts[nextIndexA]]) + + currIndexA = nextIndexA + currIndexO = nextIndexO + + +class LoftedSurface: + @staticmethod + def FromSelection(): + selObjects = bpy.context.selected_objects + if len(selObjects) != 2: raise Exception("len(selObjects) != 2") # shouldn't be possible + + blenderActiveCurve = bpy.context.active_object + blenderOtherCurve = selObjects[0] + if blenderActiveCurve == blenderOtherCurve: blenderOtherCurve = selObjects[1] + + aCurve = curves.Curve(blenderActiveCurve) + oCurve = curves.Curve(blenderOtherCurve) + + name = "TODO: autoname" + + return LoftedSurface(aCurve, oCurve, name) + + + def __init__(self, activeCurve, otherCurve, name = "LoftedSurface"): + self.curveA = activeCurve + self.curveO = otherCurve + self.name = name + + self.nrSplines = self.curveA.nrSplines + if self.curveO.nrSplines < self.nrSplines: self.nrSplines = self.curveO.nrSplines + + self.bMesh = bmesh.new() + + self.splineSurfaces = self.SetupSplineSurfaces() + + self.Apply() + + + def SetupSplineSurfaces(self): + rvSplineSurfaces = [] + + currV0Index = 0 + for i in range(self.nrSplines): + splineA = self.curveA.splines[i] + splineO = self.curveO.splines[i] + + res = splineA.resolution + if splineO.resolution < res: res = splineO.resolution + + for iv in range(2 * res): self.bMesh.verts.new() + + splSurf = LoftedSplineSurface(splineA, splineO, self.bMesh, currV0Index, res) + splSurf.AddFaces() + rvSplineSurfaces.append(splSurf) + + currV0Index += 2 * res + + return rvSplineSurfaces + + + def Apply(self): + for splineSurface in self.splineSurfaces: splineSurface.Apply(self.curveA.worldMatrix, self.curveO.worldMatrix) + + + def AddToScene(self): + mesh = bpy.data.meshes.new("Mesh" + self.name) + + self.bMesh.to_mesh(mesh) + mesh.update() + + meshObject = bpy.data.objects.new(self.name, mesh) + + bpy.context.collection.objects.link(meshObject) + + + +# active spline is swept over other spline (rail) +class SweptSplineSurface: + def __init__(self, activeSpline, otherSpline, bMesh, vert0Index, resolutionA, resolutionO): + self.splineA = activeSpline + self.splineO = otherSpline + + self.bMesh = bMesh + self.vert0Index = vert0Index + self.resolutionA = resolutionA + self.resolutionO = resolutionO + + + def Apply(self, worldMatrixA, worldMatrixO): + localPointsA = [] + fltResAm1 = float(self.resolutionA - 1) + for i in range(self.resolutionA): + par = float(i) / fltResAm1 + pointA = self.splineA.CalcPoint(par) + localPointsA.append(pointA) + + + worldPointsO = [] + localDerivativesO = [] + fltResOm1 = float(self.resolutionO - 1) + for i in range(self.resolutionO): + par = float(i) / fltResOm1 + + pointO = self.splineO.CalcPoint(par) + worldPointsO.append(worldMatrixO @ pointO) + + derivativeO = self.splineO.CalcDerivative(par) + localDerivativesO.append(derivativeO) + + + currWorldMatrixA = worldMatrixA + worldMatrixOInv = worldMatrixO.inverted() + prevDerivativeO = localDerivativesO[0] + for iO in range(self.resolutionO): + currDerivativeO = localDerivativesO[iO] + localRotMatO = mathematics.CalcRotationMatrix(prevDerivativeO, currDerivativeO) + + currLocalAToLocalO = worldMatrixOInv @ currWorldMatrixA + worldPointsA = [] + for iA in range(self.resolutionA): + pointALocalToO = currLocalAToLocalO @ localPointsA[iA] + rotatedPointA = localRotMatO @ pointALocalToO + worldPointsA.append(worldMatrixO @ rotatedPointA) + + worldOffsetsA = [] + worldPoint0A = worldPointsA[0] + for i in range(self.resolutionA): worldOffsetsA.append(worldPointsA[i] - worldPoint0A) + + + for iA in range(self.resolutionA): + iVert = self.vert0Index + (self.resolutionA * iO) + iA + currVert = worldPointsO[iO] + worldOffsetsA[iA] + self.bMesh.verts[iVert].co = currVert + + prevDerivativeO = currDerivativeO + currWorldMatrixA = worldMatrixO @ localRotMatO @ currLocalAToLocalO + + + def AddFaces(self): + bmVerts = self.bMesh.verts + bmVerts.ensure_lookup_table() + + for iO in range(self.resolutionO - 1): + for iA in range(self.resolutionA - 1): + currIndexA1 = self.vert0Index + (self.resolutionA * iO) + iA + currIndexA2 = currIndexA1 + 1 + nextIndexA1 = self.vert0Index + (self.resolutionA * (iO + 1)) + iA + nextIndexA2 = nextIndexA1 + 1 + + self.bMesh.faces.new([bmVerts[currIndexA1], bmVerts[currIndexA2], bmVerts[nextIndexA2], bmVerts[nextIndexA1]]) + + + +class SweptSurface: + @staticmethod + def FromSelection(): + selObjects = bpy.context.selected_objects + if len(selObjects) != 2: raise Exception("len(selObjects) != 2") # shouldn't be possible + + blenderActiveCurve = bpy.context.active_object + blenderOtherCurve = selObjects[0] + if blenderActiveCurve == blenderOtherCurve: blenderOtherCurve = selObjects[1] + + aCurve = curves.Curve(blenderActiveCurve) + oCurve = curves.Curve(blenderOtherCurve) + + name = "TODO: autoname" + + return SweptSurface(aCurve, oCurve, name) + + + def __init__(self, activeCurve, otherCurve, name = "SweptSurface"): + self.curveA = activeCurve + self.curveO = otherCurve + self.name = name + + self.nrSplines = self.curveA.nrSplines + if self.curveO.nrSplines < self.nrSplines: self.nrSplines = self.curveO.nrSplines + + self.bMesh = bmesh.new() + + self.splineSurfaces = self.SetupSplineSurfaces() + + self.Apply() + + + def SetupSplineSurfaces(self): + rvSplineSurfaces = [] + + currV0Index = 0 + for i in range(self.nrSplines): + splineA = self.curveA.splines[i] + splineO = self.curveO.splines[i] + + resA = splineA.resolution + resO = splineO.resolution + + for iv in range(resA * resO): self.bMesh.verts.new() + + splSurf = SweptSplineSurface(splineA, splineO, self.bMesh, currV0Index, resA, resO) + splSurf.AddFaces() + rvSplineSurfaces.append(splSurf) + + currV0Index += resA * resO + + return rvSplineSurfaces + + + def Apply(self): + for splineSurface in self.splineSurfaces: splineSurface.Apply(self.curveA.worldMatrix, self.curveO.worldMatrix) + + + def AddToScene(self): + mesh = bpy.data.meshes.new("Mesh" + self.name) + + self.bMesh.to_mesh(mesh) + mesh.update() + + meshObject = bpy.data.objects.new(self.name, mesh) + + bpy.context.collection.objects.link(meshObject) + + + +# profileSpline is swept over rail1Spline and scaled/rotated to have its endpoint on rail2Spline +class BirailedSplineSurface: + def __init__(self, rail1Spline, rail2Spline, profileSpline, bMesh, vert0Index, resolutionRails, resolutionProfile): + self.rail1Spline = rail1Spline + self.rail2Spline = rail2Spline + self.profileSpline = profileSpline + + self.bMesh = bMesh + self.vert0Index = vert0Index + self.resolutionRails = resolutionRails + self.resolutionProfile = resolutionProfile + + + def Apply(self, worldMatrixRail1, worldMatrixRail2, worldMatrixProfile): + localPointsProfile = [] + fltResProfilem1 = float(self.resolutionProfile - 1) + for i in range(self.resolutionProfile): + par = float(i) / fltResProfilem1 + pointProfile = self.profileSpline.CalcPoint(par) + localPointsProfile.append(pointProfile) + + + worldPointsRail1 = [] + localDerivativesRail1 = [] + worldPointsRail2 = [] + fltResRailsm1 = float(self.resolutionRails - 1) + for i in range(self.resolutionRails): + par = float(i) / fltResRailsm1 + + pointRail1 = self.rail1Spline.CalcPoint(par) + worldPointsRail1.append(worldMatrixRail1 @ pointRail1) + + derivativeRail1 = self.rail1Spline.CalcDerivative(par) + localDerivativesRail1.append(derivativeRail1) + + pointRail2 = self.rail2Spline.CalcPoint(par) + worldPointsRail2.append(worldMatrixRail2 @ pointRail2) + + + currWorldMatrixProfile = worldMatrixProfile + worldMatrixRail1Inv = worldMatrixRail1.inverted() + prevDerivativeRail1 = localDerivativesRail1[0] + for iRail in range(self.resolutionRails): + currDerivativeRail1 = localDerivativesRail1[iRail] + localRotMatRail1 = mathematics.CalcRotationMatrix(prevDerivativeRail1, currDerivativeRail1) + + currLocalProfileToLocalRail1 = worldMatrixRail1Inv @ currWorldMatrixProfile + worldPointsProfileRail1 = [] + for iProfile in range(self.resolutionProfile): + pointProfileLocalToRail1 = currLocalProfileToLocalRail1 @ localPointsProfile[iProfile] + rotatedPointProfile = localRotMatRail1 @ pointProfileLocalToRail1 + worldPointsProfileRail1.append(worldMatrixRail1 @ rotatedPointProfile) + + worldOffsetsProfileRail1 = [] + worldPoint0ProfileRail1 = worldPointsProfileRail1[0] + for iProfile in range(self.resolutionProfile): worldOffsetsProfileRail1.append(worldPointsProfileRail1[iProfile] - worldPoint0ProfileRail1) + + worldStartPointProfileRail1 = worldPointsRail1[iRail] + worldEndPointProfileRail1 = worldStartPointProfileRail1 + worldOffsetsProfileRail1[-1] + v3From = worldEndPointProfileRail1 - worldStartPointProfileRail1 + v3To = worldPointsRail2[iRail] - worldStartPointProfileRail1 + if not v3From.magnitude == 0: + scaleFactorRail2 = v3To.magnitude / v3From.magnitude + else: + scaleFactorRail2 = 1 + rotMatRail2 = mathematics.CalcRotationMatrix(v3From, v3To) + + worldOffsetsProfileRail2 = [] + for iProfile in range(self.resolutionProfile): + offsetProfileRail1 = worldOffsetsProfileRail1[iProfile] + worldOffsetsProfileRail2.append(rotMatRail2 @ (offsetProfileRail1 * scaleFactorRail2)) + + + for iProfile in range(self.resolutionProfile): + iVert = self.vert0Index + (self.resolutionProfile * iRail) + iProfile + currVert = worldPointsRail1[iRail] + worldOffsetsProfileRail2[iProfile] + self.bMesh.verts[iVert].co = currVert + + prevDerivativeRail1 = currDerivativeRail1 + currWorldMatrixProfile = worldMatrixRail1 @ localRotMatRail1 @ currLocalProfileToLocalRail1 + + + def AddFaces(self): + bmVerts = self.bMesh.verts + bmVerts.ensure_lookup_table() + + for iRail in range(self.resolutionRails - 1): + for iProfile in range(self.resolutionProfile - 1): + currIndex1 = self.vert0Index + (self.resolutionProfile * iRail) + iProfile + currIndex2 = currIndex1 + 1 + nextIndex1 = self.vert0Index + (self.resolutionProfile * (iRail + 1)) + iProfile + nextIndex2 = nextIndex1 + 1 + + self.bMesh.faces.new([bmVerts[currIndex1], bmVerts[currIndex2], bmVerts[nextIndex2], bmVerts[nextIndex1]]) + + + +class BirailedSurface: + @staticmethod + def FromSelection(): + nrSelectedObjects = bpy.context.scene.curvetools.NrSelectedObjects + if nrSelectedObjects != 3: raise Exception("nrSelectedObjects != 3") # shouldn't be possible + + + selectedObjects = bpy.context.scene.curvetools.SelectedObjects + selectedObjectValues = selectedObjects.values() + + curveName = selectedObjectValues[0].name + rail1BlenderCurve = None + try: rail1BlenderCurve = bpy.data.objects[curveName] + except: rail1BlenderCurve = None + if rail1BlenderCurve is None: raise Exception("rail1BlenderCurve is None") + + curveName = selectedObjectValues[1].name + rail2BlenderCurve = None + try: rail2BlenderCurve = bpy.data.objects[curveName] + except: rail2BlenderCurve = None + if rail2BlenderCurve is None: raise Exception("rail2BlenderCurve is None") + + curveName = selectedObjectValues[2].name + profileBlenderCurve = None + try: profileBlenderCurve = bpy.data.objects[curveName] + except: profileBlenderCurve = None + if profileBlenderCurve is None: raise Exception("profileBlenderCurve is None") + + + rail1Curve = curves.Curve(rail1BlenderCurve) + rail2Curve = curves.Curve(rail2BlenderCurve) + profileCurve = curves.Curve(profileBlenderCurve) + + name = "TODO: autoname" + + return BirailedSurface(rail1Curve, rail2Curve, profileCurve, name) + + + def __init__(self, rail1Curve, rail2Curve, profileCurve, name = "BirailedSurface"): + self.rail1Curve = rail1Curve + self.rail2Curve = rail2Curve + self.profileCurve = profileCurve + self.name = name + + self.nrSplines = self.rail1Curve.nrSplines + if self.rail2Curve.nrSplines < self.nrSplines: self.nrSplines = self.rail2Curve.nrSplines + if self.profileCurve.nrSplines < self.nrSplines: self.nrSplines = self.profileCurve.nrSplines + + self.bMesh = bmesh.new() + + self.splineSurfaces = self.SetupSplineSurfaces() + + self.Apply() + + + def SetupSplineSurfaces(self): + rvSplineSurfaces = [] + + currV0Index = 0 + for i in range(self.nrSplines): + splineRail1 = self.rail1Curve.splines[i] + splineRail2 = self.rail2Curve.splines[i] + splineProfile = self.profileCurve.splines[i] + + resProfile = splineProfile.resolution + resRails = splineRail1.resolution + if splineRail2.resolution < resRails: resRails = splineRail2.resolution + + for iv in range(resProfile * resRails): self.bMesh.verts.new() + + splSurf = BirailedSplineSurface(splineRail1, splineRail2, splineProfile, self.bMesh, currV0Index, resRails, resProfile) + splSurf.AddFaces() + rvSplineSurfaces.append(splSurf) + + currV0Index += resProfile * resRails + + return rvSplineSurfaces + + + def Apply(self): + for splineSurface in self.splineSurfaces: splineSurface.Apply(self.rail1Curve.worldMatrix, self.rail2Curve.worldMatrix, self.profileCurve.worldMatrix) + + + def AddToScene(self): + mesh = bpy.data.meshes.new("Mesh" + self.name) + + self.bMesh.to_mesh(mesh) + mesh.update() + + meshObject = bpy.data.objects.new(self.name, mesh) + + bpy.context.collection.objects.link(meshObject) diff --git a/curve_tools/utils.py b/curve_tools/utils.py new file mode 100644 index 00000000..30596697 --- /dev/null +++ b/curve_tools/utils.py @@ -0,0 +1,178 @@ +import bpy +from mathutils import * + + +def GetSelectedCurves(): + rvList = [] + + for obj in bpy.context.selected_objects: + try: + if obj.type == "CURVE": rvList.append(obj) + except: + pass + + return rvList + + +def GetSelectedMeshes(): + rvList = [] + + for obj in bpy.context.selected_objects: + try: + if obj.type == "MESH": rvList.append(obj) + except: + pass + + return rvList + + +def Selected1Curve(): + try: + if len(GetSelectedCurves()) == 1: + return (bpy.context.active_object.type == "CURVE") + except: + pass + + return False + + +def Selected1Mesh(): + try: + if len(GetSelectedMeshes()) == 1: + return (bpy.context.active_object.type == "MESH") + except: + pass + + return False + + +def Selected1SingleSplineCurve(): + try: + if Selected1Curve(): + return (len(bpy.context.active_object.data.splines) == 1) + except: + pass + + return False + + +def Selected2Curves(): + try: + if len(GetSelectedCurves()) == 2: + return (bpy.context.active_object.type == "CURVE") + except: + pass + + return False + + +def Selected3Curves(): + try: + if len(GetSelectedCurves()) == 3: + return (bpy.context.active_object.type == "CURVE") + except: + pass + + return False + + +def Selected1OrMoreCurves(): + try: + if len(GetSelectedCurves()) > 0: + return (bpy.context.active_object.type == "CURVE") + except: + pass + + return False + +def Selected2OrMoreCurves(): + try: + if len(GetSelectedCurves()) > 1: + return (bpy.context.active_object.type == "CURVE") + except: + pass + + return False + + +def Selected1OrMoreMesh(): + try: + if len(GetSelectedMeshes()) > 0: + return (bpy.context.active_object.type == "MESH") + except: + pass + + return False + + +def GetToolsRegion(): + for area in bpy.context.screen.areas: + if area.type == 'VIEW_3D': + for region in area.regions: + if region.type == 'TOOLS': return region + + return None + + +def GetFirstRegionView3D(): + for area in bpy.context.screen.areas: + if area.type == 'VIEW_3D': + return area.spaces[0].region_3d + + return None + + +def LogFirstRegionView3D(): + print("LogFirstRegionView3D()") + regionView3D = GetFirstRegionView3D() + if regionView3D is None: + print("--", "ERROR:", "regionView3D is None") + return + + print("--", "view_matrix:") + print("--", "--", regionView3D.view_matrix) + print("--", "view_location:") + print("--", "--", regionView3D.view_location) + + +class Intersection: + # listIP: list of BezierSplineIntersectionPoint + # return: list of splines + @staticmethod + def GetBezierSplines(listIP): + rvList = [] + + for ip in listIP: + if not (ip.spline in rvList): rvList.append(ip.spline) + + return rvList + + + # listIP: list of BezierSplineIntersectionPoint + # return: list of segments + @staticmethod + def GetBezierSegments(listIP, spline): + rvList = [] + + for ip in listIP: + if not ip.spline is spline: continue + + segIP = ip.bezierSegmentIntersectionPoint + if not (segIP.segment in rvList): rvList.append(segIP.segment) + + return rvList + + + # listIP: list of BezierSplineIntersectionPoint + # return: list of floats (not necessarily ordered) + @staticmethod + def GetBezierSegmentParameters(listIP, segment): + rvList = [] + + for ip in listIP: + segIP = ip.bezierSegmentIntersectionPoint + if not segIP.segment is segment: continue + + rvList.append(segIP.parameter) + + return rvList -- cgit v1.2.3