2 Triangle Mesh holds the faces and edges of a triangular mesh.
6 from __future__ import absolute_import
7 #Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module.
10 from fabmetheus_utilities.geometry.geometry_tools import face
11 from fabmetheus_utilities.geometry.geometry_tools import dictionary
12 from fabmetheus_utilities.geometry.geometry_tools import vertex
13 from fabmetheus_utilities.geometry.geometry_utilities import evaluate
14 from fabmetheus_utilities.geometry.geometry_utilities import matrix
15 from fabmetheus_utilities.geometry.solids import group
16 from fabmetheus_utilities import xml_simple_writer
17 from fabmetheus_utilities.vector3 import Vector3
18 from fabmetheus_utilities.vector3index import Vector3Index
19 from fabmetheus_utilities import euclidean
20 from fabmetheus_utilities import intercircle
21 from fabmetheus_utilities import settings
25 __author__ = 'Enrique Perez (perez_enrique@yahoo.com)'
26 __credits__ = 'Art of Illusion <http://www.artofillusion.org/>'
27 __date__ = '$Date: 2008/02/05 $'
28 __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
31 def addEdgePair( edgePairTable, edges, faceEdgeIndex, remainingEdgeIndex, remainingEdgeTable ):
32 'Add edge pair to the edge pair table.'
33 if faceEdgeIndex == remainingEdgeIndex:
35 if not faceEdgeIndex in remainingEdgeTable:
37 edgePair = EdgePair().getFromIndexesEdges( [ remainingEdgeIndex, faceEdgeIndex ], edges )
38 edgePairTable[ str( edgePair ) ] = edgePair
40 def addFacesByConcaveLoop(faces, indexedLoop):
41 'Add faces from a polygon which is concave.'
42 if len(indexedLoop) < 3:
44 remainingLoop = indexedLoop[:]
45 while len(remainingLoop) > 2:
46 remainingLoop = getRemainingLoopAddFace(faces, remainingLoop)
48 def addFacesByConvex(faces, indexedLoop):
49 'Add faces from a convex polygon.'
50 if len(indexedLoop) < 3:
52 indexBegin = indexedLoop[0].index
53 for indexedPointIndex in xrange(1, len(indexedLoop) - 1):
54 indexCenter = indexedLoop[indexedPointIndex].index
55 indexEnd = indexedLoop[(indexedPointIndex + 1) % len(indexedLoop) ].index
56 if indexBegin != indexCenter and indexCenter != indexEnd and indexEnd != indexBegin:
57 faceFromConvex = face.Face()
58 faceFromConvex.index = len(faces)
59 faceFromConvex.vertexIndexes.append(indexBegin)
60 faceFromConvex.vertexIndexes.append(indexCenter)
61 faceFromConvex.vertexIndexes.append(indexEnd)
62 faces.append(faceFromConvex)
64 def addFacesByConvexBottomTopLoop(faces, indexedLoopBottom, indexedLoopTop):
65 'Add faces from loops.'
66 if len(indexedLoopBottom) == 0 or len(indexedLoopTop) == 0:
68 for indexedPointIndex in xrange(max(len(indexedLoopBottom), len(indexedLoopTop))):
70 if len(indexedLoopBottom) > 1:
71 indexedConvex.append(indexedLoopBottom[indexedPointIndex])
72 indexedConvex.append(indexedLoopBottom[(indexedPointIndex + 1) % len(indexedLoopBottom)])
74 indexedConvex.append(indexedLoopBottom[0])
75 if len(indexedLoopTop) > 1:
76 indexedConvex.append(indexedLoopTop[(indexedPointIndex + 1) % len(indexedLoopTop)])
77 indexedConvex.append(indexedLoopTop[indexedPointIndex])
79 indexedConvex.append(indexedLoopTop[0])
80 addFacesByConvex(faces, indexedConvex)
82 def addFacesByConvexLoops(faces, indexedLoops):
83 'Add faces from loops.'
84 if len(indexedLoops) < 2:
86 for indexedLoopsIndex in xrange(len(indexedLoops) - 2):
87 addFacesByConvexBottomTopLoop(faces, indexedLoops[indexedLoopsIndex], indexedLoops[indexedLoopsIndex + 1])
88 indexedLoopBottom = indexedLoops[-2]
89 indexedLoopTop = indexedLoops[-1]
90 if len(indexedLoopTop) < 1:
91 indexedLoopTop = indexedLoops[0]
92 addFacesByConvexBottomTopLoop(faces, indexedLoopBottom, indexedLoopTop)
94 def addFacesByConvexReversed(faces, indexedLoop):
95 'Add faces from a reversed convex polygon.'
96 addFacesByConvex(faces, indexedLoop[: : -1])
98 def addFacesByGrid(faces, grid):
99 'Add faces from grid.'
100 cellTopLoops = getIndexedCellLoopsFromIndexedGrid(grid)
101 for cellTopLoop in cellTopLoops:
102 addFacesByConvex(faces, cellTopLoop)
104 def addFacesByLoop(faces, indexedLoop):
105 'Add faces from a polygon which may be concave.'
106 if len(indexedLoop) < 3:
109 for pointIndex, point in enumerate(indexedLoop):
110 center = indexedLoop[(pointIndex + 1) % len(indexedLoop)]
111 end = indexedLoop[(pointIndex + 2) % len(indexedLoop)]
112 normal = euclidean.getNormalWeighted(point, center, end)
113 if abs(normal) > 0.0:
114 if lastNormal != None:
115 if lastNormal.dot(normal) < 0.0:
116 addFacesByConcaveLoop(faces, indexedLoop)
119 # totalNormal = Vector3()
120 # for pointIndex, point in enumerate(indexedLoop):
121 # center = indexedLoop[(pointIndex + 1) % len(indexedLoop)]
122 # end = indexedLoop[(pointIndex + 2) % len(indexedLoop)]
123 # totalNormal += euclidean.getNormalWeighted(point, center, end)
124 # totalNormal.normalize()
125 addFacesByConvex(faces, indexedLoop)
127 def addFacesByLoopReversed(faces, indexedLoop):
128 'Add faces from a reversed convex polygon.'
129 addFacesByLoop(faces, indexedLoop[: : -1])
131 def addFacesByMeldedConvexLoops(faces, indexedLoops):
132 'Add faces from melded loops.'
133 if len(indexedLoops) < 2:
135 for indexedLoopsIndex in xrange(len(indexedLoops) - 2):
136 FaceGenerator(faces, indexedLoops[indexedLoopsIndex], indexedLoops[indexedLoopsIndex + 1])
137 indexedLoopBottom = indexedLoops[-2]
138 indexedLoopTop = indexedLoops[-1]
139 if len(indexedLoopTop) < 1:
140 indexedLoopTop = indexedLoops[0]
141 FaceGenerator(faces, indexedLoopBottom, indexedLoopTop)
143 def addLoopToPointTable(loop, pointTable):
144 'Add the points in the loop to the point table.'
146 pointTable[point] = None
148 def addMeldedPillarByLoops(faces, indexedLoops):
149 'Add melded pillar by loops which may be concave.'
150 if len(indexedLoops) < 1:
152 if len(indexedLoops[-1]) < 1:
153 addFacesByMeldedConvexLoops(faces, indexedLoops)
155 addFacesByLoopReversed(faces, indexedLoops[0])
156 addFacesByMeldedConvexLoops(faces, indexedLoops)
157 addFacesByLoop(faces, indexedLoops[-1])
159 def addPillarByLoops(faces, indexedLoops):
160 'Add pillar by loops which may be concave.'
161 if len(indexedLoops) < 1:
163 if len(indexedLoops[-1]) < 1:
164 addFacesByConvexLoops(faces, indexedLoops)
166 addFacesByLoopReversed(faces, indexedLoops[0])
167 addFacesByConvexLoops(faces, indexedLoops)
168 addFacesByLoop(faces, indexedLoops[-1])
170 def addPillarFromConvexLoopsGrids(faces, indexedGrids, indexedLoops):
171 'Add pillar from convex loops and grids.'
172 cellBottomLoops = getIndexedCellLoopsFromIndexedGrid(indexedGrids[0])
173 for cellBottomLoop in cellBottomLoops:
174 addFacesByConvexReversed(faces, cellBottomLoop)
175 addFacesByConvexLoops(faces, indexedLoops)
176 addFacesByGrid(faces, indexedGrids[-1])
178 def addPillarFromConvexLoopsGridTop(faces, indexedGridTop, indexedLoops):
179 'Add pillar from convex loops and grid top.'
180 addFacesByLoopReversed(faces, indexedLoops[0])
181 addFacesByConvexLoops(faces, indexedLoops)
182 addFacesByGrid(faces, indexedGridTop)
184 def addPointsAtZ(edgePair, points, radius, vertexes, z):
185 'Add point complexes on the segment between the edge intersections with z.'
186 carveIntersectionFirst = getCarveIntersectionFromEdge(edgePair.edges[0], vertexes, z)
187 carveIntersectionSecond = getCarveIntersectionFromEdge(edgePair.edges[1], vertexes, z)
188 # threshold radius above 0.8 can create extra holes on Screw Holder, 0.7 should be safe for everything
189 intercircle.addPointsFromSegment(carveIntersectionFirst, carveIntersectionSecond, points, radius, 0.7)
191 def addSymmetricXPath(outputs, path, x):
192 'Add x path output to outputs.'
194 loops = [getSymmetricXLoop(path, vertexes, -x), getSymmetricXLoop(path, vertexes, x)]
195 outputs.append(getPillarOutput(loops))
197 def addSymmetricXPaths(outputs, paths, x):
198 'Add x paths outputs to outputs.'
200 addSymmetricXPath(outputs, path, x)
202 def addSymmetricYPath(outputs, path, y):
203 'Add y path output to outputs.'
205 loops = [getSymmetricYLoop(path, vertexes, -y), getSymmetricYLoop(path, vertexes, y)]
206 outputs.append(getPillarOutput(loops))
208 def addSymmetricYPaths(outputs, paths, y):
209 'Add y paths outputs to outputs.'
211 addSymmetricYPath(outputs, path, y)
213 def addVector3Loop(loop, loops, vertexes, z):
214 'Add vector3Loop to loops if there is something in it, for inset and outset.'
217 vector3Index = Vector3Index(len(vertexes), point.real, point.imag, z)
218 vector3Loop.append(vector3Index)
219 vertexes.append(vector3Index)
220 if len(vector3Loop) > 0:
221 loops.append(vector3Loop)
223 def addWithLeastLength(importRadius, loops, point):
224 'Insert a point into a loop, at the index at which the loop would be shortest.'
225 close = 1.65 * importRadius # a bit over the experimental minimum additional loop length to restore a right angle
226 shortestAdditionalLength = close
228 shortestPointIndex = None
231 for pointIndex in xrange(len(loop)):
232 additionalLoopLength = getAdditionalLoopLength(loop, point, pointIndex)
233 if additionalLoopLength < shortestAdditionalLength:
234 if getIsPointCloseInline(close, loop, point, pointIndex):
235 shortestAdditionalLength = additionalLoopLength
237 shortestPointIndex = pointIndex
238 if shortestPointIndex != None:
239 shortestLoop.insert( shortestPointIndex, point )
241 def convertElementNode(elementNode, geometryOutput):
242 'Convert the xml element to a TriangleMesh xml element.'
243 elementNode.linkObject(TriangleMesh())
244 matrix.getBranchMatrixSetElementNode(elementNode)
245 vertex.addGeometryList(elementNode, geometryOutput['vertex'])
246 face.addGeometryList(elementNode, geometryOutput['face'])
247 elementNode.getXMLProcessor().processChildNodes(elementNode)
249 def getAddIndexedGrid( grid, vertexes, z ):
250 'Get and add an indexed grid.'
254 indexedGrid.append( indexedRow )
255 for pointComplex in row:
256 vector3index = Vector3Index( len(vertexes), pointComplex.real, pointComplex.imag, z )
257 indexedRow.append(vector3index)
258 vertexes.append(vector3index)
261 def getAddIndexedLoop(loop, vertexes, z):
262 'Get and add an indexed loop.'
264 for index in xrange(len(loop)):
265 pointComplex = loop[index]
266 vector3index = Vector3Index(len(vertexes), pointComplex.real, pointComplex.imag, z)
267 indexedLoop.append(vector3index)
268 vertexes.append(vector3index)
271 def getAddIndexedLoops( loop, vertexes, zList ):
272 'Get and add indexed loops.'
275 indexedLoop = getAddIndexedLoop( loop, vertexes, z )
276 indexedLoops.append(indexedLoop)
279 def getAdditionalLoopLength(loop, point, pointIndex):
280 'Get the additional length added by inserting a point into a loop.'
281 afterPoint = loop[pointIndex]
282 beforePoint = loop[(pointIndex + len(loop) - 1) % len(loop)]
283 return abs(point - beforePoint) + abs(point - afterPoint) - abs(afterPoint - beforePoint)
285 def getCarveIntersectionFromEdge(edge, vertexes, z):
286 'Get the complex where the carve intersects the edge.'
287 firstVertex = vertexes[ edge.vertexIndexes[0] ]
288 firstVertexComplex = firstVertex.dropAxis()
289 secondVertex = vertexes[ edge.vertexIndexes[1] ]
290 secondVertexComplex = secondVertex.dropAxis()
291 zMinusFirst = z - firstVertex.z
292 up = secondVertex.z - firstVertex.z
293 return zMinusFirst * ( secondVertexComplex - firstVertexComplex ) / up + firstVertexComplex
295 def getClosestDistanceIndexToPoint(point, loop):
296 'Get the distance squared to the closest point of the loop and index of that point.'
297 smallestDistance = 987654321987654321.0
298 closestDistanceIndex = None
299 pointComplex = point.dropAxis()
300 for otherPointIndex, otherPoint in enumerate(loop):
301 distance = abs(pointComplex - otherPoint.dropAxis())
302 if distance < smallestDistance:
303 smallestDistance = distance
304 closestDistanceIndex = euclidean.DistanceIndex(distance, otherPointIndex)
305 return closestDistanceIndex
307 def getDescendingAreaLoops(allPoints, corners, importRadius):
308 'Get descending area loops which include most of the points.'
309 loops = intercircle.getCentersFromPoints(allPoints, importRadius)
310 descendingAreaLoops = []
311 sortLoopsInOrderOfArea(True, loops)
314 if len(loop) > 2 and getOverlapRatio(loop, pointDictionary) < 0.3 and intercircle.getIsLarge(loop, importRadius):
315 intercircle.directLoop(not euclidean.getIsInFilledRegion(descendingAreaLoops, loop[0]), loop)
316 descendingAreaLoops.append(loop)
317 addLoopToPointTable(loop, pointDictionary)
318 descendingAreaLoops = euclidean.getSimplifiedLoops(descendingAreaLoops, importRadius)
319 return getLoopsWithCorners(corners, importRadius, descendingAreaLoops, pointDictionary)
321 def getDescendingAreaOrientedLoops(allPoints, corners, importRadius):
322 'Get descending area oriented loops which include most of the points.'
323 return getOrientedLoops(getDescendingAreaLoops(allPoints, corners, importRadius))
325 def getGeometryOutputByFacesVertexes(faces, vertexes):
326 'Get geometry output dictionary by faces and vertexes.'
327 return {'trianglemesh' : {'vertex' : vertexes, 'face' : faces}}
329 def getGeometryOutputCopy(object):
330 'Get the geometry output copy.'
331 objectClass = object.__class__
332 if objectClass == dict:
335 objectCopy[key] = getGeometryOutputCopy(object[key])
337 if objectClass == list:
340 objectCopy.append(getGeometryOutputCopy(value))
342 if objectClass == face.Face or objectClass == Vector3 or objectClass == Vector3Index:
346 def getIndexedCellLoopsFromIndexedGrid( grid ):
347 'Get indexed cell loops from an indexed grid.'
348 indexedCellLoops = []
349 for rowIndex in xrange( len( grid ) - 1 ):
350 rowBottom = grid[ rowIndex ]
351 rowTop = grid[ rowIndex + 1 ]
352 for columnIndex in xrange( len( rowBottom ) - 1 ):
353 columnIndexEnd = columnIndex + 1
355 indexedConvex.append( rowBottom[ columnIndex ] )
356 indexedConvex.append( rowBottom[ columnIndex + 1 ] )
357 indexedConvex.append( rowTop[ columnIndex + 1 ] )
358 indexedConvex.append( rowTop[ columnIndex ] )
359 indexedCellLoops.append( indexedConvex )
360 return indexedCellLoops
362 def getIndexedLoopFromIndexedGrid( indexedGrid ):
363 'Get indexed loop from around the indexed grid.'
364 indexedLoop = indexedGrid[0][:]
365 for row in indexedGrid[1 : -1]:
366 indexedLoop.append( row[-1] )
367 indexedLoop += indexedGrid[-1][: : -1]
368 for row in indexedGrid[ len( indexedGrid ) - 2 : 0 : - 1 ]:
369 indexedLoop.append( row[0] )
372 def getInfillDictionary(arounds, aroundWidth, infillInset, infillWidth, pixelTable, rotatedLoops, testLoops=None):
373 'Get combined fill loops which include most of the points.'
374 slightlyGreaterThanInfillInset = intercircle.globalIntercircleMultiplier * infillInset
375 allPoints = intercircle.getPointsFromLoops(rotatedLoops, infillInset, 0.7)
376 centers = intercircle.getCentersFromPoints(allPoints, slightlyGreaterThanInfillInset)
377 infillDictionary = {}
378 for center in centers:
379 insetCenter = intercircle.getSimplifiedInsetFromClockwiseLoop(center, infillInset)
380 insetPoint = insetCenter[0]
381 if len(insetCenter) > 2 and intercircle.getIsLarge(insetCenter, infillInset) and euclidean.getIsInFilledRegion(rotatedLoops, insetPoint):
382 around = euclidean.getSimplifiedLoop(center, infillInset)
383 euclidean.addLoopToPixelTable(around, pixelTable, aroundWidth)
384 arounds.append(around)
385 insetLoop = intercircle.getSimplifiedInsetFromClockwiseLoop(center, infillInset)
386 euclidean.addXIntersectionsFromLoopForTable(insetLoop, infillDictionary, infillWidth)
387 if testLoops != None:
388 testLoops.append(insetLoop)
389 return infillDictionary
391 def getInsetPoint( loop, tinyRadius ):
392 'Get the inset vertex.'
393 pointIndex = getWideAnglePointIndex(loop)
394 point = loop[ pointIndex % len(loop) ]
395 afterPoint = loop[(pointIndex + 1) % len(loop)]
396 beforePoint = loop[ ( pointIndex - 1 ) % len(loop) ]
397 afterSegmentNormalized = euclidean.getNormalized( afterPoint - point )
398 beforeSegmentNormalized = euclidean.getNormalized( beforePoint - point )
399 afterClockwise = complex( afterSegmentNormalized.imag, - afterSegmentNormalized.real )
400 beforeWiddershins = complex( - beforeSegmentNormalized.imag, beforeSegmentNormalized.real )
401 midpoint = afterClockwise + beforeWiddershins
402 midpointNormalized = midpoint / abs( midpoint )
403 return point + midpointNormalized * tinyRadius
405 def getIsPathEntirelyOutsideTriangle(begin, center, end, vector3Path):
406 'Determine if a path is entirely outside another loop.'
407 loop = [begin.dropAxis(), center.dropAxis(), end.dropAxis()]
408 for vector3 in vector3Path:
409 point = vector3.dropAxis()
410 if euclidean.isPointInsideLoop(loop, point):
414 def getIsPointCloseInline(close, loop, point, pointIndex):
415 'Insert a point into a loop, at the index at which the loop would be shortest.'
416 afterCenterComplex = loop[pointIndex]
417 if abs(afterCenterComplex - point) > close:
419 afterEndComplex = loop[(pointIndex + 1) % len(loop)]
420 if not isInline( point, afterCenterComplex, afterEndComplex ):
422 beforeCenterComplex = loop[(pointIndex + len(loop) - 1) % len(loop)]
423 if abs(beforeCenterComplex - point) > close:
425 beforeEndComplex = loop[(pointIndex + len(loop) - 2) % len(loop)]
426 return isInline(point, beforeCenterComplex, beforeEndComplex)
428 def getLoopsFromCorrectMesh( edges, faces, vertexes, z ):
429 'Get loops from a carve of a correct mesh.'
430 remainingEdgeTable = getRemainingEdgeTable(edges, vertexes, z)
431 remainingValues = remainingEdgeTable.values()
433 for edge in remainingValues:
434 if len( edge.faceIndexes ) < 2:
435 if not hasattr(edge, 'errorReported'):
436 print('Model error(hole): ' + str(vertexes[edge.vertexIndexes[0]]) + ' ' + str(vertexes[edge.vertexIndexes[1]]))
437 edge.errorReported = True
442 while isPathAdded( edges, faces, loops, remainingEdgeTable, vertexes, z ):
446 for idx in xrange(0, len(loops)-1):
450 if euclidean.isLineIntersectingLoops(loops[idx+1:], p0, p1):
452 print('Warning, the triangle mesh slice intersects itself in getLoopsFromCorrectMesh in triangle_mesh.')
453 print('Model error(intersect): (%f, %f, %f) (%f, %f, %f)' % (p0.real, p0.imag, z, p1.real, p1.imag, z))
460 # for boundingLoop in boundingLoops:
461 # if not boundingLoop.isIntersectingList( untouchables ):
462 # untouchables.append( boundingLoop )
463 # if len( untouchables ) < len( boundingLoops ):
464 # print('This should never happen, the carve layer intersects itself. Something will still be printed, but there is no guarantee that it will be the correct shape.')
465 # print('Once the gcode is saved, you should check over the layer with a z of:')
467 # remainingLoops = []
468 # for untouchable in untouchables:
469 # remainingLoops.append( untouchable.loop )
470 # return remainingLoops
472 def getLoopsFromUnprovenMesh(edges, faces, importRadius, vertexes, z):
473 'Get loops from a carve of an unproven mesh.'
476 remainingEdgeTable = getRemainingEdgeTable(edges, vertexes, z)
477 remainingEdgeTableKeys = remainingEdgeTable.keys()
478 for remainingEdgeIndexKey in remainingEdgeTable:
479 edge = remainingEdgeTable[remainingEdgeIndexKey]
480 carveIntersection = getCarveIntersectionFromEdge(edge, vertexes, z)
481 corners.append(carveIntersection)
482 for edgeFaceIndex in edge.faceIndexes:
483 face = faces[edgeFaceIndex]
484 for edgeIndex in face.edgeIndexes:
485 addEdgePair(edgePairTable, edges, edgeIndex, remainingEdgeIndexKey, remainingEdgeTable)
486 allPoints = corners[:]
487 for edgePairValue in edgePairTable.values():
488 addPointsAtZ(edgePairValue, allPoints, importRadius, vertexes, z)
490 return getDescendingAreaLoops(allPoints, corners, importRadius)
492 def getLoopLayerAppend(loopLayers, layerCount, z):
493 'Get next z and add extruder loops.'
494 settings.printProgressByNumber(len(loopLayers), layerCount, 'slice')
495 loopLayer = euclidean.LoopLayer(z)
496 loopLayers.append(loopLayer)
499 def getLoopsWithCorners(corners, importRadius, loops, pointTable):
500 'Add corners to the loops.'
501 for corner in corners:
502 if corner not in pointTable:
503 addWithLeastLength(importRadius, loops, corner)
504 pointTable[corner] = None
505 return euclidean.getSimplifiedLoops(loops, importRadius)
507 def getMeldedPillarOutput(loops):
508 'Get melded pillar output.'
510 vertexes = getUniqueVertexes(loops)
511 addMeldedPillarByLoops(faces, loops)
512 return getGeometryOutputByFacesVertexes(faces, vertexes)
514 def getNewDerivation(elementNode):
515 'Get new derivation.'
516 return evaluate.EmptyObject(elementNode)
518 def getNextEdgeIndexAroundZ(edge, faces, remainingEdgeTable):
519 'Get the next edge index in the mesh carve.'
520 for faceIndex in edge.faceIndexes:
521 face = faces[faceIndex]
522 for edgeIndex in face.edgeIndexes:
523 if edgeIndex in remainingEdgeTable:
527 def getOrientedLoops(loops):
528 'Orient the loops which must be in descending order.'
529 for loopIndex, loop in enumerate(loops):
530 leftPoint = euclidean.getLeftPoint(loop)
531 isInFilledRegion = euclidean.getIsInFilledRegion(loops[: loopIndex] + loops[loopIndex + 1 :], leftPoint)
532 if isInFilledRegion == euclidean.isWiddershins(loop):
536 def getOverlapRatio( loop, pointTable ):
537 'Get the overlap ratio between the loop and the point table.'
540 if point in pointTable:
541 numberOfOverlaps += 1
542 return float( numberOfOverlaps ) / float(len(loop))
544 def getPath( edges, pathIndexes, loop, z ):
545 'Get the path from the edge intersections.'
547 for pathIndexIndex in xrange( len( pathIndexes ) ):
548 pathIndex = pathIndexes[ pathIndexIndex ]
549 edge = edges[ pathIndex ]
550 carveIntersection = getCarveIntersectionFromEdge( edge, loop, z )
551 path.append( carveIntersection )
554 def getPillarOutput(loops):
557 vertexes = getUniqueVertexes(loops)
558 addPillarByLoops(faces, loops)
559 return getGeometryOutputByFacesVertexes(faces, vertexes)
561 def getPillarsOutput(loopLists):
562 'Get pillars output.'
564 for loopList in loopLists:
565 pillarsOutput.append(getPillarOutput(loopList))
566 return getUnifiedOutput(pillarsOutput)
568 def getRemainingEdgeTable(edges, vertexes, z):
569 'Get the remaining edge hashtable.'
570 remainingEdgeTable = {}
572 if edges[0].zMinimum == None:
574 setEdgeMaximumMinimum(edge, vertexes)
575 for edgeIndex in xrange(len(edges)):
576 edge = edges[edgeIndex]
577 if (edge.zMinimum < z) and (edge.zMaximum > z):
578 remainingEdgeTable[edgeIndex] = edge
579 return remainingEdgeTable
581 def getRemainingLoopAddFace(faces, remainingLoop):
582 'Get the remaining loop and add face.'
583 for indexedVertexIndex, indexedVertex in enumerate(remainingLoop):
584 nextIndex = (indexedVertexIndex + 1) % len(remainingLoop)
585 previousIndex = (indexedVertexIndex + len(remainingLoop) - 1) % len(remainingLoop)
586 nextVertex = remainingLoop[nextIndex]
587 previousVertex = remainingLoop[previousIndex]
588 remainingPath = euclidean.getAroundLoop((indexedVertexIndex + 2) % len(remainingLoop), previousIndex, remainingLoop)
589 if len(remainingLoop) < 4 or getIsPathEntirelyOutsideTriangle(previousVertex, indexedVertex, nextVertex, remainingPath):
590 faceConvex = face.Face()
591 faceConvex.index = len(faces)
592 faceConvex.vertexIndexes.append(indexedVertex.index)
593 faceConvex.vertexIndexes.append(nextVertex.index)
594 faceConvex.vertexIndexes.append(previousVertex.index)
595 faces.append(faceConvex)
596 return euclidean.getAroundLoop(nextIndex, indexedVertexIndex, remainingLoop)
597 print('Warning, could not decompose polygon in getRemainingLoopAddFace in trianglemesh for:')
601 def getSharedFace( firstEdge, faces, secondEdge ):
602 'Get the face which is shared by two edges.'
603 for firstEdgeFaceIndex in firstEdge.faceIndexes:
604 for secondEdgeFaceIndex in secondEdge.faceIndexes:
605 if firstEdgeFaceIndex == secondEdgeFaceIndex:
606 return faces[ firstEdgeFaceIndex ]
609 def getSymmetricXLoop(path, vertexes, x):
610 'Get symmetrix x loop.'
613 vector3Index = Vector3Index(len(vertexes), x, point.real, point.imag)
614 loop.append(vector3Index)
615 vertexes.append(vector3Index)
618 def getSymmetricYLoop(path, vertexes, y):
619 'Get symmetrix y loop.'
622 vector3Index = Vector3Index(len(vertexes), point.real, y, point.imag)
623 loop.append(vector3Index)
624 vertexes.append(vector3Index)
627 def getUnifiedOutput(outputs):
628 'Get unified output.'
631 if len(outputs) == 1:
633 return {'union' : {'shapes' : outputs}}
635 def getUniqueVertexes(loops):
636 'Get unique vertexes.'
637 vertexDictionary = {}
640 for vertexIndex, vertex in enumerate(loop):
641 vertexTuple = (vertex.x, vertex.y, vertex.z)
642 if vertexTuple in vertexDictionary:
643 loop[vertexIndex] = vertexDictionary[vertexTuple]
645 if vertex.__class__ == Vector3Index:
646 loop[vertexIndex].index = len(vertexDictionary)
648 loop[vertexIndex] = Vector3Index(len(vertexDictionary), vertex.x, vertex.y, vertex.z)
649 vertexDictionary[vertexTuple] = loop[vertexIndex]
650 uniqueVertexes.append(loop[vertexIndex])
651 return uniqueVertexes
653 def getWideAnglePointIndex(loop):
654 'Get a point index which has a wide enough angle, most point indexes have a wide enough angle, this is just to make sure.'
655 dotProductMinimum = 9999999.9
657 for pointIndex in xrange(len(loop)):
658 point = loop[ pointIndex % len(loop) ]
659 afterPoint = loop[(pointIndex + 1) % len(loop)]
660 beforePoint = loop[ ( pointIndex - 1 ) % len(loop) ]
661 afterSegmentNormalized = euclidean.getNormalized( afterPoint - point )
662 beforeSegmentNormalized = euclidean.getNormalized( beforePoint - point )
663 dotProduct = euclidean.getDotProduct( afterSegmentNormalized, beforeSegmentNormalized )
666 if dotProduct < dotProductMinimum:
667 dotProductMinimum = dotProduct
668 widestPointIndex = pointIndex
669 return widestPointIndex
671 def isInline( beginComplex, centerComplex, endComplex ):
672 'Determine if the three complex points form a line.'
673 centerBeginComplex = beginComplex - centerComplex
674 centerEndComplex = endComplex - centerComplex
675 centerBeginLength = abs( centerBeginComplex )
676 centerEndLength = abs( centerEndComplex )
677 if centerBeginLength <= 0.0 or centerEndLength <= 0.0:
679 centerBeginComplex /= centerBeginLength
680 centerEndComplex /= centerEndLength
681 return euclidean.getDotProduct( centerBeginComplex, centerEndComplex ) < -0.999
683 def isPathAdded( edges, faces, loops, remainingEdgeTable, vertexes, z ):
684 'Get the path indexes around a triangle mesh carve and add the path to the flat loops.'
685 if len( remainingEdgeTable ) < 1:
688 remainingEdgeIndexKey = remainingEdgeTable.keys()[0]
689 pathIndexes.append( remainingEdgeIndexKey )
690 del remainingEdgeTable[remainingEdgeIndexKey]
691 nextEdgeIndexAroundZ = getNextEdgeIndexAroundZ( edges[remainingEdgeIndexKey], faces, remainingEdgeTable )
692 while nextEdgeIndexAroundZ != - 1:
693 pathIndexes.append( nextEdgeIndexAroundZ )
694 del remainingEdgeTable[ nextEdgeIndexAroundZ ]
695 nextEdgeIndexAroundZ = getNextEdgeIndexAroundZ( edges[ nextEdgeIndexAroundZ ], faces, remainingEdgeTable )
696 if len( pathIndexes ) < 3:
697 print('Dangling edges, will use intersecting circles to get import layer at height %s' % z)
698 for idx in pathIndexes:
699 if not hasattr(edges[idx], 'errorReported'):
700 print('Model error(dangle): ' + str(vertexes[edges[idx].vertexIndexes[0]]) + ' ' + str(vertexes[edges[idx].vertexIndexes[1]]))
701 edges[idx].errorReported = True
704 loops.append( getPath( edges, pathIndexes, vertexes, z ) )
707 def processElementNode(elementNode):
708 'Process the xml element.'
709 evaluate.processArchivable(TriangleMesh, elementNode)
711 def setEdgeMaximumMinimum(edge, vertexes):
712 'Set the edge maximum and minimum.'
713 beginIndex = edge.vertexIndexes[0]
714 endIndex = edge.vertexIndexes[1]
715 if beginIndex >= len(vertexes) or endIndex >= len(vertexes):
716 print('Warning, there are duplicate vertexes in setEdgeMaximumMinimum in triangle_mesh.')
717 print('Something might still be printed, but there is no guarantee that it will be the correct shape.' )
718 edge.zMaximum = -987654321.0
719 edge.zMinimum = -987654321.0
721 beginZ = vertexes[beginIndex].z
722 endZ = vertexes[endIndex].z
723 edge.zMinimum = min(beginZ, endZ)
724 edge.zMaximum = max(beginZ, endZ)
726 def sortLoopsInOrderOfArea(isDescending, loops):
727 'Sort the loops in the order of area according isDescending.'
728 loops.sort(key=euclidean.getAreaLoopAbsolute, reverse=isDescending)
733 'Pair of edges on a face.'
734 self.edgeIndexes = []
738 'Get the string representation of this EdgePair.'
739 return str( self.edgeIndexes )
741 def getFromIndexesEdges( self, edgeIndexes, edges ):
742 'Initialize from edge indices.'
743 self.edgeIndexes = edgeIndexes[:]
744 self.edgeIndexes.sort()
745 for edgeIndex in self.edgeIndexes:
746 self.edges.append( edges[ edgeIndex ] )
752 def __init__(self, faces, indexedLoopBottom, indexedLoopTop):
755 if len(indexedLoopBottom) == 0 or len(indexedLoopTop) == 0:
757 smallestDistance = 987654321987654321.0
758 for pointIndex, point in enumerate(indexedLoopBottom):
759 distanceIndex = getClosestDistanceIndexToPoint(point, indexedLoopTop)
760 if distanceIndex.distance < smallestDistance:
761 smallestDistance = distanceIndex.distance
762 offsetBottom = pointIndex
763 offsetTop = distanceIndex.index
764 self.indexedLoopBottom = indexedLoopBottom[offsetBottom :] + indexedLoopBottom[: offsetBottom]
765 self.indexedLoopTop = indexedLoopTop[offsetTop :] + indexedLoopTop[: offsetTop]
766 for bottomIndex in xrange(len(self.indexedLoopBottom)):
767 self.addFacesByBottomIndex(bottomIndex, faces)
768 subsetTop = self.indexedLoopTop[self.startTop :]
769 subsetTop.append(self.indexedLoopTop[0])
770 addFacesByConvexBottomTopLoop(faces, [self.indexedLoopBottom[0]], subsetTop[: : -1])
772 def addFacesByBottomIndex(self, bottomIndex, faces):
773 'Add faces from the bottom index to the next index.'
774 bottomPoint = self.indexedLoopBottom[bottomIndex % len(self.indexedLoopBottom)]
775 bottomPointNext = self.indexedLoopBottom[(bottomIndex + 1) % len(self.indexedLoopBottom)]
776 topIndex = self.startTop + getClosestDistanceIndexToPoint(bottomPointNext, self.indexedLoopTop[self.startTop :]).index
777 topIndexPlusOne = topIndex + 1
778 betweenIndex = self.getBetweenIndex(bottomPoint, bottomPointNext, topIndexPlusOne)
779 betweenIndexPlusOne = betweenIndex + 1
780 subsetStart = self.indexedLoopTop[self.startTop : betweenIndexPlusOne]
781 subsetEnd = self.indexedLoopTop[betweenIndex : topIndexPlusOne]
782 addFacesByConvexBottomTopLoop(faces, [bottomPoint], subsetStart[: : -1])
783 addFacesByConvexBottomTopLoop(faces, [bottomPoint, bottomPointNext], [self.indexedLoopTop[betweenIndex]])
784 addFacesByConvexBottomTopLoop(faces, [bottomPointNext], subsetEnd[: : -1])
785 self.startTop = topIndex
787 def getBetweenIndex(self, bottomPoint, bottomPointNext, topIndexPlusOne):
788 'Get the index of the last point along the loop which is closer to the bottomPoint.'
789 betweenIndex = self.startTop
790 bottomPointComplex = bottomPoint.dropAxis()
791 bottomPointNextComplex = bottomPointNext.dropAxis()
792 for topPointIndex in xrange(self.startTop, topIndexPlusOne):
793 topPointComplex = self.indexedLoopTop[topPointIndex].dropAxis()
794 if abs(topPointComplex - bottomPointComplex) > abs(topPointComplex - bottomPointNextComplex):
796 betweenIndex = topPointIndex
800 class TriangleMesh( group.Group ):
804 group.Group.__init__(self)
808 self.importCoarseness = 1.0
809 self.isCorrectMesh = True
811 self.oldChainTetragrid = None
812 self.transformedVertexes = None
815 def addXMLSection(self, depth, output):
816 'Add the xml section for this object.'
817 xml_simple_writer.addXMLFromVertexes( depth, output, self.vertexes )
818 xml_simple_writer.addXMLFromObjects( depth, self.faces, output )
820 def getCarveBoundaryLayers(self):
821 'Get the boundary layers.'
822 if self.getMinimumZ() == None:
824 halfHeight = 0.5 * self.layerHeight
825 self.zoneArrangement = ZoneArrangement(self.layerHeight, self.getTransformedVertexes())
826 layerTop = self.cornerMaximum.z - halfHeight * 0.5
827 z = self.cornerMinimum.z + halfHeight
828 layerCount = int((layerTop - z) / self.layerHeight) + 1
830 getLoopLayerAppend(self.loopLayers, layerCount, z).loops = self.getLoopsFromMesh(self.zoneArrangement.getEmptyZ(z))
831 z += self.layerHeight
832 return self.loopLayers
834 def getCarveCornerMaximum(self):
835 'Get the corner maximum of the vertexes.'
836 return self.cornerMaximum
838 def getCarveCornerMinimum(self):
839 'Get the corner minimum of the vertexes.'
840 return self.cornerMinimum
842 def getCarveLayerHeight(self):
843 'Get the layer height.'
844 return self.layerHeight
846 def getFabmetheusXML(self):
847 'Return the fabmetheus XML.'
850 def getGeometryOutput(self):
851 'Get geometry output dictionary.'
852 return getGeometryOutputByFacesVertexes(self.faces, self.vertexes)
854 def getInterpretationSuffix(self):
855 'Return the suffix for a triangle mesh.'
858 def getLoops(self, importRadius, z):
859 'Get loops sliced through shape.'
860 self.importRadius = importRadius
861 return self.getLoopsFromMesh(z)
863 def getLoopsFromMesh( self, z ):
864 'Get loops from a carve of a mesh.'
866 self.setEdgesForAllFaces()
867 if self.isCorrectMesh:
868 originalLoops = getLoopsFromCorrectMesh( self.edges, self.faces, self.getTransformedVertexes(), z )
869 if len( originalLoops ) < 1:
870 originalLoops = getLoopsFromUnprovenMesh( self.edges, self.faces, self.importRadius, self.getTransformedVertexes(), z )
871 loops = euclidean.getSimplifiedLoops(originalLoops, self.importRadius)
872 sortLoopsInOrderOfArea(True, loops)
873 return getOrientedLoops(loops)
875 def getMinimumZ(self):
877 self.cornerMaximum = Vector3(-987654321.0, -987654321.0, -987654321.0)
878 self.cornerMinimum = Vector3(987654321.0, 987654321.0, 987654321.0)
879 transformedVertexes = self.getTransformedVertexes()
880 if len(transformedVertexes) < 1:
882 for point in transformedVertexes:
883 self.cornerMaximum.maximize(point)
884 self.cornerMinimum.minimize(point)
885 return self.cornerMinimum.z
887 def getTransformedVertexes(self):
888 'Get all transformed vertexes.'
889 if self.elementNode == None:
891 chainTetragrid = self.getMatrixChainTetragrid()
892 if self.oldChainTetragrid != chainTetragrid:
893 self.oldChainTetragrid = matrix.getTetragridCopy(chainTetragrid)
894 self.transformedVertexes = None
895 if self.transformedVertexes == None:
896 if len(self.edges) > 0:
897 self.edges[0].zMinimum = None
898 self.transformedVertexes = matrix.getTransformedVector3s(chainTetragrid, self.vertexes)
899 return self.transformedVertexes
901 def getTriangleMeshes(self):
902 'Get all triangleMeshes.'
905 def getVertexes(self):
907 self.transformedVertexes = None
910 def setCarveImportRadius( self, importRadius ):
911 'Set the import radius.'
912 self.importRadius = importRadius
914 def setCarveIsCorrectMesh( self, isCorrectMesh ):
915 'Set the is correct mesh flag.'
916 self.isCorrectMesh = isCorrectMesh
918 def setCarveLayerHeight( self, layerHeight ):
919 'Set the layer height.'
920 self.layerHeight = layerHeight
922 def setEdgesForAllFaces(self):
923 'Set the face edges of all the faces.'
925 for face in self.faces:
926 face.setEdgeIndexesToVertexIndexes( self.edges, edgeTable )
929 class ZoneArrangement:
930 'A zone arrangement.'
931 def __init__(self, layerHeight, vertexes):
932 'Initialize the zone interval and the zZone table.'
933 self.zoneInterval = layerHeight / math.sqrt(len(vertexes)) / 1000.0
934 self.zZoneSet = set()
935 for point in vertexes:
936 zoneIndexFloat = point.z / self.zoneInterval
937 self.zZoneSet.add(math.floor(zoneIndexFloat))
938 self.zZoneSet.add(math.ceil(zoneIndexFloat ))
940 def getEmptyZ(self, z):
941 'Get the first z which is not in the zone table.'
942 zoneIndex = round(z / self.zoneInterval)
943 if zoneIndex not in self.zZoneSet:
947 zoneDown = zoneIndex - zoneAround
948 if zoneDown not in self.zZoneSet:
949 return zoneDown * self.zoneInterval
950 zoneUp = zoneIndex + zoneAround
951 if zoneUp not in self.zZoneSet:
952 return zoneUp * self.zoneInterval