chiark / gitweb /
Add uppercase STL and HEX to file dialog filters for linux/MacOS
[cura.git] / Cura / cura_sf / fabmetheus_utilities / geometry / creation / lineation.py
1 """
2 Polygon path.
3
4 """
5
6 from __future__ import absolute_import
7 #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.
8 import __init__
9
10 from fabmetheus_utilities.geometry.geometry_tools import path
11 from fabmetheus_utilities.geometry.geometry_utilities import evaluate
12 from fabmetheus_utilities.geometry.geometry_utilities import matrix
13 from fabmetheus_utilities.vector3 import Vector3
14 from fabmetheus_utilities import euclidean
15 import math
16
17
18 __author__ = 'Enrique Perez (perez_enrique@yahoo.com)'
19 __credits__ = 'Art of Illusion <http://www.artofillusion.org/>'
20 __date__ = '$Date: 2008/02/05 $'
21 __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
22
23
24 def getComplexByDictionary(dictionary, valueComplex):
25         'Get complex by dictionary.'
26         if 'x' in dictionary:
27                 valueComplex = complex(euclidean.getFloatFromValue(dictionary['x']),valueComplex.imag)
28         if 'y' in dictionary:
29                 valueComplex = complex(valueComplex.real, euclidean.getFloatFromValue(dictionary['y']))
30         return valueComplex
31
32 def getComplexByDictionaryListValue(value, valueComplex):
33         'Get complex by dictionary, list or value.'
34         if value.__class__ == complex:
35                 return value
36         if value.__class__ == dict:
37                 return getComplexByDictionary(value, valueComplex)
38         if value.__class__ == list:
39                 return getComplexByFloatList(value, valueComplex)
40         floatFromValue = euclidean.getFloatFromValue(value)
41         if floatFromValue ==  None:
42                 return valueComplex
43         return complex( floatFromValue, floatFromValue )
44
45 def getComplexByFloatList( floatList, valueComplex ):
46         'Get complex by float list.'
47         if len(floatList) > 0:
48                 valueComplex = complex(euclidean.getFloatFromValue(floatList[0]), valueComplex.imag)
49         if len(floatList) > 1:
50                 valueComplex = complex(valueComplex.real, euclidean.getFloatFromValue(floatList[1]))
51         return valueComplex
52
53 def getComplexByMultiplierPrefix(elementNode, multiplier, prefix, valueComplex):
54         'Get complex from multiplier, prefix and xml element.'
55         if multiplier == 0.0:
56                 return valueComplex
57         oldMultipliedValueComplex = valueComplex * multiplier
58         complexByPrefix = getComplexByPrefix(elementNode, prefix, oldMultipliedValueComplex)
59         if complexByPrefix == oldMultipliedValueComplex:
60                 return valueComplex
61         return complexByPrefix / multiplier
62
63 def getComplexByMultiplierPrefixes(elementNode, multiplier, prefixes, valueComplex):
64         'Get complex from multiplier, prefixes and xml element.'
65         for prefix in prefixes:
66                 valueComplex = getComplexByMultiplierPrefix(elementNode, multiplier, prefix, valueComplex)
67         return valueComplex
68
69 def getComplexByPrefix(elementNode, prefix, valueComplex):
70         'Get complex from prefix and xml element.'
71         value = evaluate.getEvaluatedValue(None, elementNode, prefix)
72         if value != None:
73                 valueComplex = getComplexByDictionaryListValue(value, valueComplex)
74         x = evaluate.getEvaluatedFloat(None, elementNode, prefix + '.x')
75         if x != None:
76                 valueComplex = complex( x, getComplexIfNone( valueComplex ).imag )
77         y = evaluate.getEvaluatedFloat(None, elementNode, prefix + '.y')
78         if y != None:
79                 valueComplex = complex( getComplexIfNone( valueComplex ).real, y )
80         return valueComplex
81
82 def getComplexByPrefixBeginEnd(elementNode, prefixBegin, prefixEnd, valueComplex):
83         'Get complex from element node, prefixBegin and prefixEnd.'
84         valueComplex = getComplexByPrefix(elementNode, prefixBegin, valueComplex)
85         if prefixEnd in elementNode.attributes:
86                 return 0.5 * getComplexByPrefix(elementNode, valueComplex + valueComplex, prefixEnd)
87         else:
88                 return valueComplex
89
90 def getComplexByPrefixes(elementNode, prefixes, valueComplex):
91         'Get complex from prefixes and xml element.'
92         for prefix in prefixes:
93                 valueComplex = getComplexByPrefix(elementNode, prefix, valueComplex)
94         return valueComplex
95
96 def getComplexIfNone( valueComplex ):
97         'Get new complex if the original complex is none.'
98         if valueComplex == None:
99                 return complex()
100         return valueComplex
101
102 def getFloatByPrefixBeginEnd(elementNode, prefixBegin, prefixEnd, valueFloat):
103         'Get float from prefixBegin, prefixEnd and xml element.'
104         valueFloat = evaluate.getEvaluatedFloat(valueFloat, elementNode, prefixBegin)
105         if prefixEnd in elementNode.attributes:
106                 return 0.5 * evaluate.getEvaluatedFloat(valueFloat + valueFloat, elementNode, prefixEnd)
107         return valueFloat
108
109 def getFloatByPrefixSide(defaultValue, elementNode, prefix, side):
110         'Get float by prefix and side.'
111         if elementNode == None:
112                 return defaultValue
113         if side != None:
114                 key = prefix + 'OverSide'
115                 if key in elementNode.attributes:
116                         defaultValue = euclidean.getFloatFromValue(evaluate.getEvaluatedValueObliviously(elementNode, key)) * side
117         return evaluate.getEvaluatedFloat(defaultValue, elementNode, prefix)
118
119 def getGeometryOutput(derivation, elementNode):
120         'Get geometry output from paths.'
121         if derivation == None:
122                 derivation = LineationDerivation(elementNode)
123         geometryOutput = []
124         for path in derivation.target:
125                 sideLoop = SideLoop(path)
126                 geometryOutput += getGeometryOutputByLoop(elementNode, sideLoop)
127         return geometryOutput
128
129 def getGeometryOutputByArguments(arguments, elementNode):
130         'Get vector3 vertexes from attribute dictionary by arguments.'
131         return getGeometryOutput(None, elementNode)
132
133 def getGeometryOutputByLoop(elementNode, sideLoop):
134         'Get geometry output by side loop.'
135         sideLoop.rotate(elementNode)
136         return getGeometryOutputByManipulation(elementNode, sideLoop)
137
138 def getGeometryOutputByManipulation(elementNode, sideLoop):
139         'Get geometry output by manipulation.'
140         sideLoop.loop = euclidean.getLoopWithoutCloseSequentialPoints( sideLoop.close, sideLoop.loop )
141         return sideLoop.getManipulationPluginLoops(elementNode)
142
143 def getInradius(defaultInradius, elementNode):
144         'Get inradius.'
145         defaultInradius = getComplexByPrefixes(elementNode, ['demisize', 'inradius'], defaultInradius)
146         return getComplexByMultiplierPrefix(elementNode, 2.0, 'size', defaultInradius)
147
148 def getMinimumRadius(beginComplexSegmentLength, endComplexSegmentLength, radius):
149         'Get minimum radius.'
150         return min(abs(radius), 0.5 * min(beginComplexSegmentLength, endComplexSegmentLength))
151
152 def getNewDerivation(elementNode):
153         'Get new derivation.'
154         return LineationDerivation(elementNode)
155
156 def getNumberOfBezierPoints(begin, elementNode, end):
157         'Get the numberOfBezierPoints.'
158         numberOfBezierPoints = int(math.ceil(0.5 * evaluate.getSidesMinimumThreeBasedOnPrecision(elementNode, abs(end - begin))))
159         return evaluate.getEvaluatedInt(numberOfBezierPoints, elementNode, 'sides')
160
161 def getPackedGeometryOutputByLoop(elementNode, sideLoop):
162         'Get packed geometry output by side loop.'
163         sideLoop.rotate(elementNode)
164         return getGeometryOutputByManipulation(elementNode, sideLoop)
165
166 def getRadiusAverage(radiusComplex):
167         'Get average radius from radiusComplex.'
168         return math.sqrt(radiusComplex.real * radiusComplex.imag)
169
170 def getRadiusComplex(elementNode, radius):
171         'Get radius complex for elementNode.'
172         radius = getComplexByPrefixes(elementNode, ['demisize', 'radius'], radius)
173         return getComplexByMultiplierPrefixes(elementNode, 2.0, ['diameter', 'size'], radius)
174
175 def getStrokeRadiusByPrefix(elementNode, prefix):
176         'Get strokeRadius by prefix.'
177         strokeRadius = getFloatByPrefixBeginEnd(elementNode, prefix + 'strokeRadius', prefix + 'strokeWidth', 1.0)
178         return getFloatByPrefixBeginEnd(elementNode, prefix + 'radius', prefix + 'diameter', strokeRadius)
179
180 def processElementNode(elementNode):
181         'Process the xml element.'
182         path.convertElementNode(elementNode, getGeometryOutput(None, elementNode))
183
184 def processElementNodeByFunction(elementNode, manipulationFunction):
185         'Process the xml element by the manipulationFunction.'
186         elementAttributesCopy = elementNode.attributes.copy()
187         targets = evaluate.getElementNodesByKey(elementNode, 'target')
188         for target in targets:
189                 targetAttributesCopy = target.attributes.copy()
190                 target.attributes = elementAttributesCopy
191                 processTargetByFunction(manipulationFunction, target)
192                 target.attributes = targetAttributesCopy
193
194 def processTargetByFunction(manipulationFunction, target):
195         'Process the target by the manipulationFunction.'
196         if target.xmlObject == None:
197                 print('Warning, there is no object in processTargetByFunction in lineation for:')
198                 print(target)
199                 return
200         geometryOutput = []
201         transformedPaths = target.xmlObject.getTransformedPaths()
202         for transformedPath in transformedPaths:
203                 sideLoop = SideLoop(transformedPath)
204                 sideLoop.rotate(target)
205                 sideLoop.loop = euclidean.getLoopWithoutCloseSequentialPoints( sideLoop.close, sideLoop.loop )
206                 geometryOutput += manipulationFunction(sideLoop.close, target, sideLoop.loop, '', sideLoop.sideLength)
207         if len(geometryOutput) < 1:
208                 print('Warning, there is no geometryOutput in processTargetByFunction in lineation for:')
209                 print(target)
210                 return
211         removeChildNodesFromElementObject(target)
212         path.convertElementNode(target, geometryOutput)
213
214 def removeChildNodesFromElementObject(elementNode):
215         'Process the xml element by manipulationFunction.'
216         elementNode.removeChildNodesFromIDNameParent()
217         if elementNode.xmlObject != None:
218                 if elementNode.parentNode.xmlObject != None:
219                         if elementNode.xmlObject in elementNode.parentNode.xmlObject.archivableObjects:
220                                 elementNode.parentNode.xmlObject.archivableObjects.remove(elementNode.xmlObject)
221
222 def setClosedAttribute(elementNode, revolutions):
223         'Set the closed attribute of the elementNode.'
224         closedBoolean = evaluate.getEvaluatedBoolean(revolutions <= 1, elementNode, 'closed')
225         elementNode.attributes['closed'] = str(closedBoolean).lower()
226
227
228 class LineationDerivation:
229         'Class to hold lineation variables.'
230         def __init__(self, elementNode):
231                 'Set defaults.'
232                 self.target = evaluate.getTransformedPathsByKey([], elementNode, 'target')
233
234
235 class SideLoop:
236         'Class to handle loop, side angle and side length.'
237         def __init__(self, loop, sideAngle=None, sideLength=None):
238                 'Initialize.'
239                 if sideAngle == None:
240                         if len(loop) > 0:
241                                 sideAngle = 2.0 * math.pi / float(len(loop))
242                         else:
243                                 sideAngle = 1.0
244                                 print('Warning, loop has no sides in SideLoop in lineation.')
245                 if sideLength == None:
246                         if len(loop) > 0:
247                                 sideLength = euclidean.getLoopLength(loop) / float(len(loop))
248                         else:
249                                 sideLength = 1.0
250                                 print('Warning, loop has no length in SideLoop in lineation.')
251                 self.loop = loop
252                 self.sideAngle = abs(sideAngle)
253                 self.sideLength = abs(sideLength)
254                 self.close = 0.001 * sideLength
255
256         def getManipulationPluginLoops(self, elementNode):
257                 'Get loop manipulated by the plugins in the manipulation paths folder.'
258                 xmlProcessor = elementNode.getXMLProcessor()
259                 matchingPlugins = evaluate.getMatchingPlugins(elementNode, xmlProcessor.manipulationMatrixDictionary)
260                 matchingPlugins += evaluate.getMatchingPlugins(elementNode, xmlProcessor.manipulationPathDictionary)
261                 matchingPlugins += evaluate.getMatchingPlugins(elementNode, xmlProcessor.manipulationShapeDictionary)
262                 matchingPlugins.sort(evaluate.compareExecutionOrderAscending)
263                 loops = [self.loop]
264                 for matchingPlugin in matchingPlugins:
265                         matchingLoops = []
266                         prefix = matchingPlugin.__name__.replace('_', '') + '.'
267                         for loop in loops:
268                                 matchingLoops += matchingPlugin.getManipulatedPaths(self.close, elementNode, loop, prefix, self.sideLength)
269                         loops = matchingLoops
270                 return loops
271
272         def rotate(self, elementNode):
273                 'Rotate.'
274                 rotation = math.radians(evaluate.getEvaluatedFloat(0.0, elementNode, 'rotation'))
275                 rotation += evaluate.getEvaluatedFloat(0.0, elementNode, 'rotationOverSide') * self.sideAngle
276                 if rotation != 0.0:
277                         planeRotation = euclidean.getWiddershinsUnitPolar( rotation )
278                         for vertex in self.loop:
279                                 rotatedComplex = vertex.dropAxis() * planeRotation
280                                 vertex.x = rotatedComplex.real
281                                 vertex.y = rotatedComplex.imag
282                 if 'clockwise' in elementNode.attributes:
283                         isClockwise = euclidean.getBooleanFromValue(evaluate.getEvaluatedValueObliviously(elementNode, 'clockwise'))
284                         if isClockwise == euclidean.getIsWiddershinsByVector3( self.loop ):
285                                 self.loop.reverse()
286
287
288 class Spiral:
289         'Class to add a spiral.'
290         def __init__(self, spiral, stepRatio):
291                 'Initialize.'
292                 self.spiral = spiral
293                 if self.spiral == None:
294                         return
295                 self.spiralIncrement = self.spiral * stepRatio
296                 self.spiralTotal = Vector3()
297
298         def __repr__(self):
299                 'Get the string representation of this Spiral.'
300                 return self.spiral
301
302         def getSpiralPoint(self, unitPolar, vector3):
303                 'Add spiral to the vector.'
304                 if self.spiral == None:
305                         return vector3
306                 vector3 += Vector3(unitPolar.real * self.spiralTotal.x, unitPolar.imag * self.spiralTotal.y, self.spiralTotal.z)
307                 self.spiralTotal += self.spiralIncrement
308                 return vector3