From 6002d409143a6726899a4de15c3a6b279a6b1d71 Mon Sep 17 00:00:00 2001 From: jaseg Date: Fri, 27 Sep 2019 10:07:38 +0200 Subject: Directory reorg: Put renderer into its own subdir --- renderer/support/pogojig/inkscape/effect.py | 279 ++++++++++++++++++++++++++++ 1 file changed, 279 insertions(+) create mode 100644 renderer/support/pogojig/inkscape/effect.py (limited to 'renderer/support/pogojig/inkscape/effect.py') diff --git a/renderer/support/pogojig/inkscape/effect.py b/renderer/support/pogojig/inkscape/effect.py new file mode 100644 index 0000000..89824e9 --- /dev/null +++ b/renderer/support/pogojig/inkscape/effect.py @@ -0,0 +1,279 @@ +""" +Based on code from Aaron Spike. See +http://www.bobcookdev.com/inkscape/inkscape-dxf.html +""" + +import collections +import itertools +import os +import pkgutil +import re +from lxml import etree + +from . import inkex, simpletransform, cubicsuperpath, cspsubdiv, inkscape + + +def _get_unit_factors_map(): + # Fluctuates somewhat between Inkscape releases _and_ between SVG version. + 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 ExportEffect(inkex.Effect): + _unit_factors = _get_unit_factors_map() + _asymptote_all_paths_name = 'all' + + def __init__(self): + inkex.Effect.__init__(self) + + self._flatness = float(os.environ.get('INKSCAPE_DXF_FLATNESS', 0.1)) + + self._layers = None + self._paths = None + + def _get_document_scale(self): + """ + Return scaling factor applied to the document because of a viewBox + setting. This currently ignores any setting of a preserveAspectRatio + attribute (like Inkscape). + """ + document_height = self._get_height() + view_box = self._get_view_box() + + if view_box is None or document_height is None: + return 1 + else: + _, _, _, view_box_height = view_box + + return document_height / view_box_height + + def _get_document_height(self): + """ + Get the height of the document in pixels in the document coordinate + system as it is interpreted by Inkscape. + """ + view_box = self._get_view_box() + document_height = self._get_height() + + if view_box is not None: + _, _, _, view_box_height = view_box + + return view_box_height + elif document_height is not None: + return document_height + else: + return 0 + + def _get_height(self): + height_attr = self.document.getroot().get('height') + + if height_attr is None: + return None + else: + return self._measure_to_pixels(height_attr) + + def _get_view_box(self): + view_box_attr = self.document.getroot().get('viewBox') + + if view_box_attr is None: + return None + else: + return [float(i) for i in view_box_attr.split()] + + def _get_shape_paths(self, node, transform): + shape = cubicsuperpath.parsePath(node.get('d')) + + transform = simpletransform.composeTransform( + transform, + simpletransform.composeParents(node, [[1, 0, 0], [0, 1, 0]])) + + simpletransform.applyTransformToPath(transform, shape) + + def iter_paths(): + for path in shape: + cspsubdiv.subdiv(path, self._flatness) + + # path contains two control point coordinates and the actual + # coordinates per point. + yield [i for _, i, _ in path] + + return list(iter_paths()) + + def effect(self): + document_height = self._get_document_height() + document_scale = self._get_document_scale() + + transform = simpletransform.composeTransform( + [[document_scale, 0, 0], [0, document_scale, 0]], + [[1, 0, 0], [0, -1, document_height]]) + + layers = inkscape.get_inkscape_layers(self.svg_file) + layers_by_inkscape_name = {i.inkscape_name: i for i in layers} + + def iter_paths(): + for node in self.document.getroot().xpath('//svg:path', namespaces=inkex.NSS): + layer = layers_by_inkscape_name.get(self._get_inkscape_layer_name(node)) + + for path in self._get_shape_paths(node, transform): + yield layer, path + + self._layers = layers + self._paths = list(iter_paths()) + + def write_dxf(self, file): + # Scales pixels to millimeters. This is the predominant unit in CAD. + unit_factor = self._unit_factors['mm'] + + layer_indices = {l: i for i, l in enumerate(self._layers)} + + file.write(pkgutil.get_data(__name__, 'dxf_header.txt').decode('ASCII')) + + def write_instruction(code, value): + print(code, file=file) + print(value, file=file) + + handle_iter = itertools.count(256) + + for layer, path in self._paths: + for (x1, y1), (x2, y2) in zip(path, path[1:]): + write_instruction(0, 'LINE') + + if layer is not None: + write_instruction(8, layer.export_name) + write_instruction(62, layer_indices.get(layer, 0)) + + write_instruction(5, '{:x}'.format(next(handle_iter))) + write_instruction(100, 'AcDbEntity') + write_instruction(100, 'AcDbLine') + write_instruction(10, repr(x1 / unit_factor)) + write_instruction(20, repr(y1 / unit_factor)) + write_instruction(30, 0.0) + write_instruction(11, repr(x2 / unit_factor)) + write_instruction(21, repr(y2 / unit_factor)) + write_instruction(31, 0.0) + + file.write(pkgutil.get_data(__name__, 'dxf_footer.txt').decode('ASCII')) + + def write_asy(self, file): + def write_line(format, *args): + print >> file, format.format(*args) + ';' + + # Scales pixels to points. Asymptote uses PostScript points (1 / 72 + # inch) by default. + unit_factor = self._unit_factors['pt'] + + paths_by_layer = collections.defaultdict(list) + variable_names = [] + + for layer, path in self._paths: + paths_by_layer[layer].append(path) + + for layer in self._layers + [None]: + paths = paths_by_layer[layer] + variable_name = self._asymptote_identifier_from_layer(layer) + write_line('path[] {}', variable_name) + + variable_names.append(variable_name) + + for path in paths: + point_strs = ['({}, {})'.format(x / unit_factor, y / unit_factor) for x, y in path] + + # Hack. We should determine this from whether Z or z was used + # to close the path in the SVG document. + if path[0] == path[-1]: + point_strs[-1] = 'cycle' + + write_line('{}.push({})', variable_name, ' -- '.join(point_strs)) + + if self._asymptote_all_paths_name not in variable_names: + write_line('path[] {}', self._asymptote_all_paths_name) + + for i in variable_names: + write_line('{}.append({})', self._asymptote_all_paths_name, i) + + @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): + """ + Parse a string containing a measure and return it's value converted + to pixels. + """ + value, unit = cls._parse_measure(string) + + return value * cls._get_unit_factor(unit) + + @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): + if unit is None: + return 1 + else: + return cls._unit_factors[unit] + + @classmethod + def _asymptote_identifier_from_layer(cls, layer): + if layer is None: + return '_' + else: + return re.sub('[^a-zA-Z0-9]', '_', layer.export_name) + + @classmethod + def check_document_units(cls, path): + with open(path, 'r') as file: + p = etree.XMLParser(huge_tree = True) + document = etree.parse(file, parser = p) + + height_attr = document.getroot().get('height') + + if height_attr is None: + raise ValueError( + 'SVG document has no height attribute. See ' + 'https://github.com/Feuermurmel/openscad-template/wiki/Absolute-Measurements') + + _, height_unit = cls._parse_measure(height_attr) + + if height_unit is None or height_unit == 'px': + raise ValueError( + 'Height of SVG document is not an absolute measure. See ' + 'https://github.com/Feuermurmel/openscad-template/wiki/Absolute-Measurements') + + if document.getroot().get('viewBox') is None: + raise ValueError( + 'SVG document has no viewBox attribute. See ' + 'https://github.com/Feuermurmel/openscad-template/wiki/Absolute-Measurements') -- cgit