# SPDX-License-Identifier: GPL-2.0-or-later 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(): selectedObjects = bpy.context.selected_objects rail1Curve = curves.Curve(selectedObjects[0]) rail2Curve = curves.Curve(selectedObjects[1]) profileCurve = curves.Curve(selectedObjects[2]) name = "BirailedSurface" 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)