From 14f078c8217450317b4824cb450b336ba6590011 Mon Sep 17 00:00:00 2001 From: Michael Schwarz Date: Wed, 5 Aug 2015 13:57:53 +0200 Subject: Inkscape export: Renamed module to inkscape. This module will later be used for other export types than just DXF. --- support/inkscape/effect.py | 201 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 support/inkscape/effect.py (limited to 'support/inkscape/effect.py') diff --git a/support/inkscape/effect.py b/support/inkscape/effect.py new file mode 100644 index 0000000..c72037c --- /dev/null +++ b/support/inkscape/effect.py @@ -0,0 +1,201 @@ +""" +Based on code from Aaron Spike. See http://www.bobcookdev.com/inkscape/inkscape-dxf.html +""" + +import pkgutil, os, re +from . import inkex, simpletransform, cubicsuperpath, cspsubdiv + + +def _get_unit_factors_map(): + # Fluctuates somewhat between Inkscape releases. + pixels_per_inch = 96. + pixels_per_mm = pixels_per_inch / 25.4 + + return { + 'px': 1.0, + 'mm': pixels_per_mm, + 'cm': pixels_per_mm * 10, + 'm' : pixels_per_mm * 1e3, + 'km': pixels_per_mm * 1e6, + 'pt': pixels_per_inch / 72, + 'pc': pixels_per_inch / 6, + 'in': pixels_per_inch, + 'ft': pixels_per_inch * 12, + 'yd': pixels_per_inch * 36 } + + +class Layer(object): + def __init__(self, inkscape_name, export_name, use_paths): + self.inkscape_name = inkscape_name + self.export_name = export_name + self.use_paths = use_paths + + +class ExportEffect(inkex.Effect): + _unit_factors = _get_unit_factors_map() + + def __init__(self, layers): + inkex.Effect.__init__(self) + + self._layers_by_inkscape_name = { i.inkscape_name: i for i in layers } + self._lines = [] + self._handle = 255 + self._layer_indices = { } + self._flatness = float(os.environ['DXF_FLATNESS']) + + def _get_user_unit(self): + """ + Return the size in pixels of the unit used for measures without an explicit unit. + """ + + document_height = self._measure_to_pixels(self._get_document_height_attr()) + view_box_attr = self.document.getroot().get('viewBox') + + if view_box_attr: + _, _, _, view_box_height = map(float, view_box_attr.split()) + else: + view_box_height = document_height + + return document_height / view_box_height + + def _get_document_unit(self): + """ + Return the size in pixels that the user is working with in Inkscape. + """ + + inkscape_unit_attrs = self.document.getroot().xpath('./sodipodi:namedview/@inkscape:document-units', namespaces = inkex.NSS) + + if inkscape_unit_attrs: + unit = inkscape_unit_attrs[0] + else: + _, unit = self._parse_measure(self._get_document_height_attr()) + + return self._get_unit_factor(unit) + + def _get_document_height_attr(self): + return self.document.getroot().xpath('@height', namespaces = inkex.NSS)[0] + + def _get_layer_index(self, layer_name): + index = self._layer_indices.get(layer_name) + + if index is None: + index = len(self._layer_indices) + self._layer_indices[layer_name] = index + + return index + + def _add_line(self, layer_name, csp): + (x1, y1), (x2, y2) = csp + line = layer_name, x1, y1, x2, y2 + + self._lines.append(line) + + def _add_path(self, layer_name, path): + cspsubdiv.cspsubdiv(path, self._flatness) + + for sub in path: + for i in range(len(sub) - 1): + self._handle += 1 + s = sub[i] + e = sub[i + 1] + self._add_line(layer_name, [s[1], e[1]]) + + def _add_shape(self, node, document_transform, element_transform): + path = cubicsuperpath.parsePath(node.get('d')) + layer = self._layers_by_inkscape_name.get(self._get_inkscape_layer_name(node)) + + if layer is None: + layer_name = '' + else: + layer_name = layer.export_name + + transform = simpletransform.composeTransform( + document_transform, + simpletransform.composeParents(node, element_transform)) + + simpletransform.applyTransformToPath(transform, path) + + self._add_path(layer_name, path) + + def effect(self): + user_unit = self._get_user_unit() + document_unit = self._get_document_unit() + height = self._measure_to_pixels(self._get_document_height_attr()) + + document_transform = simpletransform.composeTransform( + [[1 / document_unit, 0, 0], [0, 1 / document_unit, 0]], + [[1, 0, 0], [0, -1, height]]) + + element_transform = [[user_unit, 0, 0], [0, user_unit, 0]] + + for node in self.document.getroot().xpath('//svg:path', namespaces = inkex.NSS): + self._add_shape(node, document_transform, element_transform) + + def write_dxf(self, file): + file.write(pkgutil.get_data(__name__, 'dxf_header.txt')) + + def _write_instruction(code, value): + print >> file, code + print >> file, value + + for layer_name, x1, y1, x2, y2 in self._lines: + _write_instruction(0, 'LINE') + _write_instruction(8, layer_name) + _write_instruction(62, self._get_layer_index(layer_name)) + _write_instruction(5, '{:x}'.format(self._handle)) + _write_instruction(100, 'AcDbEntity') + _write_instruction(100, 'AcDbLine') + _write_instruction(10, repr(x1)) + _write_instruction(20, repr(y1)) + _write_instruction(30, 0.0) + _write_instruction(11, repr(x2)) + _write_instruction(21, repr(y2)) + _write_instruction(31, 0.0) + + file.write(pkgutil.get_data(__name__, 'dxf_footer.txt')) + + @classmethod + def _parse_measure(cls, string): + value_match = re.match(r'(([-+]?[0-9]+(\.[0-9]*)?|[-+]?\.[0-9]+)([eE][-+]?[0-9]+)?)', string) + unit_match = re.search('(%s)$' % '|'.join(cls._unit_factors.keys()), string) + + value = float(string[value_match.start():value_match.end()]) + + if unit_match: + unit = string[unit_match.start():unit_match.end()] + else: + unit = None + + return value, unit + + @classmethod + def _measure_to_pixels(cls, string, default_unit_factor = None): + """ + Parse a string containing a measure and return it's value converted to pixels. If the measure has no unit, it will be assumed that the unit has the size of the specified number of pixels. + """ + + value, unit = cls._parse_measure(string) + + return value * cls._get_unit_factor(unit, default_unit_factor) + + @classmethod + def _get_inkscape_layer_name(cls, node): + while node is not None: + layer = node.get(inkex.addNS('label', 'inkscape')) + + if layer is not None: + return layer + + node = node.getparent() + + return None + + @classmethod + def _get_unit_factor(cls, unit, default = None): + if unit is None: + if default is None: + default = 1 + + return default + else: + return cls._unit_factors[unit] -- cgit