blender-addons/curve_tools/surfaces.py
2023-07-05 09:41:03 +02:00

441 lines
15 KiB
Python

# 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)