1 from . import mathematics
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
15 self
.handle_right
= handle_right
19 return BezierPoint(self
.handle_left
.copy(), self
.co
.copy(), self
.handle_right
.copy())
22 return BezierPoint(self
.handle_right
, self
.co
, self
.handle_left
)
25 tmp
= self
.handle_left
26 self
.handle_left
= self
.handle_right
27 self
.handle_right
= tmp
32 def FromBlenderBezierPoints(blenderBezierPoint1
, blenderBezierPoint2
):
33 bp1
= BezierPoint
.FromBlenderBezierPoint(blenderBezierPoint1
)
34 bp2
= BezierPoint
.FromBlenderBezierPoint(blenderBezierPoint2
)
36 return BezierSegment(bp1
, bp2
)
40 return BezierSegment(self
.bezierPoint1
.Copy(), self
.bezierPoint2
.Copy())
43 return BezierSegment(self
.bezierPoint2
.Reversed(), self
.bezierPoint1
.Reversed())
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
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
88 def CalcLength(self
, nrSamples
= 2):
89 nrSamplesFloat
= float(nrSamples
)
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
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
]
131 def FromSegments(listSegments
):
132 rvSpline
= BezierSpline(None)
134 rvSpline
.segments
= listSegments
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
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":
167 for seg
in self
.segments
: rvList
.append(seg
.bezierPoint1
)
168 if not self
.isCyclic
: rvList
.append(self
.segments
[-1].bezierPoint2
)
172 if attrName
== "resolutionPerSegment":
173 try: rvResPS
= int(self
.resolution
/ self
.nrSegments
)
175 if rvResPS
< 2: rvResPS
= 2
179 if attrName
== "length":
180 return self
.CalcLength()
185 def SetupSegments(self
):
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
))
195 bezierPoint1
= self
.bezierSpline
.bezier_points
[-1]
196 bezierPoint2
= self
.bezierSpline
.bezier_points
[0]
197 rvSegments
.append(BezierSegment
.FromBlenderBezierPoints(bezierPoint1
, bezierPoint2
))
202 def UpdateSegments(self
, newSegments
):
203 prevNrSegments
= len(self
.segments
)
204 diffNrSegments
= len(newSegments
) - prevNrSegments
205 if diffNrSegments
> 0:
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
]
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
226 print("### WARNING: UpdateSegments(): not diffNrSegments > 0")
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
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")
256 iSeg
= self
.segments
.index(segment
)
257 dPar
= 1.0 / self
.nrSegments
258 splinePar
= dPar
* (parameter
+ float(iSeg
))
260 res1
= int(splinePar
* self
.resolution
)
262 print("### WARNING: CalcDivideResolution(): res1 < 2 -- res1: %d" % res1
, "-- setting it to 2")
265 res2
= int((1.0 - splinePar
) * self
.resolution
)
267 print("### WARNING: CalcDivideResolution(): res2 < 2 -- res2: %d" % res2
, "-- setting it to 2")
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")
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
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")
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
)
365 if mode
== 'Insert_segment':
366 self
.JoinInsertSegment(spline2
)
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
]
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
427 for segment
in self
.segments
:
428 rvLength
+= segment
.CalcLength(nrSamples
= nrSamplesPerSegment
)
433 def GetLengthIsSmallerThan(self
, threshold
):
434 try: nrSamplesPerSegment
= int(self
.resolution
/ self
.nrSegments
)
435 except: nrSamplesPerSegment
= 2
436 if nrSamplesPerSegment
< 2: nrSamplesPerSegment
= 2
439 for segment
in self
.segments
:
440 length
+= segment
.CalcLength(nrSamples
= nrSamplesPerSegment
)
441 if not length
< threshold
: return False
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
470 def SetupSplines(self
):
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")
477 try: newSpline
= BezierSpline(spline
)
479 print("## EXCEPTION: newSpline = BezierSpline(spline)")
482 rvSplines
.append(newSpline
)
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
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
]
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
):
518 for spline
in self
.splines
:
519 rvLength
+= spline
.length
524 def RemoveShortSplines(self
, threshold
):
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
):
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])
550 def JoinGetFirstPair(self
, startEnd
, threshold
):
551 nrSplines
= len(self
.splines
)
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
]
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
):
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
]