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.
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
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'
24 def getComplexByDictionary(dictionary, valueComplex):
25 'Get complex by dictionary.'
27 valueComplex = complex(euclidean.getFloatFromValue(dictionary['x']),valueComplex.imag)
29 valueComplex = complex(valueComplex.real, euclidean.getFloatFromValue(dictionary['y']))
32 def getComplexByDictionaryListValue(value, valueComplex):
33 'Get complex by dictionary, list or value.'
34 if value.__class__ == complex:
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:
43 return complex( floatFromValue, floatFromValue )
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]))
53 def getComplexByMultiplierPrefix(elementNode, multiplier, prefix, valueComplex):
54 'Get complex from multiplier, prefix and xml element.'
57 oldMultipliedValueComplex = valueComplex * multiplier
58 complexByPrefix = getComplexByPrefix(elementNode, prefix, oldMultipliedValueComplex)
59 if complexByPrefix == oldMultipliedValueComplex:
61 return complexByPrefix / multiplier
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)
69 def getComplexByPrefix(elementNode, prefix, valueComplex):
70 'Get complex from prefix and xml element.'
71 value = evaluate.getEvaluatedValue(None, elementNode, prefix)
73 valueComplex = getComplexByDictionaryListValue(value, valueComplex)
74 x = evaluate.getEvaluatedFloat(None, elementNode, prefix + '.x')
76 valueComplex = complex( x, getComplexIfNone( valueComplex ).imag )
77 y = evaluate.getEvaluatedFloat(None, elementNode, prefix + '.y')
79 valueComplex = complex( getComplexIfNone( valueComplex ).real, y )
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)
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)
96 def getComplexIfNone( valueComplex ):
97 'Get new complex if the original complex is none.'
98 if valueComplex == None:
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)
109 def getFloatByPrefixSide(defaultValue, elementNode, prefix, side):
110 'Get float by prefix and side.'
111 if elementNode == 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)
119 def getGeometryOutput(derivation, elementNode):
120 'Get geometry output from paths.'
121 if derivation == None:
122 derivation = LineationDerivation(elementNode)
124 for path in derivation.target:
125 sideLoop = SideLoop(path)
126 geometryOutput += getGeometryOutputByLoop(elementNode, sideLoop)
127 return geometryOutput
129 def getGeometryOutputByArguments(arguments, elementNode):
130 'Get vector3 vertexes from attribute dictionary by arguments.'
131 return getGeometryOutput(None, elementNode)
133 def getGeometryOutputByLoop(elementNode, sideLoop):
134 'Get geometry output by side loop.'
135 sideLoop.rotate(elementNode)
136 return getGeometryOutputByManipulation(elementNode, sideLoop)
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)
143 def getInradius(defaultInradius, elementNode):
145 defaultInradius = getComplexByPrefixes(elementNode, ['demisize', 'inradius'], defaultInradius)
146 return getComplexByMultiplierPrefix(elementNode, 2.0, 'size', defaultInradius)
148 def getMinimumRadius(beginComplexSegmentLength, endComplexSegmentLength, radius):
149 'Get minimum radius.'
150 return min(abs(radius), 0.5 * min(beginComplexSegmentLength, endComplexSegmentLength))
152 def getNewDerivation(elementNode):
153 'Get new derivation.'
154 return LineationDerivation(elementNode)
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')
161 def getPackedGeometryOutputByLoop(elementNode, sideLoop):
162 'Get packed geometry output by side loop.'
163 sideLoop.rotate(elementNode)
164 return getGeometryOutputByManipulation(elementNode, sideLoop)
166 def getRadiusAverage(radiusComplex):
167 'Get average radius from radiusComplex.'
168 return math.sqrt(radiusComplex.real * radiusComplex.imag)
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)
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)
180 def processElementNode(elementNode):
181 'Process the xml element.'
182 path.convertElementNode(elementNode, getGeometryOutput(None, elementNode))
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
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:')
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:')
211 removeChildNodesFromElementObject(target)
212 path.convertElementNode(target, geometryOutput)
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)
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()
228 class LineationDerivation:
229 'Class to hold lineation variables.'
230 def __init__(self, elementNode):
232 self.target = evaluate.getTransformedPathsByKey([], elementNode, 'target')
236 'Class to handle loop, side angle and side length.'
237 def __init__(self, loop, sideAngle=None, sideLength=None):
239 if sideAngle == None:
241 sideAngle = 2.0 * math.pi / float(len(loop))
244 print('Warning, loop has no sides in SideLoop in lineation.')
245 if sideLength == None:
247 sideLength = euclidean.getLoopLength(loop) / float(len(loop))
250 print('Warning, loop has no length in SideLoop in lineation.')
252 self.sideAngle = abs(sideAngle)
253 self.sideLength = abs(sideLength)
254 self.close = 0.001 * sideLength
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)
264 for matchingPlugin in matchingPlugins:
266 prefix = matchingPlugin.__name__.replace('_', '') + '.'
268 matchingLoops += matchingPlugin.getManipulatedPaths(self.close, elementNode, loop, prefix, self.sideLength)
269 loops = matchingLoops
272 def rotate(self, elementNode):
274 rotation = math.radians(evaluate.getEvaluatedFloat(0.0, elementNode, 'rotation'))
275 rotation += evaluate.getEvaluatedFloat(0.0, elementNode, 'rotationOverSide') * self.sideAngle
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 ):
289 'Class to add a spiral.'
290 def __init__(self, spiral, stepRatio):
293 if self.spiral == None:
295 self.spiralIncrement = self.spiral * stepRatio
296 self.spiralTotal = Vector3()
299 'Get the string representation of this Spiral.'
302 def getSpiralPoint(self, unitPolar, vector3):
303 'Add spiral to the vector.'
304 if self.spiral == None:
306 vector3 += Vector3(unitPolar.real * self.spiralTotal.x, unitPolar.imag * self.spiralTotal.y, self.spiralTotal.z)
307 self.spiralTotal += self.spiralIncrement