chiark / gitweb /
Move SF into its own directory, to seperate SF and Cura. Rename newui to gui.
[cura.git] / Cura / cura_sf / fabmetheus_utilities / geometry / geometry_utilities / boolean_solid.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 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
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 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:
40                 return
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)
50
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:
55                 return
56         if xIntersection < min( segmentFirstX, segmentSecondX ):
57                 return
58         if xIntersection <= max( segmentFirstX, segmentSecondX ):
59                 lineLoopsIntersections.append( xIntersection )
60
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 )
67
68 def addLoopsXSegmentIntersections( lineLoopsIntersections, loops, segmentFirstX, segmentSecondX, segmentYMirror, y ):
69         'Add intersections of the loops with the x segment.'
70         for loop in loops:
71                 addLoopXSegmentIntersections( lineLoopsIntersections, loop, segmentFirstX, segmentSecondX, segmentYMirror, y )
72
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 )
80
81 def getInBetweenLoopsFromLoops(loops, radius):
82         'Get the in between loops from loops.'
83         inBetweenLoops = []
84         for loop in loops:
85                 inBetweenLoop = []
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)
91         return inBetweenLoops
92
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
103
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
110
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
119
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)
134
135 def getLoopsIntersection(importRadius, loopLists):
136         'Get intersection loops.'
137         intercircle.directLoopLists(True, loopLists)
138         if len(loopLists) < 1:
139                 return []
140         if len(loopLists) < 2:
141                 return loopLists[0]
142         intercircle.directLoopLists(True, loopLists)
143         loopsIntersection = loopLists[0]
144         for loopList in loopLists[1 :]:
145                 loopsIntersection = getLoopsIntersectionByPair(importRadius, loopsIntersection, loopList)
146         return loopsIntersection
147
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
152         corners = []
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)
159
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
168
169 def getLoopsLoopsIntersections( loops, otherLoops ):
170         'Get all the intersections of the loops with the other loops.'
171         loopsLoopsIntersections = []
172         for loop in loops:
173                 addLoopLoopsIntersections( loop, loopsLoopsIntersections, otherLoops )
174         return loopsLoopsIntersections
175
176 def getLoopsUnion(importRadius, loopLists):
177         'Get joined loops sliced through shape.'
178         allPoints = []
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)
190
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
198
199
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)
205
206         def getIntersection(self, importRadius, visibleObjectLoopsList):
207                 'Get intersected loops sliced through shape.'
208                 return getLoopsIntersection(importRadius, visibleObjectLoopsList)
209
210         def getLoops(self, importRadius, z):
211                 'Get loops sliced through shape.'
212                 visibleObjects = evaluate.getVisibleObjects(self.archivableObjects)
213                 if len( visibleObjects ) < 1:
214                         return []
215                 visibleObjectLoopsList = getVisibleObjectLoopsList( importRadius, visibleObjects, z )
216                 loops = self.getLoopsFromObjectLoopsList(importRadius, visibleObjectLoopsList)
217                 return euclidean.getSimplifiedLoops( loops, importRadius )
218
219         def getLoopsFromObjectLoopsList(self, importRadius, visibleObjectLoopsList):
220                 'Get loops from visible object loops list.'
221                 return self.operationFunction(importRadius, visibleObjectLoopsList)
222
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)
228
229         def getUnion(self, importRadius, visibleObjectLoopsList):
230                 'Get joined loops sliced through shape.'
231                 return getLoopsUnion(importRadius, visibleObjectLoopsList)
232
233         def getXMLLocalName(self):
234                 'Get xml class name.'
235                 return self.operationFunction.__name__.lower()[ len('get') : ]