2 Euclidean is a collection of python utilities for complex numbers, paths, polygons & Vector3s.
4 To use euclidean, install python 2.x on your machine, which is avaliable from http://www.python.org/download/
6 Then in the folder which euclidean is in, type 'python' in a shell to run the python interpreter. Finally type 'import euclidean' to import these utilities and 'from vector3 import Vector3' to import the Vector3 class.
9 Below are examples of euclidean use.
11 >>> from euclidean import *
13 >>> right=complex(1.0,0.0)
14 >>> back=complex(0.0,1.0)
15 >>> getMaximum(right,back)
17 >>> polygon=[origin, right, back]
18 >>> getLoopLength(polygon)
20 >>> getAreaLoop(polygon)
24 from __future__ import absolute_import
26 from fabmetheus_utilities.vector3 import Vector3
27 from fabmetheus_utilities import xml_simple_writer
33 if sys.version_info[0] < 3:
36 import io as cStringIO
38 __author__ = 'Enrique Perez (perez_enrique@yahoo.com)'
39 __date__ = '$Date: 2008/21/04 $'
40 __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
43 globalGoldenAngle = 3.8832220774509332 # (math.sqrt(5.0) - 1.0) * math.pi
44 globalGoldenRatio = 1.6180339887498948482045868 # math.sqrt(1.25) + 0.5
45 globalTau = math.pi + math.pi # http://tauday.com/
48 def addElementToListDictionary(element, key, listDictionary):
49 'Add an element to the list table.'
50 if key in listDictionary:
51 listDictionary[key].append(element)
53 listDictionary[key] = [element]
55 def addElementToListDictionaryIfNotThere(element, key, listDictionary):
56 'Add the value to the lists.'
57 if key in listDictionary:
58 elements = listDictionary[key]
59 if element not in elements:
60 elements.append(element)
62 listDictionary[key] = [element]
64 def addElementToPixelList( element, pixelDictionary, x, y ):
65 'Add an element to the pixel list.'
66 addElementToListDictionary( element, (x, y), pixelDictionary )
68 def addElementToPixelListFromPoint( element, pixelDictionary, point ):
69 'Add an element to the pixel list.'
70 addElementToPixelList( element, pixelDictionary, int( round( point.real ) ), int( round( point.imag ) ) )
72 def addHorizontallyBoundedPoint(begin, center, end, horizontalBegin, horizontalEnd, path):
73 'Add point if it is within the horizontal bounds.'
74 if horizontalEnd <= center.real <= horizontalBegin:
78 if center.real > horizontalBegin >= end.real:
79 centerMinusEnd = center - end
80 along = (center.real - horizontalBegin) / centerMinusEnd.real
81 path.append(center - along * centerMinusEnd)
84 if center.real < horizontalEnd <= begin.real:
85 centerMinusBegin = center - begin
86 along = (center.real - horizontalEnd) / centerMinusBegin.real
87 path.append(center - along * centerMinusBegin)
89 def addListToListTable( elementList, key, listDictionary ):
90 'Add a list to the list table.'
91 if key in listDictionary:
92 listDictionary[key] += elementList
94 listDictionary[key] = elementList
96 def addLoopToPixelTable( loop, pixelDictionary, width ):
97 'Add loop to the pixel table.'
98 for pointIndex in xrange(len(loop)):
99 pointBegin = loop[pointIndex]
100 pointEnd = loop[(pointIndex + 1) % len(loop)]
101 addValueSegmentToPixelTable( pointBegin, pointEnd, pixelDictionary, None, width )
103 def addNestedRingBeginning(distanceFeedRate, loop, z):
104 'Add nested ring beginning to gcode output.'
105 distanceFeedRate.addLine('(<nestedRing>)')
106 distanceFeedRate.addLine('(<boundaryPerimeter>)')
108 pointVector3 = Vector3(point.real, point.imag, z)
109 distanceFeedRate.addLine(distanceFeedRate.getBoundaryLine(pointVector3))
111 def addPathToPixelTable( path, pixelDictionary, value, width ):
112 'Add path to the pixel table.'
113 for pointIndex in xrange( len(path) - 1 ):
114 pointBegin = path[pointIndex]
115 pointEnd = path[pointIndex + 1]
116 addValueSegmentToPixelTable( pointBegin, pointEnd, pixelDictionary, value, width )
118 def addPixelTableToPixelTable( fromPixelTable, intoPixelTable ):
119 'Add from pixel table to the into pixel table.'
120 for fromPixelTableKey in fromPixelTable.keys():
121 intoPixelTable[ fromPixelTableKey ] = fromPixelTable[ fromPixelTableKey ]
123 def addPixelToPixelTableWithSteepness( isSteep, pixelDictionary, value, x, y ):
124 'Add pixels to the pixel table with steepness.'
126 pixelDictionary[(y, x)] = value
128 pixelDictionary[(x, y)] = value
130 def addPointToPath( path, pixelDictionary, point, value, width ):
131 'Add a point to a path and the pixel table.'
136 addValueSegmentToPixelTable( begin, point, pixelDictionary, value, width )
138 def addSegmentToPixelTable( beginComplex, endComplex, pixelDictionary, shortenDistanceBegin, shortenDistanceEnd, width ):
139 'Add line segment to the pixel table.'
140 if abs( beginComplex - endComplex ) <= 0.0:
142 beginComplex /= width
144 if shortenDistanceBegin > 0.0:
145 endMinusBeginComplex = endComplex - beginComplex
146 endMinusBeginComplexLength = abs( endMinusBeginComplex )
147 if endMinusBeginComplexLength < shortenDistanceBegin:
149 beginComplex = beginComplex + endMinusBeginComplex * shortenDistanceBegin / endMinusBeginComplexLength
150 if shortenDistanceEnd > 0.0:
151 beginMinusEndComplex = beginComplex - endComplex
152 beginMinusEndComplexLength = abs( beginMinusEndComplex )
153 if beginMinusEndComplexLength < 0.0:
155 endComplex = endComplex + beginMinusEndComplex * shortenDistanceEnd / beginMinusEndComplexLength
156 deltaX = endComplex.real - beginComplex.real
157 deltaY = endComplex.imag - beginComplex.imag
158 isSteep = abs( deltaY ) > abs( deltaX )
160 beginComplex = complex( beginComplex.imag, beginComplex.real )
161 endComplex = complex( endComplex.imag, endComplex.real )
162 if beginComplex.real > endComplex.real:
163 endComplex, beginComplex = beginComplex, endComplex
164 deltaX = endComplex.real - beginComplex.real
165 deltaY = endComplex.imag - beginComplex.imag
167 gradient = deltaY / deltaX
170 print('Warning, deltaX in addSegmentToPixelTable in euclidean is 0.')
173 print(shortenDistanceBegin)
174 print(shortenDistanceEnd)
176 xBegin = int(round(beginComplex.real))
177 xEnd = int(round(endComplex.real))
178 yIntersection = beginComplex.imag - beginComplex.real * gradient
180 pixelDictionary[( int( round( beginComplex.imag ) ), xBegin)] = None
181 pixelDictionary[( int( round( endComplex.imag ) ), xEnd )] = None
182 for x in xrange( xBegin + 1, xEnd ):
183 y = int( math.floor( yIntersection + x * gradient ) )
184 pixelDictionary[(y, x)] = None
185 pixelDictionary[(y + 1, x)] = None
187 pixelDictionary[(xBegin, int( round( beginComplex.imag ) ) )] = None
188 pixelDictionary[(xEnd, int( round( endComplex.imag ) ) )] = None
189 for x in xrange( xBegin + 1, xEnd ):
190 y = int( math.floor( yIntersection + x * gradient ) )
191 pixelDictionary[(x, y)] = None
192 pixelDictionary[(x, y + 1)] = None
194 def addSquareTwoToPixelDictionary(pixelDictionary, point, value, width):
195 'Add square with two pixels around the center to pixel dictionary.'
197 x = int(round(point.real))
198 y = int(round(point.imag))
199 for xStep in xrange(x - 2, x + 3):
200 for yStep in xrange(y - 2, y + 3):
201 pixelDictionary[(xStep, yStep)] = value
203 def addToThreadsFromLoop(extrusionHalfWidth, gcodeType, loop, oldOrderedLocation, skein):
204 'Add to threads from the last location from loop.'
205 loop = getLoopStartingClosest(extrusionHalfWidth, oldOrderedLocation.dropAxis(), loop)
206 oldOrderedLocation.x = loop[0].real
207 oldOrderedLocation.y = loop[0].imag
208 gcodeTypeStart = gcodeType
209 if isWiddershins(loop):
210 skein.distanceFeedRate.addLine('(<%s> outer )' % gcodeType)
212 skein.distanceFeedRate.addLine('(<%s> inner )' % gcodeType)
213 skein.addGcodeFromThreadZ(loop + [loop[0]], oldOrderedLocation.z)
214 skein.distanceFeedRate.addLine('(</%s>)' % gcodeType)
216 def addToThreadsRemove(extrusionHalfWidth, nestedRings, oldOrderedLocation, skein, threadSequence):
217 'Add to threads from the last location from nested rings.'
218 while len(nestedRings) > 0:
219 getTransferClosestNestedRing(extrusionHalfWidth, nestedRings, oldOrderedLocation, skein, threadSequence)
221 def addValueSegmentToPixelTable( beginComplex, endComplex, pixelDictionary, value, width ):
222 'Add line segment to the pixel table.'
223 if abs( beginComplex - endComplex ) <= 0.0:
225 beginComplex /= width
227 deltaX = endComplex.real - beginComplex.real
228 deltaY = endComplex.imag - beginComplex.imag
229 isSteep = abs( deltaY ) > abs( deltaX )
231 beginComplex = complex( beginComplex.imag, beginComplex.real )
232 endComplex = complex( endComplex.imag, endComplex.real )
233 if beginComplex.real > endComplex.real:
234 endComplex, beginComplex = beginComplex, endComplex
235 deltaX = endComplex.real - beginComplex.real
236 deltaY = endComplex.imag - beginComplex.imag
238 gradient = deltaY / deltaX
241 print('Warning, deltaX in addValueSegmentToPixelTable in euclidean is 0.')
246 xBegin = int(round(beginComplex.real))
247 xEnd = int(round(endComplex.real))
248 yIntersection = beginComplex.imag - beginComplex.real * gradient
250 pixelDictionary[(int( round( beginComplex.imag ) ), xBegin)] = value
251 pixelDictionary[(int( round( endComplex.imag ) ), xEnd)] = value
252 for x in xrange( xBegin + 1, xEnd ):
253 y = int( math.floor( yIntersection + x * gradient ) )
254 pixelDictionary[(y, x)] = value
255 pixelDictionary[(y + 1, x)] = value
257 pixelDictionary[(xBegin, int( round( beginComplex.imag ) ))] = value
258 pixelDictionary[(xEnd, int( round( endComplex.imag ) ))] = value
259 for x in xrange( xBegin + 1, xEnd ):
260 y = int( math.floor( yIntersection + x * gradient ) )
261 pixelDictionary[(x, y)] = value
262 pixelDictionary[(x, y + 1)] = value
264 def addValueToOutput(depth, keyInput, output, value):
265 'Add value to the output.'
266 depthStart = ' ' * depth
267 output.write('%s%s:' % (depthStart, keyInput))
268 if value.__class__ == dict:
273 addValueToOutput(depth + 1, key, output, value[key])
275 if value.__class__ == list:
277 for elementIndex, element in enumerate(value):
278 addValueToOutput(depth + 1, elementIndex, output, element)
280 output.write(' %s\n' % value)
282 def addXIntersectionIndexesFromLoopListsY( loopLists, xIntersectionIndexList, y ):
283 'Add the x intersection indexes for the loop lists.'
284 for loopListIndex in xrange( len(loopLists) ):
285 loopList = loopLists[ loopListIndex ]
286 addXIntersectionIndexesFromLoopsY( loopList, loopListIndex, xIntersectionIndexList, y )
288 def addXIntersectionIndexesFromLoopsY( loops, solidIndex, xIntersectionIndexList, y ):
289 'Add the x intersection indexes for the loops.'
291 addXIntersectionIndexesFromLoopY( loop, solidIndex, xIntersectionIndexList, y )
293 def addXIntersectionIndexesFromLoopY( loop, solidIndex, xIntersectionIndexList, y ):
294 'Add the x intersection indexes for a loop.'
295 for pointIndex in xrange(len(loop)):
296 pointFirst = loop[pointIndex]
297 pointSecond = loop[(pointIndex + 1) % len(loop)]
298 xIntersection = getXIntersectionIfExists( pointFirst, pointSecond, y )
299 if xIntersection != None:
300 xIntersectionIndexList.append( XIntersectionIndex( solidIndex, xIntersection ) )
302 def addXIntersectionIndexesFromSegment( index, segment, xIntersectionIndexList ):
303 'Add the x intersection indexes from the segment.'
304 for endpoint in segment:
305 xIntersectionIndexList.append( XIntersectionIndex( index, endpoint.point.real ) )
307 def addXIntersectionIndexesFromSegments( index, segments, xIntersectionIndexList ):
308 'Add the x intersection indexes from the segments.'
309 for segment in segments:
310 addXIntersectionIndexesFromSegment( index, segment, xIntersectionIndexList )
312 def addXIntersectionIndexesFromXIntersections( index, xIntersectionIndexList, xIntersections ):
313 'Add the x intersection indexes from the XIntersections.'
314 for xIntersection in xIntersections:
315 xIntersectionIndexList.append( XIntersectionIndex( index, xIntersection ) )
317 def addXIntersections( loop, xIntersections, y ):
318 'Add the x intersections for a loop.'
319 for pointIndex in xrange(len(loop)):
320 pointFirst = loop[pointIndex]
321 pointSecond = loop[(pointIndex + 1) % len(loop)]
322 xIntersection = getXIntersectionIfExists( pointFirst, pointSecond, y )
323 if xIntersection != None:
324 xIntersections.append( xIntersection )
326 def addXIntersectionsFromLoopForTable(loop, xIntersectionsTable, width):
327 'Add the x intersections for a loop into a table.'
328 for pointIndex in xrange(len(loop)):
329 pointBegin = loop[pointIndex]
330 pointEnd = loop[(pointIndex + 1) % len(loop)]
331 if pointBegin.imag > pointEnd.imag:
332 pointOriginal = pointBegin
333 pointBegin = pointEnd
334 pointEnd = pointOriginal
335 fillBegin = int( math.ceil( pointBegin.imag / width ) )
336 fillEnd = int( math.ceil( pointEnd.imag / width ) )
337 if fillEnd > fillBegin:
338 secondMinusFirstComplex = pointEnd - pointBegin
339 secondMinusFirstImaginaryOverReal = secondMinusFirstComplex.real / secondMinusFirstComplex.imag
340 beginRealMinusImaginary = pointBegin.real - pointBegin.imag * secondMinusFirstImaginaryOverReal
341 for fillLine in xrange( fillBegin, fillEnd ):
343 xIntersection = y * secondMinusFirstImaginaryOverReal + beginRealMinusImaginary
344 addElementToListDictionary( xIntersection, fillLine, xIntersectionsTable )
346 def addXIntersectionsFromLoops(loops, xIntersections, y):
347 'Add the x intersections for the loops.'
349 addXIntersections(loop, xIntersections, y)
351 def addXIntersectionsFromLoopsForTable(loops, xIntersectionsTable, width):
352 'Add the x intersections for a loop into a table.'
354 addXIntersectionsFromLoopForTable(loop, xIntersectionsTable, width)
356 def compareSegmentLength( endpoint, otherEndpoint ):
357 'Get comparison in order to sort endpoints in ascending order of segment length.'
358 if endpoint.segmentLength > otherEndpoint.segmentLength:
360 if endpoint.segmentLength < otherEndpoint.segmentLength:
364 def concatenateRemovePath(connectedPaths, pathIndex, paths, pixelDictionary, segments, sharpestProduct, width):
365 'Get connected paths from paths.'
366 bottomSegment = segments[pathIndex]
367 path = paths[pathIndex]
368 if bottomSegment == None:
369 connectedPaths.append(path)
371 endpoints = getEndpointsFromSegments(segments[pathIndex + 1 :])
372 bottomSegmentEndpoint = bottomSegment[0]
373 nextEndpoint = bottomSegmentEndpoint.getClosestMissCheckEndpointPath(endpoints, bottomSegmentEndpoint.path, pixelDictionary, sharpestProduct, width)
374 if nextEndpoint == None:
375 bottomSegmentEndpoint = bottomSegment[1]
376 nextEndpoint = bottomSegmentEndpoint.getClosestMissCheckEndpointPath(endpoints, bottomSegmentEndpoint.path, pixelDictionary, sharpestProduct, width)
377 if nextEndpoint == None:
378 connectedPaths.append(path)
380 if len(bottomSegmentEndpoint.path) > 0 and len(nextEndpoint.path) > 0:
381 bottomEnd = bottomSegmentEndpoint.path[-1]
382 nextBegin = nextEndpoint.path[-1]
383 nextMinusBottomNormalized = getNormalized(nextBegin - bottomEnd)
384 if len( bottomSegmentEndpoint.path ) > 1:
385 bottomPenultimate = bottomSegmentEndpoint.path[-2]
386 if getDotProduct(getNormalized(bottomPenultimate - bottomEnd), nextMinusBottomNormalized) > 0.99:
387 connectedPaths.append(path)
389 if len( nextEndpoint.path ) > 1:
390 nextPenultimate = nextEndpoint.path[-2]
391 if getDotProduct(getNormalized(nextPenultimate - nextBegin), - nextMinusBottomNormalized) > 0.99:
392 connectedPaths.append(path)
394 nextEndpoint.path.reverse()
395 concatenatedPath = bottomSegmentEndpoint.path + nextEndpoint.path
396 paths[nextEndpoint.pathIndex] = concatenatedPath
397 segments[nextEndpoint.pathIndex] = getSegmentFromPath(concatenatedPath, nextEndpoint.pathIndex)
398 addValueSegmentToPixelTable(bottomSegmentEndpoint.point, nextEndpoint.point, pixelDictionary, None, width)
400 def getAngleAroundZAxisDifference( subtractFromVec3, subtractVec3 ):
401 'Get the angle around the Z axis difference between a pair of Vector3s.'
402 subtractVectorMirror = complex( subtractVec3.x , - subtractVec3.y )
403 differenceVector = getRoundZAxisByPlaneAngle( subtractVectorMirror, subtractFromVec3 )
404 return math.atan2( differenceVector.y, differenceVector.x )
406 def getAngleDifferenceByComplex( subtractFromComplex, subtractComplex ):
407 'Get the angle between a pair of normalized complexes.'
408 subtractComplexMirror = complex( subtractComplex.real , - subtractComplex.imag )
409 differenceComplex = subtractComplexMirror * subtractFromComplex
410 return math.atan2( differenceComplex.imag, differenceComplex.real )
412 def getAreaLoop(loop):
413 'Get the area of a complex polygon.'
415 for pointIndex, point in enumerate(loop):
416 pointEnd = loop[(pointIndex + 1) % len(loop)]
417 areaLoopDouble += point.real * pointEnd.imag - pointEnd.real * point.imag
418 return 0.5 * areaLoopDouble
420 def getAreaLoopAbsolute(loop):
421 'Get the absolute area of a complex polygon.'
422 return abs(getAreaLoop(loop))
424 def getAreaLoops(loops):
425 'Get the area of a list of complex polygons.'
428 areaLoops += getAreaLoop(loop)
431 def getAreaVector3LoopAbsolute(loop):
432 'Get the absolute area of a vector3 polygon.'
433 return getAreaLoopAbsolute(getComplexPath(loop))
435 def getAroundLoop(begin, end, loop):
436 'Get an arc around a loop.'
440 for pointIndex in xrange(begin, end):
441 aroundLoop.append(loop[pointIndex % len(loop)])
444 def getAwayPath(path, radius):
445 'Get a path with only the points that are far enough away from each other, except for the last point.'
449 awayPath = getAwayPoints(path, radius)
450 if len(awayPath) == 0:
452 if abs(lastPoint - awayPath[-1]) > 0.001 * radius:
453 awayPath.append(lastPoint)
456 def getAwayPoints(points, radius):
457 'Get a path with only the points that are far enough away from each other.'
459 oneOverOverlapDistance = 1000.0 / radius
462 x = int(point.real * oneOverOverlapDistance)
463 y = int(point.imag * oneOverOverlapDistance)
464 if not getSquareIsOccupied(pixelDictionary, x, y):
465 awayPoints.append(point)
466 pixelDictionary[(x, y)] = None
469 def getBooleanFromDictionary(defaultBoolean, dictionary, key):
470 'Get boolean from the dictionary and key.'
471 if key not in dictionary:
472 return defaultBoolean
473 return getBooleanFromValue(dictionary[key])
475 def getBooleanFromValue(value):
476 'Get boolean from the word.'
477 firstCharacter = str(value).lower().lstrip()[: 1]
478 return firstCharacter == 't' or firstCharacter == '1'
480 def getBottomByPath(path):
481 'Get the bottom of the path.'
482 bottom = 987654321987654321.0
484 bottom = min(bottom, point.z)
487 def getBottomByPaths(paths):
488 'Get the bottom of the paths.'
489 bottom = 987654321987654321.0
492 bottom = min(bottom, point.z)
495 def getClippedAtEndLoopPath( clip, loopPath ):
496 'Get a clipped loop path.'
499 loopPathLength = getPathLength(loopPath)
500 clip = min( clip, 0.3 * loopPathLength )
504 clippedLength = loopPathLength - clip
505 while totalLength < clippedLength and pointIndex < len(loopPath) - 1:
506 firstPoint = loopPath[pointIndex]
507 secondPoint = loopPath[pointIndex + 1]
509 lastLength = totalLength
510 totalLength += abs(firstPoint - secondPoint)
511 remainingLength = clippedLength - lastLength
512 clippedLoopPath = loopPath[ : pointIndex ]
513 ultimateClippedPoint = loopPath[pointIndex]
514 penultimateClippedPoint = clippedLoopPath[-1]
515 segment = ultimateClippedPoint - penultimateClippedPoint
516 segmentLength = abs(segment)
517 if segmentLength <= 0.0:
518 return clippedLoopPath
519 newUltimatePoint = penultimateClippedPoint + segment * remainingLength / segmentLength
520 return clippedLoopPath + [newUltimatePoint]
522 def getClippedLoopPath(clip, loopPath):
523 'Get a clipped loop path.'
526 loopPathLength = getPathLength(loopPath)
527 clip = min(clip, 0.3 * loopPathLength)
531 while totalLength < clip and pointIndex < len(loopPath) - 1:
532 firstPoint = loopPath[pointIndex]
533 secondPoint = loopPath[pointIndex + 1]
535 lastLength = totalLength
536 totalLength += abs(firstPoint - secondPoint)
537 remainingLength = clip - lastLength
538 clippedLoopPath = loopPath[pointIndex :]
539 ultimateClippedPoint = clippedLoopPath[0]
540 penultimateClippedPoint = loopPath[pointIndex - 1]
541 segment = ultimateClippedPoint - penultimateClippedPoint
542 segmentLength = abs(segment)
543 loopPath = clippedLoopPath
544 if segmentLength > 0.0:
545 newUltimatePoint = penultimateClippedPoint + segment * remainingLength / segmentLength
546 loopPath = [newUltimatePoint] + loopPath
547 return getClippedAtEndLoopPath(clip, loopPath)
549 def getClippedSimplifiedLoopPath(clip, loopPath, radius):
550 'Get a clipped and simplified loop path.'
551 return getSimplifiedPath(getClippedLoopPath(clip, loopPath), radius)
553 def getClosestDistanceIndexToLine(point, loop):
554 'Get the distance squared to the closest segment of the loop and index of that segment.'
555 smallestDistance = 987654321987654321.0
556 closestDistanceIndex = None
557 for pointIndex in xrange(len(loop)):
558 segmentBegin = loop[pointIndex]
559 segmentEnd = loop[(pointIndex + 1) % len(loop)]
560 distance = getDistanceToPlaneSegment(segmentBegin, segmentEnd, point)
561 if distance < smallestDistance:
562 smallestDistance = distance
563 closestDistanceIndex = DistanceIndex(distance, pointIndex)
564 return closestDistanceIndex
566 def getClosestPointOnSegment(segmentBegin, segmentEnd, point):
567 'Get the closest point on the segment.'
568 segmentDifference = segmentEnd - segmentBegin
569 if abs(segmentDifference) <= 0.0:
571 pointMinusSegmentBegin = point - segmentBegin
572 beginPlaneDot = getDotProduct(pointMinusSegmentBegin, segmentDifference)
573 differencePlaneDot = getDotProduct(segmentDifference, segmentDifference)
574 intercept = beginPlaneDot / differencePlaneDot
575 intercept = max(intercept, 0.0)
576 intercept = min(intercept, 1.0)
577 return segmentBegin + segmentDifference * intercept
579 def getComplexByCommaString( valueCommaString ):
580 'Get the commaString as a complex.'
582 splitLine = valueCommaString.replace(',', ' ').split()
583 return complex( float( splitLine[0] ), float(splitLine[1]) )
588 def getComplexByWords(words, wordIndex=0):
589 'Get the complex by the first two words.'
591 return complex(float(words[wordIndex]), float(words[wordIndex + 1]))
596 def getComplexDefaultByDictionary( defaultComplex, dictionary, key ):
597 'Get the value as a complex.'
598 if key in dictionary:
599 return complex( dictionary[key].strip().replace('(', '').replace(')', '') )
600 return defaultComplex
602 def getComplexDefaultByDictionaryKeys( defaultComplex, dictionary, keyX, keyY ):
603 'Get the value as a complex.'
604 x = getFloatDefaultByDictionary( defaultComplex.real, dictionary, keyX )
605 y = getFloatDefaultByDictionary( defaultComplex.real, dictionary, keyY )
608 def getComplexPath(vector3Path):
609 'Get the complex path from the vector3 path.'
611 for point in vector3Path:
612 complexPath.append(point.dropAxis())
615 def getComplexPathByMultiplier(multiplier, path):
616 'Get the multiplied complex path.'
619 complexPath.append(multiplier * point)
622 def getComplexPaths(vector3Paths):
623 'Get the complex paths from the vector3 paths.'
625 for vector3Path in vector3Paths:
626 complexPaths.append(getComplexPath(vector3Path))
629 def getComplexPolygon(center, radius, sides, startAngle=0.0):
630 'Get the complex polygon.'
632 sideAngle = 2.0 * math.pi / float(sides)
633 for side in xrange(abs(sides)):
634 unitPolar = getWiddershinsUnitPolar(startAngle)
635 complexPolygon.append(unitPolar * radius + center)
636 startAngle += sideAngle
637 return complexPolygon
639 def getComplexPolygonByComplexRadius(radius, sides, startAngle=0.0):
640 'Get the complex polygon.'
642 sideAngle = 2.0 * math.pi / float(sides)
643 for side in xrange(abs(sides)):
644 unitPolar = getWiddershinsUnitPolar(startAngle)
645 complexPolygon.append(complex(unitPolar.real * radius.real, unitPolar.imag * radius.imag))
646 startAngle += sideAngle
647 return complexPolygon
649 def getComplexPolygonByStartEnd(endAngle, radius, sides, startAngle=0.0):
650 'Get the complex polygon by start and end angle.'
651 angleExtent = endAngle - startAngle
652 sideAngle = 2.0 * math.pi / float(sides)
653 sides = int(math.ceil(abs(angleExtent / sideAngle)))
654 sideAngle = angleExtent / float(sides)
656 for side in xrange(abs(sides) + 1):
657 unitPolar = getWiddershinsUnitPolar(startAngle)
658 complexPolygon.append(unitPolar * radius)
659 startAngle += sideAngle
660 return getLoopWithoutCloseEnds(0.000001 * radius, complexPolygon)
662 def getConcatenatedList(originalLists):
663 'Get the lists as one concatenated list.'
664 concatenatedList = []
665 for originalList in originalLists:
666 concatenatedList += originalList
667 return concatenatedList
669 def getConnectedPaths(paths, pixelDictionary, sharpestProduct, width):
670 'Get connected paths from paths.'
675 for pathIndex in xrange(len(paths)):
676 path = paths[pathIndex]
677 segments.append(getSegmentFromPath(path, pathIndex))
678 for pathIndex in xrange(0, len(paths) - 1):
679 concatenateRemovePath(connectedPaths, pathIndex, paths, pixelDictionary, segments, sharpestProduct, width)
680 connectedPaths.append(paths[-1])
681 return connectedPaths
683 def getCrossProduct(firstComplex, secondComplex):
684 'Get z component cross product of a pair of complexes.'
685 return firstComplex.real * secondComplex.imag - firstComplex.imag * secondComplex.real
687 def getDecimalPlacesCarried(extraDecimalPlaces, value):
688 'Get decimal places carried by the decimal places of the value plus the extraDecimalPlaces.'
689 return max(0, 1 + int(math.ceil(extraDecimalPlaces - math.log10(value))))
691 def getDiagonalFlippedLoop(loop):
692 'Get loop flipped over the dialogonal, in other words with the x and y swapped.'
693 diagonalFlippedLoop = []
695 diagonalFlippedLoop.append( complex( point.imag, point.real ) )
696 return diagonalFlippedLoop
698 def getDiagonalFlippedLoops(loops):
699 'Get loops flipped over the dialogonal, in other words with the x and y swapped.'
700 diagonalFlippedLoops = []
702 diagonalFlippedLoops.append( getDiagonalFlippedLoop(loop) )
703 return diagonalFlippedLoops
705 def getDictionaryString(dictionary):
706 'Get the dictionary string.'
707 output = cStringIO.StringIO()
708 keys = dictionary.keys()
711 addValueToOutput(0, key, output, dictionary[key])
712 return output.getvalue()
714 def getDistanceToLine(begin, end, point):
715 'Get the distance from a vector3 point to an infinite line.'
716 pointMinusBegin = point - begin
718 return abs(pointMinusBegin)
719 endMinusBegin = end - begin
720 return abs(endMinusBegin.cross(pointMinusBegin)) / abs(endMinusBegin)
722 def getDistanceToLineByPath(begin, end, path):
723 'Get the maximum distance from a path to an infinite line.'
724 distanceToLine = -987654321.0
726 distanceToLine = max(getDistanceToLine(begin, end, point), distanceToLine)
727 return distanceToLine
729 def getDistanceToLineByPaths(begin, end, paths):
730 'Get the maximum distance from paths to an infinite line.'
731 distanceToLine = -987654321.0
733 distanceToLine = max(getDistanceToLineByPath(begin, end, path), distanceToLine)
734 return distanceToLine
736 def getDistanceToPlaneSegment( segmentBegin, segmentEnd, point ):
737 'Get the distance squared from a point to the x & y components of a segment.'
738 segmentDifference = segmentEnd - segmentBegin
739 pointMinusSegmentBegin = point - segmentBegin
740 beginPlaneDot = getDotProduct( pointMinusSegmentBegin, segmentDifference )
741 if beginPlaneDot <= 0.0:
742 return abs( point - segmentBegin ) * abs( point - segmentBegin )
743 differencePlaneDot = getDotProduct( segmentDifference, segmentDifference )
744 if differencePlaneDot <= beginPlaneDot:
745 return abs( point - segmentEnd ) * abs( point - segmentEnd )
746 intercept = beginPlaneDot / differencePlaneDot
747 interceptPerpendicular = segmentBegin + segmentDifference * intercept
748 return abs( point - interceptPerpendicular ) * abs( point - interceptPerpendicular )
750 def getDotProduct(firstComplex, secondComplex):
751 'Get the dot product of a pair of complexes.'
752 return firstComplex.real * secondComplex.real + firstComplex.imag * secondComplex.imag
754 def getDotProductPlusOne( firstComplex, secondComplex ):
755 'Get the dot product plus one of the x and y components of a pair of Vector3s.'
756 return 1.0 + getDotProduct( firstComplex, secondComplex )
758 def getDurationString( seconds ):
759 'Get the duration string.'
760 secondsRounded = int( round( seconds ) )
761 durationString = getPluralString( secondsRounded % 60, 'second')
763 return durationString
764 durationString = '%s %s' % ( getPluralString( ( secondsRounded / 60 ) % 60, 'minute'), durationString )
766 return durationString
767 return '%s %s' % ( getPluralString( secondsRounded / 3600, 'hour'), durationString )
769 def getEndpointFromPath( path, pathIndex ):
770 'Get endpoint segment from a path.'
773 endpointBegin = Endpoint()
774 endpointEnd = Endpoint().getFromOtherPoint( endpointBegin, end )
775 endpointBegin.getFromOtherPoint( endpointEnd, begin )
776 endpointBegin.path = path
777 endpointBegin.pathIndex = pathIndex
780 def getEndpointsFromSegments( segments ):
781 'Get endpoints from segments.'
783 for segment in segments:
784 for endpoint in segment:
785 endpoints.append( endpoint )
788 def getEndpointsFromSegmentTable( segmentTable ):
789 'Get the endpoints from the segment table.'
791 segmentTableKeys = segmentTable.keys()
792 segmentTableKeys.sort()
793 for segmentTableKey in segmentTableKeys:
794 for segment in segmentTable[ segmentTableKey ]:
795 for endpoint in segment:
796 endpoints.append( endpoint )
799 def getEnumeratorKeys(enumerator, keys):
800 'Get enumerator keys.'
803 return getEnumeratorKeysExceptForOneArgument(enumerator, keys)
805 def getEnumeratorKeysAlwaysList(enumerator, keys):
806 'Get enumerator keys.'
807 if keys.__class__ != list:
811 return getEnumeratorKeysExceptForOneArgument(enumerator, keys)
813 def getEnumeratorKeysExceptForOneArgument(enumerator, keys):
814 'Get enumerator keys, except when there is one argument.'
816 return range(0, len(enumerator))
820 if beginIndex == None:
823 endIndex = len(enumerator)
824 return range(beginIndex, endIndex)
826 beginIndexDefault = 0
827 endIndexDefault = len(enumerator)
829 beginIndexDefault = endIndexDefault - 1
831 if beginIndex == None:
832 beginIndex = beginIndexDefault
834 endIndex = endIndexDefault
835 return range(beginIndex, endIndex, step)
837 def getFillOfSurroundings(nestedRings, penultimateFillLoops):
838 'Get extra fill loops of nested rings.'
839 fillOfSurroundings = []
840 for nestedRing in nestedRings:
841 fillOfSurroundings += nestedRing.getFillLoops(penultimateFillLoops)
842 return fillOfSurroundings
844 def getFlattenedNestedRings(nestedRings):
845 'Get flattened nested rings.'
846 flattenedNestedRings = []
847 for nestedRing in nestedRings:
848 nestedRing.addFlattenedNestedRings(flattenedNestedRings)
849 return flattenedNestedRings
851 def getFloatDefaultByDictionary( defaultFloat, dictionary, key ):
852 'Get the value as a float.'
853 evaluatedFloat = None
854 if key in dictionary:
855 evaluatedFloat = getFloatFromValue(dictionary[key])
856 if evaluatedFloat == None:
858 return evaluatedFloat
860 def getFloatFromValue(value):
861 'Get the value as a float.'
868 def getFourSignificantFigures(number):
869 'Get number rounded to four significant figures as a string.'
872 absoluteNumber = abs(number)
873 if absoluteNumber >= 100.0:
874 return getRoundedToPlacesString( 2, number )
875 if absoluteNumber < 0.000000001:
876 return getRoundedToPlacesString( 13, number )
877 return getRoundedToPlacesString( 3 - math.floor( math.log10( absoluteNumber ) ), number )
879 def getHalfSimplifiedLoop( loop, radius, remainder ):
880 'Get the loop with half of the points inside the channel removed.'
883 channelRadius = abs(radius * .01)
887 addIndex = len(loop) - 1
888 for pointIndex in xrange(len(loop)):
889 point = loop[pointIndex]
890 if pointIndex % 2 == remainder or pointIndex == addIndex:
891 simplified.append(point)
892 elif not isWithinChannel( channelRadius, pointIndex, loop ):
893 simplified.append(point)
896 def getHalfSimplifiedPath(path, radius, remainder):
897 'Get the path with half of the points inside the channel removed.'
900 channelRadius = abs(radius * .01)
901 simplified = [path[0]]
902 for pointIndex in xrange(1, len(path) - 1):
903 point = path[pointIndex]
904 if pointIndex % 2 == remainder:
905 simplified.append(point)
906 elif not isWithinChannel(channelRadius, pointIndex, path):
907 simplified.append(point)
908 simplified.append(path[-1])
911 def getHorizontallyBoundedPath(horizontalBegin, horizontalEnd, path):
912 'Get horizontally bounded path.'
913 horizontallyBoundedPath = []
914 for pointIndex, point in enumerate(path):
916 previousIndex = pointIndex - 1
917 if previousIndex >= 0:
918 begin = path[previousIndex]
920 nextIndex = pointIndex + 1
921 if nextIndex < len(path):
922 end = path[nextIndex]
923 addHorizontallyBoundedPoint(begin, point, end, horizontalBegin, horizontalEnd, horizontallyBoundedPath)
924 return horizontallyBoundedPath
926 def getIncrementFromRank( rank ):
927 'Get the increment from the rank which is 0 at 1 and increases by three every power of ten.'
928 rankZone = int( math.floor( rank / 3 ) )
929 rankModulo = rank % 3
930 powerOfTen = pow( 10, rankZone )
931 moduloMultipliers = ( 1, 2, 5 )
932 return float( powerOfTen * moduloMultipliers[ rankModulo ] )
934 def getInsidesAddToOutsides( loops, outsides ):
935 'Add loops to either the insides or outsides.'
937 for loopIndex in xrange( len(loops) ):
938 loop = loops[loopIndex]
939 if isInsideOtherLoops( loopIndex, loops ):
942 outsides.append(loop)
945 def getIntermediateLocation( alongWay, begin, end ):
946 'Get the intermediate location between begin and end.'
947 return begin * ( 1.0 - alongWay ) + end * alongWay
949 def getIntersectionOfXIntersectionIndexes( totalSolidSurfaceThickness, xIntersectionIndexList ):
950 'Get x intersections from surrounding layers.'
951 xIntersectionList = []
954 xIntersectionIndexList.sort()
955 for xIntersectionIndex in xIntersectionIndexList:
956 toggleHashtable(solidTable, xIntersectionIndex.index, '')
958 solid = len(solidTable) >= totalSolidSurfaceThickness
959 if oldSolid != solid:
960 xIntersectionList.append(xIntersectionIndex.x)
961 return xIntersectionList
963 def getIntersectionOfXIntersectionsTables(xIntersectionsTables):
964 'Get the intersection of the XIntersections tables.'
965 if len(xIntersectionsTables) == 0:
967 intersectionOfXIntersectionsTables = {}
968 firstIntersectionTable = xIntersectionsTables[0]
969 for firstIntersectionTableKey in firstIntersectionTable.keys():
970 xIntersectionIndexList = []
971 for xIntersectionsTableIndex in xrange(len(xIntersectionsTables)):
972 xIntersectionsTable = xIntersectionsTables[xIntersectionsTableIndex]
973 if firstIntersectionTableKey in xIntersectionsTable:
974 addXIntersectionIndexesFromXIntersections(xIntersectionsTableIndex, xIntersectionIndexList, xIntersectionsTable[firstIntersectionTableKey])
975 xIntersections = getIntersectionOfXIntersectionIndexes(len(xIntersectionsTables), xIntersectionIndexList)
976 if len(xIntersections) > 0:
977 intersectionOfXIntersectionsTables[firstIntersectionTableKey] = xIntersections
978 return intersectionOfXIntersectionsTables
980 def getIntFromValue(value):
981 'Get the value as an int.'
988 def getIsInFilledRegion(loops, point):
989 'Determine if the point is in the filled region of the loops.'
990 return getNumberOfIntersectionsToLeftOfLoops(loops, point) % 2 == 1
992 def getIsInFilledRegionByPaths(loops, paths):
993 'Determine if the point of any path is in the filled region of the loops.'
996 if getIsInFilledRegion(loops, path[0]):
1000 def getIsRadianClose(firstRadian, secondRadian):
1001 'Determine if the firstRadian is close to the secondRadian.'
1002 return abs(math.pi - abs(math.pi - ((firstRadian - secondRadian) % (math.pi + math.pi) ))) < 0.000001
1004 def getIsWiddershinsByVector3( polygon ):
1005 'Determine if the polygon goes round in the widdershins direction.'
1006 return isWiddershins( getComplexPath( polygon ) )
1008 def getJoinOfXIntersectionIndexes( xIntersectionIndexList ):
1009 'Get joined x intersections from surrounding layers.'
1013 xIntersectionIndexList.sort()
1014 for xIntersectionIndex in xIntersectionIndexList:
1015 toggleHashtable(solidTable, xIntersectionIndex.index, '')
1017 solid = len(solidTable) > 0
1018 if oldSolid != solid:
1019 xIntersections.append(xIntersectionIndex.x)
1020 return xIntersections
1022 def getLargestLoop(loops):
1023 'Get largest loop from loops.'
1024 largestArea = -987654321.0
1027 loopArea = abs(getAreaLoopAbsolute(loop))
1028 if loopArea > largestArea:
1029 largestArea = loopArea
1033 def getLeftPoint(points):
1034 'Get the leftmost complex point in the points.'
1035 leftmost = 987654321.0
1036 leftPointComplex = None
1037 for pointComplex in points:
1038 if pointComplex.real < leftmost:
1039 leftmost = pointComplex.real
1040 leftPointComplex = pointComplex
1041 return leftPointComplex
1043 def getLeftPointIndex(points):
1044 'Get the index of the leftmost complex point in the points.'
1048 for pointIndex in xrange( len(points) ):
1049 if points[pointIndex].real < points[ leftPointIndex ].real:
1050 leftPointIndex = pointIndex
1051 return leftPointIndex
1053 def getListTableElements( listDictionary ):
1054 'Get all the element in a list table.'
1055 listDictionaryElements = []
1056 for listDictionaryValue in listDictionary.values():
1057 listDictionaryElements += listDictionaryValue
1058 return listDictionaryElements
1060 def getLoopCentroid(polygonComplex):
1061 'Get the area of a complex polygon using http://en.wikipedia.org/wiki/Centroid.'
1062 polygonDoubleArea = 0.0
1064 for pointIndex in xrange( len(polygonComplex) ):
1065 pointBegin = polygonComplex[pointIndex]
1066 pointEnd = polygonComplex[ (pointIndex + 1) % len(polygonComplex) ]
1067 doubleArea = pointBegin.real * pointEnd.imag - pointEnd.real * pointBegin.imag
1068 doubleCenter = complex( pointBegin.real + pointEnd.real, pointBegin.imag + pointEnd.imag )
1069 polygonDoubleArea += doubleArea
1070 polygonTorque += doubleArea * doubleCenter
1071 torqueMultiplier = 0.333333333333333333333333 / polygonDoubleArea
1072 return polygonTorque * torqueMultiplier
1074 def getLoopConvex(points):
1075 'Get convex hull of points using gift wrap algorithm.'
1078 for point in points:
1079 if point not in pointSet:
1081 loopConvex.append(point)
1082 if len(loopConvex) < 4:
1084 leftPoint = getLeftPoint(loopConvex)
1085 lastPoint = leftPoint
1086 pointSet.remove(leftPoint)
1087 loopConvex = [leftPoint]
1088 lastSegment = complex(0.0, 1.0)
1090 greatestDotProduct = -9.9
1091 greatestPoint = None
1092 greatestSegment = None
1093 if len(loopConvex) > 2:
1094 nextSegment = getNormalized(leftPoint - lastPoint)
1095 if abs(nextSegment) > 0.0:
1096 greatestDotProduct = getDotProduct(nextSegment, lastSegment)
1097 for point in pointSet:
1098 nextSegment = getNormalized(point - lastPoint)
1099 if abs(nextSegment) > 0.0:
1100 dotProduct = getDotProduct(nextSegment, lastSegment)
1101 if dotProduct > greatestDotProduct:
1102 greatestDotProduct = dotProduct
1103 greatestPoint = point
1104 greatestSegment = nextSegment
1105 if greatestPoint == None:
1107 lastPoint = greatestPoint
1108 loopConvex.append(greatestPoint)
1109 pointSet.remove(greatestPoint)
1110 lastSegment = greatestSegment
1113 def getLoopConvexCentroid(polygonComplex):
1114 'Get centroid of the convex hull of a complex polygon.'
1115 return getLoopCentroid( getLoopConvex(polygonComplex) )
1117 def getLoopInsideContainingLoop( containingLoop, loops ):
1118 'Get a loop that is inside the containing loop.'
1120 if loop != containingLoop:
1121 if isPathInsideLoop( containingLoop, loop ):
1125 def getLoopLength( polygon ):
1126 'Get the length of a polygon perimeter.'
1128 for pointIndex in xrange( len( polygon ) ):
1129 point = polygon[pointIndex]
1130 secondPoint = polygon[ (pointIndex + 1) % len( polygon ) ]
1131 polygonLength += abs( point - secondPoint )
1132 return polygonLength
1134 def getLoopStartingClosest(extrusionHalfWidth, location, loop):
1135 'Add to threads from the last location from loop.'
1136 closestIndex = getClosestDistanceIndexToLine(location, loop).index
1137 loop = getAroundLoop(closestIndex, closestIndex, loop)
1138 closestPoint = getClosestPointOnSegment(loop[0], loop[1], location)
1139 if abs(closestPoint - loop[0]) > extrusionHalfWidth and abs(closestPoint - loop[1]) > extrusionHalfWidth:
1140 loop = [closestPoint] + loop[1 :] + [loop[0]]
1141 elif abs(closestPoint - loop[0]) > abs(closestPoint - loop[1]):
1142 loop = loop[1 :] + [loop[0]]
1145 def getLoopWithoutCloseEnds(close, loop):
1146 'Get loop without close ends.'
1149 if abs(loop[0] - loop[-1]) > close:
1153 def getLoopWithoutCloseSequentialPoints(close, loop):
1154 'Get loop without close sequential points.'
1157 lastPoint = loop[-1]
1158 loopWithoutCloseSequentialPoints = []
1160 if abs(point - lastPoint) > close:
1161 loopWithoutCloseSequentialPoints.append(point)
1163 return loopWithoutCloseSequentialPoints
1165 def getMaximum(firstComplex, secondComplex):
1166 'Get a complex with each component the maximum of the respective components of a pair of complexes.'
1167 return complex(max(firstComplex.real, secondComplex.real), max(firstComplex.imag, secondComplex.imag))
1169 def getMaximumByComplexPath(path):
1170 'Get a complex with each component the maximum of the respective components of a complex path.'
1171 maximum = complex(-987654321987654321.0, -987654321987654321.0)
1173 maximum = getMaximum(maximum, point)
1176 def getMaximumByComplexPaths(paths):
1177 'Get a complex with each component the maximum of the respective components of complex paths.'
1178 maximum = complex(-987654321987654321.0, -987654321987654321.0)
1181 maximum = getMaximum(maximum, point)
1184 def getMaximumByVector3Path(path):
1185 'Get a vector3 with each component the maximum of the respective components of a vector3 path.'
1186 maximum = Vector3(-987654321987654321.0, -987654321987654321.0, -987654321987654321.0)
1188 maximum.maximize(point)
1191 def getMaximumByVector3Paths(paths):
1192 'Get a complex with each component the maximum of the respective components of a complex path.'
1193 maximum = Vector3(-987654321987654321.0, -987654231987654321.0, -987654321987654321.0)
1196 maximum.maximize(point)
1199 def getMaximumSpan(loop):
1200 'Get the maximum span of the loop.'
1201 extent = getMaximumByComplexPath(loop) - getMinimumByComplexPath(loop)
1202 return max(extent.real, extent.imag)
1204 def getMinimum(firstComplex, secondComplex):
1205 'Get a complex with each component the minimum of the respective components of a pair of complexes.'
1206 return complex(min(firstComplex.real, secondComplex.real), min(firstComplex.imag, secondComplex.imag))
1208 def getMinimumByComplexPath(path):
1209 'Get a complex with each component the minimum of the respective components of a complex path.'
1210 minimum = complex(987654321987654321.0, 987654321987654321.0)
1212 minimum = getMinimum(minimum, point)
1215 def getMinimumByComplexPaths(paths):
1216 'Get a complex with each component the minimum of the respective components of complex paths.'
1217 minimum = complex(987654321987654321.0, 987654321987654321.0)
1220 minimum = getMinimum(minimum, point)
1223 def getMinimumByVector3Path(path):
1224 'Get a vector3 with each component the minimum of the respective components of a vector3 path.'
1225 minimum = Vector3(987654321987654321.0, 987654321987654321.0, 987654321987654321.0)
1227 minimum.minimize(point)
1230 def getMinimumByVector3Paths(paths):
1231 'Get a complex with each component the minimum of the respective components of a complex path.'
1232 minimum = Vector3(987654321987654321.0, 987654321987654321.0, 987654321987654321.0)
1235 minimum.minimize(point)
1238 def getMirrorPath(path):
1240 close = 0.001 * getPathLength(path)
1241 for pointIndex in xrange(len(path) - 1, -1, -1):
1242 point = path[pointIndex]
1243 flipPoint = complex(-point.real, point.imag)
1244 if abs(flipPoint - path[-1]) > close:
1245 path.append(flipPoint)
1248 def getNormal(begin, center, end):
1250 centerMinusBegin = (center - begin).getNormalized()
1251 endMinusCenter = (end - center).getNormalized()
1252 return centerMinusBegin.cross(endMinusCenter)
1254 def getNormalByPath(path):
1255 'Get normal by path.'
1256 totalNormal = Vector3()
1257 for pointIndex, point in enumerate(path):
1258 center = path[(pointIndex + 1) % len(path)]
1259 end = path[(pointIndex + 2) % len(path)]
1260 totalNormal += getNormalWeighted(point, center, end)
1261 return totalNormal.getNormalized()
1263 def getNormalized(complexNumber):
1264 'Get the normalized complex.'
1265 complexNumberLength = abs(complexNumber)
1266 if complexNumberLength > 0.0:
1267 return complexNumber / complexNumberLength
1268 return complexNumber
1270 def getNormalWeighted(begin, center, end):
1271 'Get weighted normal.'
1272 return (center - begin).cross(end - center)
1274 def getNumberOfIntersectionsToLeft(loop, point):
1275 'Get the number of intersections through the loop for the line going left.'
1276 numberOfIntersectionsToLeft = 0
1277 for pointIndex in xrange(len(loop)):
1278 firstPointComplex = loop[pointIndex]
1279 secondPointComplex = loop[(pointIndex + 1) % len(loop)]
1280 xIntersection = getXIntersectionIfExists(firstPointComplex, secondPointComplex, point.imag)
1281 if xIntersection != None:
1282 if xIntersection < point.real:
1283 numberOfIntersectionsToLeft += 1
1284 return numberOfIntersectionsToLeft
1286 def getNumberOfIntersectionsToLeftOfLoops(loops, point):
1287 'Get the number of intersections through the loop for the line starting from the left point and going left.'
1288 totalNumberOfIntersectionsToLeft = 0
1290 totalNumberOfIntersectionsToLeft += getNumberOfIntersectionsToLeft(loop, point)
1291 return totalNumberOfIntersectionsToLeft
1293 def getOrderedNestedRings(nestedRings):
1294 'Get ordered nestedRings from nestedRings.'
1296 orderedNestedRings = []
1297 for loopIndex in xrange(len(nestedRings)):
1298 nestedRing = nestedRings[loopIndex]
1300 for beforeIndex in xrange(loopIndex):
1301 otherLoops.append(nestedRings[beforeIndex].boundary)
1302 for afterIndex in xrange(loopIndex + 1, len(nestedRings)):
1303 otherLoops.append(nestedRings[afterIndex].boundary)
1304 if isPathEntirelyInsideLoops(otherLoops, nestedRing.boundary):
1305 insides.append(nestedRing)
1307 orderedNestedRings.append(nestedRing)
1308 for outside in orderedNestedRings:
1309 outside.getFromInsideSurroundings(insides)
1310 return orderedNestedRings
1312 def getPathCopy(path):
1316 pathCopy.append(point.copy())
1319 def getPathLength(path):
1320 'Get the length of a path ( an open polyline ).'
1322 for pointIndex in xrange( len(path) - 1 ):
1323 firstPoint = path[pointIndex]
1324 secondPoint = path[pointIndex + 1]
1325 pathLength += abs(firstPoint - secondPoint)
1328 def getPathsFromEndpoints(endpoints, maximumConnectionLength, pixelDictionary, sharpestProduct, width):
1329 'Get paths from endpoints.'
1330 if len(endpoints) < 2:
1332 endpoints = endpoints[:] # so that the first two endpoints aren't removed when used again
1333 for beginningEndpoint in endpoints[: : 2]:
1334 beginningPoint = beginningEndpoint.point
1335 addSegmentToPixelTable(beginningPoint, beginningEndpoint.otherEndpoint.point, pixelDictionary, 0, 0, width)
1336 endpointFirst = endpoints[0]
1337 endpoints.remove(endpointFirst)
1338 otherEndpoint = endpointFirst.otherEndpoint
1339 endpoints.remove(otherEndpoint)
1343 if len(endpoints) > 1:
1344 nextEndpoint = otherEndpoint.getClosestMiss(endpoints, path, pixelDictionary, sharpestProduct, width)
1345 if nextEndpoint != None:
1346 if abs(nextEndpoint.point - endpointFirst.point) < abs(nextEndpoint.point - otherEndpoint.point):
1347 endpointFirst = endpointFirst.otherEndpoint
1348 otherEndpoint = endpointFirst.otherEndpoint
1349 addPointToPath(path, pixelDictionary, endpointFirst.point, None, width)
1350 addPointToPath(path, pixelDictionary, otherEndpoint.point, len(paths) - 1, width)
1351 oneOverEndpointWidth = 1.0 / maximumConnectionLength
1353 for endpoint in endpoints:
1354 addElementToPixelListFromPoint(endpoint, endpointTable, endpoint.point * oneOverEndpointWidth)
1355 while len(endpointTable) > 0:
1356 if len(endpointTable) == 1:
1357 if len(endpointTable.values()[0]) < 2:
1359 endpoints = getSquareValuesFromPoint(endpointTable, otherEndpoint.point * oneOverEndpointWidth)
1360 nextEndpoint = otherEndpoint.getClosestMiss(endpoints, path, pixelDictionary, sharpestProduct, width)
1361 if nextEndpoint == None:
1364 endpoints = getListTableElements(endpointTable)
1365 nextEndpoint = otherEndpoint.getClosestEndpoint(endpoints)
1366 # this commented code should be faster than the getListTableElements code, but it isn't, someday a spiral algorithim could be tried
1367 # endpoints = getSquareValuesFromPoint( endpointTable, otherEndpoint.point * oneOverEndpointWidth )
1368 # nextEndpoint = otherEndpoint.getClosestEndpoint(endpoints)
1369 # if nextEndpoint == None:
1371 # for endpointTableValue in endpointTable.values():
1372 # endpoints.append( endpointTableValue[0] )
1373 # nextEndpoint = otherEndpoint.getClosestEndpoint(endpoints)
1374 # endpoints = getSquareValuesFromPoint( endpointTable, nextEndpoint.point * oneOverEndpointWidth )
1375 # nextEndpoint = otherEndpoint.getClosestEndpoint(endpoints)
1376 addPointToPath(path, pixelDictionary, nextEndpoint.point, len(paths) - 1, width)
1377 removeElementFromPixelListFromPoint(nextEndpoint, endpointTable, nextEndpoint.point * oneOverEndpointWidth)
1378 otherEndpoint = nextEndpoint.otherEndpoint
1379 addPointToPath(path, pixelDictionary, otherEndpoint.point, len(paths) - 1, width)
1380 removeElementFromPixelListFromPoint(otherEndpoint, endpointTable, otherEndpoint.point * oneOverEndpointWidth)
1383 def getPlaneDot( vec3First, vec3Second ):
1384 'Get the dot product of the x and y components of a pair of Vector3s.'
1385 return vec3First.x * vec3Second.x + vec3First.y * vec3Second.y
1387 def getPluralString( number, suffix ):
1388 'Get the plural string.'
1390 return '1 %s' % suffix
1391 return '%s %ss' % ( number, suffix )
1393 def getPointPlusSegmentWithLength( length, point, segment ):
1394 'Get point plus a segment scaled to a given length.'
1395 return segment * length / abs(segment) + point
1397 def getPointsByHorizontalDictionary(width, xIntersectionsDictionary):
1398 'Get points from the horizontalXIntersectionsDictionary.'
1400 xIntersectionsDictionaryKeys = xIntersectionsDictionary.keys()
1401 xIntersectionsDictionaryKeys.sort()
1402 for xIntersectionsDictionaryKey in xIntersectionsDictionaryKeys:
1403 for xIntersection in xIntersectionsDictionary[xIntersectionsDictionaryKey]:
1404 points.append(complex(xIntersection, xIntersectionsDictionaryKey * width))
1407 def getPointsByVerticalDictionary(width, xIntersectionsDictionary):
1408 'Get points from the verticalXIntersectionsDictionary.'
1410 xIntersectionsDictionaryKeys = xIntersectionsDictionary.keys()
1411 xIntersectionsDictionaryKeys.sort()
1412 for xIntersectionsDictionaryKey in xIntersectionsDictionaryKeys:
1413 for xIntersection in xIntersectionsDictionary[xIntersectionsDictionaryKey]:
1414 points.append(complex(xIntersectionsDictionaryKey * width, xIntersection))
1417 def getRadiusArealizedMultiplier(sides):
1418 'Get the radius multiplier for a polygon of equal area.'
1419 return math.sqrt(globalTau / sides / math.sin(globalTau / sides))
1421 def getRandomComplex(begin, end):
1422 'Get random complex.'
1423 endMinusBegin = end - begin
1424 return begin + complex(random.random() * endMinusBegin.real, random.random() * endMinusBegin.imag)
1427 'Get the rank which is 0 at 1 and increases by three every power of ten.'
1428 return int(math.floor(3.0 * math.log10(width)))
1430 def getRotatedComplexes(planeAngle, points):
1431 'Get points rotated by the plane angle'
1432 rotatedComplexes = []
1433 for point in points:
1434 rotatedComplexes.append(planeAngle * point)
1435 return rotatedComplexes
1437 def getRotatedComplexLists(planeAngle, pointLists):
1438 'Get point lists rotated by the plane angle'
1439 rotatedComplexLists = []
1440 for pointList in pointLists:
1441 rotatedComplexLists.append(getRotatedComplexes(planeAngle, pointList))
1442 return rotatedComplexLists
1444 def getRotatedWiddershinsQuarterAroundZAxis(vector3):
1445 'Get Vector3 rotated a quarter widdershins turn around Z axis.'
1446 return Vector3(-vector3.y, vector3.x, vector3.z)
1448 def getRoundedPoint(point):
1449 'Get point with each component rounded.'
1450 return Vector3(round(point.x), round( point.y ), round(point.z))
1452 def getRoundedToPlaces(decimalPlaces, number):
1453 'Get number rounded to a number of decimal places.'
1454 decimalPlacesRounded = max(1, int(round(decimalPlaces)))
1455 return round(number, decimalPlacesRounded)
1457 def getRoundedToPlacesString(decimalPlaces, number):
1458 'Get number rounded to a number of decimal places as a string, without exponential formatting.'
1459 roundedToPlaces = getRoundedToPlaces(decimalPlaces, number)
1460 roundedToPlacesString = str(roundedToPlaces)
1461 if 'e' in roundedToPlacesString:
1462 return ('%.15f' % roundedToPlaces).rstrip('0')
1463 return roundedToPlacesString
1465 def getRoundedToThreePlaces(number):
1466 'Get number rounded to three places as a string.'
1467 return str(round(number, 3))
1469 def getRoundZAxisByPlaneAngle( planeAngle, vector3 ):
1470 'Get Vector3 rotated by a plane angle.'
1471 return Vector3( vector3.x * planeAngle.real - vector3.y * planeAngle.imag, vector3.x * planeAngle.imag + vector3.y * planeAngle.real, vector3.z )
1473 def getSegmentFromPath( path, pathIndex ):
1474 'Get endpoint segment from a path.'
1479 forwardEndpoint = getEndpointFromPath( path, pathIndex )
1480 reversePath = path[:]
1481 reversePath.reverse()
1482 reverseEndpoint = getEndpointFromPath( reversePath, pathIndex )
1483 return ( forwardEndpoint, reverseEndpoint )
1485 def getSegmentFromPoints( begin, end ):
1486 'Get endpoint segment from a pair of points.'
1487 endpointFirst = Endpoint()
1488 endpointSecond = Endpoint().getFromOtherPoint( endpointFirst, end )
1489 endpointFirst.getFromOtherPoint( endpointSecond, begin )
1490 return ( endpointFirst, endpointSecond )
1492 def getSegmentsFromXIntersectionIndexes( xIntersectionIndexList, y ):
1493 'Get endpoint segments from the x intersection indexes.'
1494 xIntersections = getXIntersectionsFromIntersections( xIntersectionIndexList )
1495 return getSegmentsFromXIntersections( xIntersections, y )
1497 def getSegmentsFromXIntersections( xIntersections, y ):
1498 'Get endpoint segments from the x intersections.'
1500 end = len( xIntersections )
1501 if len( xIntersections ) % 2 == 1:
1503 for xIntersectionIndex in xrange( 0, end, 2 ):
1504 firstX = xIntersections[ xIntersectionIndex ]
1505 secondX = xIntersections[ xIntersectionIndex + 1 ]
1506 if firstX != secondX:
1507 segments.append( getSegmentFromPoints( complex( firstX, y ), complex( secondX, y ) ) )
1510 def getSimplifiedLoop( loop, radius ):
1511 'Get loop with points inside the channel removed.'
1514 simplificationMultiplication = 256
1515 simplificationRadius = radius / float( simplificationMultiplication )
1516 maximumIndex = len(loop) * simplificationMultiplication
1518 while pointIndex < maximumIndex:
1519 oldLoopLength = len(loop)
1520 loop = getHalfSimplifiedLoop( loop, simplificationRadius, 0 )
1521 loop = getHalfSimplifiedLoop( loop, simplificationRadius, 1 )
1522 simplificationRadius += simplificationRadius
1523 if oldLoopLength == len(loop):
1524 if simplificationRadius > radius:
1525 return getAwayPoints( loop, radius )
1527 simplificationRadius *= 1.5
1528 simplificationRadius = min( simplificationRadius, radius )
1529 pointIndex += pointIndex
1530 return getAwayPoints( loop, radius )
1532 def getSimplifiedLoops( loops, radius ):
1533 'Get the simplified loops.'
1534 simplifiedLoops = []
1536 simplifiedLoops.append( getSimplifiedLoop( loop, radius ) )
1537 return simplifiedLoops
1539 def getSimplifiedPath(path, radius):
1540 'Get path with points inside the channel removed.'
1543 simplificationMultiplication = 256
1544 simplificationRadius = radius / float(simplificationMultiplication)
1545 maximumIndex = len(path) * simplificationMultiplication
1547 while pointIndex < maximumIndex:
1548 oldPathLength = len(path)
1549 path = getHalfSimplifiedPath(path, simplificationRadius, 0)
1550 path = getHalfSimplifiedPath(path, simplificationRadius, 1)
1551 simplificationRadius += simplificationRadius
1552 if oldPathLength == len(path):
1553 if simplificationRadius > radius:
1554 return getAwayPath(path, radius)
1556 simplificationRadius *= 1.5
1557 simplificationRadius = min(simplificationRadius, radius)
1558 pointIndex += pointIndex
1559 return getAwayPath(path, radius)
1561 def getSquareIsOccupied( pixelDictionary, x, y ):
1562 'Determine if a square around the x and y pixel coordinates is occupied.'
1564 for xStep in xrange(x - 1, x + 2):
1565 for yStep in xrange(y - 1, y + 2):
1566 if (xStep, yStep) in pixelDictionary:
1570 def getSquareLoopWiddershins(beginComplex, endComplex):
1571 'Get a square loop from the beginning to the end and back.'
1572 loop = [beginComplex, complex(endComplex.real, beginComplex.imag), endComplex]
1573 loop.append(complex(beginComplex.real, endComplex.imag))
1576 def getSquareValues( pixelDictionary, x, y ):
1577 'Get a list of the values in a square around the x and y pixel coordinates.'
1579 for xStep in xrange(x - 1, x + 2):
1580 for yStep in xrange(y - 1, y + 2):
1581 stepKey = (xStep, yStep)
1582 if stepKey in pixelDictionary:
1583 squareValues += pixelDictionary[ stepKey ]
1586 def getSquareValuesFromPoint( pixelDictionary, point ):
1587 'Get a list of the values in a square around the point.'
1588 return getSquareValues(pixelDictionary, int(round(point.real)), int(round(point.imag)))
1590 def getStepKeyFromPoint(point):
1591 'Get step key for the point.'
1592 return (int(round(point.real)), int(round(point.imag)))
1594 def getThreeSignificantFigures(number):
1595 'Get number rounded to three significant figures as a string.'
1596 absoluteNumber = abs(number)
1597 if absoluteNumber >= 10.0:
1598 return getRoundedToPlacesString( 1, number )
1599 if absoluteNumber < 0.000000001:
1600 return getRoundedToPlacesString( 12, number )
1601 return getRoundedToPlacesString( 1 - math.floor( math.log10( absoluteNumber ) ), number )
1603 def getTopPath(path):
1604 'Get the top of the path.'
1605 top = -987654321987654321.0
1607 top = max(top, point.z)
1610 def getTopPaths(paths):
1611 'Get the top of the paths.'
1612 top = -987654321987654321.0
1615 top = max(top, point.z)
1618 def getTransferClosestNestedRing(extrusionHalfWidth, nestedRings, oldOrderedLocation, skein, threadSequence):
1619 'Get and transfer the closest remaining nested ring.'
1620 if len(nestedRings) > 0:
1621 oldOrderedLocation.z = nestedRings[0].z
1622 closestDistance = 987654321987654321.0
1623 closestNestedRing = None
1624 for remainingNestedRing in nestedRings:
1625 distance = getClosestDistanceIndexToLine(oldOrderedLocation.dropAxis(), remainingNestedRing.boundary).distance
1626 if distance < closestDistance:
1627 closestDistance = distance
1628 closestNestedRing = remainingNestedRing
1629 nestedRings.remove(closestNestedRing)
1630 closestNestedRing.addToThreads(extrusionHalfWidth, oldOrderedLocation, skein, threadSequence)
1631 return closestNestedRing
1633 def getTransferredNestedRings( insides, loop ):
1634 'Get transferred paths from inside nested rings.'
1635 transferredSurroundings = []
1636 for insideIndex in xrange( len( insides ) - 1, - 1, - 1 ):
1637 insideSurrounding = insides[ insideIndex ]
1638 if isPathInsideLoop( loop, insideSurrounding.boundary ):
1639 transferredSurroundings.append( insideSurrounding )
1640 del insides[ insideIndex ]
1641 return transferredSurroundings
1643 def getTransferredPaths( insides, loop ):
1644 'Get transferred paths from inside paths.'
1645 transferredPaths = []
1646 for insideIndex in xrange( len( insides ) - 1, - 1, - 1 ):
1647 inside = insides[ insideIndex ]
1648 if isPathInsideLoop( loop, inside ):
1649 transferredPaths.append( inside )
1650 del insides[ insideIndex ]
1651 return transferredPaths
1653 def getTranslatedComplexPath(path, translateComplex):
1654 'Get the translated complex path.'
1655 translatedComplexPath = []
1657 translatedComplexPath.append(point + translateComplex)
1658 return translatedComplexPath
1660 def getVector3Path(complexPath, z=0.0):
1661 'Get the vector3 path from the complex path.'
1663 for complexPoint in complexPath:
1664 vector3Path.append(Vector3(complexPoint.real, complexPoint.imag, z))
1667 def getVector3Paths(complexPaths, z=0.0):
1668 'Get the vector3 paths from the complex paths.'
1670 for complexPath in complexPaths:
1671 vector3Paths.append(getVector3Path(complexPath, z))
1674 def getWiddershinsUnitPolar(angle):
1675 'Get polar complex from counterclockwise angle from 1, 0.'
1676 return complex(math.cos(angle), math.sin(angle))
1678 def getXIntersectionIfExists( beginComplex, endComplex, y ):
1679 'Get the x intersection if it exists.'
1680 if ( y > beginComplex.imag ) == ( y > endComplex.imag ):
1682 endMinusBeginComplex = endComplex - beginComplex
1683 return ( y - beginComplex.imag ) / endMinusBeginComplex.imag * endMinusBeginComplex.real + beginComplex.real
1685 def getXIntersectionsFromIntersections( xIntersectionIndexList ):
1686 'Get x intersections from the x intersection index list, in other words subtract non negative intersections from negatives.'
1691 xIntersectionIndexList.sort()
1692 for solidX in xIntersectionIndexList:
1693 if solidX.index >= 0:
1694 toggleHashtable( solidTable, solidX.index, '' )
1698 solid = ( len( solidTable ) == 0 and fill )
1699 if oldSolid != solid:
1700 xIntersections.append( solidX.x )
1701 return xIntersections
1703 def getXYComplexFromVector3(vector3):
1704 'Get an xy complex from a vector3 if it exists, otherwise return None.'
1707 return vector3.dropAxis()
1709 def getYIntersectionIfExists( beginComplex, endComplex, x ):
1710 'Get the y intersection if it exists.'
1711 if ( x > beginComplex.real ) == ( x > endComplex.real ):
1713 endMinusBeginComplex = endComplex - beginComplex
1714 return ( x - beginComplex.real ) / endMinusBeginComplex.real * endMinusBeginComplex.imag + beginComplex.imag
1716 def getZComponentCrossProduct( vec3First, vec3Second ):
1717 'Get z component cross product of a pair of Vector3s.'
1718 return vec3First.x * vec3Second.y - vec3First.y * vec3Second.x
1720 def isInsideOtherLoops( loopIndex, loops ):
1721 'Determine if a loop in a list is inside another loop in that list.'
1722 return isPathInsideLoops( loops[ : loopIndex ] + loops[loopIndex + 1 :], loops[loopIndex] )
1724 def isLineIntersectingInsideXSegment( beginComplex, endComplex, segmentFirstX, segmentSecondX, y ):
1725 'Determine if the line is crossing inside the x segment.'
1726 xIntersection = getXIntersectionIfExists( beginComplex, endComplex, y )
1727 if xIntersection == None:
1729 if xIntersection < min( segmentFirstX, segmentSecondX ):
1731 return xIntersection <= max( segmentFirstX, segmentSecondX )
1733 def isLineIntersectingLoop( loop, pointBegin, pointEnd ):
1734 'Determine if the line is intersecting loops.'
1735 normalizedSegment = pointEnd - pointBegin
1736 normalizedSegmentLength = abs( normalizedSegment )
1737 if normalizedSegmentLength > 0.0:
1738 normalizedSegment /= normalizedSegmentLength
1739 segmentYMirror = complex(normalizedSegment.real, -normalizedSegment.imag)
1740 pointBeginRotated = segmentYMirror * pointBegin
1741 pointEndRotated = segmentYMirror * pointEnd
1742 if isLoopIntersectingInsideXSegment( loop, pointBeginRotated.real, pointEndRotated.real, segmentYMirror, pointBeginRotated.imag ):
1746 def isLineIntersectingLoops( loops, pointBegin, pointEnd ):
1747 'Determine if the line is intersecting loops.'
1748 normalizedSegment = pointEnd - pointBegin
1749 normalizedSegmentLength = abs( normalizedSegment )
1750 if normalizedSegmentLength > 0.0:
1751 normalizedSegment /= normalizedSegmentLength
1752 segmentYMirror = complex(normalizedSegment.real, -normalizedSegment.imag)
1753 pointBeginRotated = segmentYMirror * pointBegin
1754 pointEndRotated = segmentYMirror * pointEnd
1755 if isLoopListIntersectingInsideXSegment( loops, pointBeginRotated.real, pointEndRotated.real, segmentYMirror, pointBeginRotated.imag ):
1759 def isLoopIntersectingInsideXSegment( loop, segmentFirstX, segmentSecondX, segmentYMirror, y ):
1760 'Determine if the loop is intersecting inside the x segment.'
1761 rotatedLoop = getRotatedComplexes( segmentYMirror, loop )
1762 for pointIndex in xrange( len( rotatedLoop ) ):
1763 pointFirst = rotatedLoop[pointIndex]
1764 pointSecond = rotatedLoop[ (pointIndex + 1) % len( rotatedLoop ) ]
1765 if isLineIntersectingInsideXSegment( pointFirst, pointSecond, segmentFirstX, segmentSecondX, y ):
1769 def isLoopIntersectingLoop( loop, otherLoop ):
1770 'Determine if the loop is intersecting the other loop.'
1771 for pointIndex in xrange(len(loop)):
1772 pointBegin = loop[pointIndex]
1773 pointEnd = loop[(pointIndex + 1) % len(loop)]
1774 if isLineIntersectingLoop( otherLoop, pointBegin, pointEnd ):
1778 def isLoopIntersectingLoops( loop, otherLoops ):
1779 'Determine if the loop is intersecting other loops.'
1780 for pointIndex in xrange(len(loop)):
1781 pointBegin = loop[pointIndex]
1782 pointEnd = loop[(pointIndex + 1) % len(loop)]
1783 if isLineIntersectingLoops( otherLoops, pointBegin, pointEnd ):
1787 def isLoopListIntersecting(loops):
1788 'Determine if a loop in the list is intersecting the other loops.'
1789 for loopIndex in xrange(len(loops) - 1):
1790 loop = loops[loopIndex]
1791 if isLoopIntersectingLoops(loop, loops[loopIndex + 1 :]):
1795 def isLoopListIntersectingInsideXSegment( loopList, segmentFirstX, segmentSecondX, segmentYMirror, y ):
1796 'Determine if the loop list is crossing inside the x segment.'
1797 for alreadyFilledLoop in loopList:
1798 if isLoopIntersectingInsideXSegment( alreadyFilledLoop, segmentFirstX, segmentSecondX, segmentYMirror, y ):
1802 def isPathEntirelyInsideLoop(loop, path):
1803 'Determine if a path is entirely inside another loop.'
1805 if not isPointInsideLoop(loop, point):
1809 def isPathEntirelyInsideLoops(loops, path):
1810 'Determine if a path is entirely inside another loop in a list.'
1812 if isPathEntirelyInsideLoop(loop, path):
1816 def isPathInsideLoop(loop, path):
1817 'Determine if a path is inside another loop.'
1818 return isPointInsideLoop(loop, getLeftPoint(path))
1820 def isPathInsideLoops(loops, path):
1821 'Determine if a path is inside another loop in a list.'
1823 if isPathInsideLoop(loop, path):
1827 def isPixelTableIntersecting( bigTable, littleTable, maskTable = {} ):
1828 'Add path to the pixel table.'
1829 littleTableKeys = littleTable.keys()
1830 for littleTableKey in littleTableKeys:
1831 if littleTableKey not in maskTable:
1832 if littleTableKey in bigTable:
1836 def isPointInsideLoop(loop, point):
1837 'Determine if a point is inside another loop.'
1838 return getNumberOfIntersectionsToLeft(loop, point) % 2 == 1
1840 def isSegmentCompletelyInX( segment, xFirst, xSecond ):
1841 'Determine if the segment overlaps within x.'
1842 segmentFirstX = segment[0].point.real
1843 segmentSecondX = segment[1].point.real
1844 if max( segmentFirstX, segmentSecondX ) > max( xFirst, xSecond ):
1846 return min( segmentFirstX, segmentSecondX ) >= min( xFirst, xSecond )
1848 def isWiddershins(polygonComplex):
1849 'Determine if the complex polygon goes round in the widdershins direction.'
1850 return getAreaLoop(polygonComplex) > 0.0
1852 def isWithinChannel( channelRadius, pointIndex, loop ):
1853 'Determine if the the point is within the channel between two adjacent points.'
1854 point = loop[pointIndex]
1855 behindSegmentComplex = loop[(pointIndex + len(loop) - 1) % len(loop)] - point
1856 behindSegmentComplexLength = abs( behindSegmentComplex )
1857 if behindSegmentComplexLength < channelRadius:
1859 aheadSegmentComplex = loop[(pointIndex + 1) % len(loop)] - point
1860 aheadSegmentComplexLength = abs( aheadSegmentComplex )
1861 if aheadSegmentComplexLength < channelRadius:
1863 behindSegmentComplex /= behindSegmentComplexLength
1864 aheadSegmentComplex /= aheadSegmentComplexLength
1865 absoluteZ = getDotProductPlusOne( aheadSegmentComplex, behindSegmentComplex )
1866 if behindSegmentComplexLength * absoluteZ < channelRadius:
1868 return aheadSegmentComplexLength * absoluteZ < channelRadius
1870 def isXSegmentIntersectingPath( path, segmentFirstX, segmentSecondX, segmentYMirror, y ):
1871 'Determine if a path is crossing inside the x segment.'
1872 rotatedPath = getRotatedComplexes( segmentYMirror, path )
1873 for pointIndex in xrange( len( rotatedPath ) - 1 ):
1874 pointFirst = rotatedPath[pointIndex]
1875 pointSecond = rotatedPath[pointIndex + 1]
1876 if isLineIntersectingInsideXSegment( pointFirst, pointSecond, segmentFirstX, segmentSecondX, y ):
1880 def isXSegmentIntersectingPaths( paths, segmentFirstX, segmentSecondX, segmentYMirror, y ):
1881 'Determine if a path list is crossing inside the x segment.'
1883 if isXSegmentIntersectingPath( path, segmentFirstX, segmentSecondX, segmentYMirror, y ):
1887 def joinSegmentTables( fromTable, intoTable ):
1888 'Join both segment tables and put the join into the intoTable.'
1889 intoTableKeys = intoTable.keys()
1890 fromTableKeys = fromTable.keys()
1892 concatenatedTableKeys = intoTableKeys + fromTableKeys
1893 for concatenatedTableKey in concatenatedTableKeys:
1894 joinedKeyTable[ concatenatedTableKey ] = None
1895 joinedKeys = joinedKeyTable.keys()
1897 for joinedKey in joinedKeys:
1898 xIntersectionIndexList = []
1899 if joinedKey in intoTable:
1900 addXIntersectionIndexesFromSegments( 0, intoTable[ joinedKey ], xIntersectionIndexList )
1901 if joinedKey in fromTable:
1902 addXIntersectionIndexesFromSegments( 1, fromTable[ joinedKey ], xIntersectionIndexList )
1903 xIntersections = getJoinOfXIntersectionIndexes( xIntersectionIndexList )
1904 lineSegments = getSegmentsFromXIntersections( xIntersections, joinedKey )
1905 if len( lineSegments ) > 0:
1906 intoTable[ joinedKey ] = lineSegments
1908 print('This should never happen, there are no line segments in joinSegments in euclidean')
1910 def joinXIntersectionsTables( fromTable, intoTable ):
1911 'Join both XIntersections tables and put the join into the intoTable.'
1913 concatenatedTableKeys = fromTable.keys() + intoTable.keys()
1914 for concatenatedTableKey in concatenatedTableKeys:
1915 joinedKeyTable[ concatenatedTableKey ] = None
1916 for joinedKey in joinedKeyTable.keys():
1917 xIntersectionIndexList = []
1918 if joinedKey in intoTable:
1919 addXIntersectionIndexesFromXIntersections( 0, xIntersectionIndexList, intoTable[ joinedKey ] )
1920 if joinedKey in fromTable:
1921 addXIntersectionIndexesFromXIntersections( 1, xIntersectionIndexList, fromTable[ joinedKey ] )
1922 xIntersections = getJoinOfXIntersectionIndexes( xIntersectionIndexList )
1923 if len( xIntersections ) > 0:
1924 intoTable[ joinedKey ] = xIntersections
1926 print('This should never happen, there are no line segments in joinSegments in euclidean')
1928 def overwriteDictionary(fromDictionary, keys, toDictionary):
1929 'Overwrite the dictionary.'
1931 if key in fromDictionary:
1932 toDictionary[key] = fromDictionary[key]
1934 def removeElementFromDictionary(dictionary, key):
1935 'Remove element from the dictionary.'
1936 if key in dictionary:
1939 def removeElementFromListTable(element, key, listDictionary):
1940 'Remove an element from the list table.'
1941 if key not in listDictionary:
1943 elementList = listDictionary[key]
1944 if len( elementList ) < 2:
1945 del listDictionary[key]
1947 if element in elementList:
1948 elementList.remove(element)
1950 def removeElementFromPixelListFromPoint( element, pixelDictionary, point ):
1951 'Remove an element from the pixel list.'
1952 stepKey = getStepKeyFromPoint(point)
1953 removeElementFromListTable( element, stepKey, pixelDictionary )
1955 def removeElementsFromDictionary(dictionary, keys):
1956 'Remove list from the dictionary.'
1958 removeElementFromDictionary(dictionary, key)
1960 def removePixelTableFromPixelTable( pixelDictionaryToBeRemoved, pixelDictionaryToBeRemovedFrom ):
1961 'Remove pixel from the pixel table.'
1962 removeElementsFromDictionary( pixelDictionaryToBeRemovedFrom, pixelDictionaryToBeRemoved.keys() )
1964 def removePrefixFromDictionary( dictionary, prefix ):
1965 'Remove the attributes starting with the prefix from the dictionary.'
1966 for key in dictionary.keys():
1967 if key.startswith( prefix ):
1970 def removeTrueFromDictionary(dictionary, key):
1971 'Remove key from the dictionary in the value is true.'
1972 if key in dictionary:
1973 if getBooleanFromValue(dictionary[key]):
1976 def removeTrueListFromDictionary( dictionary, keys ):
1977 'Remove list from the dictionary in the value is true.'
1979 removeTrueFromDictionary( dictionary, key )
1981 def subtractXIntersectionsTable( subtractFromTable, subtractTable ):
1982 'Subtract the subtractTable from the subtractFromTable.'
1983 subtractFromTableKeys = subtractFromTable.keys()
1984 subtractFromTableKeys.sort()
1985 for subtractFromTableKey in subtractFromTableKeys:
1986 xIntersectionIndexList = []
1987 addXIntersectionIndexesFromXIntersections( - 1, xIntersectionIndexList, subtractFromTable[ subtractFromTableKey ] )
1988 if subtractFromTableKey in subtractTable:
1989 addXIntersectionIndexesFromXIntersections( 0, xIntersectionIndexList, subtractTable[ subtractFromTableKey ] )
1990 xIntersections = getXIntersectionsFromIntersections( xIntersectionIndexList )
1991 if len( xIntersections ) > 0:
1992 subtractFromTable[ subtractFromTableKey ] = xIntersections
1994 del subtractFromTable[ subtractFromTableKey ]
1996 def swapList( elements, indexBegin, indexEnd ):
1997 'Swap the list elements.'
1998 elements[ indexBegin ], elements[ indexEnd ] = elements[ indexEnd ], elements[ indexBegin ]
2000 def toggleHashtable( hashtable, key, value ):
2001 'Toggle a hashtable between having and not having a key.'
2002 if key in hashtable:
2005 hashtable[key] = value
2007 def transferClosestFillLoop(extrusionHalfWidth, oldOrderedLocation, remainingFillLoops, skein):
2008 'Transfer the closest remaining fill loop.'
2009 closestDistance = 987654321987654321.0
2010 closestFillLoop = None
2011 for remainingFillLoop in remainingFillLoops:
2012 distance = getClosestDistanceIndexToLine(oldOrderedLocation.dropAxis(), remainingFillLoop).distance
2013 if distance < closestDistance:
2014 closestDistance = distance
2015 closestFillLoop = remainingFillLoop
2016 newClosestFillLoop = getLoopInsideContainingLoop(closestFillLoop, remainingFillLoops)
2017 while newClosestFillLoop != None:
2018 closestFillLoop = newClosestFillLoop
2019 newClosestFillLoop = getLoopInsideContainingLoop(closestFillLoop, remainingFillLoops)
2020 remainingFillLoops.remove(closestFillLoop)
2021 addToThreadsFromLoop(extrusionHalfWidth, 'loop', closestFillLoop[:], oldOrderedLocation, skein)
2023 def transferClosestPath( oldOrderedLocation, remainingPaths, skein ):
2024 'Transfer the closest remaining path.'
2025 closestDistance = 987654321987654321.0
2027 oldOrderedLocationComplex = oldOrderedLocation.dropAxis()
2028 for remainingPath in remainingPaths:
2029 distance = min( abs( oldOrderedLocationComplex - remainingPath[0] ), abs( oldOrderedLocationComplex - remainingPath[-1] ) )
2030 if distance < closestDistance:
2031 closestDistance = distance
2032 closestPath = remainingPath
2033 remainingPaths.remove( closestPath )
2034 skein.addGcodeFromThreadZ( closestPath, oldOrderedLocation.z )
2035 oldOrderedLocation.x = closestPath[-1].real
2036 oldOrderedLocation.y = closestPath[-1].imag
2038 def transferClosestPaths(oldOrderedLocation, remainingPaths, skein):
2039 'Transfer the closest remaining paths.'
2040 while len(remainingPaths) > 0:
2041 transferClosestPath(oldOrderedLocation, remainingPaths, skein)
2043 def transferPathsToNestedRings(nestedRings, paths):
2044 'Transfer paths to nested rings.'
2045 for nestedRing in nestedRings:
2046 nestedRing.transferPaths(paths)
2048 def translateVector3Path(path, translateVector3):
2049 'Translate the vector3 path.'
2051 point.setToVector3(point + translateVector3)
2053 def translateVector3Paths(paths, translateVector3):
2054 'Translate the vector3 paths.'
2056 translateVector3Path(path, translateVector3)
2058 def unbuckleBasis( basis, maximumUnbuckling, normal ):
2060 normalDot = basis.dot( normal )
2061 dotComplement = math.sqrt( 1.0 - normalDot * normalDot )
2062 unbuckling = maximumUnbuckling
2063 if dotComplement > 0.0:
2064 unbuckling = min( 1.0 / dotComplement, maximumUnbuckling )
2065 basis.setToVector3( basis * unbuckling )
2068 class DistanceIndex(object):
2069 'A class to hold the distance and the index of the loop.'
2070 def __init__(self, distance, index):
2072 self.distance = distance
2076 'Get the string representation of this distance index.'
2077 return '%s, %s' % (self.distance, self.index)
2080 class Endpoint(object):
2081 'The endpoint of a segment.'
2083 'Get the string representation of this Endpoint.'
2084 return 'Endpoint %s, %s' % ( self.point, self.otherEndpoint.point )
2086 def getClosestEndpoint( self, endpoints ):
2087 'Get closest endpoint.'
2088 smallestDistance = 987654321987654321.0
2089 closestEndpoint = None
2090 for endpoint in endpoints:
2091 distance = abs( self.point - endpoint.point )
2092 if distance < smallestDistance:
2093 smallestDistance = distance
2094 closestEndpoint = endpoint
2095 return closestEndpoint
2097 def getClosestMiss(self, endpoints, path, pixelDictionary, sharpestProduct, width):
2098 'Get the closest endpoint which the segment to that endpoint misses the other extrusions.'
2100 smallestDistance = 987654321.0
2101 penultimateMinusPoint = complex(0.0, 0.0)
2103 penultimatePoint = path[-2]
2104 addSegmentToPixelTable(penultimatePoint, self.point, pathMaskTable, 0, 0, width)
2105 penultimateMinusPoint = penultimatePoint - self.point
2106 if abs(penultimateMinusPoint) > 0.0:
2107 penultimateMinusPoint /= abs(penultimateMinusPoint)
2108 for endpoint in endpoints:
2109 endpoint.segment = endpoint.point - self.point
2110 endpoint.segmentLength = abs(endpoint.segment)
2111 if endpoint.segmentLength <= 0.0:
2113 endpoints.sort(compareSegmentLength)
2114 for endpoint in endpoints[: 15]: # increasing the number of searched endpoints increases the search time, with 20 fill took 600 seconds for cilinder.gts, with 10 fill took 533 seconds
2115 normalizedSegment = endpoint.segment / endpoint.segmentLength
2116 isOverlappingSelf = getDotProduct(penultimateMinusPoint, normalizedSegment) > sharpestProduct
2117 if not isOverlappingSelf:
2119 segmentYMirror = complex(normalizedSegment.real, -normalizedSegment.imag)
2120 pointRotated = segmentYMirror * self.point
2121 endpointPointRotated = segmentYMirror * endpoint.point
2122 if isXSegmentIntersectingPath(path[max(0, len(path) - 21) : -1], pointRotated.real, endpointPointRotated.real, segmentYMirror, pointRotated.imag):
2123 isOverlappingSelf = True
2124 if not isOverlappingSelf:
2125 totalMaskTable = pathMaskTable.copy()
2126 addSegmentToPixelTable(endpoint.point, endpoint.otherEndpoint.point, totalMaskTable, 0, 0, width)
2128 addSegmentToPixelTable(self.point, endpoint.point, segmentTable, 0, 0, width)
2129 if not isPixelTableIntersecting(pixelDictionary, segmentTable, totalMaskTable):
2133 def getClosestMissCheckEndpointPath(self, endpoints, path, pixelDictionary, sharpestProduct, width):
2134 'Get the closest endpoint which the segment to that endpoint misses the other extrusions, also checking the path of the endpoint.'
2136 smallestDistance = 987654321.0
2137 penultimateMinusPoint = complex(0.0, 0.0)
2139 penultimatePoint = path[-2]
2140 addSegmentToPixelTable(penultimatePoint, self.point, pathMaskTable, 0, 0, width)
2141 penultimateMinusPoint = penultimatePoint - self.point
2142 if abs(penultimateMinusPoint) > 0.0:
2143 penultimateMinusPoint /= abs(penultimateMinusPoint)
2144 for endpoint in endpoints:
2145 endpoint.segment = endpoint.point - self.point
2146 endpoint.segmentLength = abs(endpoint.segment)
2147 if endpoint.segmentLength <= 0.0:
2149 endpoints.sort( compareSegmentLength )
2150 for endpoint in endpoints[ : 15 ]: # increasing the number of searched endpoints increases the search time, with 20 fill took 600 seconds for cilinder.gts, with 10 fill took 533 seconds
2151 normalizedSegment = endpoint.segment / endpoint.segmentLength
2152 isOverlappingSelf = getDotProduct(penultimateMinusPoint, normalizedSegment) > sharpestProduct
2153 if not isOverlappingSelf:
2155 segmentYMirror = complex(normalizedSegment.real, -normalizedSegment.imag)
2156 pointRotated = segmentYMirror * self.point
2157 endpointPointRotated = segmentYMirror * endpoint.point
2158 if isXSegmentIntersectingPath(path[ max(0, len(path) - 21) : -1], pointRotated.real, endpointPointRotated.real, segmentYMirror, pointRotated.imag):
2159 isOverlappingSelf = True
2160 endpointPath = endpoint.path
2161 if len(endpointPath) > 2:
2162 segmentYMirror = complex(normalizedSegment.real, -normalizedSegment.imag)
2163 pointRotated = segmentYMirror * self.point
2164 endpointPointRotated = segmentYMirror * endpoint.point
2165 if isXSegmentIntersectingPath(endpointPath, pointRotated.real, endpointPointRotated.real, segmentYMirror, pointRotated.imag):
2166 isOverlappingSelf = True
2167 if not isOverlappingSelf:
2168 totalMaskTable = pathMaskTable.copy()
2169 addSegmentToPixelTable(endpoint.point, endpoint.otherEndpoint.point, totalMaskTable, 0, 0, width)
2171 addSegmentToPixelTable(self.point, endpoint.point, segmentTable, 0, 0, width)
2172 if not isPixelTableIntersecting(pixelDictionary, segmentTable, totalMaskTable):
2176 def getFromOtherPoint( self, otherEndpoint, point ):
2177 'Initialize from other endpoint.'
2178 self.otherEndpoint = otherEndpoint
2183 class LoopLayer(object):
2185 def __init__(self, z):
2191 'Get the string representation of this loop layer.'
2192 return '%s, %s' % (self.z, self.loops)
2195 class NestedRing(object):
2200 self.innerNestedRings = None
2203 'Get the string representation of this nested ring.'
2204 return str(self.__dict__)
2206 def addFlattenedNestedRings(self, flattenedNestedRings):
2207 'Add flattened nested rings.'
2208 flattenedNestedRings.append(self)
2209 for innerNestedRing in self.innerNestedRings:
2210 flattenedNestedRings += getFlattenedNestedRings(innerNestedRing.innerNestedRings)
2212 def getFromInsideSurroundings(self, inputSurroundingInsides):
2213 'Initialize from inside nested rings.'
2214 transferredSurroundings = getTransferredNestedRings(inputSurroundingInsides, self.boundary)
2215 self.innerNestedRings = getOrderedNestedRings(transferredSurroundings)
2219 class NestedBand(NestedRing):
2220 'A loop that surrounds paths.'
2223 NestedRing.__init__(self)
2225 self.extraLoops = []
2226 self.infillBoundaries = []
2227 self.infillPaths = []
2228 # self.lastExistingFillLoops = None
2229 self.lastFillLoops = None
2231 self.penultimateFillLoops = []
2235 'Get the string representation of this nested ring.'
2236 stringRepresentation = 'boundary\n%s\n' % self.boundary
2237 stringRepresentation += 'loop\n%s\n' % self.loop
2238 stringRepresentation += 'inner nested rings\n%s\n' % self.innerNestedRings
2239 stringRepresentation += 'infillPaths\n'
2240 for infillPath in self.infillPaths:
2241 stringRepresentation += 'infillPath\n%s\n' % infillPath
2242 stringRepresentation += 'edgePaths\n'
2243 for edgePath in self.edgePaths:
2244 stringRepresentation += 'edgePath\n%s\n' % edgePath
2245 return stringRepresentation + '\n'
2247 def addPerimeterInner(self, extrusionHalfWidth, oldOrderedLocation, skein, threadSequence):
2248 'Add to the edge and the inner island.'
2249 if self.loop == None:
2250 skein.distanceFeedRate.addLine('(<edgePath>)')
2251 transferClosestPaths(oldOrderedLocation, self.edgePaths[:], skein)
2252 skein.distanceFeedRate.addLine('(</edgePath>)')
2254 addToThreadsFromLoop(extrusionHalfWidth, 'edge', self.loop[:], oldOrderedLocation, skein)
2255 skein.distanceFeedRate.addLine('(</boundaryPerimeter>)')
2256 addToThreadsRemove(extrusionHalfWidth, self.innerNestedRings[:], oldOrderedLocation, skein, threadSequence)
2258 def addToBoundary(self, vector3):
2259 'Add vector3 to boundary.'
2260 self.boundary.append(vector3.dropAxis())
2263 def addToLoop(self, vector3):
2264 'Add vector3 to loop.'
2265 if self.loop == None:
2267 self.loop.append(vector3.dropAxis())
2270 def addToThreads(self, extrusionHalfWidth, oldOrderedLocation, skein, threadSequence):
2271 'Add to paths from the last location.'
2272 addNestedRingBeginning(skein.distanceFeedRate, self.boundary, self.z)
2273 threadFunctionDictionary = {
2274 'infill' : self.transferInfillPaths, 'loops' : self.transferClosestFillLoops, 'edge' : self.addPerimeterInner}
2275 for threadType in threadSequence:
2276 threadFunctionDictionary[threadType](extrusionHalfWidth, oldOrderedLocation, skein, threadSequence)
2277 skein.distanceFeedRate.addLine('(</nestedRing>)')
2279 def getFillLoops(self, penultimateFillLoops):
2280 'Get last fill loops from the outside loop and the loops inside the inside loops.'
2281 fillLoops = self.getLoopsToBeFilled()[:]
2282 surroundingBoundaries = self.getSurroundingBoundaries()
2284 if penultimateFillLoops == None:
2285 penultimateFillLoops = self.penultimateFillLoops
2286 if penultimateFillLoops == None:
2287 print('Warning, penultimateFillLoops == None in getFillLoops in NestedBand in euclidean.')
2289 for penultimateFillLoop in penultimateFillLoops:
2290 if len(penultimateFillLoop) > 2:
2291 if getIsInFilledRegion(surroundingBoundaries, penultimateFillLoop[0]):
2292 withinLoops.append(penultimateFillLoop)
2293 if not getIsInFilledRegionByPaths(self.penultimateFillLoops, fillLoops):
2294 fillLoops += self.penultimateFillLoops
2295 for nestedRing in self.innerNestedRings:
2296 fillLoops += getFillOfSurroundings(nestedRing.innerNestedRings, penultimateFillLoops)
2299 # def getLastExistingFillLoops(self):
2300 # 'Get last existing fill loops.'
2301 # lastExistingFillLoops = self.lastExistingFillLoops[:]
2302 # for nestedRing in self.innerNestedRings:
2303 # lastExistingFillLoops += nestedRing.getLastExistingFillLoops()
2304 # return lastExistingFillLoops
2306 def getLoopsToBeFilled(self):
2307 'Get last fill loops from the outside loop and the loops inside the inside loops.'
2308 if self.lastFillLoops == None:
2309 return self.getSurroundingBoundaries()
2310 return self.lastFillLoops
2312 def getSurroundingBoundaries(self):
2313 'Get the boundary of the surronding loop plus any boundaries of the innerNestedRings.'
2314 surroundingBoundaries = [self.boundary]
2315 for nestedRing in self.innerNestedRings:
2316 surroundingBoundaries.append(nestedRing.boundary)
2317 return surroundingBoundaries
2319 def transferClosestFillLoops(self, extrusionHalfWidth, oldOrderedLocation, skein, threadSequence):
2320 'Transfer closest fill loops.'
2321 if len( self.extraLoops ) < 1:
2323 remainingFillLoops = self.extraLoops[:]
2324 while len( remainingFillLoops ) > 0:
2325 transferClosestFillLoop(extrusionHalfWidth, oldOrderedLocation, remainingFillLoops, skein)
2327 def transferInfillPaths(self, extrusionHalfWidth, oldOrderedLocation, skein, threadSequence):
2328 'Transfer the infill paths.'
2329 if len(self.infillBoundaries) == 0 and len(self.infillPaths) == 0:
2331 skein.distanceFeedRate.addLine('(<infill>)')
2332 for infillBoundary in self.infillBoundaries:
2333 skein.distanceFeedRate.addLine('(<infillBoundary>)')
2334 for infillPoint in infillBoundary:
2335 infillPointVector3 = Vector3(infillPoint.real, infillPoint.imag, self.z)
2336 skein.distanceFeedRate.addLine(skein.distanceFeedRate.getInfillBoundaryLine(infillPointVector3))
2337 skein.distanceFeedRate.addLine('(</infillBoundary>)')
2338 transferClosestPaths(oldOrderedLocation, self.infillPaths[:], skein)
2339 skein.distanceFeedRate.addLine('(</infill>)')
2341 def transferPaths(self, paths):
2343 for nestedRing in self.innerNestedRings:
2344 transferPathsToNestedRings(nestedRing.innerNestedRings, paths)
2345 self.infillPaths = getTransferredPaths(paths, self.boundary)
2348 class PathZ(object):
2349 'Complex path with a z.'
2350 def __init__( self, z ):
2355 'Get the string representation of this path z.'
2356 return '%s, %s' % ( self.z, self.path )
2359 class ProjectiveSpace(object):
2360 'Class to define a projective space.'
2361 def __init__( self, basisX = Vector3(1.0, 0.0, 0.0), basisY = Vector3( 0.0, 1.0, 0.0 ), basisZ = Vector3(0.0, 0.0, 1.0) ):
2362 'Initialize the basis vectors.'
2363 self.basisX = basisX
2364 self.basisY = basisY
2365 self.basisZ = basisZ
2368 'Get the string representation of this ProjectivePlane.'
2369 return '%s, %s, %s' % ( self.basisX, self.basisY, self.basisZ )
2371 def getByBasisXZ( self, basisX, basisZ ):
2372 'Get by x basis x and y basis.'
2373 self.basisX = basisX
2374 self.basisZ = basisZ
2375 self.basisX.normalize()
2376 self.basisY = basisZ.cross(self.basisX)
2377 self.basisY.normalize()
2380 def getByBasisZFirst(self, basisZ, firstVector3):
2381 'Get by basisZ and first.'
2382 self.basisZ = basisZ
2383 self.basisY = basisZ.cross(firstVector3)
2384 self.basisY.normalize()
2385 self.basisX = self.basisY.cross(self.basisZ)
2386 self.basisX.normalize()
2389 def getByBasisZTop(self, basisZ, top):
2390 'Get by basisZ and top.'
2391 return self.getByBasisXZ(top.cross(basisZ), basisZ)
2393 def getByLatitudeLongitude( self, viewpointLatitude, viewpointLongitude ):
2394 'Get by latitude and longitude.'
2395 longitudeComplex = getWiddershinsUnitPolar( math.radians( 90.0 - viewpointLongitude ) )
2396 viewpointLatitudeRatio = getWiddershinsUnitPolar( math.radians( viewpointLatitude ) )
2397 basisZ = Vector3( viewpointLatitudeRatio.imag * longitudeComplex.real, viewpointLatitudeRatio.imag * longitudeComplex.imag, viewpointLatitudeRatio.real )
2398 return self.getByBasisXZ( Vector3( - longitudeComplex.imag, longitudeComplex.real, 0.0 ), basisZ )
2400 def getByTilt( self, tilt ):
2401 'Get by latitude and longitude.'
2402 xPlaneAngle = getWiddershinsUnitPolar( tilt.real )
2403 self.basisX = Vector3( xPlaneAngle.real, 0.0, xPlaneAngle.imag )
2404 yPlaneAngle = getWiddershinsUnitPolar( tilt.imag )
2405 self.basisY = Vector3( 0.0, yPlaneAngle.real, yPlaneAngle.imag )
2406 self.basisZ = self.basisX.cross(self.basisY)
2409 def getComplexByComplex( self, pointComplex ):
2410 'Get complex by complex point.'
2411 return self.basisX.dropAxis() * pointComplex.real + self.basisY.dropAxis() * pointComplex.imag
2415 return ProjectiveSpace( self.basisX, self.basisY, self.basisZ )
2417 def getDotComplex(self, point):
2418 'Get the dot complex.'
2419 return complex( point.dot(self.basisX), point.dot(self.basisY) )
2421 def getDotVector3(self, point):
2422 'Get the dot vector3.'
2423 return Vector3(point.dot(self.basisX), point.dot(self.basisY), point.dot(self.basisZ))
2425 def getNextSpace( self, nextNormal ):
2426 'Get next space by next normal.'
2427 nextSpace = self.getCopy()
2428 nextSpace.normalize()
2429 dotNext = nextSpace.basisZ.dot( nextNormal )
2430 if dotNext > 0.999999:
2432 if dotNext < - 0.999999:
2433 nextSpace.basisX = - nextSpace.basisX
2435 crossNext = nextSpace.basisZ.cross( nextNormal )
2436 oldBasis = ProjectiveSpace().getByBasisZTop( nextSpace.basisZ, crossNext )
2437 newBasis = ProjectiveSpace().getByBasisZTop( nextNormal, crossNext )
2438 nextSpace.basisX = newBasis.getVector3ByPoint( oldBasis.getDotVector3( nextSpace.basisX ) )
2439 nextSpace.basisY = newBasis.getVector3ByPoint( oldBasis.getDotVector3( nextSpace.basisY ) )
2440 nextSpace.basisZ = newBasis.getVector3ByPoint( oldBasis.getDotVector3( nextSpace.basisZ ) )
2441 nextSpace.normalize()
2444 def getSpaceByXYScaleAngle( self, angle, scale ):
2445 'Get space by angle and scale.'
2446 spaceByXYScaleRotation = ProjectiveSpace()
2447 planeAngle = getWiddershinsUnitPolar(angle)
2448 spaceByXYScaleRotation.basisX = self.basisX * scale.real * planeAngle.real + self.basisY * scale.imag * planeAngle.imag
2449 spaceByXYScaleRotation.basisY = - self.basisX * scale.real * planeAngle.imag + self.basisY * scale.imag * planeAngle.real
2450 spaceByXYScaleRotation.basisZ = self.basisZ
2451 return spaceByXYScaleRotation
2453 def getVector3ByPoint(self, point):
2454 'Get vector3 by point.'
2455 return self.basisX * point.x + self.basisY * point.y + self.basisZ * point.z
2457 def normalize(self):
2459 self.basisX.normalize()
2460 self.basisY.normalize()
2461 self.basisZ.normalize()
2463 def unbuckle( self, maximumUnbuckling, normal ):
2465 unbuckleBasis( self.basisX, maximumUnbuckling, normal )
2466 unbuckleBasis( self.basisY, maximumUnbuckling, normal )
2469 class XIntersectionIndex(object):
2470 'A class to hold the x intersection position and the index of the loop which intersected.'
2471 def __init__( self, index, x ):
2476 def __cmp__(self, other):
2477 'Get comparison in order to sort x intersections in ascending order of x.'
2478 if self.x < other.x:
2480 return int( self.x > other.x )
2482 def __eq__(self, other):
2483 'Determine whether this XIntersectionIndex is identical to other one.'
2486 if other.__class__ != self.__class__:
2488 return self.index == other.index and self.x == other.x
2490 def __ne__(self, other):
2491 'Determine whether this XIntersectionIndex is not identical to other one.'
2492 return not self.__eq__(other)
2495 'Get the string representation of this x intersection.'
2496 return 'XIntersectionIndex index %s; x %s ' % ( self.index, self.x )