Package fabmetheus_utilities :: Package geometry :: Package solids :: Module triangle_mesh
[hide private]
[frames] | no frames]

Source Code for Module fabmetheus_utilities.geometry.solids.triangle_mesh

  1  """ 
  2  Triangle Mesh holds the faces and edges of a triangular mesh. 
  3   
  4  """ 
  5   
  6  from fabmetheus_utilities.geometry.geometry_tools import face 
  7  from fabmetheus_utilities.geometry.geometry_tools import vertex 
  8  from fabmetheus_utilities.geometry.geometry_utilities import evaluate 
  9  from fabmetheus_utilities.geometry.geometry_utilities import matrix 
 10  from fabmetheus_utilities.geometry.solids import group 
 11  from fabmetheus_utilities import xml_simple_writer 
 12  from fabmetheus_utilities.vector3 import Vector3 
 13  from fabmetheus_utilities.vector3index import Vector3Index 
 14  from fabmetheus_utilities import euclidean 
 15  from fabmetheus_utilities import intercircle 
 16  import cmath 
 17  import math 
 18   
 19   
 20  __author__ = 'Enrique Perez (perez_enrique@yahoo.com)' 
 21  __credits__ = 'Art of Illusion <http://www.artofillusion.org/>' 
 22  __date__ = '$Date: 2008/02/05 $' 
 23  __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' 
 24   
 25   
26 -def addEdgePair( edgePairTable, edges, faceEdgeIndex, remainingEdgeIndex, remainingEdgeTable ):
27 'Add edge pair to the edge pair table.' 28 if faceEdgeIndex == remainingEdgeIndex: 29 return 30 if not faceEdgeIndex in remainingEdgeTable: 31 return 32 edgePair = EdgePair().getFromIndexesEdges( [ remainingEdgeIndex, faceEdgeIndex ], edges ) 33 edgePairTable[ str( edgePair ) ] = edgePair
34
35 -def addFacesByConcaveLoop(faces, indexedLoop):
36 'Add faces from a polygon which is concave.' 37 if len(indexedLoop) < 3: 38 return 39 remainingLoop = indexedLoop[:] 40 while len(remainingLoop) > 2: 41 remainingLoop = getRemainingLoopAddFace(faces, remainingLoop)
42
43 -def addFacesByConvex(faces, indexedLoop):
44 'Add faces from a convex polygon.' 45 if len(indexedLoop) < 3: 46 return 47 indexBegin = indexedLoop[0].index 48 for indexedPointIndex in xrange(1, len(indexedLoop) - 1): 49 indexCenter = indexedLoop[indexedPointIndex].index 50 indexEnd = indexedLoop[(indexedPointIndex + 1) % len(indexedLoop) ].index 51 if indexBegin != indexCenter and indexCenter != indexEnd and indexEnd != indexBegin: 52 faceFromConvex = face.Face() 53 faceFromConvex.index = len(faces) 54 faceFromConvex.vertexIndexes.append(indexBegin) 55 faceFromConvex.vertexIndexes.append(indexCenter) 56 faceFromConvex.vertexIndexes.append(indexEnd) 57 faces.append(faceFromConvex)
58
59 -def addFacesByConvexBottomTopLoop(faces, indexedLoopBottom, indexedLoopTop):
60 'Add faces from loops.' 61 for indexedLoopIndex in xrange(max(len(indexedLoopBottom), len(indexedLoopTop))): 62 indexedLoopIndexEnd = (indexedLoopIndex + 1) % len(indexedLoopBottom) 63 indexedConvex = [] 64 if len(indexedLoopBottom) > 1: 65 indexedConvex.append(indexedLoopBottom[indexedLoopIndex]) 66 indexedConvex.append(indexedLoopBottom[(indexedLoopIndex + 1) % len(indexedLoopBottom)]) 67 else: 68 indexedConvex.append(indexedLoopBottom[0]) 69 if len(indexedLoopTop) > 1: 70 indexedConvex.append(indexedLoopTop[(indexedLoopIndex + 1) % len(indexedLoopTop)]) 71 indexedConvex.append(indexedLoopTop[indexedLoopIndex]) 72 else: 73 indexedConvex.append(indexedLoopTop[0]) 74 addFacesByConvex(faces, indexedConvex)
75
76 -def addFacesByConvexLoops(faces, indexedLoops):
77 'Add faces from loops.' 78 if len(indexedLoops) < 2: 79 return 80 for indexedLoopsIndex in xrange(len(indexedLoops) - 2): 81 addFacesByConvexBottomTopLoop(faces, indexedLoops[indexedLoopsIndex], indexedLoops[indexedLoopsIndex + 1]) 82 indexedLoopBottom = indexedLoops[-2] 83 indexedLoopTop = indexedLoops[-1] 84 if len(indexedLoopTop) < 1: 85 indexedLoopTop = indexedLoops[0] 86 addFacesByConvexBottomTopLoop(faces, indexedLoopBottom, indexedLoopTop)
87
88 -def addFacesByConvexReversed(faces, indexedLoop):
89 'Add faces from a reversed convex polygon.' 90 addFacesByConvex(faces, indexedLoop[: : -1])
91
92 -def addFacesByGrid(faces, grid):
93 'Add faces from grid.' 94 cellTopLoops = getIndexedCellLoopsFromIndexedGrid(grid) 95 for cellTopLoop in cellTopLoops: 96 addFacesByConvex(faces, cellTopLoop)
97
98 -def addFacesByLoop(faces, indexedLoop):
99 'Add faces from a polygon which may be concave.' 100 if len(indexedLoop) < 3: 101 return 102 lastNormal = None 103 for pointIndex, point in enumerate(indexedLoop): 104 center = indexedLoop[(pointIndex + 1) % len(indexedLoop)] 105 end = indexedLoop[(pointIndex + 2) % len(indexedLoop)] 106 normal = euclidean.getNormalWeighted(point, center, end) 107 if abs(normal) > 0.0: 108 if lastNormal != None: 109 if lastNormal.dot(normal) < 0.0: 110 addFacesByConcaveLoop(faces, indexedLoop) 111 return 112 lastNormal = normal 113 # totalNormal = Vector3() 114 # for pointIndex, point in enumerate(indexedLoop): 115 # center = indexedLoop[(pointIndex + 1) % len(indexedLoop)] 116 # end = indexedLoop[(pointIndex + 2) % len(indexedLoop)] 117 # totalNormal += euclidean.getNormalWeighted(point, center, end) 118 # totalNormal.normalize() 119 addFacesByConvex(faces, indexedLoop)
120
121 -def addFacesByLoopReversed(faces, indexedLoop):
122 'Add faces from a reversed convex polygon.' 123 addFacesByLoop(faces, indexedLoop[: : -1])
124
125 -def addLoopToPointTable(loop, pointTable):
126 'Add the points in the loop to the point table.' 127 for point in loop: 128 pointTable[point] = None
129
130 -def addPillarByLoops(faces, indexedLoops):
131 'Add pillar by loops which may be concave.' 132 if len(indexedLoops) < 1: 133 return 134 if len(indexedLoops[-1]) < 1: 135 addFacesByConvexLoops(faces, indexedLoops) 136 return 137 addFacesByLoopReversed(faces, indexedLoops[0]) 138 addFacesByConvexLoops(faces, indexedLoops) 139 addFacesByLoop(faces, indexedLoops[-1])
140
141 -def addPillarFromConvexLoopsGrids(faces, indexedGrids, indexedLoops):
142 'Add pillar from convex loops and grids.' 143 cellBottomLoops = getIndexedCellLoopsFromIndexedGrid(indexedGrids[0]) 144 for cellBottomLoop in cellBottomLoops: 145 addFacesByConvexReversed(faces, cellBottomLoop) 146 addFacesByConvexLoops(faces, indexedLoops) 147 addFacesByGrid(faces, indexedGrids[-1])
148
149 -def addPillarFromConvexLoopsGridTop(faces, indexedGridTop, indexedLoops):
150 'Add pillar from convex loops and grid top.' 151 addFacesByLoopReversed(faces, indexedLoops[0]) 152 addFacesByConvexLoops(faces, indexedLoops) 153 addFacesByGrid(faces, indexedGridTop)
154
155 -def addPointsAtZ(edgePair, points, radius, vertexes, z):
156 'Add point complexes on the segment between the edge intersections with z.' 157 carveIntersectionFirst = getCarveIntersectionFromEdge(edgePair.edges[0], vertexes, z) 158 carveIntersectionSecond = getCarveIntersectionFromEdge(edgePair.edges[1], vertexes, z) 159 # threshold radius above 0.8 can create extra holes on Screw Holder, 0.7 should be safe for everything 160 intercircle.addPointsFromSegment(carveIntersectionFirst, carveIntersectionSecond, points, radius, 0.7)
161
162 -def addSymmetricXPath(outputs, path, x):
163 'Add x path output to outputs.' 164 vertexes = [] 165 loops = [getSymmetricXLoop(path, vertexes, -x), getSymmetricXLoop(path, vertexes, x)] 166 outputs.append(getPillarOutput(loops))
167
168 -def addSymmetricXPaths(outputs, paths, x):
169 'Add x paths outputs to outputs.' 170 for path in paths: 171 addSymmetricXPath(outputs, path, x)
172
173 -def addSymmetricYPath(outputs, path, y):
174 'Add y path output to outputs.' 175 vertexes = [] 176 loops = [getSymmetricYLoop(path, vertexes, -y), getSymmetricYLoop(path, vertexes, y)] 177 outputs.append(getPillarOutput(loops))
178
179 -def addSymmetricYPaths(outputs, paths, y):
180 'Add y paths outputs to outputs.' 181 for path in paths: 182 addSymmetricYPath(outputs, path, y)
183
184 -def addWithLeastLength(importRadius, loops, point):
185 'Insert a point into a loop, at the index at which the loop would be shortest.' 186 close = importRadius + importRadius 187 shortestAdditionalLength = close 188 shortestLoop = None 189 shortestPointIndex = None 190 for loop in loops: 191 if len(loop) > 3: 192 for pointIndex in xrange(len(loop)): 193 additionalLoopLength = getAdditionalLoopLength(loop, point, pointIndex) 194 if additionalLoopLength < shortestAdditionalLength: 195 if getIsPointCloseInline(close, loop, point, pointIndex): 196 shortestAdditionalLength = additionalLoopLength 197 shortestLoop = loop 198 shortestPointIndex = pointIndex 199 if shortestPointIndex != None: 200 shortestLoop.insert( shortestPointIndex, point )
201
202 -def convertXMLElement(geometryOutput, xmlElement):
203 'Convert the xml element to a TriangleMesh xml element.' 204 xmlElement.linkObject(TriangleMesh()) 205 matrix.getBranchMatrixSetXMLElement(xmlElement) 206 vertex.addGeometryList(geometryOutput['vertex'], xmlElement) 207 face.addGeometryList(geometryOutput['face'], xmlElement) 208 xmlElement.getXMLProcessor().processChildNodes(xmlElement)
209
210 -def getAddIndexedGrid( grid, vertexes, z ):
211 'Get and add an indexed grid.' 212 indexedGrid = [] 213 for row in grid: 214 indexedRow = [] 215 indexedGrid.append( indexedRow ) 216 for pointComplex in row: 217 vector3index = Vector3Index( len(vertexes), pointComplex.real, pointComplex.imag, z ) 218 indexedRow.append(vector3index) 219 vertexes.append(vector3index) 220 return indexedGrid
221
222 -def getAddIndexedLoop(loop, vertexes, z):
223 'Get and add an indexed loop.' 224 indexedLoop = [] 225 for index in xrange(len(loop)): 226 pointComplex = loop[index] 227 vector3index = Vector3Index(len(vertexes), pointComplex.real, pointComplex.imag, z) 228 indexedLoop.append(vector3index) 229 vertexes.append(vector3index) 230 return indexedLoop
231
232 -def getAddIndexedLoops( loop, vertexes, zList ):
233 'Get and add indexed loops.' 234 indexedLoops = [] 235 for z in zList: 236 indexedLoop = getAddIndexedLoop( loop, vertexes, z ) 237 indexedLoops.append(indexedLoop) 238 return indexedLoops
239
240 -def getAdditionalLoopLength(loop, point, pointIndex):
241 'Get the additional length added by inserting a point into a loop.' 242 afterPoint = loop[pointIndex] 243 beforePoint = loop[(pointIndex + len(loop) - 1) % len(loop)] 244 return abs(point - beforePoint) + abs(point - afterPoint) - abs(afterPoint - beforePoint)
245
246 -def getBridgeDirection( belowLoops, layerLoops, layerThickness ):
247 'Get span direction for the majority of the overhanging extrusion perimeter, if any.' 248 if len( belowLoops ) < 1: 249 return None 250 belowOutsetLoops = [] 251 overhangInset = 1.875 * layerThickness 252 slightlyGreaterThanOverhang = 1.1 * overhangInset 253 for loop in belowLoops: 254 centers = intercircle.getCentersFromLoopDirection( True, loop, slightlyGreaterThanOverhang ) 255 for center in centers: 256 outset = intercircle.getSimplifiedInsetFromClockwiseLoop( center, overhangInset ) 257 if intercircle.isLargeSameDirection( outset, center, overhangInset ): 258 belowOutsetLoops.append( outset ) 259 bridgeRotation = complex() 260 for loop in layerLoops: 261 for pointIndex in xrange(len(loop)): 262 previousIndex = ( pointIndex + len(loop) - 1 ) % len(loop) 263 bridgeRotation += getOverhangDirection( belowOutsetLoops, loop[ previousIndex ], loop[pointIndex] ) 264 if abs( bridgeRotation ) < 0.7853 * layerThickness: 265 return None 266 else: 267 bridgeRotation /= abs( bridgeRotation ) 268 return cmath.sqrt( bridgeRotation )
269
270 -def getBridgeLoops( layerThickness, loop ):
271 'Get the inset bridge loops from the loop.' 272 halfWidth = 1.5 * layerThickness 273 slightlyGreaterThanHalfWidth = 1.1 * halfWidth 274 extrudateLoops = [] 275 centers = intercircle.getCentersFromLoop( loop, slightlyGreaterThanHalfWidth ) 276 for center in centers: 277 extrudateLoop = intercircle.getSimplifiedInsetFromClockwiseLoop( center, halfWidth ) 278 if intercircle.isLargeSameDirection( extrudateLoop, center, halfWidth ): 279 if euclidean.isPathInsideLoop( loop, extrudateLoop ) == euclidean.isWiddershins(loop): 280 extrudateLoop.reverse() 281 extrudateLoops.append( extrudateLoop ) 282 return extrudateLoops
283
284 -def getCarveIntersectionFromEdge(edge, vertexes, z):
285 'Get the complex where the carve intersects the edge.' 286 firstVertex = vertexes[ edge.vertexIndexes[0] ] 287 firstVertexComplex = firstVertex.dropAxis() 288 secondVertex = vertexes[ edge.vertexIndexes[1] ] 289 secondVertexComplex = secondVertex.dropAxis() 290 zMinusFirst = z - firstVertex.z 291 up = secondVertex.z - firstVertex.z 292 return zMinusFirst * ( secondVertexComplex - firstVertexComplex ) / up + firstVertexComplex
293
294 -def getDescendingAreaLoops(allPoints, corners, importRadius):
295 'Get descending area loops which include most of the points.' 296 loops = intercircle.getCentersFromPoints(allPoints, importRadius) 297 descendingAreaLoops = [] 298 sortLoopsInOrderOfArea(True, loops) 299 pointDictionary = {} 300 for loop in loops: 301 if len(loop) > 2 and getOverlapRatio(loop, pointDictionary) < 0.2: 302 intercircle.directLoop(not euclidean.getIsInFilledRegion(descendingAreaLoops, loop[0]), loop) 303 descendingAreaLoops.append(loop) 304 addLoopToPointTable(loop, pointDictionary) 305 descendingAreaLoops = euclidean.getSimplifiedLoops(descendingAreaLoops, importRadius) 306 return getLoopsWithCorners(corners, importRadius, descendingAreaLoops, pointDictionary)
307
308 -def getDescendingAreaOrientedLoops(allPoints, corners, importRadius):
309 'Get descending area oriented loops which include most of the points.' 310 return getOrientedLoops(getDescendingAreaLoops(allPoints, corners, importRadius))
311
312 -def getDoubledRoundZ( overhangingSegment, segmentRoundZ ):
313 'Get doubled plane angle around z of the overhanging segment.' 314 endpoint = overhangingSegment[0] 315 roundZ = endpoint.point - endpoint.otherEndpoint.point 316 roundZ *= segmentRoundZ 317 if abs( roundZ ) == 0.0: 318 return complex() 319 if roundZ.real < 0.0: 320 roundZ *= - 1.0 321 roundZLength = abs( roundZ ) 322 return roundZ * roundZ / roundZLength
323
324 -def getGeometryOutputByFacesVertexes(faces, vertexes):
325 'Get geometry output dictionary by faces and vertexes.' 326 return {'trianglemesh' : {'vertex' : vertexes, 'face' : faces}}
327
328 -def getGeometryOutputCopy(object):
329 'Get the geometry output copy.' 330 objectClass = object.__class__ 331 if objectClass == dict: 332 objectCopy = {} 333 for key in object: 334 objectCopy[key] = getGeometryOutputCopy(object[key]) 335 return objectCopy 336 if objectClass == list: 337 objectCopy = [] 338 for value in object: 339 objectCopy.append(getGeometryOutputCopy(value)) 340 return objectCopy 341 if objectClass == face.Face or objectClass == Vector3 or objectClass == Vector3Index: 342 return object.copy() 343 return object
344
345 -def getIndexedCellLoopsFromIndexedGrid( grid ):
346 'Get indexed cell loops from an indexed grid.' 347 indexedCellLoops = [] 348 for rowIndex in xrange( len( grid ) - 1 ): 349 rowBottom = grid[ rowIndex ] 350 rowTop = grid[ rowIndex + 1 ] 351 for columnIndex in xrange( len( rowBottom ) - 1 ): 352 columnIndexEnd = columnIndex + 1 353 indexedConvex = [] 354 indexedConvex.append( rowBottom[ columnIndex ] ) 355 indexedConvex.append( rowBottom[ columnIndex + 1 ] ) 356 indexedConvex.append( rowTop[ columnIndex + 1 ] ) 357 indexedConvex.append( rowTop[ columnIndex ] ) 358 indexedCellLoops.append( indexedConvex ) 359 return indexedCellLoops
360
361 -def getIndexedLoopFromIndexedGrid( indexedGrid ):
362 'Get indexed loop from around the indexed grid.' 363 indexedLoop = indexedGrid[0][:] 364 for row in indexedGrid[1 : -1]: 365 indexedLoop.append( row[-1] ) 366 indexedLoop += indexedGrid[-1][: : -1] 367 for row in indexedGrid[ len( indexedGrid ) - 2 : 0 : - 1 ]: 368 indexedLoop.append( row[0] ) 369 return indexedLoop
370
371 -def getInsetPoint( loop, tinyRadius ):
372 'Get the inset vertex.' 373 pointIndex = getWideAnglePointIndex(loop) 374 point = loop[ pointIndex % len(loop) ] 375 afterPoint = loop[(pointIndex + 1) % len(loop)] 376 beforePoint = loop[ ( pointIndex - 1 ) % len(loop) ] 377 afterSegmentNormalized = euclidean.getNormalized( afterPoint - point ) 378 beforeSegmentNormalized = euclidean.getNormalized( beforePoint - point ) 379 afterClockwise = complex( afterSegmentNormalized.imag, - afterSegmentNormalized.real ) 380 beforeWiddershins = complex( - beforeSegmentNormalized.imag, beforeSegmentNormalized.real ) 381 midpoint = afterClockwise + beforeWiddershins 382 midpointNormalized = midpoint / abs( midpoint ) 383 return point + midpointNormalized * tinyRadius
384
385 -def getIsPointCloseInline(close, loop, point, pointIndex):
386 'Insert a point into a loop, at the index at which the loop would be shortest.' 387 afterCenterComplex = loop[pointIndex] 388 if abs(afterCenterComplex - point) > close: 389 return False 390 afterEndComplex = loop[(pointIndex + 1) % len(loop)] 391 if not isInline( point, afterCenterComplex, afterEndComplex ): 392 return False 393 beforeCenterComplex = loop[(pointIndex + len(loop) - 1) % len(loop)] 394 if abs(beforeCenterComplex - point) > close: 395 return False 396 beforeEndComplex = loop[(pointIndex + len(loop) - 2) % len(loop)] 397 return isInline(point, beforeCenterComplex, beforeEndComplex)
398
399 -def getIsPathEntirelyOutsideTriangle(begin, center, end, vector3Path):
400 'Determine if a path is entirely outside another loop.' 401 loop = [begin.dropAxis(), center.dropAxis(), end.dropAxis()] 402 for vector3 in vector3Path: 403 point = vector3.dropAxis() 404 if euclidean.isPointInsideLoop(loop, point): 405 return False 406 return True
407
408 -def getLoopsFromCorrectMesh( edges, faces, vertexes, z ):
409 'Get loops from a carve of a correct mesh.' 410 remainingEdgeTable = getRemainingEdgeTable(edges, vertexes, z) 411 remainingValues = remainingEdgeTable.values() 412 for edge in remainingValues: 413 if len( edge.faceIndexes ) < 2: 414 print('This should never happen, there is a hole in the triangle mesh, each edge should have two faces.') 415 print(edge) 416 print('Something will still be printed, but there is no guarantee that it will be the correct shape.' ) 417 print('Once the gcode is saved, you should check over the layer with a z of:') 418 print(z) 419 return [] 420 loops = [] 421 while isPathAdded( edges, faces, loops, remainingEdgeTable, vertexes, z ): 422 pass 423 if euclidean.isLoopListIntersecting(loops): 424 print('Warning, the triangle mesh slice intersects itself in getLoopsFromCorrectMesh in triangle_mesh.') 425 print('Something will still be printed, but there is no guarantee that it will be the correct shape.') 426 print('Once the gcode is saved, you should check over the layer with a z of:') 427 print(z) 428 return [] 429 return loops
430 # untouchables = [] 431 # for boundingLoop in boundingLoops: 432 # if not boundingLoop.isIntersectingList( untouchables ): 433 # untouchables.append( boundingLoop ) 434 # if len( untouchables ) < len( boundingLoops ): 435 # 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.') 436 # print('Once the gcode is saved, you should check over the layer with a z of:') 437 # print(z) 438 # remainingLoops = [] 439 # for untouchable in untouchables: 440 # remainingLoops.append( untouchable.loop ) 441 # return remainingLoops 442
443 -def getLoopsFromUnprovenMesh(edges, faces, importRadius, vertexes, z):
444 'Get loops from a carve of an unproven mesh.' 445 edgePairTable = {} 446 corners = [] 447 remainingEdgeTable = getRemainingEdgeTable(edges, vertexes, z) 448 remainingEdgeTableKeys = remainingEdgeTable.keys() 449 for remainingEdgeIndexKey in remainingEdgeTable: 450 edge = remainingEdgeTable[remainingEdgeIndexKey] 451 carveIntersection = getCarveIntersectionFromEdge(edge, vertexes, z) 452 corners.append(carveIntersection) 453 for edgeFaceIndex in edge.faceIndexes: 454 face = faces[edgeFaceIndex] 455 for edgeIndex in face.edgeIndexes: 456 addEdgePair(edgePairTable, edges, edgeIndex, remainingEdgeIndexKey, remainingEdgeTable) 457 allPoints = corners[:] 458 for edgePairValue in edgePairTable.values(): 459 addPointsAtZ(edgePairValue, allPoints, importRadius, vertexes, z) 460 pointTable = {} 461 return getDescendingAreaLoops(allPoints, corners, importRadius)
462
463 -def getLoopsWithCorners(corners, importRadius, loops, pointTable):
464 'Add corners to the loops.' 465 for corner in corners: 466 if corner not in pointTable: 467 addWithLeastLength(importRadius, loops, corner) 468 pointTable[corner] = None 469 return euclidean.getSimplifiedLoops(loops, importRadius)
470
471 -def getNextEdgeIndexAroundZ( edge, faces, remainingEdgeTable ):
472 'Get the next edge index in the mesh carve.' 473 for faceIndex in edge.faceIndexes: 474 face = faces[ faceIndex ] 475 for edgeIndex in face.edgeIndexes: 476 if edgeIndex in remainingEdgeTable: 477 return edgeIndex 478 return - 1
479
480 -def getOrientedLoops(loops):
481 'Orient the loops which must be in descending order.' 482 for loopIndex, loop in enumerate(loops): 483 leftPoint = euclidean.getLeftPoint(loop) 484 isInFilledRegion = euclidean.getIsInFilledRegion(loops[: loopIndex] + loops[loopIndex + 1 :], leftPoint) 485 if isInFilledRegion == euclidean.isWiddershins(loop): 486 loop.reverse() 487 return loops
488
489 -def getOverhangDirection( belowOutsetLoops, segmentBegin, segmentEnd ):
490 'Add to span direction from the endpoint segments which overhang the layer below.' 491 segment = segmentEnd - segmentBegin 492 normalizedSegment = euclidean.getNormalized( complex( segment.real, segment.imag ) ) 493 segmentYMirror = complex(normalizedSegment.real, -normalizedSegment.imag) 494 segmentBegin = segmentYMirror * segmentBegin 495 segmentEnd = segmentYMirror * segmentEnd 496 solidXIntersectionList = [] 497 y = segmentBegin.imag 498 solidXIntersectionList.append( euclidean.XIntersectionIndex( - 1.0, segmentBegin.real ) ) 499 solidXIntersectionList.append( euclidean.XIntersectionIndex( - 1.0, segmentEnd.real ) ) 500 for belowLoopIndex in xrange( len( belowOutsetLoops ) ): 501 belowLoop = belowOutsetLoops[ belowLoopIndex ] 502 rotatedOutset = euclidean.getPointsRoundZAxis( segmentYMirror, belowLoop ) 503 euclidean.addXIntersectionIndexesFromLoopY( rotatedOutset, belowLoopIndex, solidXIntersectionList, y ) 504 overhangingSegments = euclidean.getSegmentsFromXIntersectionIndexes( solidXIntersectionList, y ) 505 overhangDirection = complex() 506 for overhangingSegment in overhangingSegments: 507 overhangDirection += getDoubledRoundZ( overhangingSegment, normalizedSegment ) 508 return overhangDirection
509
510 -def getOverlapRatio( loop, pointTable ):
511 'Get the overlap ratio between the loop and the point table.' 512 numberOfOverlaps = 0 513 for point in loop: 514 if point in pointTable: 515 numberOfOverlaps += 1 516 return float( numberOfOverlaps ) / float(len(loop))
517
518 -def getPath( edges, pathIndexes, loop, z ):
519 'Get the path from the edge intersections.' 520 path = [] 521 for pathIndexIndex in xrange( len( pathIndexes ) ): 522 pathIndex = pathIndexes[ pathIndexIndex ] 523 edge = edges[ pathIndex ] 524 carveIntersection = getCarveIntersectionFromEdge( edge, loop, z ) 525 path.append( carveIntersection ) 526 return path
527
528 -def getPillarOutput(loops):
529 'Get pillar output.' 530 faces = [] 531 vertexes = getUniqueVertexes(loops) 532 addPillarByLoops(faces, loops) 533 return getGeometryOutputByFacesVertexes(faces, vertexes)
534
535 -def getPillarsOutput(loopLists):
536 'Get pillars output.' 537 pillarsOutput = [] 538 for loopList in loopLists: 539 pillarsOutput.append(getPillarOutput(loopList)) 540 return getUnifiedOutput(pillarsOutput)
541
542 -def getRemainingEdgeTable(edges, vertexes, z):
543 'Get the remaining edge hashtable.' 544 remainingEdgeTable = {} 545 if len(edges) > 0: 546 if edges[0].zMinimum == None: 547 for edge in edges: 548 setEdgeMaximumMinimum(edge, vertexes) 549 for edgeIndex in xrange(len(edges)): 550 edge = edges[edgeIndex] 551 if (edge.zMinimum < z) and (edge.zMaximum > z): 552 remainingEdgeTable[edgeIndex] = edge 553 return remainingEdgeTable
554
555 -def getRemainingLoopAddFace(faces, remainingLoop):
556 'Get the remaining loop and add face.' 557 for indexedVertexIndex, indexedVertex in enumerate(remainingLoop): 558 nextIndex = (indexedVertexIndex + 1) % len(remainingLoop) 559 previousIndex = (indexedVertexIndex + len(remainingLoop) - 1) % len(remainingLoop) 560 nextVertex = remainingLoop[nextIndex] 561 previousVertex = remainingLoop[previousIndex] 562 remainingPath = euclidean.getAroundLoop((indexedVertexIndex + 2) % len(remainingLoop), previousIndex, remainingLoop) 563 if len(remainingLoop) < 4 or getIsPathEntirelyOutsideTriangle(previousVertex, indexedVertex, nextVertex, remainingPath): 564 faceConvex = face.Face() 565 faceConvex.index = len(faces) 566 faceConvex.vertexIndexes.append(indexedVertex.index) 567 faceConvex.vertexIndexes.append(nextVertex.index) 568 faceConvex.vertexIndexes.append(previousVertex.index) 569 faces.append(faceConvex) 570 return euclidean.getAroundLoop(nextIndex, indexedVertexIndex, remainingLoop) 571 print('Warning, could not decompose polygon in getRemainingLoopAddFace in trianglemesh for:') 572 print(remainingLoop) 573 return []
574
575 -def getSharedFace( firstEdge, faces, secondEdge ):
576 'Get the face which is shared by two edges.' 577 for firstEdgeFaceIndex in firstEdge.faceIndexes: 578 for secondEdgeFaceIndex in secondEdge.faceIndexes: 579 if firstEdgeFaceIndex == secondEdgeFaceIndex: 580 return faces[ firstEdgeFaceIndex ] 581 return None
582
583 -def getSymmetricXLoop(path, vertexes, x):
584 'Get symmetrix x loop.' 585 loop = [] 586 for point in path: 587 vector3Index = Vector3Index(len(vertexes), x, point.real, point.imag) 588 loop.append(vector3Index) 589 vertexes.append(vector3Index) 590 return loop
591
592 -def getSymmetricYLoop(path, vertexes, y):
593 'Get symmetrix y loop.' 594 loop = [] 595 for point in path: 596 vector3Index = Vector3Index(len(vertexes), point.real, y, point.imag) 597 loop.append(vector3Index) 598 vertexes.append(vector3Index) 599 return loop
600
601 -def getUnifiedOutput(outputs):
602 'Get unified output.' 603 if len(outputs) < 1: 604 return {} 605 if len(outputs) == 1: 606 return outputs[0] 607 return {'union' : {'shapes' : outputs}}
608
609 -def getUniqueVertexes(loops):
610 'Get unique vertexes.' 611 vertexDictionary = {} 612 uniqueVertexes = [] 613 for loop in loops: 614 for vertexIndex, vertex in enumerate(loop): 615 vertexTuple = (vertex.x, vertex.y, vertex.z) 616 if vertexTuple in vertexDictionary: 617 loop[vertexIndex] = vertexDictionary[vertexTuple] 618 else: 619 if vertex.__class__ == Vector3Index: 620 loop[vertexIndex].index = len(vertexDictionary) 621 else: 622 loop[vertexIndex] = Vector3Index(len(vertexDictionary), vertex.x, vertex.y, vertex.z) 623 vertexDictionary[vertexTuple] = loop[vertexIndex] 624 uniqueVertexes.append(loop[vertexIndex]) 625 return uniqueVertexes
626
627 -def getWideAnglePointIndex(loop):
628 'Get a point index which has a wide enough angle, most point indexes have a wide enough angle, this is just to make sure.' 629 dotProductMinimum = 9999999.9 630 widestPointIndex = 0 631 for pointIndex in xrange(len(loop)): 632 point = loop[ pointIndex % len(loop) ] 633 afterPoint = loop[(pointIndex + 1) % len(loop)] 634 beforePoint = loop[ ( pointIndex - 1 ) % len(loop) ] 635 afterSegmentNormalized = euclidean.getNormalized( afterPoint - point ) 636 beforeSegmentNormalized = euclidean.getNormalized( beforePoint - point ) 637 dotProduct = euclidean.getDotProduct( afterSegmentNormalized, beforeSegmentNormalized ) 638 if dotProduct < .99: 639 return pointIndex 640 if dotProduct < dotProductMinimum: 641 dotProductMinimum = dotProduct 642 widestPointIndex = pointIndex 643 return widestPointIndex
644
645 -def getZAddExtruderPathsBySolidCarving(rotatedLoopLayer, solidCarving, z):
646 'Get next z and add extruder loops by solid carving.' 647 solidCarving.rotatedLoopLayers.append(rotatedLoopLayer) 648 nextZ = z + solidCarving.layerThickness 649 if not solidCarving.infillInDirectionOfBridge: 650 return nextZ 651 allExtrudateLoops = [] 652 for loop in rotatedLoopLayer.loops: 653 allExtrudateLoops += getBridgeLoops(solidCarving.layerThickness, loop) 654 rotatedLoopLayer.rotation = getBridgeDirection(solidCarving.belowLoops, allExtrudateLoops, solidCarving.layerThickness) 655 solidCarving.belowLoops = allExtrudateLoops 656 return nextZ
657
658 -def isInline( beginComplex, centerComplex, endComplex ):
659 'Determine if the three complex points form a line.' 660 centerBeginComplex = beginComplex - centerComplex 661 centerEndComplex = endComplex - centerComplex 662 centerBeginLength = abs( centerBeginComplex ) 663 centerEndLength = abs( centerEndComplex ) 664 if centerBeginLength <= 0.0 or centerEndLength <= 0.0: 665 return False 666 centerBeginComplex /= centerBeginLength 667 centerEndComplex /= centerEndLength 668 return euclidean.getDotProduct( centerBeginComplex, centerEndComplex ) < -0.999
669
670 -def isPathAdded( edges, faces, loops, remainingEdgeTable, vertexes, z ):
671 'Get the path indexes around a triangle mesh carve and add the path to the flat loops.' 672 if len( remainingEdgeTable ) < 1: 673 return False 674 pathIndexes = [] 675 remainingEdgeIndexKey = remainingEdgeTable.keys()[0] 676 pathIndexes.append( remainingEdgeIndexKey ) 677 del remainingEdgeTable[remainingEdgeIndexKey] 678 nextEdgeIndexAroundZ = getNextEdgeIndexAroundZ( edges[remainingEdgeIndexKey], faces, remainingEdgeTable ) 679 while nextEdgeIndexAroundZ != - 1: 680 pathIndexes.append( nextEdgeIndexAroundZ ) 681 del remainingEdgeTable[ nextEdgeIndexAroundZ ] 682 nextEdgeIndexAroundZ = getNextEdgeIndexAroundZ( edges[ nextEdgeIndexAroundZ ], faces, remainingEdgeTable ) 683 if len( pathIndexes ) < 3: 684 print('Dangling edges, will use intersecting circles to get import layer at height %s' % z) 685 del loops[:] 686 return False 687 loops.append( getPath( edges, pathIndexes, vertexes, z ) ) 688 return True
689
690 -def processXMLElement(xmlElement):
691 'Process the xml element.' 692 evaluate.processArchivable(TriangleMesh, xmlElement)
693
694 -def setEdgeMaximumMinimum(edge, vertexes):
695 'Set the edge maximum and minimum.' 696 beginIndex = edge.vertexIndexes[0] 697 endIndex = edge.vertexIndexes[1] 698 if beginIndex >= len(vertexes) or endIndex >= len(vertexes): 699 print('Warning, there are duplicate vertexes in setEdgeMaximumMinimum in triangle_mesh.') 700 print('Something might still be printed, but there is no guarantee that it will be the correct shape.' ) 701 edge.zMaximum = -987654321.0 702 edge.zMinimum = -987654321.0 703 return 704 beginZ = vertexes[beginIndex].z 705 endZ = vertexes[endIndex].z 706 edge.zMinimum = min(beginZ, endZ) 707 edge.zMaximum = max(beginZ, endZ)
708
709 -def sortLoopsInOrderOfArea(isDescending, loops):
710 'Sort the loops in the order of area according isDescending.' 711 loops.sort(key=euclidean.getAreaLoopAbsolute, reverse=isDescending)
712 713
714 -class EdgePair:
715 - def __init__(self):
716 'Pair of edges on a face.' 717 self.edgeIndexes = [] 718 self.edges = []
719
720 - def __repr__(self):
721 'Get the string representation of this EdgePair.' 722 return str( self.edgeIndexes )
723
724 - def getFromIndexesEdges( self, edgeIndexes, edges ):
725 'Initialize from edge indices.' 726 self.edgeIndexes = edgeIndexes[:] 727 self.edgeIndexes.sort() 728 for edgeIndex in self.edgeIndexes: 729 self.edges.append( edges[ edgeIndex ] ) 730 return self
731 732
733 -class TriangleMesh( group.Group ):
734 'A triangle mesh.'
735 - def __init__(self):
736 'Add empty lists.' 737 group.Group.__init__(self) 738 self.belowLoops = [] 739 self.infillInDirectionOfBridge = False 740 self.edges = [] 741 self.faces = [] 742 self.importCoarseness = 1.0 743 self.isCorrectMesh = True 744 self.oldChainTetragrid = None 745 self.rotatedLoopLayers = [] 746 self.transformedVertexes = None 747 self.vertexes = []
748
749 - def addXMLSection(self, depth, output):
750 'Add the xml section for this object.' 751 xml_simple_writer.addXMLFromVertexes( depth, output, self.vertexes ) 752 xml_simple_writer.addXMLFromObjects( depth, self.faces, output )
753
754 - def getCarveCornerMaximum(self):
755 'Get the corner maximum of the vertexes.' 756 return self.cornerMaximum
757
758 - def getCarveCornerMinimum(self):
759 'Get the corner minimum of the vertexes.' 760 return self.cornerMinimum
761
762 - def getCarveLayerThickness(self):
763 'Get the layer thickness.' 764 return self.layerThickness
765
767 'Get the rotated boundary layers.' 768 if self.getMinimumZ() == None: 769 return [] 770 halfHeight = 0.5 * self.layerThickness 771 self.zoneArrangement = ZoneArrangement(self.layerThickness, self.getTransformedVertexes()) 772 layerTop = self.cornerMaximum.z - halfHeight * 0.5 773 z = self.cornerMinimum.z + halfHeight 774 while z < layerTop: 775 z = self.getZAddExtruderPaths(z) 776 return self.rotatedLoopLayers
777
778 - def getFabmetheusXML(self):
779 'Return the fabmetheus XML.' 780 return None
781
782 - def getGeometryOutput(self):
783 'Get geometry output dictionary.' 784 return getGeometryOutputByFacesVertexes(self.faces, self.vertexes)
785
786 - def getInterpretationSuffix(self):
787 'Return the suffix for a triangle mesh.' 788 return 'xml'
789
790 - def getLoops(self, importRadius, z):
791 'Get loops sliced through shape.' 792 self.importRadius = importRadius 793 return self.getLoopsFromMesh(z)
794
795 - def getLoopsFromMesh( self, z ):
796 'Get loops from a carve of a mesh.' 797 originalLoops = [] 798 self.setEdgesForAllFaces() 799 if self.isCorrectMesh: 800 originalLoops = getLoopsFromCorrectMesh( self.edges, self.faces, self.getTransformedVertexes(), z ) 801 if len( originalLoops ) < 1: 802 originalLoops = getLoopsFromUnprovenMesh( self.edges, self.faces, self.importRadius, self.getTransformedVertexes(), z ) 803 loops = euclidean.getSimplifiedLoops(originalLoops, self.importRadius) 804 sortLoopsInOrderOfArea(True, loops) 805 return getOrientedLoops(loops)
806
807 - def getMinimumZ(self):
808 'Get the minimum z.' 809 self.cornerMaximum = Vector3(-987654321.0, -987654321.0, -987654321.0) 810 self.cornerMinimum = Vector3(987654321.0, 987654321.0, 987654321.0) 811 transformedVertexes = self.getTransformedVertexes() 812 if len(transformedVertexes) < 1: 813 return None 814 for point in transformedVertexes: 815 self.cornerMaximum.maximize(point) 816 self.cornerMinimum.minimize(point) 817 return self.cornerMinimum.z
818
819 - def getTransformedVertexes(self):
820 'Get all transformed vertexes.' 821 if self.xmlElement == None: 822 return self.vertexes 823 chainTetragrid = self.getMatrixChainTetragrid() 824 if self.oldChainTetragrid != chainTetragrid: 825 self.oldChainTetragrid = chainTetragrid 826 self.transformedVertexes = None 827 if self.transformedVertexes == None: 828 if len(self.edges) > 0: 829 self.edges[0].zMinimum = None 830 self.transformedVertexes = matrix.getTransformedVector3s(chainTetragrid, self.vertexes) 831 return self.transformedVertexes
832
833 - def getTriangleMeshes(self):
834 'Get all triangleMeshes.' 835 return [self]
836
837 - def getVertexes(self):
838 'Get all vertexes.' 839 self.transformedVertexes = None 840 return self.vertexes
841
842 - def getZAddExtruderPaths(self, z):
843 'Get next z and add extruder loops.' 844 #settings.printProgress(len(self.rotatedLoopLayers), 'slice') 845 846 rotatedLoopLayer = euclidean.RotatedLoopLayer(z) 847 rotatedLoopLayer.loops = self.getLoopsFromMesh(self.zoneArrangement.getEmptyZ(z)) 848 return getZAddExtruderPathsBySolidCarving(rotatedLoopLayer, self, z)
849
850 - def liftByMinimumZ(self, minimumZ):
851 'Lift the triangle mesh to the altitude.' 852 altitude = evaluate.getEvaluatedFloat(None, 'altitude', self.xmlElement) 853 if altitude == None: 854 return 855 lift = altitude - minimumZ 856 for vertex in self.vertexes: 857 vertex.z += lift
858
859 - def setCarveInfillInDirectionOfBridge( self, infillInDirectionOfBridge ):
860 'Set the infill in direction of bridge.' 861 self.infillInDirectionOfBridge = infillInDirectionOfBridge
862
863 - def setCarveLayerThickness( self, layerThickness ):
864 'Set the layer thickness.' 865 self.layerThickness = layerThickness
866
867 - def setCarveImportRadius( self, importRadius ):
868 'Set the import radius.' 869 self.importRadius = importRadius
870
871 - def setCarveIsCorrectMesh( self, isCorrectMesh ):
872 'Set the is correct mesh flag.' 873 self.isCorrectMesh = isCorrectMesh
874
875 - def setEdgesForAllFaces(self):
876 'Set the face edges of all the faces.' 877 edgeTable = {} 878 for face in self.faces: 879 face.setEdgeIndexesToVertexIndexes( self.edges, edgeTable )
880 881
882 -class ZoneArrangement:
883 'A zone arrangement.'
884 - def __init__(self, layerThickness, vertexes):
885 'Initialize the zone interval and the zZone table.' 886 self.zoneInterval = layerThickness / math.sqrt(len(vertexes)) / 1000.0 887 self.zZoneSet = set() 888 for point in vertexes: 889 zoneIndexFloat = point.z / self.zoneInterval 890 self.zZoneSet.add(math.floor(zoneIndexFloat)) 891 self.zZoneSet.add(math.ceil(zoneIndexFloat ))
892
893 - def getEmptyZ(self, z):
894 'Get the first z which is not in the zone table.' 895 zoneIndex = round(z / self.zoneInterval) 896 if zoneIndex not in self.zZoneSet: 897 return z 898 zoneAround = 1 899 while 1: 900 zoneDown = zoneIndex - zoneAround 901 if zoneDown not in self.zZoneSet: 902 return zoneDown * self.zoneInterval 903 zoneUp = zoneIndex + zoneAround 904 if zoneUp not in self.zZoneSet: 905 return zoneUp * self.zoneInterval 906 zoneAround += 1
907