Sun position: remove unused prop in HDRI mode
[blender-addons.git] / curve_tools / curves.py
blob202487dee25d75d0189ccb58133bc109f3cfb3cb
1 from . import mathematics
3 import bpy
6 class BezierPoint:
7 @staticmethod
8 def FromBlenderBezierPoint(blenderBezierPoint):
9 return BezierPoint(blenderBezierPoint.handle_left, blenderBezierPoint.co, blenderBezierPoint.handle_right)
12 def __init__(self, handle_left, co, handle_right):
13 self.handle_left = handle_left
14 self.co = co
15 self.handle_right = handle_right
18 def Copy(self):
19 return BezierPoint(self.handle_left.copy(), self.co.copy(), self.handle_right.copy())
21 def Reversed(self):
22 return BezierPoint(self.handle_right, self.co, self.handle_left)
24 def Reverse(self):
25 tmp = self.handle_left
26 self.handle_left = self.handle_right
27 self.handle_right = tmp
30 class BezierSegment:
31 @staticmethod
32 def FromBlenderBezierPoints(blenderBezierPoint1, blenderBezierPoint2):
33 bp1 = BezierPoint.FromBlenderBezierPoint(blenderBezierPoint1)
34 bp2 = BezierPoint.FromBlenderBezierPoint(blenderBezierPoint2)
36 return BezierSegment(bp1, bp2)
39 def Copy(self):
40 return BezierSegment(self.bezierPoint1.Copy(), self.bezierPoint2.Copy())
42 def Reversed(self):
43 return BezierSegment(self.bezierPoint2.Reversed(), self.bezierPoint1.Reversed())
45 def Reverse(self):
46 # make a copy, otherwise neighboring segment may be affected
47 tmp = self.bezierPoint1.Copy()
48 self.bezierPoint1 = self.bezierPoint2.Copy()
49 self.bezierPoint2 = tmp
50 self.bezierPoint1.Reverse()
51 self.bezierPoint2.Reverse()
54 def __init__(self, bezierPoint1, bezierPoint2):
55 # bpy.types.BezierSplinePoint
56 # ## NOTE/TIP: copy() helps with repeated (intersection) action -- ??
57 self.bezierPoint1 = bezierPoint1.Copy()
58 self.bezierPoint2 = bezierPoint2.Copy()
60 self.ctrlPnt0 = self.bezierPoint1.co
61 self.ctrlPnt1 = self.bezierPoint1.handle_right
62 self.ctrlPnt2 = self.bezierPoint2.handle_left
63 self.ctrlPnt3 = self.bezierPoint2.co
65 self.coeff0 = self.ctrlPnt0
66 self.coeff1 = self.ctrlPnt0 * (-3.0) + self.ctrlPnt1 * (+3.0)
67 self.coeff2 = self.ctrlPnt0 * (+3.0) + self.ctrlPnt1 * (-6.0) + self.ctrlPnt2 * (+3.0)
68 self.coeff3 = self.ctrlPnt0 * (-1.0) + self.ctrlPnt1 * (+3.0) + self.ctrlPnt2 * (-3.0) + self.ctrlPnt3
71 def CalcPoint(self, parameter = 0.5):
72 parameter2 = parameter * parameter
73 parameter3 = parameter * parameter2
75 rvPoint = self.coeff0 + self.coeff1 * parameter + self.coeff2 * parameter2 + self.coeff3 * parameter3
77 return rvPoint
80 def CalcDerivative(self, parameter = 0.5):
81 parameter2 = parameter * parameter
83 rvPoint = self.coeff1 + self.coeff2 * parameter * 2.0 + self.coeff3 * parameter2 * 3.0
85 return rvPoint
88 def CalcLength(self, nrSamples = 2):
89 nrSamplesFloat = float(nrSamples)
90 rvLength = 0.0
91 for iSample in range(nrSamples):
92 par1 = float(iSample) / nrSamplesFloat
93 par2 = float(iSample + 1) / nrSamplesFloat
95 point1 = self.CalcPoint(parameter = par1)
96 point2 = self.CalcPoint(parameter = par2)
97 diff12 = point1 - point2
99 rvLength += diff12.magnitude
101 return rvLength
104 #http://en.wikipedia.org/wiki/De_Casteljau's_algorithm
105 def CalcSplitPoint(self, parameter = 0.5):
106 par1min = 1.0 - parameter
108 bez00 = self.ctrlPnt0
109 bez01 = self.ctrlPnt1
110 bez02 = self.ctrlPnt2
111 bez03 = self.ctrlPnt3
113 bez10 = bez00 * par1min + bez01 * parameter
114 bez11 = bez01 * par1min + bez02 * parameter
115 bez12 = bez02 * par1min + bez03 * parameter
117 bez20 = bez10 * par1min + bez11 * parameter
118 bez21 = bez11 * par1min + bez12 * parameter
120 bez30 = bez20 * par1min + bez21 * parameter
122 bezPoint1 = BezierPoint(self.bezierPoint1.handle_left, bez00, bez10)
123 bezPointNew = BezierPoint(bez20, bez30, bez21)
124 bezPoint2 = BezierPoint(bez12, bez03, self.bezierPoint2.handle_right)
126 return [bezPoint1, bezPointNew, bezPoint2]
129 class BezierSpline:
130 @staticmethod
131 def FromSegments(listSegments):
132 rvSpline = BezierSpline(None)
134 rvSpline.segments = listSegments
136 return rvSpline
139 def __init__(self, blenderBezierSpline):
140 if not blenderBezierSpline is None:
141 if blenderBezierSpline.type != 'BEZIER':
142 print("## ERROR:", "blenderBezierSpline.type != 'BEZIER'")
143 raise Exception("blenderBezierSpline.type != 'BEZIER'")
144 if len(blenderBezierSpline.bezier_points) < 1:
145 if not blenderBezierSpline.use_cyclic_u:
146 print("## ERROR:", "len(blenderBezierSpline.bezier_points) < 1")
147 raise Exception("len(blenderBezierSpline.bezier_points) < 1")
149 self.bezierSpline = blenderBezierSpline
151 self.resolution = 12
152 self.isCyclic = False
153 if not self.bezierSpline is None:
154 self.resolution = self.bezierSpline.resolution_u
155 self.isCyclic = self.bezierSpline.use_cyclic_u
157 self.segments = self.SetupSegments()
160 def __getattr__(self, attrName):
161 if attrName == "nrSegments":
162 return len(self.segments)
164 if attrName == "bezierPoints":
165 rvList = []
167 for seg in self.segments: rvList.append(seg.bezierPoint1)
168 if not self.isCyclic: rvList.append(self.segments[-1].bezierPoint2)
170 return rvList
172 if attrName == "resolutionPerSegment":
173 try: rvResPS = int(self.resolution / self.nrSegments)
174 except: rvResPS = 2
175 if rvResPS < 2: rvResPS = 2
177 return rvResPS
179 if attrName == "length":
180 return self.CalcLength()
182 return None
185 def SetupSegments(self):
186 rvSegments = []
187 if self.bezierSpline is None: return rvSegments
189 nrBezierPoints = len(self.bezierSpline.bezier_points)
190 for iBezierPoint in range(nrBezierPoints - 1):
191 bezierPoint1 = self.bezierSpline.bezier_points[iBezierPoint]
192 bezierPoint2 = self.bezierSpline.bezier_points[iBezierPoint + 1]
193 rvSegments.append(BezierSegment.FromBlenderBezierPoints(bezierPoint1, bezierPoint2))
194 if self.isCyclic:
195 bezierPoint1 = self.bezierSpline.bezier_points[-1]
196 bezierPoint2 = self.bezierSpline.bezier_points[0]
197 rvSegments.append(BezierSegment.FromBlenderBezierPoints(bezierPoint1, bezierPoint2))
199 return rvSegments
202 def UpdateSegments(self, newSegments):
203 prevNrSegments = len(self.segments)
204 diffNrSegments = len(newSegments) - prevNrSegments
205 if diffNrSegments > 0:
206 newBezierPoints = []
207 for segment in newSegments: newBezierPoints.append(segment.bezierPoint1)
208 if not self.isCyclic: newBezierPoints.append(newSegments[-1].bezierPoint2)
210 self.bezierSpline.bezier_points.add(diffNrSegments)
212 for i, bezPoint in enumerate(newBezierPoints):
213 blBezPoint = self.bezierSpline.bezier_points[i]
215 blBezPoint.tilt = 0
216 blBezPoint.radius = 1.0
218 blBezPoint.handle_left_type = 'FREE'
219 blBezPoint.handle_left = bezPoint.handle_left
220 blBezPoint.co = bezPoint.co
221 blBezPoint.handle_right_type = 'FREE'
222 blBezPoint.handle_right = bezPoint.handle_right
224 self.segments = newSegments
225 else:
226 print("### WARNING: UpdateSegments(): not diffNrSegments > 0")
229 def Reversed(self):
230 revSegments = []
232 for iSeg in reversed(range(self.nrSegments)): revSegments.append(self.segments[iSeg].Reversed())
234 rvSpline = BezierSpline.FromSegments(revSegments)
235 rvSpline.resolution = self.resolution
236 rvSpline.isCyclic = self.isCyclic
238 return rvSpline
241 def Reverse(self):
242 revSegments = []
244 for iSeg in reversed(range(self.nrSegments)):
245 self.segments[iSeg].Reverse()
246 revSegments.append(self.segments[iSeg])
248 self.segments = revSegments
251 def CalcDivideResolution(self, segment, parameter):
252 if not segment in self.segments:
253 print("### WARNING: InsertPoint(): not segment in self.segments")
254 return None
256 iSeg = self.segments.index(segment)
257 dPar = 1.0 / self.nrSegments
258 splinePar = dPar * (parameter + float(iSeg))
260 res1 = int(splinePar * self.resolution)
261 if res1 < 2:
262 print("### WARNING: CalcDivideResolution(): res1 < 2 -- res1: %d" % res1, "-- setting it to 2")
263 res1 = 2
265 res2 = int((1.0 - splinePar) * self.resolution)
266 if res2 < 2:
267 print("### WARNING: CalcDivideResolution(): res2 < 2 -- res2: %d" % res2, "-- setting it to 2")
268 res2 = 2
270 return [res1, res2]
271 # return [self.resolution, self.resolution]
274 def CalcPoint(self, parameter):
275 nrSegs = self.nrSegments
277 segmentIndex = int(nrSegs * parameter)
278 if segmentIndex < 0: segmentIndex = 0
279 if segmentIndex > (nrSegs - 1): segmentIndex = nrSegs - 1
281 segmentParameter = nrSegs * parameter - segmentIndex
282 if segmentParameter < 0.0: segmentParameter = 0.0
283 if segmentParameter > 1.0: segmentParameter = 1.0
285 return self.segments[segmentIndex].CalcPoint(parameter = segmentParameter)
288 def CalcDerivative(self, parameter):
289 nrSegs = self.nrSegments
291 segmentIndex = int(nrSegs * parameter)
292 if segmentIndex < 0: segmentIndex = 0
293 if segmentIndex > (nrSegs - 1): segmentIndex = nrSegs - 1
295 segmentParameter = nrSegs * parameter - segmentIndex
296 if segmentParameter < 0.0: segmentParameter = 0.0
297 if segmentParameter > 1.0: segmentParameter = 1.0
299 return self.segments[segmentIndex].CalcDerivative(parameter = segmentParameter)
302 def InsertPoint(self, segment, parameter):
303 if not segment in self.segments:
304 print("### WARNING: InsertPoint(): not segment in self.segments")
305 return
306 iSeg = self.segments.index(segment)
307 nrSegments = len(self.segments)
309 splitPoints = segment.CalcSplitPoint(parameter = parameter)
310 bezPoint1 = splitPoints[0]
311 bezPointNew = splitPoints[1]
312 bezPoint2 = splitPoints[2]
314 segment.bezierPoint1.handle_right = bezPoint1.handle_right
315 segment.bezierPoint2 = bezPointNew
317 if iSeg < (nrSegments - 1):
318 nextSeg = self.segments[iSeg + 1]
319 nextSeg.bezierPoint1.handle_left = bezPoint2.handle_left
320 else:
321 if self.isCyclic:
322 nextSeg = self.segments[0]
323 nextSeg.bezierPoint1.handle_left = bezPoint2.handle_left
326 newSeg = BezierSegment(bezPointNew, bezPoint2)
327 self.segments.insert(iSeg + 1, newSeg)
330 def Split(self, segment, parameter):
331 if not segment in self.segments:
332 print("### WARNING: InsertPoint(): not segment in self.segments")
333 return None
334 iSeg = self.segments.index(segment)
335 nrSegments = len(self.segments)
337 splitPoints = segment.CalcSplitPoint(parameter = parameter)
338 bezPoint1 = splitPoints[0]
339 bezPointNew = splitPoints[1]
340 bezPoint2 = splitPoints[2]
343 newSpline1Segments = []
344 for iSeg1 in range(iSeg): newSpline1Segments.append(self.segments[iSeg1])
345 if len(newSpline1Segments) > 0: newSpline1Segments[-1].bezierPoint2.handle_right = bezPoint1.handle_right
346 newSpline1Segments.append(BezierSegment(bezPoint1, bezPointNew))
348 newSpline2Segments = []
349 newSpline2Segments.append(BezierSegment(bezPointNew, bezPoint2))
350 for iSeg2 in range(iSeg + 1, nrSegments): newSpline2Segments.append(self.segments[iSeg2])
351 if len(newSpline2Segments) > 1: newSpline2Segments[1].bezierPoint1.handle_left = newSpline2Segments[0].bezierPoint2.handle_left
354 newSpline1 = BezierSpline.FromSegments(newSpline1Segments)
355 newSpline2 = BezierSpline.FromSegments(newSpline2Segments)
357 return [newSpline1, newSpline2]
360 def Join(self, spline2, mode = 'At_midpoint'):
361 if mode == 'At_midpoint':
362 self.JoinAtMidpoint(spline2)
363 return
365 if mode == 'Insert_segment':
366 self.JoinInsertSegment(spline2)
367 return
369 print("### ERROR: Join(): unknown mode:", mode)
372 def JoinAtMidpoint(self, spline2):
373 bezPoint1 = self.segments[-1].bezierPoint2
374 bezPoint2 = spline2.segments[0].bezierPoint1
376 mpHandleLeft = bezPoint1.handle_left.copy()
377 mpCo = (bezPoint1.co + bezPoint2.co) * 0.5
378 mpHandleRight = bezPoint2.handle_right.copy()
379 mpBezPoint = BezierPoint(mpHandleLeft, mpCo, mpHandleRight)
381 self.segments[-1].bezierPoint2 = mpBezPoint
382 spline2.segments[0].bezierPoint1 = mpBezPoint
383 for seg2 in spline2.segments: self.segments.append(seg2)
385 self.resolution += spline2.resolution
386 self.isCyclic = False # is this ok?
389 def JoinInsertSegment(self, spline2):
390 self.segments.append(BezierSegment(self.segments[-1].bezierPoint2, spline2.segments[0].bezierPoint1))
391 for seg2 in spline2.segments: self.segments.append(seg2)
393 self.resolution += spline2.resolution # extra segment will usually be short -- impact on resolution negligable
395 self.isCyclic = False # is this ok?
398 def RefreshInScene(self):
399 bezierPoints = self.bezierPoints
401 currNrBezierPoints = len(self.bezierSpline.bezier_points)
402 diffNrBezierPoints = len(bezierPoints) - currNrBezierPoints
403 if diffNrBezierPoints > 0: self.bezierSpline.bezier_points.add(diffNrBezierPoints)
405 for i, bezPoint in enumerate(bezierPoints):
406 blBezPoint = self.bezierSpline.bezier_points[i]
408 blBezPoint.tilt = 0
409 blBezPoint.radius = 1.0
411 blBezPoint.handle_left_type = 'FREE'
412 blBezPoint.handle_left = bezPoint.handle_left
413 blBezPoint.co = bezPoint.co
414 blBezPoint.handle_right_type = 'FREE'
415 blBezPoint.handle_right = bezPoint.handle_right
417 self.bezierSpline.use_cyclic_u = self.isCyclic
418 self.bezierSpline.resolution_u = self.resolution
421 def CalcLength(self):
422 try: nrSamplesPerSegment = int(self.resolution / self.nrSegments)
423 except: nrSamplesPerSegment = 2
424 if nrSamplesPerSegment < 2: nrSamplesPerSegment = 2
426 rvLength = 0.0
427 for segment in self.segments:
428 rvLength += segment.CalcLength(nrSamples = nrSamplesPerSegment)
430 return rvLength
433 def GetLengthIsSmallerThan(self, threshold):
434 try: nrSamplesPerSegment = int(self.resolution / self.nrSegments)
435 except: nrSamplesPerSegment = 2
436 if nrSamplesPerSegment < 2: nrSamplesPerSegment = 2
438 length = 0.0
439 for segment in self.segments:
440 length += segment.CalcLength(nrSamples = nrSamplesPerSegment)
441 if not length < threshold: return False
443 return True
446 class Curve:
447 def __init__(self, blenderCurve):
448 self.curve = blenderCurve
449 self.curveData = blenderCurve.data
451 self.splines = self.SetupSplines()
454 def __getattr__(self, attrName):
455 if attrName == "nrSplines":
456 return len(self.splines)
458 if attrName == "length":
459 return self.CalcLength()
461 if attrName == "worldMatrix":
462 return self.curve.matrix_world
464 if attrName == "location":
465 return self.curve.location
467 return None
470 def SetupSplines(self):
471 rvSplines = []
472 for spline in self.curveData.splines:
473 if spline.type != 'BEZIER':
474 print("## WARNING: only bezier splines are supported, atm; other types are ignored")
475 continue
477 try: newSpline = BezierSpline(spline)
478 except:
479 print("## EXCEPTION: newSpline = BezierSpline(spline)")
480 continue
482 rvSplines.append(newSpline)
484 return rvSplines
487 def RebuildInScene(self):
488 self.curveData.splines.clear()
490 for spline in self.splines:
491 blSpline = self.curveData.splines.new('BEZIER')
492 blSpline.use_cyclic_u = spline.isCyclic
493 blSpline.resolution_u = spline.resolution
495 bezierPoints = []
496 for segment in spline.segments: bezierPoints.append(segment.bezierPoint1)
497 if not spline.isCyclic: bezierPoints.append(spline.segments[-1].bezierPoint2)
498 #else: print("????", "spline.isCyclic")
500 nrBezierPoints = len(bezierPoints)
501 blSpline.bezier_points.add(nrBezierPoints - 1)
503 for i, blBezPoint in enumerate(blSpline.bezier_points):
504 bezPoint = bezierPoints[i]
506 blBezPoint.tilt = 0
507 blBezPoint.radius = 1.0
509 blBezPoint.handle_left_type = 'FREE'
510 blBezPoint.handle_left = bezPoint.handle_left
511 blBezPoint.co = bezPoint.co
512 blBezPoint.handle_right_type = 'FREE'
513 blBezPoint.handle_right = bezPoint.handle_right
516 def CalcLength(self):
517 rvLength = 0.0
518 for spline in self.splines:
519 rvLength += spline.length
521 return rvLength
524 def RemoveShortSplines(self, threshold):
525 splinesToRemove = []
527 for spline in self.splines:
528 if spline.GetLengthIsSmallerThan(threshold): splinesToRemove.append(spline)
530 for spline in splinesToRemove: self.splines.remove(spline)
532 return len(splinesToRemove)
535 def JoinNeighbouringSplines(self, startEnd, threshold, mode):
536 nrJoins = 0
538 while True:
539 firstPair = self.JoinGetFirstPair(startEnd, threshold)
540 if firstPair is None: break
542 firstPair[0].Join(firstPair[1], mode)
543 self.splines.remove(firstPair[1])
545 nrJoins += 1
547 return nrJoins
550 def JoinGetFirstPair(self, startEnd, threshold):
551 nrSplines = len(self.splines)
553 if startEnd:
554 for iCurrentSpline in range(nrSplines):
555 currentSpline = self.splines[iCurrentSpline]
557 for iNextSpline in range(iCurrentSpline + 1, nrSplines):
558 nextSpline = self.splines[iNextSpline]
560 currEndPoint = currentSpline.segments[-1].bezierPoint2.co
561 nextStartPoint = nextSpline.segments[0].bezierPoint1.co
562 if mathematics.IsSamePoint(currEndPoint, nextStartPoint, threshold): return [currentSpline, nextSpline]
564 nextEndPoint = nextSpline.segments[-1].bezierPoint2.co
565 currStartPoint = currentSpline.segments[0].bezierPoint1.co
566 if mathematics.IsSamePoint(nextEndPoint, currStartPoint, threshold): return [nextSpline, currentSpline]
568 return None
569 else:
570 for iCurrentSpline in range(nrSplines):
571 currentSpline = self.splines[iCurrentSpline]
573 for iNextSpline in range(iCurrentSpline + 1, nrSplines):
574 nextSpline = self.splines[iNextSpline]
576 currEndPoint = currentSpline.segments[-1].bezierPoint2.co
577 nextStartPoint = nextSpline.segments[0].bezierPoint1.co
578 if mathematics.IsSamePoint(currEndPoint, nextStartPoint, threshold): return [currentSpline, nextSpline]
580 nextEndPoint = nextSpline.segments[-1].bezierPoint2.co
581 currStartPoint = currentSpline.segments[0].bezierPoint1.co
582 if mathematics.IsSamePoint(nextEndPoint, currStartPoint, threshold): return [nextSpline, currentSpline]
584 if mathematics.IsSamePoint(currEndPoint, nextEndPoint, threshold):
585 nextSpline.Reverse()
586 #print("## ", "nextSpline.Reverse()")
587 return [currentSpline, nextSpline]
589 if mathematics.IsSamePoint(currStartPoint, nextStartPoint, threshold):
590 currentSpline.Reverse()
591 #print("## ", "currentSpline.Reverse()")
592 return [currentSpline, nextSpline]
594 return None