2 Svg_writer is a class and collection of utilities to read from and write to an svg file.
4 Svg_writer uses the layer_template.svg file in the templates folder in the same folder as svg_writer, to output an svg file.
8 from __future__ import absolute_import
9 #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.
12 from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret
13 from fabmetheus_utilities.vector3 import Vector3
14 from fabmetheus_utilities.xml_simple_reader import DocumentNode
15 from fabmetheus_utilities import archive
16 from fabmetheus_utilities import euclidean
17 from fabmetheus_utilities import gcodec
18 from fabmetheus_utilities import xml_simple_reader
19 from fabmetheus_utilities import xml_simple_writer
25 __author__ = 'Enrique Perez (perez_enrique@yahoo.com)'
26 __date__ = '$Date: 2008/02/05 $'
27 __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
30 globalOriginalTextString = '<!-- Original XML Text:\n'
33 def getCarving(fileName):
34 'Get a carving for the file using an import plugin.'
35 pluginModule = fabmetheus_interpret.getInterpretPlugin(fileName)
36 if pluginModule == None:
38 return pluginModule.getCarving(fileName)
40 def getCommentElement(elementNode):
41 'Get a carving for the file using an import plugin.'
42 for childNode in elementNode.childNodes:
43 if childNode.getNodeName() == '#comment':
44 if childNode.getTextContent().startswith(globalOriginalTextString):
48 def getSliceDictionary(elementNode):
49 'Get the metadata slice attribute dictionary.'
50 for metadataElement in elementNode.getChildElementsByLocalName('metadata'):
51 for childNode in metadataElement.childNodes:
52 if childNode.getNodeName().lower() == 'slice:layers':
53 return childNode.attributes
56 def getSliceElementNodes(elementNode):
57 'Get the slice elements.'
58 gElementNodes = elementNode.getElementsByLocalName('g')
59 sliceElementNodes = []
60 for gElementNode in gElementNodes:
61 if 'id' in gElementNode.attributes:
62 idValue = gElementNode.attributes['id'].strip()
63 if idValue.startswith('z:'):
64 sliceElementNodes.append(gElementNode)
65 return sliceElementNodes
67 def getSVGByLoopLayers(addLayerTemplateToSVG, carving, loopLayers):
69 if len(loopLayers) < 1:
71 decimalPlacesCarried = max(0, 2 - int(math.floor(math.log10(carving.layerHeight))))
72 svgWriter = SVGWriter(
73 addLayerTemplateToSVG,
74 carving.getCarveCornerMaximum(),
75 carving.getCarveCornerMinimum(),
77 carving.getCarveLayerHeight())
78 return svgWriter.getReplacedSVGTemplate(carving.fileName, loopLayers, 'basic', carving.getFabmetheusXML())
80 def getTruncatedRotatedBoundaryLayers(loopLayers, repository):
81 'Get the truncated rotated boundary layers.'
82 return loopLayers[repository.layersFrom.value : repository.layersTo.value]
84 def setSVGCarvingCorners(cornerMaximum, cornerMinimum, layerHeight, loopLayers):
85 'Parse SVG text and store the layers.'
86 for loopLayer in loopLayers:
87 for loop in loopLayer.loops:
89 pointVector3 = Vector3(point.real, point.imag, loopLayer.z)
90 cornerMaximum.maximize(pointVector3)
91 cornerMinimum.minimize(pointVector3)
92 halfLayerThickness = 0.5 * layerHeight
93 cornerMaximum.z += halfLayerThickness
94 cornerMinimum.z -= halfLayerThickness
98 'A base class to get an svg skein from a carving.'
100 addLayerTemplateToSVG,
103 decimalPlacesCarried,
107 self.addLayerTemplateToSVG = addLayerTemplateToSVG
108 self.cornerMaximum = cornerMaximum
109 self.cornerMinimum = cornerMinimum
110 self.decimalPlacesCarried = decimalPlacesCarried
111 self.edgeWidth = edgeWidth
112 self.layerHeight = layerHeight
113 self.textHeight = 22.5
116 def addLayerBegin(self, layerIndex, loopLayer):
117 'Add the start lines for the layer.'
118 zRounded = self.getRounded(loopLayer.z)
119 self.graphicsCopy = self.graphicsElementNode.getCopy(zRounded, self.graphicsElementNode.parentNode)
120 if self.addLayerTemplateToSVG:
121 translateXRounded = self.getRounded(self.controlBoxWidth + self.margin + self.margin)
122 layerTranslateY = self.marginTop
123 layerTranslateY += layerIndex * self.textHeight + (layerIndex + 1) * (self.extent.y * self.unitScale + self.margin)
124 translateYRounded = self.getRounded(layerTranslateY)
125 self.graphicsCopy.attributes['transform'] = 'translate(%s, %s)' % (translateXRounded, translateYRounded)
126 layerString = 'Layer %s, z:%s' % (layerIndex, zRounded)
127 self.graphicsCopy.getFirstChildByLocalName('text').setTextContent(layerString)
128 self.graphicsCopy.attributes['inkscape:groupmode'] = 'layer'
129 self.graphicsCopy.attributes['inkscape:label'] = layerString
130 self.pathElementNode = self.graphicsCopy.getFirstChildByLocalName('path')
131 self.pathDictionary = self.pathElementNode.attributes
133 def addLoopLayersToOutput(self, loopLayers):
134 'Add rotated boundary layers to the output.'
135 for loopLayerIndex, loopLayer in enumerate(loopLayers):
136 self.addLoopLayerToOutput(loopLayerIndex, loopLayer)
138 def addLoopLayerToOutput(self, layerIndex, loopLayer):
139 'Add rotated boundary layer to the output.'
140 self.addLayerBegin(layerIndex, loopLayer)
141 if self.addLayerTemplateToSVG:
142 self.pathDictionary['transform'] = self.getTransformString()
144 del self.pathDictionary['transform']
145 self.pathDictionary['d'] = self.getSVGStringForLoops(loopLayer.loops)
147 def addOriginalAsComment(self, elementNode):
148 'Add original elementNode as a comment.'
149 if elementNode == None:
151 if elementNode.getNodeName() == '#comment':
152 elementNode.setParentAddToChildNodes(self.svgElement)
154 elementNodeOutput = cStringIO.StringIO()
155 elementNode.addXML(0, elementNodeOutput)
156 textLines = archive.getTextLines(elementNodeOutput.getvalue())
157 commentNodeOutput = cStringIO.StringIO()
159 for textLine in textLines:
160 lineStripped = textLine.strip()
161 if lineStripped[: len('<!--')] == '<!--':
164 if len(textLine) > 0:
165 commentNodeOutput.write(textLine + '\n')
166 if '-->' in lineStripped:
168 xml_simple_reader.CommentNode(self.svgElement, '%s%s-->\n' % (globalOriginalTextString, commentNodeOutput.getvalue())).appendSelfToParent()
170 def getReplacedSVGTemplate(self, fileName, loopLayers, procedureName, elementNode=None):
171 'Get the lines of text from the layer_template.svg file.'
172 self.extent = self.cornerMaximum - self.cornerMinimum
173 svgTemplateText = archive.getFileText(archive.getTemplatesPath('layer_template.svg'))
174 documentNode = DocumentNode(fileName, svgTemplateText)
175 self.svgElement = documentNode.getDocumentElement()
176 svgElementDictionary = self.svgElement.attributes
177 self.sliceDictionary = getSliceDictionary(self.svgElement)
178 self.controlBoxHeight = float(self.sliceDictionary['controlBoxHeight'])
179 self.controlBoxWidth = float(self.sliceDictionary['controlBoxWidth'])
180 self.margin = float(self.sliceDictionary['margin'])
181 self.marginTop = float(self.sliceDictionary['marginTop'])
182 self.textHeight = float(self.sliceDictionary['textHeight'])
183 self.unitScale = float(self.sliceDictionary['unitScale'])
184 svgMinWidth = float(self.sliceDictionary['svgMinWidth'])
185 self.controlBoxHeightMargin = self.controlBoxHeight + self.marginTop
186 if not self.addLayerTemplateToSVG:
187 self.svgElement.getElementNodeByID('layerTextTemplate').removeFromIDNameParent()
188 del self.svgElement.getElementNodeByID('sliceElementTemplate').attributes['transform']
189 self.graphicsElementNode = self.svgElement.getElementNodeByID('sliceElementTemplate')
190 self.graphicsElementNode.attributes['id'] = 'z:'
191 self.addLoopLayersToOutput(loopLayers)
192 self.setMetadataNoscriptElement('layerHeight', 'Layer Height: ', self.layerHeight)
193 self.setMetadataNoscriptElement('maxX', 'X: ', self.cornerMaximum.x)
194 self.setMetadataNoscriptElement('minX', 'X: ', self.cornerMinimum.x)
195 self.setMetadataNoscriptElement('maxY', 'Y: ', self.cornerMaximum.y)
196 self.setMetadataNoscriptElement('minY', 'Y: ', self.cornerMinimum.y)
197 self.setMetadataNoscriptElement('maxZ', 'Z: ', self.cornerMaximum.z)
198 self.setMetadataNoscriptElement('minZ', 'Z: ', self.cornerMinimum.z)
199 self.textHeight = float( self.sliceDictionary['textHeight'] )
200 controlTop = len(loopLayers) * (self.margin + self.extent.y * self.unitScale + self.textHeight) + self.marginTop + self.textHeight
201 self.svgElement.getFirstChildByLocalName('title').setTextContent(os.path.basename(fileName) + ' - Slice Layers')
202 svgElementDictionary['height'] = '%spx' % self.getRounded(max(controlTop, self.controlBoxHeightMargin))
203 width = max(self.extent.x * self.unitScale, svgMinWidth)
204 svgElementDictionary['width'] = '%spx' % self.getRounded( width )
205 self.sliceDictionary['decimalPlacesCarried'] = str( self.decimalPlacesCarried )
206 if self.edgeWidth != None:
207 self.sliceDictionary['edgeWidth'] = self.getRounded( self.edgeWidth )
208 self.sliceDictionary['yAxisPointingUpward'] = 'true'
209 self.sliceDictionary['procedureName'] = procedureName
210 self.setDimensionTexts('dimX', 'X: ' + self.getRounded(self.extent.x))
211 self.setDimensionTexts('dimY', 'Y: ' + self.getRounded(self.extent.y))
212 self.setDimensionTexts('dimZ', 'Z: ' + self.getRounded(self.extent.z))
213 self.setTexts('numberOfLayers', 'Number of Layers: %s' % len(loopLayers))
215 for loopLayer in loopLayers:
216 volume += euclidean.getAreaLoops(loopLayer.loops)
217 volume *= 0.001 * self.layerHeight
218 self.setTexts('volume', 'Volume: %s cm3' % self.getRounded(volume))
219 if not self.addLayerTemplateToSVG:
220 self.svgElement.getFirstChildByLocalName('script').removeFromIDNameParent()
221 self.svgElement.getElementNodeByID('controls').removeFromIDNameParent()
222 self.graphicsElementNode.removeFromIDNameParent()
223 self.addOriginalAsComment(elementNode)
224 return documentNode.__repr__()
226 def getRounded(self, number):
227 'Get number rounded to the number of carried decimal places as a string.'
228 return euclidean.getRoundedToPlacesString(self.decimalPlacesCarried, number)
230 def getRoundedComplexString(self, point):
231 'Get the rounded complex string.'
232 return self.getRounded( point.real ) + ' ' + self.getRounded( point.imag )
234 def getSVGStringForLoop( self, loop ):
235 'Get the svg loop string.'
238 return self.getSVGStringForPath(loop) + ' z'
240 def getSVGStringForLoops( self, loops ):
241 'Get the svg loops string.'
244 loopString += self.getSVGStringForLoop( loops[0] )
245 for loop in loops[1 :]:
246 loopString += ' ' + self.getSVGStringForLoop(loop)
249 def getSVGStringForPath( self, path ):
250 'Get the svg path string.'
253 stringBeginning = 'M '
254 if len( svgLoopString ) > 0:
255 stringBeginning = ' L '
256 svgLoopString += stringBeginning + self.getRoundedComplexString(point)
259 def getTransformString(self):
260 'Get the svg transform string.'
261 cornerMinimumXString = self.getRounded(-self.cornerMinimum.x)
262 cornerMinimumYString = self.getRounded(-self.cornerMinimum.y)
263 return 'scale(%s, %s) translate(%s, %s)' % (self.unitScale, - self.unitScale, cornerMinimumXString, cornerMinimumYString)
265 def setDimensionTexts(self, key, valueString):
266 'Set the texts to the valueString followed by mm.'
267 self.setTexts(key, valueString + ' mm')
269 def setMetadataNoscriptElement(self, key, prefix, value):
270 'Set the metadata value and the text.'
271 valueString = self.getRounded(value)
272 self.sliceDictionary[key] = valueString
273 self.setDimensionTexts(key, prefix + valueString)
275 def setTexts(self, key, valueString):
276 'Set the texts to the valueString.'
277 self.svgElement.getElementNodeByID(key + 'Iso').setTextContent(valueString)
278 self.svgElement.getElementNodeByID(key + 'Layer').setTextContent(valueString)
279 self.svgElement.getElementNodeByID(key + 'Scroll').setTextContent(valueString)