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 evaluate
20 from fabmetheus_utilities.geometry.solids import group
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 gcodec
25 from fabmetheus_utilities import intercircle
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 addLineLoopsIntersections( loopLoopsIntersections, loops, pointBegin, pointEnd ):
36 'Add intersections of the line with the loops.'
37 normalizedSegment = pointEnd - pointBegin
38 normalizedSegmentLength = abs( normalizedSegment )
39 if normalizedSegmentLength <= 0.0:
41 lineLoopsIntersections = []
42 normalizedSegment /= normalizedSegmentLength
43 segmentYMirror = complex(normalizedSegment.real, -normalizedSegment.imag)
44 pointBeginRotated = segmentYMirror * pointBegin
45 pointEndRotated = segmentYMirror * pointEnd
46 addLoopsXSegmentIntersections( lineLoopsIntersections, loops, pointBeginRotated.real, pointEndRotated.real, segmentYMirror, pointBeginRotated.imag )
47 for lineLoopsIntersection in lineLoopsIntersections:
48 point = complex( lineLoopsIntersection, pointBeginRotated.imag ) * normalizedSegment
49 loopLoopsIntersections.append(point)
51 def addLineXSegmentIntersection( lineLoopsIntersections, segmentFirstX, segmentSecondX, vector3First, vector3Second, y ):
52 'Add intersections of the line with the x segment.'
53 xIntersection = euclidean.getXIntersectionIfExists( vector3First, vector3Second, y )
54 if xIntersection == None:
56 if xIntersection < min( segmentFirstX, segmentSecondX ):
58 if xIntersection <= max( segmentFirstX, segmentSecondX ):
59 lineLoopsIntersections.append( xIntersection )
61 def addLoopLoopsIntersections( loop, loopsLoopsIntersections, otherLoops ):
62 'Add intersections of the loop with the other loops.'
63 for pointIndex in xrange(len(loop)):
64 pointBegin = loop[pointIndex]
65 pointEnd = loop[(pointIndex + 1) % len(loop)]
66 addLineLoopsIntersections( loopsLoopsIntersections, otherLoops, pointBegin, pointEnd )
68 def addLoopsXSegmentIntersections( lineLoopsIntersections, loops, segmentFirstX, segmentSecondX, segmentYMirror, y ):
69 'Add intersections of the loops with the x segment.'
71 addLoopXSegmentIntersections( lineLoopsIntersections, loop, segmentFirstX, segmentSecondX, segmentYMirror, y )
73 def addLoopXSegmentIntersections( lineLoopsIntersections, loop, segmentFirstX, segmentSecondX, segmentYMirror, y ):
74 'Add intersections of the loop with the x segment.'
75 rotatedLoop = euclidean.getRotatedComplexes( segmentYMirror, loop )
76 for pointIndex in xrange( len( rotatedLoop ) ):
77 pointFirst = rotatedLoop[pointIndex]
78 pointSecond = rotatedLoop[ (pointIndex + 1) % len( rotatedLoop ) ]
79 addLineXSegmentIntersection( lineLoopsIntersections, segmentFirstX, segmentSecondX, pointFirst, pointSecond, y )
81 def getInBetweenLoopsFromLoops(loops, radius):
82 'Get the in between loops from loops.'
86 for pointIndex in xrange(len(loop)):
87 pointBegin = loop[pointIndex]
88 pointEnd = loop[(pointIndex + 1) % len(loop)]
89 intercircle.addPointsFromSegment(pointBegin, pointEnd, inBetweenLoop, radius)
90 inBetweenLoops.append(inBetweenLoop)
93 def getInsetPointsByInsetLoop( insetLoop, inside, loops, radius ):
94 'Get the inset points of the inset loop inside the loops.'
95 insetPointsByInsetLoop = []
96 for pointIndex in xrange( len( insetLoop ) ):
97 pointBegin = insetLoop[ ( pointIndex + len( insetLoop ) - 1 ) % len( insetLoop ) ]
98 pointCenter = insetLoop[pointIndex]
99 pointEnd = insetLoop[ (pointIndex + 1) % len( insetLoop ) ]
100 if getIsInsetPointInsideLoops( inside, loops, pointBegin, pointCenter, pointEnd, radius ):
101 insetPointsByInsetLoop.append( pointCenter )
102 return insetPointsByInsetLoop
104 def getInsetPointsByInsetLoops( insetLoops, inside, loops, radius ):
105 'Get the inset points of the inset loops inside the loops.'
106 insetPointsByInsetLoops = []
107 for insetLoop in insetLoops:
108 insetPointsByInsetLoops += getInsetPointsByInsetLoop( insetLoop, inside, loops, radius )
109 return insetPointsByInsetLoops
111 def getIsInsetPointInsideLoops( inside, loops, pointBegin, pointCenter, pointEnd, radius ):
112 'Determine if the inset point is inside the loops.'
113 centerMinusBegin = euclidean.getNormalized( pointCenter - pointBegin )
114 centerMinusBeginWiddershins = complex( - centerMinusBegin.imag, centerMinusBegin.real )
115 endMinusCenter = euclidean.getNormalized( pointEnd - pointCenter )
116 endMinusCenterWiddershins = complex( - endMinusCenter.imag, endMinusCenter.real )
117 widdershinsNormalized = euclidean.getNormalized( centerMinusBeginWiddershins + endMinusCenterWiddershins ) * radius
118 return euclidean.getIsInFilledRegion( loops, pointCenter + widdershinsNormalized ) == inside
120 def getLoopsDifference(importRadius, loopLists):
121 'Get difference loops.'
122 halfImportRadius = 0.5 * importRadius # so that there are no misses on shallow angles
123 radiusSide = 0.01 * importRadius
124 negativeLoops = getLoopsUnion(importRadius, loopLists[1 :])
125 intercircle.directLoops(False, negativeLoops)
126 positiveLoops = loopLists[0]
127 intercircle.directLoops(True, positiveLoops)
128 corners = getInsetPointsByInsetLoops(negativeLoops, True, positiveLoops, radiusSide)
129 corners += getInsetPointsByInsetLoops(positiveLoops, False, negativeLoops, radiusSide)
130 allPoints = corners[:]
131 allPoints += getInsetPointsByInsetLoops(getInBetweenLoopsFromLoops(negativeLoops, halfImportRadius), True, positiveLoops, radiusSide)
132 allPoints += getInsetPointsByInsetLoops(getInBetweenLoopsFromLoops(positiveLoops, halfImportRadius), False, negativeLoops, radiusSide)
133 return triangle_mesh.getDescendingAreaOrientedLoops(allPoints, corners, importRadius)
135 def getLoopsIntersection(importRadius, loopLists):
136 'Get intersection loops.'
137 intercircle.directLoopLists(True, loopLists)
138 if len(loopLists) < 1:
140 if len(loopLists) < 2:
142 intercircle.directLoopLists(True, loopLists)
143 loopsIntersection = loopLists[0]
144 for loopList in loopLists[1 :]:
145 loopsIntersection = getLoopsIntersectionByPair(importRadius, loopsIntersection, loopList)
146 return loopsIntersection
148 def getLoopsIntersectionByPair(importRadius, loopsFirst, loopsLast):
149 'Get intersection loops for a pair of loop lists.'
150 halfImportRadius = 0.5 * importRadius # so that there are no misses on shallow angles
151 radiusSide = 0.01 * importRadius
153 corners += getInsetPointsByInsetLoops(loopsFirst, True, loopsLast, radiusSide)
154 corners += getInsetPointsByInsetLoops(loopsLast, True, loopsFirst, radiusSide)
155 allPoints = corners[:]
156 allPoints += getInsetPointsByInsetLoops(getInBetweenLoopsFromLoops(loopsFirst, halfImportRadius), True, loopsLast, radiusSide)
157 allPoints += getInsetPointsByInsetLoops(getInBetweenLoopsFromLoops(loopsLast, halfImportRadius), True, loopsFirst, radiusSide)
158 return triangle_mesh.getDescendingAreaOrientedLoops(allPoints, corners, importRadius)
160 def getLoopsListsIntersections( loopsList ):
161 'Get intersections betweens the loops lists.'
162 loopsListsIntersections = []
163 for loopsIndex in xrange( len( loopsList ) ):
164 loops = loopsList[ loopsIndex ]
165 for otherLoops in loopsList[ : loopsIndex ]:
166 loopsListsIntersections += getLoopsLoopsIntersections( loops, otherLoops )
167 return loopsListsIntersections
169 def getLoopsLoopsIntersections( loops, otherLoops ):
170 'Get all the intersections of the loops with the other loops.'
171 loopsLoopsIntersections = []
173 addLoopLoopsIntersections( loop, loopsLoopsIntersections, otherLoops )
174 return loopsLoopsIntersections
176 def getLoopsUnion(importRadius, loopLists):
177 'Get joined loops sliced through shape.'
179 corners = getLoopsListsIntersections(loopLists)
180 radiusSideNegative = -0.01 * importRadius
181 intercircle.directLoopLists(True, loopLists)
182 for loopListIndex in xrange(len(loopLists)):
183 insetLoops = loopLists[ loopListIndex ]
184 inBetweenInsetLoops = getInBetweenLoopsFromLoops(insetLoops, importRadius)
185 otherLoops = euclidean.getConcatenatedList(loopLists[: loopListIndex] + loopLists[loopListIndex + 1 :])
186 corners += getInsetPointsByInsetLoops(insetLoops, False, otherLoops, radiusSideNegative)
187 allPoints += getInsetPointsByInsetLoops(inBetweenInsetLoops, False, otherLoops, radiusSideNegative)
188 allPoints += corners[:]
189 return triangle_mesh.getDescendingAreaOrientedLoops(allPoints, corners, importRadius)
191 def getVisibleObjectLoopsList( importRadius, visibleObjects, z ):
192 'Get visible object loops list.'
193 visibleObjectLoopsList = []
194 for visibleObject in visibleObjects:
195 visibleObjectLoops = visibleObject.getLoops(importRadius, z)
196 visibleObjectLoopsList.append( visibleObjectLoops )
197 return visibleObjectLoopsList
200 class BooleanSolid( group.Group ):
201 'A boolean solid object.'
202 def getDifference(self, importRadius, visibleObjectLoopsList):
203 'Get subtracted loops sliced through shape.'
204 return getLoopsDifference(importRadius, visibleObjectLoopsList)
206 def getIntersection(self, importRadius, visibleObjectLoopsList):
207 'Get intersected loops sliced through shape.'
208 return getLoopsIntersection(importRadius, visibleObjectLoopsList)
210 def getLoops(self, importRadius, z):
211 'Get loops sliced through shape.'
212 visibleObjects = evaluate.getVisibleObjects(self.archivableObjects)
213 if len( visibleObjects ) < 1:
215 visibleObjectLoopsList = getVisibleObjectLoopsList( importRadius, visibleObjects, z )
216 loops = self.getLoopsFromObjectLoopsList(importRadius, visibleObjectLoopsList)
217 return euclidean.getSimplifiedLoops( loops, importRadius )
219 def getLoopsFromObjectLoopsList(self, importRadius, visibleObjectLoopsList):
220 'Get loops from visible object loops list.'
221 return self.operationFunction(importRadius, visibleObjectLoopsList)
223 def getTransformedPaths(self):
224 'Get all transformed paths.'
225 importRadius = setting.getImportRadius(self.elementNode)
226 loopsFromObjectLoopsList = self.getLoopsFromObjectLoopsList(importRadius, self.getComplexTransformedPathLists())
227 return euclidean.getVector3Paths(loopsFromObjectLoopsList)
229 def getUnion(self, importRadius, visibleObjectLoopsList):
230 'Get joined loops sliced through shape.'
231 return getLoopsUnion(importRadius, visibleObjectLoopsList)
233 def getXMLLocalName(self):
234 'Get xml class name.'
235 return self.operationFunction.__name__.lower()[ len('get') : ]