1 """
2 Svg_writer is a class and collection of utilities to read from and write to an svg file.
3
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.
5
6 """
7
8 from fabmetheus_utilities.vector3 import Vector3
9 from fabmetheus_utilities.xml_simple_reader import XMLSimpleReader
10 from fabmetheus_utilities import archive
11 from fabmetheus_utilities import euclidean
12 from fabmetheus_utilities import xml_simple_reader
13 from fabmetheus_utilities import xml_simple_writer
14 import os, sys, math, StringIO
15 from importlib import import_module
16
17 __author__ = 'Enrique Perez (perez_enrique@yahoo.com)'
18 __date__ = '$Date: 2008/02/05 $'
19 __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
20
21
22 globalOriginalTextString = '<!-- Original XML Text:\n'
23
24 __interpret_plugins_path__ = 'fabmetheus_utilities/fabmetheus_tools/interpret_plugins'
25
27 'Get a carving for the file using an import plugin.'
28
29 fileExtension = os.path.splitext(fileName)[1][1:].lower()
30 sys.path.insert(0, __interpret_plugins_path__)
31 pluginModule = import_module(fileExtension)
32 if pluginModule == None:
33 return None
34 return pluginModule.getCarving(fileName)
35
43
45 'Get the metadata slice attribute dictionary.'
46 for metadataElement in xmlElement.getChildNodesByLocalName('metadata'):
47 for childNode in metadataElement.childNodes:
48 if childNode.localName.lower() == 'slice:layers':
49 return childNode.attributeDictionary
50 return {}
51
53 'Get the slice elements.'
54 gXMLElements = xmlElement.getChildNodesByLocalNameRecursively('g')
55 sliceXMLElements = []
56 for gXMLElement in gXMLElements:
57 if 'id' in gXMLElement.attributeDictionary:
58 idValue = gXMLElement.attributeDictionary['id'].strip()
59 if idValue.startswith('z:'):
60 sliceXMLElements.append(gXMLElement)
61 return sliceXMLElements
62
75
77 'Parse SVG text and store the layers.'
78 for rotatedLoopLayer in rotatedLoopLayers:
79 for loop in rotatedLoopLayer.loops:
80 for point in loop:
81 pointVector3 = Vector3(point.real, point.imag, rotatedLoopLayer.z)
82 cornerMaximum.maximize(pointVector3)
83 cornerMinimum.minimize(pointVector3)
84 halfLayerThickness = 0.5 * layerThickness
85 cornerMaximum.z += halfLayerThickness
86 cornerMinimum.z -= halfLayerThickness
87
88
90 'A base class to get an svg skein from a carving.'
91 - def __init__(self,
92 addLayerTemplateToSVG,
93 cornerMaximum,
94 cornerMinimum,
95 decimalPlacesCarried,
96 layerThickness,
97 perimeterWidth=None):
98 'Initialize.'
99 self.addLayerTemplateToSVG = addLayerTemplateToSVG
100 self.cornerMaximum = cornerMaximum
101 self.cornerMinimum = cornerMinimum
102 self.decimalPlacesCarried = decimalPlacesCarried
103 self.layerThickness = layerThickness
104 self.perimeterWidth = perimeterWidth
105 self.textHeight = 22.5
106 self.unitScale = 3.7
107
109 'Add the start lines for the layer.'
110 zRounded = self.getRounded(rotatedLoopLayer.z)
111 self.graphicsCopy = self.graphicsXMLElement.getCopy(zRounded, self.graphicsXMLElement.parentNode)
112 if self.addLayerTemplateToSVG:
113 translateXRounded = self.getRounded(self.controlBoxWidth + self.margin + self.margin)
114 layerTranslateY = self.marginTop
115 layerTranslateY += layerIndex * self.textHeight + (layerIndex + 1) * (self.extent.y * self.unitScale + self.margin)
116 translateYRounded = self.getRounded(layerTranslateY)
117 self.graphicsCopy.attributeDictionary['transform'] = 'translate(%s, %s)' % (translateXRounded, translateYRounded)
118 layerString = 'Layer %s, z:%s' % (layerIndex, zRounded)
119 self.graphicsCopy.getFirstChildByLocalName('text').text = layerString
120 self.graphicsCopy.attributeDictionary['inkscape:groupmode'] = 'layer'
121 self.graphicsCopy.attributeDictionary['inkscape:label'] = layerString
122 self.pathXMLElement = self.graphicsCopy.getFirstChildByLocalName('path')
123 self.pathDictionary = self.pathXMLElement.attributeDictionary
124
150
152 'Add rotated boundary layer to the output.'
153 self.addLayerBegin(layerIndex, rotatedLoopLayer)
154 if rotatedLoopLayer.rotation != None:
155 self.graphicsCopy.attributeDictionary['bridgeRotation'] = str(rotatedLoopLayer.rotation)
156 if self.addLayerTemplateToSVG:
157 self.pathDictionary['transform'] = self.getTransformString()
158 else:
159 del self.pathDictionary['transform']
160 self.pathDictionary['d'] = self.getSVGStringForLoops(rotatedLoopLayer.loops)
161
163 'Add rotated boundary layers to the output.'
164 for rotatedLoopLayerIndex, rotatedLoopLayer in enumerate(rotatedLoopLayers):
165 self.addRotatedLoopLayerToOutput(rotatedLoopLayerIndex, rotatedLoopLayer)
166
168 'Get the lines of text from the layer_template.svg file.'
169 self.extent = self.cornerMaximum - self.cornerMinimum
170 svgTemplateText = archive.getFileText(archive.getTemplatesPath('layer_template.svg'))
171 self.xmlParser = XMLSimpleReader( fileName, None, svgTemplateText )
172 self.svgElement = self.xmlParser.getRoot()
173 svgElementDictionary = self.svgElement.attributeDictionary
174 self.sliceDictionary = getSliceDictionary(self.svgElement)
175 self.controlBoxHeight = float(self.sliceDictionary['controlBoxHeight'])
176 self.controlBoxWidth = float(self.sliceDictionary['controlBoxWidth'])
177 self.margin = float(self.sliceDictionary['margin'])
178 self.marginTop = float(self.sliceDictionary['marginTop'])
179 self.textHeight = float(self.sliceDictionary['textHeight'])
180 self.unitScale = float(self.sliceDictionary['unitScale'])
181 svgMinWidth = float(self.sliceDictionary['svgMinWidth'])
182 self.controlBoxHeightMargin = self.controlBoxHeight + self.marginTop
183 if not self.addLayerTemplateToSVG:
184 self.svgElement.getXMLElementByID('layerTextTemplate').removeFromIDNameParent()
185 del self.svgElement.getXMLElementByID('sliceElementTemplate').attributeDictionary['transform']
186 self.graphicsXMLElement = self.svgElement.getXMLElementByID('sliceElementTemplate')
187 self.graphicsXMLElement.attributeDictionary['id'] = 'z:'
188 self.addRotatedLoopLayersToOutput(rotatedLoopLayers)
189 self.setMetadataNoscriptElement('layerThickness', 'Layer Thickness: ', self.layerThickness)
190 self.setMetadataNoscriptElement('maxX', 'X: ', self.cornerMaximum.x)
191 self.setMetadataNoscriptElement('minX', 'X: ', self.cornerMinimum.x)
192 self.setMetadataNoscriptElement('maxY', 'Y: ', self.cornerMaximum.y)
193 self.setMetadataNoscriptElement('minY', 'Y: ', self.cornerMinimum.y)
194 self.setMetadataNoscriptElement('maxZ', 'Z: ', self.cornerMaximum.z)
195 self.setMetadataNoscriptElement('minZ', 'Z: ', self.cornerMinimum.z)
196 self.textHeight = float( self.sliceDictionary['textHeight'] )
197 controlTop = len(rotatedLoopLayers) * (self.margin + self.extent.y * self.unitScale + self.textHeight) + self.marginTop + self.textHeight
198 self.svgElement.getFirstChildByLocalName('title').text = os.path.basename(fileName) + ' - Slice Layers'
199 svgElementDictionary['height'] = '%spx' % self.getRounded(max(controlTop, self.controlBoxHeightMargin))
200 width = max(self.extent.x * self.unitScale, svgMinWidth)
201 svgElementDictionary['width'] = '%spx' % self.getRounded( width )
202 self.sliceDictionary['decimalPlacesCarried'] = str( self.decimalPlacesCarried )
203 if self.perimeterWidth != None:
204 self.sliceDictionary['perimeterWidth'] = self.getRounded( self.perimeterWidth )
205 self.sliceDictionary['yAxisPointingUpward'] = 'true'
206 self.sliceDictionary['procedureName'] = procedureName
207 self.setDimensionTexts('dimX', 'X: ' + self.getRounded(self.extent.x))
208 self.setDimensionTexts('dimY', 'Y: ' + self.getRounded(self.extent.y))
209 self.setDimensionTexts('dimZ', 'Z: ' + self.getRounded(self.extent.z))
210 self.setTexts('numberOfLayers', 'Number of Layers: %s' % len(rotatedLoopLayers))
211 volume = 0.0
212 for rotatedLoopLayer in rotatedLoopLayers:
213 volume += euclidean.getAreaLoops(rotatedLoopLayer.loops)
214 volume *= 0.001
215 self.setTexts('volume', 'Volume: %s cm3' % self.getRounded(volume))
216 if not self.addLayerTemplateToSVG:
217 self.svgElement.getFirstChildByLocalName('script').removeFromIDNameParent()
218 self.svgElement.getXMLElementByID('isoControlBox').removeFromIDNameParent()
219 self.svgElement.getXMLElementByID('layerControlBox').removeFromIDNameParent()
220 self.svgElement.getXMLElementByID('scrollControlBox').removeFromIDNameParent()
221 self.graphicsXMLElement.removeFromIDNameParent()
222 self.addOriginalAsComment(xmlElement)
223 output = StringIO.StringIO()
224 output.write(self.xmlParser.beforeRoot)
225 self.svgElement.addXML(0, output)
226 return xml_simple_writer.getBeforeRootOutput(self.xmlParser)
227
231
233 'Get the rounded complex string.'
234 return self.getRounded( point.real ) + ' ' + self.getRounded( point.imag )
235
237 'Get the svg loop string.'
238 if len(loop) < 1:
239 return ''
240 return self.getSVGStringForPath(loop) + ' z'
241
243 'Get the svg loops string.'
244 loopString = ''
245 if len(loops) > 0:
246 loopString += self.getSVGStringForLoop( loops[0] )
247 for loop in loops[1 :]:
248 loopString += ' ' + self.getSVGStringForLoop(loop)
249 return loopString
250
252 'Get the svg path string.'
253 svgLoopString = ''
254 for point in path:
255 stringBeginning = 'M '
256 if len( svgLoopString ) > 0:
257 stringBeginning = ' L '
258 svgLoopString += stringBeginning + self.getRoundedComplexString(point)
259 return svgLoopString
260
266
267 - def setDimensionTexts(self, key, valueString):
268 'Set the texts to the valueString followed by mm.'
269 self.setTexts(key, valueString + ' mm')
270
271 - def setTexts(self, key, valueString):
272 'Set the texts to the valueString.'
273 self.svgElement.getXMLElementByID(key + 'Iso').text = valueString
274 self.svgElement.getXMLElementByID(key + 'Layer').text = valueString
275 self.svgElement.getXMLElementByID(key + 'Scroll').text = valueString
276
282