1import bpy
2import bmesh
3
4from . import mathematics
5from . import curves
6
7
8
9class LoftedSplineSurface:
10    def __init__(self, activeSpline, otherSpline, bMesh, vert0Index, resolution):
11        self.splineA = activeSpline
12        self.splineO = otherSpline
13
14        self.bMesh = bMesh
15        self.vert0Index = vert0Index
16        self.resolution = resolution
17
18
19    def Apply(self, worldMatrixA, worldMatrixO):
20        #deltaPar = 1.0 / float(self.resolution - 1)
21
22        par = 0.0
23        pointA = worldMatrixA @ self.splineA.CalcPoint(par)
24        pointO = worldMatrixO @ self.splineO.CalcPoint(par)
25        self.bMesh.verts[self.vert0Index].co = pointA
26        self.bMesh.verts[self.vert0Index + 1].co = pointO
27
28        fltResm1 = float(self.resolution - 1)
29        for i in range(1, self.resolution):
30            par = float(i) / fltResm1
31
32            pointA = worldMatrixA @ self.splineA.CalcPoint(par)
33            pointO = worldMatrixO @ self.splineO.CalcPoint(par)
34            self.bMesh.verts[self.vert0Index + 2 * i].co = pointA
35            self.bMesh.verts[self.vert0Index + 2 * i + 1].co = pointO
36
37
38    def AddFaces(self):
39        currIndexA = self.vert0Index
40        currIndexO = self.vert0Index + 1
41
42        bmVerts = self.bMesh.verts
43        bmVerts.ensure_lookup_table()
44
45        for i in range(1, self.resolution):
46            nextIndexA = self.vert0Index + 2 * i
47            nextIndexO = nextIndexA + 1
48
49            self.bMesh.faces.new([bmVerts[currIndexA], bmVerts[currIndexO], bmVerts[nextIndexO], bmVerts[nextIndexA]])
50
51            currIndexA = nextIndexA
52            currIndexO = nextIndexO
53
54
55class LoftedSurface:
56    @staticmethod
57    def FromSelection():
58        selObjects = bpy.context.selected_objects
59        if len(selObjects) != 2: raise Exception("len(selObjects) != 2") # shouldn't be possible
60
61        blenderActiveCurve = bpy.context.active_object
62        blenderOtherCurve = selObjects[0]
63        if blenderActiveCurve == blenderOtherCurve: blenderOtherCurve = selObjects[1]
64
65        aCurve = curves.Curve(blenderActiveCurve)
66        oCurve = curves.Curve(blenderOtherCurve)
67
68        name = "TODO: autoname"
69
70        return LoftedSurface(aCurve, oCurve, name)
71
72
73    def __init__(self, activeCurve, otherCurve, name = "LoftedSurface"):
74        self.curveA = activeCurve
75        self.curveO = otherCurve
76        self.name  = name
77
78        self.nrSplines = self.curveA.nrSplines
79        if self.curveO.nrSplines < self.nrSplines: self.nrSplines = self.curveO.nrSplines
80
81        self.bMesh = bmesh.new()
82
83        self.splineSurfaces = self.SetupSplineSurfaces()
84
85        self.Apply()
86
87
88    def SetupSplineSurfaces(self):
89        rvSplineSurfaces = []
90
91        currV0Index = 0
92        for i in range(self.nrSplines):
93            splineA = self.curveA.splines[i]
94            splineO = self.curveO.splines[i]
95
96            res = splineA.resolution
97            if splineO.resolution < res: res = splineO.resolution
98
99            for iv in range(2 * res): self.bMesh.verts.new()
100
101            splSurf = LoftedSplineSurface(splineA, splineO, self.bMesh, currV0Index, res)
102            splSurf.AddFaces()
103            rvSplineSurfaces.append(splSurf)
104
105            currV0Index += 2 * res
106
107        return rvSplineSurfaces
108
109
110    def Apply(self):
111        for splineSurface in self.splineSurfaces: splineSurface.Apply(self.curveA.worldMatrix, self.curveO.worldMatrix)
112
113
114    def AddToScene(self):
115        mesh = bpy.data.meshes.new("Mesh" + self.name)
116
117        self.bMesh.to_mesh(mesh)
118        mesh.update()
119
120        meshObject = bpy.data.objects.new(self.name, mesh)
121
122        bpy.context.collection.objects.link(meshObject)
123
124
125
126# active spline is swept over other spline (rail)
127class SweptSplineSurface:
128    def __init__(self, activeSpline, otherSpline, bMesh, vert0Index, resolutionA, resolutionO):
129        self.splineA = activeSpline
130        self.splineO = otherSpline
131
132        self.bMesh = bMesh
133        self.vert0Index = vert0Index
134        self.resolutionA = resolutionA
135        self.resolutionO = resolutionO
136
137
138    def Apply(self, worldMatrixA, worldMatrixO):
139        localPointsA = []
140        fltResAm1 = float(self.resolutionA - 1)
141        for i in range(self.resolutionA):
142            par = float(i) / fltResAm1
143            pointA = self.splineA.CalcPoint(par)
144            localPointsA.append(pointA)
145
146
147        worldPointsO = []
148        localDerivativesO = []
149        fltResOm1 = float(self.resolutionO - 1)
150        for i in range(self.resolutionO):
151            par = float(i) / fltResOm1
152
153            pointO = self.splineO.CalcPoint(par)
154            worldPointsO.append(worldMatrixO @ pointO)
155
156            derivativeO = self.splineO.CalcDerivative(par)
157            localDerivativesO.append(derivativeO)
158
159
160        currWorldMatrixA = worldMatrixA
161        worldMatrixOInv = worldMatrixO.inverted()
162        prevDerivativeO = localDerivativesO[0]
163        for iO in range(self.resolutionO):
164            currDerivativeO = localDerivativesO[iO]
165            localRotMatO = mathematics.CalcRotationMatrix(prevDerivativeO, currDerivativeO)
166
167            currLocalAToLocalO = worldMatrixOInv @ currWorldMatrixA
168            worldPointsA = []
169            for iA in range(self.resolutionA):
170                pointALocalToO = currLocalAToLocalO @ localPointsA[iA]
171                rotatedPointA = localRotMatO @ pointALocalToO
172                worldPointsA.append(worldMatrixO @ rotatedPointA)
173
174            worldOffsetsA = []
175            worldPoint0A = worldPointsA[0]
176            for i in range(self.resolutionA): worldOffsetsA.append(worldPointsA[i] - worldPoint0A)
177
178
179            for iA in range(self.resolutionA):
180                iVert = self.vert0Index + (self.resolutionA * iO) + iA
181                currVert = worldPointsO[iO] + worldOffsetsA[iA]
182                self.bMesh.verts[iVert].co = currVert
183
184            prevDerivativeO = currDerivativeO
185            currWorldMatrixA = worldMatrixO @ localRotMatO @ currLocalAToLocalO
186
187
188    def AddFaces(self):
189        bmVerts = self.bMesh.verts
190        bmVerts.ensure_lookup_table()
191
192        for iO in range(self.resolutionO - 1):
193            for iA in range(self.resolutionA - 1):
194                currIndexA1 = self.vert0Index + (self.resolutionA * iO) + iA
195                currIndexA2 = currIndexA1 + 1
196                nextIndexA1 = self.vert0Index + (self.resolutionA * (iO + 1)) + iA
197                nextIndexA2 = nextIndexA1 + 1
198
199                self.bMesh.faces.new([bmVerts[currIndexA1], bmVerts[currIndexA2], bmVerts[nextIndexA2], bmVerts[nextIndexA1]])
200
201
202
203class SweptSurface:
204    @staticmethod
205    def FromSelection():
206        selObjects = bpy.context.selected_objects
207        if len(selObjects) != 2: raise Exception("len(selObjects) != 2") # shouldn't be possible
208
209        blenderActiveCurve = bpy.context.active_object
210        blenderOtherCurve = selObjects[0]
211        if blenderActiveCurve == blenderOtherCurve: blenderOtherCurve = selObjects[1]
212
213        aCurve = curves.Curve(blenderActiveCurve)
214        oCurve = curves.Curve(blenderOtherCurve)
215
216        name = "TODO: autoname"
217
218        return SweptSurface(aCurve, oCurve, name)
219
220
221    def __init__(self, activeCurve, otherCurve, name = "SweptSurface"):
222        self.curveA = activeCurve
223        self.curveO = otherCurve
224        self.name  = name
225
226        self.nrSplines = self.curveA.nrSplines
227        if self.curveO.nrSplines < self.nrSplines: self.nrSplines = self.curveO.nrSplines
228
229        self.bMesh = bmesh.new()
230
231        self.splineSurfaces = self.SetupSplineSurfaces()
232
233        self.Apply()
234
235
236    def SetupSplineSurfaces(self):
237        rvSplineSurfaces = []
238
239        currV0Index = 0
240        for i in range(self.nrSplines):
241            splineA = self.curveA.splines[i]
242            splineO = self.curveO.splines[i]
243
244            resA = splineA.resolution
245            resO = splineO.resolution
246
247            for iv in range(resA * resO): self.bMesh.verts.new()
248
249            splSurf = SweptSplineSurface(splineA, splineO, self.bMesh, currV0Index, resA, resO)
250            splSurf.AddFaces()
251            rvSplineSurfaces.append(splSurf)
252
253            currV0Index += resA * resO
254
255        return rvSplineSurfaces
256
257
258    def Apply(self):
259        for splineSurface in self.splineSurfaces: splineSurface.Apply(self.curveA.worldMatrix, self.curveO.worldMatrix)
260
261
262    def AddToScene(self):
263        mesh = bpy.data.meshes.new("Mesh" + self.name)
264
265        self.bMesh.to_mesh(mesh)
266        mesh.update()
267
268        meshObject = bpy.data.objects.new(self.name, mesh)
269
270        bpy.context.collection.objects.link(meshObject)
271
272
273
274# profileSpline is swept over rail1Spline and scaled/rotated to have its endpoint on rail2Spline
275class BirailedSplineSurface:
276    def __init__(self, rail1Spline, rail2Spline, profileSpline, bMesh, vert0Index, resolutionRails, resolutionProfile):
277        self.rail1Spline = rail1Spline
278        self.rail2Spline = rail2Spline
279        self.profileSpline = profileSpline
280
281        self.bMesh = bMesh
282        self.vert0Index = vert0Index
283        self.resolutionRails = resolutionRails
284        self.resolutionProfile = resolutionProfile
285
286
287    def Apply(self, worldMatrixRail1, worldMatrixRail2, worldMatrixProfile):
288        localPointsProfile = []
289        fltResProfilem1 = float(self.resolutionProfile - 1)
290        for i in range(self.resolutionProfile):
291            par = float(i) / fltResProfilem1
292            pointProfile = self.profileSpline.CalcPoint(par)
293            localPointsProfile.append(pointProfile)
294
295
296        worldPointsRail1 = []
297        localDerivativesRail1 = []
298        worldPointsRail2 = []
299        fltResRailsm1 = float(self.resolutionRails - 1)
300        for i in range(self.resolutionRails):
301            par = float(i) / fltResRailsm1
302
303            pointRail1 = self.rail1Spline.CalcPoint(par)
304            worldPointsRail1.append(worldMatrixRail1 @ pointRail1)
305
306            derivativeRail1 = self.rail1Spline.CalcDerivative(par)
307            localDerivativesRail1.append(derivativeRail1)
308
309            pointRail2 = self.rail2Spline.CalcPoint(par)
310            worldPointsRail2.append(worldMatrixRail2 @ pointRail2)
311
312
313        currWorldMatrixProfile = worldMatrixProfile
314        worldMatrixRail1Inv = worldMatrixRail1.inverted()
315        prevDerivativeRail1 = localDerivativesRail1[0]
316        for iRail in range(self.resolutionRails):
317            currDerivativeRail1 = localDerivativesRail1[iRail]
318            localRotMatRail1 = mathematics.CalcRotationMatrix(prevDerivativeRail1, currDerivativeRail1)
319
320            currLocalProfileToLocalRail1 = worldMatrixRail1Inv @ currWorldMatrixProfile
321            worldPointsProfileRail1 = []
322            for iProfile in range(self.resolutionProfile):
323                pointProfileLocalToRail1 = currLocalProfileToLocalRail1 @ localPointsProfile[iProfile]
324                rotatedPointProfile = localRotMatRail1 @ pointProfileLocalToRail1
325                worldPointsProfileRail1.append(worldMatrixRail1 @ rotatedPointProfile)
326
327            worldOffsetsProfileRail1 = []
328            worldPoint0ProfileRail1 = worldPointsProfileRail1[0]
329            for iProfile in range(self.resolutionProfile): worldOffsetsProfileRail1.append(worldPointsProfileRail1[iProfile] - worldPoint0ProfileRail1)
330
331            worldStartPointProfileRail1 = worldPointsRail1[iRail]
332            worldEndPointProfileRail1 = worldStartPointProfileRail1 + worldOffsetsProfileRail1[-1]
333            v3From = worldEndPointProfileRail1 - worldStartPointProfileRail1
334            v3To = worldPointsRail2[iRail] - worldStartPointProfileRail1
335            if not v3From.magnitude == 0:
336                scaleFactorRail2 = v3To.magnitude / v3From.magnitude
337            else:
338                scaleFactorRail2 = 1
339            rotMatRail2 = mathematics.CalcRotationMatrix(v3From, v3To)
340
341            worldOffsetsProfileRail2 = []
342            for iProfile in range(self.resolutionProfile):
343                offsetProfileRail1 = worldOffsetsProfileRail1[iProfile]
344                worldOffsetsProfileRail2.append(rotMatRail2 @ (offsetProfileRail1 * scaleFactorRail2))
345
346
347            for iProfile in range(self.resolutionProfile):
348                iVert = self.vert0Index + (self.resolutionProfile * iRail) + iProfile
349                currVert = worldPointsRail1[iRail] + worldOffsetsProfileRail2[iProfile]
350                self.bMesh.verts[iVert].co = currVert
351
352            prevDerivativeRail1 = currDerivativeRail1
353            currWorldMatrixProfile = worldMatrixRail1 @ localRotMatRail1 @ currLocalProfileToLocalRail1
354
355
356    def AddFaces(self):
357        bmVerts = self.bMesh.verts
358        bmVerts.ensure_lookup_table()
359
360        for iRail in range(self.resolutionRails - 1):
361            for iProfile in range(self.resolutionProfile - 1):
362                currIndex1 = self.vert0Index + (self.resolutionProfile * iRail) + iProfile
363                currIndex2 = currIndex1 + 1
364                nextIndex1 = self.vert0Index + (self.resolutionProfile * (iRail + 1)) + iProfile
365                nextIndex2 = nextIndex1 + 1
366
367                self.bMesh.faces.new([bmVerts[currIndex1], bmVerts[currIndex2], bmVerts[nextIndex2], bmVerts[nextIndex1]])
368
369
370
371class BirailedSurface:
372    @staticmethod
373    def FromSelection():
374        selectedObjects = bpy.context.selected_objects
375
376        rail1Curve = curves.Curve(selectedObjects[0])
377        rail2Curve = curves.Curve(selectedObjects[1])
378        profileCurve = curves.Curve(selectedObjects[2])
379
380        name = "BirailedSurface"
381
382        return BirailedSurface(rail1Curve, rail2Curve, profileCurve, name)
383
384
385    def __init__(self, rail1Curve, rail2Curve, profileCurve, name = "BirailedSurface"):
386        self.rail1Curve = rail1Curve
387        self.rail2Curve = rail2Curve
388        self.profileCurve = profileCurve
389        self.name  = name
390
391        self.nrSplines = self.rail1Curve.nrSplines
392        if self.rail2Curve.nrSplines < self.nrSplines: self.nrSplines = self.rail2Curve.nrSplines
393        if self.profileCurve.nrSplines < self.nrSplines: self.nrSplines = self.profileCurve.nrSplines
394
395        self.bMesh = bmesh.new()
396
397        self.splineSurfaces = self.SetupSplineSurfaces()
398
399        self.Apply()
400
401
402    def SetupSplineSurfaces(self):
403        rvSplineSurfaces = []
404
405        currV0Index = 0
406        for i in range(self.nrSplines):
407            splineRail1 = self.rail1Curve.splines[i]
408            splineRail2 = self.rail2Curve.splines[i]
409            splineProfile = self.profileCurve.splines[i]
410
411            resProfile = splineProfile.resolution
412            resRails = splineRail1.resolution
413            if splineRail2.resolution < resRails: resRails = splineRail2.resolution
414
415            for iv in range(resProfile * resRails): self.bMesh.verts.new()
416
417            splSurf = BirailedSplineSurface(splineRail1, splineRail2, splineProfile, self.bMesh, currV0Index, resRails, resProfile)
418            splSurf.AddFaces()
419            rvSplineSurfaces.append(splSurf)
420
421            currV0Index += resProfile * resRails
422
423        return rvSplineSurfaces
424
425
426    def Apply(self):
427        for splineSurface in self.splineSurfaces: splineSurface.Apply(self.rail1Curve.worldMatrix, self.rail2Curve.worldMatrix, self.profileCurve.worldMatrix)
428
429
430    def AddToScene(self):
431        mesh = bpy.data.meshes.new("Mesh" + self.name)
432
433        self.bMesh.to_mesh(mesh)
434        mesh.update()
435
436        meshObject = bpy.data.objects.new(self.name, mesh)
437
438        bpy.context.collection.objects.link(meshObject)
439