chiark / gitweb /
Add back the ultimaker platform, and made the platform mesh simpler.
[cura.git] / Cura / slice / cura_sf / fabmetheus_utilities / geometry / geometry_utilities / boolean_geometry.py
1 """
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.
4
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.
6
7 The getCarving function takes the file name of an xml file and returns the carving.
8
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.
10
11 """
12
13
14 from __future__ import absolute_import
15
16 from fabmetheus_utilities.geometry.geometry_utilities.evaluate_elements import setting
17 from fabmetheus_utilities.geometry.geometry_utilities import boolean_solid
18 from fabmetheus_utilities.geometry.geometry_utilities import evaluate
19 from fabmetheus_utilities.geometry.solids import triangle_mesh
20 from fabmetheus_utilities.vector3 import Vector3
21 from fabmetheus_utilities import euclidean
22 from fabmetheus_utilities import xml_simple_writer
23 import math
24
25
26 __author__ = 'Enrique Perez (perez_enrique@yahoo.com)'
27 __credits__ = 'Nophead <http://hydraraptor.blogspot.com/>\nArt of Illusion <http://www.artofillusion.org/>'
28 __date__ = '$Date: 2008/21/04 $'
29 __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
30
31
32 def getEmptyZLoops(archivableObjects, importRadius, shouldPrintWarning, z, zoneArrangement):
33         'Get loops at empty z level.'
34         emptyZ = zoneArrangement.getEmptyZ(z)
35         visibleObjects = evaluate.getVisibleObjects(archivableObjects)
36         visibleObjectLoopsList = boolean_solid.getVisibleObjectLoopsList(importRadius, visibleObjects, emptyZ)
37         loops = euclidean.getConcatenatedList(visibleObjectLoopsList)
38         if euclidean.isLoopListIntersecting(loops):
39                 loops = boolean_solid.getLoopsUnion(importRadius, visibleObjectLoopsList)
40                 if shouldPrintWarning:
41                         print('Warning, the triangle mesh slice intersects itself in getExtruderPaths in boolean_geometry.')
42                         print('Something will still be printed, but there is no guarantee that it will be the correct shape.')
43                         print('Once the gcode is saved, you should check over the layer with a z of:')
44                         print(z)
45         return loops
46
47 def getLoopLayers(archivableObjects, importRadius, layerHeight, maximumZ, shouldPrintWarning, z, zoneArrangement):
48         'Get loop layers.'
49         loopLayers = []
50         while z <= maximumZ:
51                 triangle_mesh.getLoopLayerAppend(loopLayers, z).loops = getEmptyZLoops(archivableObjects, importRadius, True, z, zoneArrangement)
52                 z += layerHeight
53         return loopLayers
54
55 def getMinimumZ(geometryObject):
56         'Get the minimum of the minimum z of the archivableObjects and the object.'
57         booleanGeometry = BooleanGeometry()
58         booleanGeometry.archivableObjects = geometryObject.archivableObjects
59         booleanGeometry.importRadius = setting.getImportRadius(geometryObject.elementNode)
60         booleanGeometry.layerHeight = setting.getLayerHeight(geometryObject.elementNode)
61         archivableMinimumZ = booleanGeometry.getMinimumZ()
62         geometryMinimumZ = geometryObject.getMinimumZ()
63         if archivableMinimumZ == None:
64                 return geometryMinimumZ
65         if geometryMinimumZ == None:
66                 return archivableMinimumZ
67         return min(archivableMinimumZ, geometryMinimumZ)
68
69
70 class BooleanGeometry(object):
71         'A boolean geometry scene.'
72         def __init__(self):
73                 'Add empty lists.'
74                 self.archivableObjects = []
75                 self.belowLoops = []
76                 self.importRadius = 0.6
77                 self.layerHeight = 0.4
78                 self.loopLayers = []
79
80         def __repr__(self):
81                 'Get the string representation of this carving.'
82                 elementNode = None
83                 if len(self.archivableObjects) > 0:
84                         elementNode = self.archivableObjects[0].elementNode
85                 output = xml_simple_writer.getBeginGeometryXMLOutput(elementNode)
86                 self.addXML( 1, output )
87                 return xml_simple_writer.getEndGeometryXMLString(output)
88
89         def addXML(self, depth, output):
90                 'Add xml for this object.'
91                 xml_simple_writer.addXMLFromObjects( depth, self.archivableObjects, output )
92
93         def getCarveBoundaryLayers(self):
94                 'Get the boundary layers.'
95                 if self.getMinimumZ() == None:
96                         return []
97                 z = self.minimumZ + 0.5 * self.layerHeight
98                 self.loopLayers = getLoopLayers(self.archivableObjects, self.importRadius, self.layerHeight, self.maximumZ, True, z, self.zoneArrangement)
99                 self.cornerMaximum = Vector3(-912345678.0, -912345678.0, -912345678.0)
100                 self.cornerMinimum = Vector3(912345678.0, 912345678.0, 912345678.0)
101                 for loopLayer in self.loopLayers:
102                         for loop in loopLayer.loops:
103                                 for point in loop:
104                                         pointVector3 = Vector3(point.real, point.imag, loopLayer.z)
105                                         self.cornerMaximum.maximize(pointVector3)
106                                         self.cornerMinimum.minimize(pointVector3)
107                 self.cornerMaximum.z += self.halfHeight
108                 self.cornerMinimum.z -= self.halfHeight
109                 for loopLayerIndex in xrange(len(self.loopLayers) -1, -1, -1):
110                         loopLayer = self.loopLayers[loopLayerIndex]
111                         if len(loopLayer.loops) > 0:
112                                 return self.loopLayers[: loopLayerIndex + 1]
113                 return []
114
115         def getCarveCornerMaximum(self):
116                 'Get the corner maximum of the vertexes.'
117                 return self.cornerMaximum
118
119         def getCarveCornerMinimum(self):
120                 'Get the corner minimum of the vertexes.'
121                 return self.cornerMinimum
122
123         def getCarveLayerHeight(self):
124                 'Get the layer height.'
125                 return self.layerHeight
126
127         def getFabmetheusXML(self):
128                 'Return the fabmetheus XML.'
129                 if len(self.archivableObjects) > 0:
130                         return self.archivableObjects[0].elementNode.getOwnerDocument().getOriginalRoot()
131                 return None
132
133         def getInterpretationSuffix(self):
134                 'Return the suffix for a boolean carving.'
135                 return 'xml'
136
137         def getMatrix4X4(self):
138                 'Get the matrix4X4.'
139                 return None
140
141         def getMatrixChainTetragrid(self):
142                 'Get the matrix chain tetragrid.'
143                 return None
144
145         def getMinimumZ(self):
146                 'Get the minimum z.'
147                 vertexes = []
148                 for visibleObject in evaluate.getVisibleObjects(self.archivableObjects):
149                         vertexes += visibleObject.getTransformedVertexes()
150                 if len(vertexes) < 1:
151                         return None
152                 self.maximumZ = -912345678.0
153                 self.minimumZ = 912345678.0
154                 for vertex in vertexes:
155                         self.maximumZ = max(self.maximumZ, vertex.z)
156                         self.minimumZ = min(self.minimumZ, vertex.z)
157                 self.zoneArrangement = triangle_mesh.ZoneArrangement(self.layerHeight, vertexes)
158                 self.halfHeight = 0.5 * self.layerHeight
159                 self.setActualMinimumZ()
160                 return self.minimumZ
161
162         def getNumberOfEmptyZLoops(self, z):
163                 'Get number of empty z loops.'
164                 return len(getEmptyZLoops(self.archivableObjects, self.importRadius, False, z, self.zoneArrangement))
165
166         def setActualMinimumZ(self):
167                 'Get the actual minimum z at the lowest rotated boundary layer.'
168                 halfHeightOverMyriad = 0.0001 * self.halfHeight
169                 while self.minimumZ < self.maximumZ:
170                         if self.getNumberOfEmptyZLoops(self.minimumZ + halfHeightOverMyriad) > 0:
171                                 if self.getNumberOfEmptyZLoops(self.minimumZ - halfHeightOverMyriad) < 1:
172                                         return
173                                 increment = -self.halfHeight
174                                 while abs(increment) > halfHeightOverMyriad:
175                                         self.minimumZ += increment
176                                         increment = 0.5 * abs(increment)
177                                         if self.getNumberOfEmptyZLoops(self.minimumZ) > 0:
178                                                 increment = -increment
179                                 self.minimumZ = round(self.minimumZ, -int(round(math.log10(halfHeightOverMyriad) + 1.5)))
180                                 return
181                         self.minimumZ += self.layerHeight
182
183         def setCarveImportRadius( self, importRadius ):
184                 'Set the import radius.'
185                 self.importRadius = importRadius
186
187         def setCarveIsCorrectMesh( self, isCorrectMesh ):
188                 'Set the is correct mesh flag.'
189                 self.isCorrectMesh = isCorrectMesh
190
191         def setCarveLayerHeight( self, layerHeight ):
192                 'Set the layer height.'
193                 self.layerHeight = layerHeight