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
10 from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret
11 from fabmetheus_utilities.vector3 import Vector3
12 from fabmetheus_utilities.xml_simple_reader import DocumentNode
13 from fabmetheus_utilities import archive
14 from fabmetheus_utilities import euclidean
15 from fabmetheus_utilities import gcodec
16 from fabmetheus_utilities import xml_simple_reader
17 from fabmetheus_utilities import xml_simple_writer
23 __author__ = 'Enrique Perez (perez_enrique@yahoo.com)'
24 __date__ = '$Date: 2008/02/05 $'
25 __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
28 globalOriginalTextString = '<!-- Original XML Text:\n'
31 def getCarving(fileName):
32 'Get a carving for the file using an import plugin.'
33 pluginModule = fabmetheus_interpret.getInterpretPlugin(fileName)
34 if pluginModule == None:
36 return pluginModule.getCarving(fileName)
38 def getCommentElement(elementNode):
39 'Get a carving for the file using an import plugin.'
40 for childNode in elementNode.childNodes:
41 if childNode.getNodeName() == '#comment':
42 if childNode.getTextContent().startswith(globalOriginalTextString):
46 def getSliceDictionary(elementNode):
47 'Get the metadata slice attribute dictionary.'
48 for metadataElement in elementNode.getChildElementsByLocalName('metadata'):
49 for childNode in metadataElement.childNodes:
50 if childNode.getNodeName().lower() == 'slice:layers':
51 return childNode.attributes
54 def getSliceElementNodes(elementNode):
55 'Get the slice elements.'
56 gElementNodes = elementNode.getElementsByLocalName('g')
57 sliceElementNodes = []
58 for gElementNode in gElementNodes:
59 if 'id' in gElementNode.attributes:
60 idValue = gElementNode.attributes['id'].strip()
61 if idValue.startswith('z:'):
62 sliceElementNodes.append(gElementNode)
63 return sliceElementNodes
65 def getSVGByLoopLayers(addLayerTemplateToSVG, carving, loopLayers):
67 if len(loopLayers) < 1:
69 decimalPlacesCarried = max(0, 2 - int(math.floor(math.log10(carving.layerHeight))))
70 svgWriter = SVGWriter(
71 addLayerTemplateToSVG,
72 carving.getCarveCornerMaximum(),
73 carving.getCarveCornerMinimum(),
75 carving.getCarveLayerHeight())
76 return svgWriter.getReplacedSVGTemplate(carving.fileName, loopLayers, 'basic', carving.getFabmetheusXML())
78 def getTruncatedRotatedBoundaryLayers(loopLayers, repository):
79 'Get the truncated rotated boundary layers.'
80 return loopLayers[repository.layersFrom.value : repository.layersTo.value]
82 def setSVGCarvingCorners(cornerMaximum, cornerMinimum, layerHeight, loopLayers):
83 'Parse SVG text and store the layers.'
84 for loopLayer in loopLayers:
85 for loop in loopLayer.loops:
87 pointVector3 = Vector3(point.real, point.imag, loopLayer.z)
88 cornerMaximum.maximize(pointVector3)
89 cornerMinimum.minimize(pointVector3)
90 halfLayerThickness = 0.5 * layerHeight
91 cornerMaximum.z += halfLayerThickness
92 cornerMinimum.z -= halfLayerThickness
95 class SVGWriter(object):
96 'A base class to get an svg skein from a carving.'
98 addLayerTemplateToSVG,
101 decimalPlacesCarried,
105 self.addLayerTemplateToSVG = addLayerTemplateToSVG
106 self.cornerMaximum = cornerMaximum
107 self.cornerMinimum = cornerMinimum
108 self.decimalPlacesCarried = decimalPlacesCarried
109 self.edgeWidth = edgeWidth
110 self.layerHeight = layerHeight
111 self.textHeight = 22.5
114 def addLayerBegin(self, layerIndex, loopLayer):
115 'Add the start lines for the layer.'
116 zRounded = self.getRounded(loopLayer.z)
117 self.graphicsCopy = self.graphicsElementNode.getCopy(zRounded, self.graphicsElementNode.parentNode)
118 if self.addLayerTemplateToSVG:
119 translateXRounded = self.getRounded(self.controlBoxWidth + self.margin + self.margin)
120 layerTranslateY = self.marginTop
121 layerTranslateY += layerIndex * self.textHeight + (layerIndex + 1) * (self.extent.y * self.unitScale + self.margin)
122 translateYRounded = self.getRounded(layerTranslateY)
123 self.graphicsCopy.attributes['transform'] = 'translate(%s, %s)' % (translateXRounded, translateYRounded)
124 layerString = 'Layer %s, z:%s' % (layerIndex, zRounded)
125 self.graphicsCopy.getFirstChildByLocalName('text').setTextContent(layerString)
126 self.graphicsCopy.attributes['inkscape:groupmode'] = 'layer'
127 self.graphicsCopy.attributes['inkscape:label'] = layerString
128 self.pathElementNode = self.graphicsCopy.getFirstChildByLocalName('path')
129 self.pathDictionary = self.pathElementNode.attributes
131 def addLoopLayersToOutput(self, loopLayers):
132 'Add rotated boundary layers to the output.'
133 for loopLayerIndex, loopLayer in enumerate(loopLayers):
134 self.addLoopLayerToOutput(loopLayerIndex, loopLayer)
136 def addLoopLayerToOutput(self, layerIndex, loopLayer):
137 'Add rotated boundary layer to the output.'
138 self.addLayerBegin(layerIndex, loopLayer)
139 if self.addLayerTemplateToSVG:
140 self.pathDictionary['transform'] = self.getTransformString()
142 del self.pathDictionary['transform']
143 self.pathDictionary['d'] = self.getSVGStringForLoops(loopLayer.loops)
145 def addOriginalAsComment(self, elementNode):
146 'Add original elementNode as a comment.'
147 if elementNode == None:
149 if elementNode.getNodeName() == '#comment':
150 elementNode.setParentAddToChildNodes(self.svgElement)
152 elementNodeOutput = cStringIO.StringIO()
153 elementNode.addXML(0, elementNodeOutput)
154 textLines = archive.getTextLines(elementNodeOutput.getvalue())
155 commentNodeOutput = cStringIO.StringIO()
157 for textLine in textLines:
158 lineStripped = textLine.strip()
159 if lineStripped[: len('<!--')] == '<!--':
162 if len(textLine) > 0:
163 commentNodeOutput.write(textLine + '\n')
164 if '-->' in lineStripped:
166 xml_simple_reader.CommentNode(self.svgElement, '%s%s-->\n' % (globalOriginalTextString, commentNodeOutput.getvalue())).appendSelfToParent()
168 def getReplacedSVGTemplate(self, fileName, loopLayers, procedureName, elementNode=None):
169 'Get the lines of text from the layer_template.svg file.'
170 self.extent = self.cornerMaximum - self.cornerMinimum
171 svgTemplateText = archive.getFileText(archive.getTemplatesPath('layer_template.svg'))
172 documentNode = DocumentNode(fileName, svgTemplateText)
173 self.svgElement = documentNode.getDocumentElement()
174 svgElementDictionary = self.svgElement.attributes
175 self.sliceDictionary = getSliceDictionary(self.svgElement)
176 self.controlBoxHeight = float(self.sliceDictionary['controlBoxHeight'])
177 self.controlBoxWidth = float(self.sliceDictionary['controlBoxWidth'])
178 self.margin = float(self.sliceDictionary['margin'])
179 self.marginTop = float(self.sliceDictionary['marginTop'])
180 self.textHeight = float(self.sliceDictionary['textHeight'])
181 self.unitScale = float(self.sliceDictionary['unitScale'])
182 svgMinWidth = float(self.sliceDictionary['svgMinWidth'])
183 self.controlBoxHeightMargin = self.controlBoxHeight + self.marginTop
184 if not self.addLayerTemplateToSVG:
185 self.svgElement.getElementNodeByID('layerTextTemplate').removeFromIDNameParent()
186 del self.svgElement.getElementNodeByID('sliceElementTemplate').attributes['transform']
187 self.graphicsElementNode = self.svgElement.getElementNodeByID('sliceElementTemplate')
188 self.graphicsElementNode.attributes['id'] = 'z:'
189 self.addLoopLayersToOutput(loopLayers)
190 self.setMetadataNoscriptElement('layerHeight', 'Layer Height: ', self.layerHeight)
191 self.setMetadataNoscriptElement('maxX', 'X: ', self.cornerMaximum.x)
192 self.setMetadataNoscriptElement('minX', 'X: ', self.cornerMinimum.x)
193 self.setMetadataNoscriptElement('maxY', 'Y: ', self.cornerMaximum.y)
194 self.setMetadataNoscriptElement('minY', 'Y: ', self.cornerMinimum.y)
195 self.setMetadataNoscriptElement('maxZ', 'Z: ', self.cornerMaximum.z)
196 self.setMetadataNoscriptElement('minZ', 'Z: ', self.cornerMinimum.z)
197 self.textHeight = float( self.sliceDictionary['textHeight'] )
198 controlTop = len(loopLayers) * (self.margin + self.extent.y * self.unitScale + self.textHeight) + self.marginTop + self.textHeight
199 self.svgElement.getFirstChildByLocalName('title').setTextContent(os.path.basename(fileName).decode("utf-8").encode('ascii', 'replace') + ' - Slice Layers')
200 svgElementDictionary['height'] = '%spx' % self.getRounded(max(controlTop, self.controlBoxHeightMargin))
201 width = max(self.extent.x * self.unitScale, svgMinWidth)
202 svgElementDictionary['width'] = '%spx' % self.getRounded( width )
203 self.sliceDictionary['decimalPlacesCarried'] = str( self.decimalPlacesCarried )
204 if self.edgeWidth != None:
205 self.sliceDictionary['edgeWidth'] = self.getRounded( self.edgeWidth )
206 self.sliceDictionary['yAxisPointingUpward'] = 'true'
207 self.sliceDictionary['procedureName'] = procedureName
208 self.setDimensionTexts('dimX', 'X: ' + self.getRounded(self.extent.x))
209 self.setDimensionTexts('dimY', 'Y: ' + self.getRounded(self.extent.y))
210 self.setDimensionTexts('dimZ', 'Z: ' + self.getRounded(self.extent.z))
211 self.setTexts('numberOfLayers', 'Number of Layers: %s' % len(loopLayers))
213 for loopLayer in loopLayers:
214 volume += euclidean.getAreaLoops(loopLayer.loops)
215 volume *= 0.001 * self.layerHeight
216 self.setTexts('volume', 'Volume: %s cm3' % self.getRounded(volume))
217 if not self.addLayerTemplateToSVG:
218 self.svgElement.getFirstChildByLocalName('script').removeFromIDNameParent()
219 self.svgElement.getElementNodeByID('controls').removeFromIDNameParent()
220 self.graphicsElementNode.removeFromIDNameParent()
221 self.addOriginalAsComment(elementNode)
222 return documentNode.__repr__()
224 def getRounded(self, number):
225 'Get number rounded to the number of carried decimal places as a string.'
226 return euclidean.getRoundedToPlacesString(self.decimalPlacesCarried, number)
228 def getRoundedComplexString(self, point):
229 'Get the rounded complex string.'
230 return self.getRounded( point.real ) + ' ' + self.getRounded( point.imag )
232 def getSVGStringForLoop( self, loop ):
233 'Get the svg loop string.'
236 return self.getSVGStringForPath(loop) + ' z'
238 def getSVGStringForLoops( self, loops ):
239 'Get the svg loops string.'
242 loopString += self.getSVGStringForLoop( loops[0] )
243 for loop in loops[1 :]:
244 loopString += ' ' + self.getSVGStringForLoop(loop)
247 def getSVGStringForPath( self, path ):
248 'Get the svg path string.'
251 stringBeginning = 'M '
252 if len( svgLoopString ) > 0:
253 stringBeginning = ' L '
254 svgLoopString += stringBeginning + self.getRoundedComplexString(point)
257 def getTransformString(self):
258 'Get the svg transform string.'
259 cornerMinimumXString = self.getRounded(-self.cornerMinimum.x)
260 cornerMinimumYString = self.getRounded(-self.cornerMinimum.y)
261 return 'scale(%s, %s) translate(%s, %s)' % (self.unitScale, - self.unitScale, cornerMinimumXString, cornerMinimumYString)
263 def setDimensionTexts(self, key, valueString):
264 'Set the texts to the valueString followed by mm.'
265 self.setTexts(key, valueString + ' mm')
267 def setMetadataNoscriptElement(self, key, prefix, value):
268 'Set the metadata value and the text.'
269 valueString = self.getRounded(value)
270 self.sliceDictionary[key] = valueString
271 self.setDimensionTexts(key, prefix + valueString)
273 def setTexts(self, key, valueString):
274 'Set the texts to the valueString.'
275 self.svgElement.getElementNodeByID(key + 'Iso').setTextContent(valueString)
276 self.svgElement.getElementNodeByID(key + 'Layer').setTextContent(valueString)
277 self.svgElement.getElementNodeByID(key + 'Scroll').setTextContent(valueString)