2 This page is in the table of contents.
3 The xml.py script is an import translator plugin to get a carving from an Art of Illusion xml file.
5 An import plugin is a script in the interpret_plugins folder which has the function getCarving. It is meant to be run from the interpret tool. To ensure that the plugin works on platforms which do not handle file capitalization properly, give the plugin a lower case name.
7 The getCarving function takes the file name of an xml file and returns the carving.
9 An xml file can be exported from Art of Illusion by going to the "File" menu, then going into the "Export" menu item, then picking the XML choice. This will bring up the XML file chooser window, choose a place to save the file then click "OK". Leave the "compressFile" checkbox unchecked. All the objects from the scene will be exported, this plugin will ignore the light and camera. If you want to fabricate more than one object at a time, you can have multiple objects in the Art of Illusion scene and they will all be carved, then fabricated together.
14 from __future__ import absolute_import
15 #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.
18 from fabmetheus_utilities.geometry.geometry_utilities.evaluate_elements import setting
19 from fabmetheus_utilities.geometry.geometry_utilities import boolean_solid
20 from fabmetheus_utilities.geometry.geometry_utilities import evaluate
21 from fabmetheus_utilities.geometry.solids import triangle_mesh
22 from fabmetheus_utilities.vector3 import Vector3
23 from fabmetheus_utilities import euclidean
24 from fabmetheus_utilities import settings
25 from fabmetheus_utilities import xml_simple_writer
29 __author__ = 'Enrique Perez (perez_enrique@yahoo.com)'
30 __credits__ = 'Nophead <http://hydraraptor.blogspot.com/>\nArt of Illusion <http://www.artofillusion.org/>'
31 __date__ = '$Date: 2008/21/04 $'
32 __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
35 def getEmptyZLoops(archivableObjects, importRadius, shouldPrintWarning, z, zoneArrangement):
36 'Get loops at empty z level.'
37 emptyZ = zoneArrangement.getEmptyZ(z)
38 visibleObjects = evaluate.getVisibleObjects(archivableObjects)
39 visibleObjectLoopsList = boolean_solid.getVisibleObjectLoopsList(importRadius, visibleObjects, emptyZ)
40 loops = euclidean.getConcatenatedList(visibleObjectLoopsList)
41 if euclidean.isLoopListIntersecting(loops):
42 loops = boolean_solid.getLoopsUnion(importRadius, visibleObjectLoopsList)
43 if shouldPrintWarning:
44 print('Warning, the triangle mesh slice intersects itself in getExtruderPaths in boolean_geometry.')
45 print('Something will still be printed, but there is no guarantee that it will be the correct shape.')
46 print('Once the gcode is saved, you should check over the layer with a z of:')
50 def getLoopLayers(archivableObjects, importRadius, layerHeight, maximumZ, shouldPrintWarning, z, zoneArrangement):
54 triangle_mesh.getLoopLayerAppend(loopLayers, z).loops = getEmptyZLoops(archivableObjects, importRadius, True, z, zoneArrangement)
58 def getMinimumZ(geometryObject):
59 'Get the minimum of the minimum z of the archivableObjects and the object.'
60 booleanGeometry = BooleanGeometry()
61 booleanGeometry.archivableObjects = geometryObject.archivableObjects
62 booleanGeometry.importRadius = setting.getImportRadius(geometryObject.elementNode)
63 booleanGeometry.layerHeight = setting.getLayerHeight(geometryObject.elementNode)
64 archivableMinimumZ = booleanGeometry.getMinimumZ()
65 geometryMinimumZ = geometryObject.getMinimumZ()
66 if archivableMinimumZ == None:
67 return geometryMinimumZ
68 if geometryMinimumZ == None:
69 return archivableMinimumZ
70 return min(archivableMinimumZ, geometryMinimumZ)
73 class BooleanGeometry:
74 'A boolean geometry scene.'
77 self.archivableObjects = []
79 self.importRadius = 0.6
80 self.layerHeight = 0.4
84 'Get the string representation of this carving.'
86 if len(self.archivableObjects) > 0:
87 elementNode = self.archivableObjects[0].elementNode
88 output = xml_simple_writer.getBeginGeometryXMLOutput(elementNode)
89 self.addXML( 1, output )
90 return xml_simple_writer.getEndGeometryXMLString(output)
92 def addXML(self, depth, output):
93 'Add xml for this object.'
94 xml_simple_writer.addXMLFromObjects( depth, self.archivableObjects, output )
96 def getCarveBoundaryLayers(self):
97 'Get the boundary layers.'
98 if self.getMinimumZ() == None:
100 z = self.minimumZ + 0.5 * self.layerHeight
101 self.loopLayers = getLoopLayers(self.archivableObjects, self.importRadius, self.layerHeight, self.maximumZ, True, z, self.zoneArrangement)
102 self.cornerMaximum = Vector3(-912345678.0, -912345678.0, -912345678.0)
103 self.cornerMinimum = Vector3(912345678.0, 912345678.0, 912345678.0)
104 for loopLayer in self.loopLayers:
105 for loop in loopLayer.loops:
107 pointVector3 = Vector3(point.real, point.imag, loopLayer.z)
108 self.cornerMaximum.maximize(pointVector3)
109 self.cornerMinimum.minimize(pointVector3)
110 self.cornerMaximum.z += self.halfHeight
111 self.cornerMinimum.z -= self.halfHeight
112 for loopLayerIndex in xrange(len(self.loopLayers) -1, -1, -1):
113 loopLayer = self.loopLayers[loopLayerIndex]
114 if len(loopLayer.loops) > 0:
115 return self.loopLayers[: loopLayerIndex + 1]
118 def getCarveCornerMaximum(self):
119 'Get the corner maximum of the vertexes.'
120 return self.cornerMaximum
122 def getCarveCornerMinimum(self):
123 'Get the corner minimum of the vertexes.'
124 return self.cornerMinimum
126 def getCarveLayerHeight(self):
127 'Get the layer height.'
128 return self.layerHeight
130 def getFabmetheusXML(self):
131 'Return the fabmetheus XML.'
132 if len(self.archivableObjects) > 0:
133 return self.archivableObjects[0].elementNode.getOwnerDocument().getOriginalRoot()
136 def getInterpretationSuffix(self):
137 'Return the suffix for a boolean carving.'
140 def getMatrix4X4(self):
144 def getMatrixChainTetragrid(self):
145 'Get the matrix chain tetragrid.'
148 def getMinimumZ(self):
151 for visibleObject in evaluate.getVisibleObjects(self.archivableObjects):
152 vertexes += visibleObject.getTransformedVertexes()
153 if len(vertexes) < 1:
155 self.maximumZ = -912345678.0
156 self.minimumZ = 912345678.0
157 for vertex in vertexes:
158 self.maximumZ = max(self.maximumZ, vertex.z)
159 self.minimumZ = min(self.minimumZ, vertex.z)
160 self.zoneArrangement = triangle_mesh.ZoneArrangement(self.layerHeight, vertexes)
161 self.halfHeight = 0.5 * self.layerHeight
162 self.setActualMinimumZ()
165 def getNumberOfEmptyZLoops(self, z):
166 'Get number of empty z loops.'
167 return len(getEmptyZLoops(self.archivableObjects, self.importRadius, False, z, self.zoneArrangement))
169 def setActualMinimumZ(self):
170 'Get the actual minimum z at the lowest rotated boundary layer.'
171 halfHeightOverMyriad = 0.0001 * self.halfHeight
172 while self.minimumZ < self.maximumZ:
173 if self.getNumberOfEmptyZLoops(self.minimumZ + halfHeightOverMyriad) > 0:
174 if self.getNumberOfEmptyZLoops(self.minimumZ - halfHeightOverMyriad) < 1:
176 increment = -self.halfHeight
177 while abs(increment) > halfHeightOverMyriad:
178 self.minimumZ += increment
179 increment = 0.5 * abs(increment)
180 if self.getNumberOfEmptyZLoops(self.minimumZ) > 0:
181 increment = -increment
182 self.minimumZ = round(self.minimumZ, -int(round(math.log10(halfHeightOverMyriad) + 1.5)))
184 self.minimumZ += self.layerHeight
186 def setCarveImportRadius( self, importRadius ):
187 'Set the import radius.'
188 self.importRadius = importRadius
190 def setCarveIsCorrectMesh( self, isCorrectMesh ):
191 'Set the is correct mesh flag.'
192 self.isCorrectMesh = isCorrectMesh
194 def setCarveLayerHeight( self, layerHeight ):
195 'Set the layer height.'
196 self.layerHeight = layerHeight