Fix io_anim_camera error exporting cameras with quotes in their name
[blender-addons.git] / curve_tools / surfaces.py
blob2d89115c6d7697f02788f2518614091b7ea04efd
1 # SPDX-License-Identifier: GPL-2.0-or-later
3 import bpy
4 import bmesh
6 from . import mathematics
7 from . import curves
11 class LoftedSplineSurface:
12 def __init__(self, activeSpline, otherSpline, bMesh, vert0Index, resolution):
13 self.splineA = activeSpline
14 self.splineO = otherSpline
16 self.bMesh = bMesh
17 self.vert0Index = vert0Index
18 self.resolution = resolution
21 def Apply(self, worldMatrixA, worldMatrixO):
22 #deltaPar = 1.0 / float(self.resolution - 1)
24 par = 0.0
25 pointA = worldMatrixA @ self.splineA.CalcPoint(par)
26 pointO = worldMatrixO @ self.splineO.CalcPoint(par)
27 self.bMesh.verts[self.vert0Index].co = pointA
28 self.bMesh.verts[self.vert0Index + 1].co = pointO
30 fltResm1 = float(self.resolution - 1)
31 for i in range(1, self.resolution):
32 par = float(i) / fltResm1
34 pointA = worldMatrixA @ self.splineA.CalcPoint(par)
35 pointO = worldMatrixO @ self.splineO.CalcPoint(par)
36 self.bMesh.verts[self.vert0Index + 2 * i].co = pointA
37 self.bMesh.verts[self.vert0Index + 2 * i + 1].co = pointO
40 def AddFaces(self):
41 currIndexA = self.vert0Index
42 currIndexO = self.vert0Index + 1
44 bmVerts = self.bMesh.verts
45 bmVerts.ensure_lookup_table()
47 for i in range(1, self.resolution):
48 nextIndexA = self.vert0Index + 2 * i
49 nextIndexO = nextIndexA + 1
51 self.bMesh.faces.new([bmVerts[currIndexA], bmVerts[currIndexO], bmVerts[nextIndexO], bmVerts[nextIndexA]])
53 currIndexA = nextIndexA
54 currIndexO = nextIndexO
57 class LoftedSurface:
58 @staticmethod
59 def FromSelection():
60 selObjects = bpy.context.selected_objects
61 if len(selObjects) != 2: raise Exception("len(selObjects) != 2") # shouldn't be possible
63 blenderActiveCurve = bpy.context.active_object
64 blenderOtherCurve = selObjects[0]
65 if blenderActiveCurve == blenderOtherCurve: blenderOtherCurve = selObjects[1]
67 aCurve = curves.Curve(blenderActiveCurve)
68 oCurve = curves.Curve(blenderOtherCurve)
70 name = "TODO: autoname"
72 return LoftedSurface(aCurve, oCurve, name)
75 def __init__(self, activeCurve, otherCurve, name = "LoftedSurface"):
76 self.curveA = activeCurve
77 self.curveO = otherCurve
78 self.name = name
80 self.nrSplines = self.curveA.nrSplines
81 if self.curveO.nrSplines < self.nrSplines: self.nrSplines = self.curveO.nrSplines
83 self.bMesh = bmesh.new()
85 self.splineSurfaces = self.SetupSplineSurfaces()
87 self.Apply()
90 def SetupSplineSurfaces(self):
91 rvSplineSurfaces = []
93 currV0Index = 0
94 for i in range(self.nrSplines):
95 splineA = self.curveA.splines[i]
96 splineO = self.curveO.splines[i]
98 res = splineA.resolution
99 if splineO.resolution < res: res = splineO.resolution
101 for iv in range(2 * res): self.bMesh.verts.new()
103 splSurf = LoftedSplineSurface(splineA, splineO, self.bMesh, currV0Index, res)
104 splSurf.AddFaces()
105 rvSplineSurfaces.append(splSurf)
107 currV0Index += 2 * res
109 return rvSplineSurfaces
112 def Apply(self):
113 for splineSurface in self.splineSurfaces: splineSurface.Apply(self.curveA.worldMatrix, self.curveO.worldMatrix)
116 def AddToScene(self):
117 mesh = bpy.data.meshes.new("Mesh" + self.name)
119 self.bMesh.to_mesh(mesh)
120 mesh.update()
122 meshObject = bpy.data.objects.new(self.name, mesh)
124 bpy.context.collection.objects.link(meshObject)
128 # active spline is swept over other spline (rail)
129 class SweptSplineSurface:
130 def __init__(self, activeSpline, otherSpline, bMesh, vert0Index, resolutionA, resolutionO):
131 self.splineA = activeSpline
132 self.splineO = otherSpline
134 self.bMesh = bMesh
135 self.vert0Index = vert0Index
136 self.resolutionA = resolutionA
137 self.resolutionO = resolutionO
140 def Apply(self, worldMatrixA, worldMatrixO):
141 localPointsA = []
142 fltResAm1 = float(self.resolutionA - 1)
143 for i in range(self.resolutionA):
144 par = float(i) / fltResAm1
145 pointA = self.splineA.CalcPoint(par)
146 localPointsA.append(pointA)
149 worldPointsO = []
150 localDerivativesO = []
151 fltResOm1 = float(self.resolutionO - 1)
152 for i in range(self.resolutionO):
153 par = float(i) / fltResOm1
155 pointO = self.splineO.CalcPoint(par)
156 worldPointsO.append(worldMatrixO @ pointO)
158 derivativeO = self.splineO.CalcDerivative(par)
159 localDerivativesO.append(derivativeO)
162 currWorldMatrixA = worldMatrixA
163 worldMatrixOInv = worldMatrixO.inverted()
164 prevDerivativeO = localDerivativesO[0]
165 for iO in range(self.resolutionO):
166 currDerivativeO = localDerivativesO[iO]
167 localRotMatO = mathematics.CalcRotationMatrix(prevDerivativeO, currDerivativeO)
169 currLocalAToLocalO = worldMatrixOInv @ currWorldMatrixA
170 worldPointsA = []
171 for iA in range(self.resolutionA):
172 pointALocalToO = currLocalAToLocalO @ localPointsA[iA]
173 rotatedPointA = localRotMatO @ pointALocalToO
174 worldPointsA.append(worldMatrixO @ rotatedPointA)
176 worldOffsetsA = []
177 worldPoint0A = worldPointsA[0]
178 for i in range(self.resolutionA): worldOffsetsA.append(worldPointsA[i] - worldPoint0A)
181 for iA in range(self.resolutionA):
182 iVert = self.vert0Index + (self.resolutionA * iO) + iA
183 currVert = worldPointsO[iO] + worldOffsetsA[iA]
184 self.bMesh.verts[iVert].co = currVert
186 prevDerivativeO = currDerivativeO
187 currWorldMatrixA = worldMatrixO @ localRotMatO @ currLocalAToLocalO
190 def AddFaces(self):
191 bmVerts = self.bMesh.verts
192 bmVerts.ensure_lookup_table()
194 for iO in range(self.resolutionO - 1):
195 for iA in range(self.resolutionA - 1):
196 currIndexA1 = self.vert0Index + (self.resolutionA * iO) + iA
197 currIndexA2 = currIndexA1 + 1
198 nextIndexA1 = self.vert0Index + (self.resolutionA * (iO + 1)) + iA
199 nextIndexA2 = nextIndexA1 + 1
201 self.bMesh.faces.new([bmVerts[currIndexA1], bmVerts[currIndexA2], bmVerts[nextIndexA2], bmVerts[nextIndexA1]])
205 class SweptSurface:
206 @staticmethod
207 def FromSelection():
208 selObjects = bpy.context.selected_objects
209 if len(selObjects) != 2: raise Exception("len(selObjects) != 2") # shouldn't be possible
211 blenderActiveCurve = bpy.context.active_object
212 blenderOtherCurve = selObjects[0]
213 if blenderActiveCurve == blenderOtherCurve: blenderOtherCurve = selObjects[1]
215 aCurve = curves.Curve(blenderActiveCurve)
216 oCurve = curves.Curve(blenderOtherCurve)
218 name = "TODO: autoname"
220 return SweptSurface(aCurve, oCurve, name)
223 def __init__(self, activeCurve, otherCurve, name = "SweptSurface"):
224 self.curveA = activeCurve
225 self.curveO = otherCurve
226 self.name = name
228 self.nrSplines = self.curveA.nrSplines
229 if self.curveO.nrSplines < self.nrSplines: self.nrSplines = self.curveO.nrSplines
231 self.bMesh = bmesh.new()
233 self.splineSurfaces = self.SetupSplineSurfaces()
235 self.Apply()
238 def SetupSplineSurfaces(self):
239 rvSplineSurfaces = []
241 currV0Index = 0
242 for i in range(self.nrSplines):
243 splineA = self.curveA.splines[i]
244 splineO = self.curveO.splines[i]
246 resA = splineA.resolution
247 resO = splineO.resolution
249 for iv in range(resA * resO): self.bMesh.verts.new()
251 splSurf = SweptSplineSurface(splineA, splineO, self.bMesh, currV0Index, resA, resO)
252 splSurf.AddFaces()
253 rvSplineSurfaces.append(splSurf)
255 currV0Index += resA * resO
257 return rvSplineSurfaces
260 def Apply(self):
261 for splineSurface in self.splineSurfaces: splineSurface.Apply(self.curveA.worldMatrix, self.curveO.worldMatrix)
264 def AddToScene(self):
265 mesh = bpy.data.meshes.new("Mesh" + self.name)
267 self.bMesh.to_mesh(mesh)
268 mesh.update()
270 meshObject = bpy.data.objects.new(self.name, mesh)
272 bpy.context.collection.objects.link(meshObject)
276 # profileSpline is swept over rail1Spline and scaled/rotated to have its endpoint on rail2Spline
277 class BirailedSplineSurface:
278 def __init__(self, rail1Spline, rail2Spline, profileSpline, bMesh, vert0Index, resolutionRails, resolutionProfile):
279 self.rail1Spline = rail1Spline
280 self.rail2Spline = rail2Spline
281 self.profileSpline = profileSpline
283 self.bMesh = bMesh
284 self.vert0Index = vert0Index
285 self.resolutionRails = resolutionRails
286 self.resolutionProfile = resolutionProfile
289 def Apply(self, worldMatrixRail1, worldMatrixRail2, worldMatrixProfile):
290 localPointsProfile = []
291 fltResProfilem1 = float(self.resolutionProfile - 1)
292 for i in range(self.resolutionProfile):
293 par = float(i) / fltResProfilem1
294 pointProfile = self.profileSpline.CalcPoint(par)
295 localPointsProfile.append(pointProfile)
298 worldPointsRail1 = []
299 localDerivativesRail1 = []
300 worldPointsRail2 = []
301 fltResRailsm1 = float(self.resolutionRails - 1)
302 for i in range(self.resolutionRails):
303 par = float(i) / fltResRailsm1
305 pointRail1 = self.rail1Spline.CalcPoint(par)
306 worldPointsRail1.append(worldMatrixRail1 @ pointRail1)
308 derivativeRail1 = self.rail1Spline.CalcDerivative(par)
309 localDerivativesRail1.append(derivativeRail1)
311 pointRail2 = self.rail2Spline.CalcPoint(par)
312 worldPointsRail2.append(worldMatrixRail2 @ pointRail2)
315 currWorldMatrixProfile = worldMatrixProfile
316 worldMatrixRail1Inv = worldMatrixRail1.inverted()
317 prevDerivativeRail1 = localDerivativesRail1[0]
318 for iRail in range(self.resolutionRails):
319 currDerivativeRail1 = localDerivativesRail1[iRail]
320 localRotMatRail1 = mathematics.CalcRotationMatrix(prevDerivativeRail1, currDerivativeRail1)
322 currLocalProfileToLocalRail1 = worldMatrixRail1Inv @ currWorldMatrixProfile
323 worldPointsProfileRail1 = []
324 for iProfile in range(self.resolutionProfile):
325 pointProfileLocalToRail1 = currLocalProfileToLocalRail1 @ localPointsProfile[iProfile]
326 rotatedPointProfile = localRotMatRail1 @ pointProfileLocalToRail1
327 worldPointsProfileRail1.append(worldMatrixRail1 @ rotatedPointProfile)
329 worldOffsetsProfileRail1 = []
330 worldPoint0ProfileRail1 = worldPointsProfileRail1[0]
331 for iProfile in range(self.resolutionProfile): worldOffsetsProfileRail1.append(worldPointsProfileRail1[iProfile] - worldPoint0ProfileRail1)
333 worldStartPointProfileRail1 = worldPointsRail1[iRail]
334 worldEndPointProfileRail1 = worldStartPointProfileRail1 + worldOffsetsProfileRail1[-1]
335 v3From = worldEndPointProfileRail1 - worldStartPointProfileRail1
336 v3To = worldPointsRail2[iRail] - worldStartPointProfileRail1
337 if not v3From.magnitude == 0:
338 scaleFactorRail2 = v3To.magnitude / v3From.magnitude
339 else:
340 scaleFactorRail2 = 1
341 rotMatRail2 = mathematics.CalcRotationMatrix(v3From, v3To)
343 worldOffsetsProfileRail2 = []
344 for iProfile in range(self.resolutionProfile):
345 offsetProfileRail1 = worldOffsetsProfileRail1[iProfile]
346 worldOffsetsProfileRail2.append(rotMatRail2 @ (offsetProfileRail1 * scaleFactorRail2))
349 for iProfile in range(self.resolutionProfile):
350 iVert = self.vert0Index + (self.resolutionProfile * iRail) + iProfile
351 currVert = worldPointsRail1[iRail] + worldOffsetsProfileRail2[iProfile]
352 self.bMesh.verts[iVert].co = currVert
354 prevDerivativeRail1 = currDerivativeRail1
355 currWorldMatrixProfile = worldMatrixRail1 @ localRotMatRail1 @ currLocalProfileToLocalRail1
358 def AddFaces(self):
359 bmVerts = self.bMesh.verts
360 bmVerts.ensure_lookup_table()
362 for iRail in range(self.resolutionRails - 1):
363 for iProfile in range(self.resolutionProfile - 1):
364 currIndex1 = self.vert0Index + (self.resolutionProfile * iRail) + iProfile
365 currIndex2 = currIndex1 + 1
366 nextIndex1 = self.vert0Index + (self.resolutionProfile * (iRail + 1)) + iProfile
367 nextIndex2 = nextIndex1 + 1
369 self.bMesh.faces.new([bmVerts[currIndex1], bmVerts[currIndex2], bmVerts[nextIndex2], bmVerts[nextIndex1]])
373 class BirailedSurface:
374 @staticmethod
375 def FromSelection():
376 selectedObjects = bpy.context.selected_objects
378 rail1Curve = curves.Curve(selectedObjects[0])
379 rail2Curve = curves.Curve(selectedObjects[1])
380 profileCurve = curves.Curve(selectedObjects[2])
382 name = "BirailedSurface"
384 return BirailedSurface(rail1Curve, rail2Curve, profileCurve, name)
387 def __init__(self, rail1Curve, rail2Curve, profileCurve, name = "BirailedSurface"):
388 self.rail1Curve = rail1Curve
389 self.rail2Curve = rail2Curve
390 self.profileCurve = profileCurve
391 self.name = name
393 self.nrSplines = self.rail1Curve.nrSplines
394 if self.rail2Curve.nrSplines < self.nrSplines: self.nrSplines = self.rail2Curve.nrSplines
395 if self.profileCurve.nrSplines < self.nrSplines: self.nrSplines = self.profileCurve.nrSplines
397 self.bMesh = bmesh.new()
399 self.splineSurfaces = self.SetupSplineSurfaces()
401 self.Apply()
404 def SetupSplineSurfaces(self):
405 rvSplineSurfaces = []
407 currV0Index = 0
408 for i in range(self.nrSplines):
409 splineRail1 = self.rail1Curve.splines[i]
410 splineRail2 = self.rail2Curve.splines[i]
411 splineProfile = self.profileCurve.splines[i]
413 resProfile = splineProfile.resolution
414 resRails = splineRail1.resolution
415 if splineRail2.resolution < resRails: resRails = splineRail2.resolution
417 for iv in range(resProfile * resRails): self.bMesh.verts.new()
419 splSurf = BirailedSplineSurface(splineRail1, splineRail2, splineProfile, self.bMesh, currV0Index, resRails, resProfile)
420 splSurf.AddFaces()
421 rvSplineSurfaces.append(splSurf)
423 currV0Index += resProfile * resRails
425 return rvSplineSurfaces
428 def Apply(self):
429 for splineSurface in self.splineSurfaces: splineSurface.Apply(self.rail1Curve.worldMatrix, self.rail2Curve.worldMatrix, self.profileCurve.worldMatrix)
432 def AddToScene(self):
433 mesh = bpy.data.meshes.new("Mesh" + self.name)
435 self.bMesh.to_mesh(mesh)
436 mesh.update()
438 meshObject = bpy.data.objects.new(self.name, mesh)
440 bpy.context.collection.objects.link(meshObject)