chiark / gitweb /
Add uppercase STL and HEX to file dialog filters for linux/MacOS
[cura.git] / Cura / 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 #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.
16 import __init__
17
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
26 import math
27
28
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'
33
34
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:')
47                         print(z)
48         return loops
49
50 def getLoopLayers(archivableObjects, importRadius, layerHeight, maximumZ, shouldPrintWarning, z, zoneArrangement):
51         'Get loop layers.'
52         loopLayers = []
53         while z <= maximumZ:
54                 triangle_mesh.getLoopLayerAppend(loopLayers, z).loops = getEmptyZLoops(archivableObjects, importRadius, True, z, zoneArrangement)
55                 z += layerHeight
56         return loopLayers
57
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)
71
72
73 class BooleanGeometry:
74         'A boolean geometry scene.'
75         def __init__(self):
76                 'Add empty lists.'
77                 self.archivableObjects = []
78                 self.belowLoops = []
79                 self.importRadius = 0.6
80                 self.layerHeight = 0.4
81                 self.loopLayers = []
82
83         def __repr__(self):
84                 'Get the string representation of this carving.'
85                 elementNode = None
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)
91
92         def addXML(self, depth, output):
93                 'Add xml for this object.'
94                 xml_simple_writer.addXMLFromObjects( depth, self.archivableObjects, output )
95
96         def getCarveBoundaryLayers(self):
97                 'Get the boundary layers.'
98                 if self.getMinimumZ() == None:
99                         return []
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:
106                                 for point in loop:
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]
116                 return []
117
118         def getCarveCornerMaximum(self):
119                 'Get the corner maximum of the vertexes.'
120                 return self.cornerMaximum
121
122         def getCarveCornerMinimum(self):
123                 'Get the corner minimum of the vertexes.'
124                 return self.cornerMinimum
125
126         def getCarveLayerHeight(self):
127                 'Get the layer height.'
128                 return self.layerHeight
129
130         def getFabmetheusXML(self):
131                 'Return the fabmetheus XML.'
132                 if len(self.archivableObjects) > 0:
133                         return self.archivableObjects[0].elementNode.getOwnerDocument().getOriginalRoot()
134                 return None
135
136         def getInterpretationSuffix(self):
137                 'Return the suffix for a boolean carving.'
138                 return 'xml'
139
140         def getMatrix4X4(self):
141                 'Get the matrix4X4.'
142                 return None
143
144         def getMatrixChainTetragrid(self):
145                 'Get the matrix chain tetragrid.'
146                 return None
147
148         def getMinimumZ(self):
149                 'Get the minimum z.'
150                 vertexes = []
151                 for visibleObject in evaluate.getVisibleObjects(self.archivableObjects):
152                         vertexes += visibleObject.getTransformedVertexes()
153                 if len(vertexes) < 1:
154                         return None
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()
163                 return self.minimumZ
164
165         def getNumberOfEmptyZLoops(self, z):
166                 'Get number of empty z loops.'
167                 return len(getEmptyZLoops(self.archivableObjects, self.importRadius, False, z, self.zoneArrangement))
168
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:
175                                         return
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)))
183                                 return
184                         self.minimumZ += self.layerHeight
185
186         def setCarveImportRadius( self, importRadius ):
187                 'Set the import radius.'
188                 self.importRadius = importRadius
189
190         def setCarveIsCorrectMesh( self, isCorrectMesh ):
191                 'Set the is correct mesh flag.'
192                 self.isCorrectMesh = isCorrectMesh
193
194         def setCarveLayerHeight( self, layerHeight ):
195                 'Set the layer height.'
196                 self.layerHeight = layerHeight