chiark / gitweb /
Add back the ultimaker platform, and made the platform mesh simpler.
[cura.git] / Cura / slice / cura_sf / fabmetheus_utilities / svg_writer.py
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 __future__ import absolute_import
9
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
18 import cStringIO
19 import math
20 import os
21
22
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'
26
27
28 globalOriginalTextString = '<!-- Original XML Text:\n'
29
30
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:
35                 return None
36         return pluginModule.getCarving(fileName)
37
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):
43                                 return childNode
44         return None
45
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
52         return {}
53
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
64
65 def getSVGByLoopLayers(addLayerTemplateToSVG, carving, loopLayers):
66         'Get the svg text.'
67         if len(loopLayers) < 1:
68                 return ''
69         decimalPlacesCarried = max(0, 2 - int(math.floor(math.log10(carving.layerHeight))))
70         svgWriter = SVGWriter(
71                 addLayerTemplateToSVG,
72                 carving.getCarveCornerMaximum(),
73                 carving.getCarveCornerMinimum(),
74                 decimalPlacesCarried,
75                 carving.getCarveLayerHeight())
76         return svgWriter.getReplacedSVGTemplate(carving.fileName, loopLayers, 'basic', carving.getFabmetheusXML())
77
78 def getTruncatedRotatedBoundaryLayers(loopLayers, repository):
79         'Get the truncated rotated boundary layers.'
80         return loopLayers[repository.layersFrom.value : repository.layersTo.value]
81
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:
86                         for point in loop:
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
93
94
95 class SVGWriter(object):
96         'A base class to get an svg skein from a carving.'
97         def __init__(self,
98                         addLayerTemplateToSVG,
99                         cornerMaximum,
100                         cornerMinimum,
101                         decimalPlacesCarried,
102                         layerHeight,
103                         edgeWidth=None):
104                 'Initialize.'
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
112                 self.unitScale = 3.7
113
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
130
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)
135
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()
141                 else:
142                         del self.pathDictionary['transform']
143                 self.pathDictionary['d'] = self.getSVGStringForLoops(loopLayer.loops)
144
145         def addOriginalAsComment(self, elementNode):
146                 'Add original elementNode as a comment.'
147                 if elementNode == None:
148                         return
149                 if elementNode.getNodeName() == '#comment':
150                         elementNode.setParentAddToChildNodes(self.svgElement)
151                         return
152                 elementNodeOutput = cStringIO.StringIO()
153                 elementNode.addXML(0, elementNodeOutput)
154                 textLines = archive.getTextLines(elementNodeOutput.getvalue())
155                 commentNodeOutput = cStringIO.StringIO()
156                 isComment = False
157                 for textLine in textLines:
158                         lineStripped = textLine.strip()
159                         if lineStripped[: len('<!--')] == '<!--':
160                                 isComment = True
161                         if not isComment:
162                                 if len(textLine) > 0:
163                                         commentNodeOutput.write(textLine + '\n')
164                         if '-->' in lineStripped:
165                                 isComment = False
166                 xml_simple_reader.CommentNode(self.svgElement, '%s%s-->\n' % (globalOriginalTextString, commentNodeOutput.getvalue())).appendSelfToParent()
167
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))
212                 volume = 0.0
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__()
223
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)
227
228         def getRoundedComplexString(self, point):
229                 'Get the rounded complex string.'
230                 return self.getRounded( point.real ) + ' ' + self.getRounded( point.imag )
231
232         def getSVGStringForLoop( self, loop ):
233                 'Get the svg loop string.'
234                 if len(loop) < 1:
235                         return ''
236                 return self.getSVGStringForPath(loop) + ' z'
237
238         def getSVGStringForLoops( self, loops ):
239                 'Get the svg loops string.'
240                 loopString = ''
241                 if len(loops) > 0:
242                         loopString += self.getSVGStringForLoop( loops[0] )
243                 for loop in loops[1 :]:
244                         loopString += ' ' + self.getSVGStringForLoop(loop)
245                 return loopString
246
247         def getSVGStringForPath( self, path ):
248                 'Get the svg path string.'
249                 svgLoopString = ''
250                 for point in path:
251                         stringBeginning = 'M '
252                         if len( svgLoopString ) > 0:
253                                 stringBeginning = ' L '
254                         svgLoopString += stringBeginning + self.getRoundedComplexString(point)
255                 return svgLoopString
256
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)
262
263         def setDimensionTexts(self, key, valueString):
264                 'Set the texts to the valueString followed by mm.'
265                 self.setTexts(key, valueString + ' mm')
266
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)
272
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)