From bb53681a4f4a6fab8cec0caffdaf4b09ba6d8622 Mon Sep 17 00:00:00 2001 From: leyghisbb Date: Fri, 9 Apr 2021 16:31:33 +0200 Subject: [PATCH 1/9] some fixes in pathops (copied from gitlab) --- extensions/fablabchemnitz/pathops/pathops.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/extensions/fablabchemnitz/pathops/pathops.py b/extensions/fablabchemnitz/pathops/pathops.py index b3bbeba6..f5012ddd 100644 --- a/extensions/fablabchemnitz/pathops/pathops.py +++ b/extensions/fablabchemnitz/pathops/pathops.py @@ -206,7 +206,7 @@ class PathOps(inkex.Effect): def get_selected_ids(self): """Return a list of valid ids for inkscape path operations.""" id_list = [] - if not len(self.svg.selected): + if len(self.svg.selected) == 0: pass else: # level = 0: unlimited recursion into groups @@ -346,14 +346,14 @@ class PathOps(inkex.Effect): defs = get_defs(self.document.getroot()) inkscape_tagrefs = defs.findall( "inkscape:tag/inkscape:tagref", namespaces=inkex.NSS) - return True if len(inkscape_tagrefs) else False + return len(inkscape_tagrefs) > 0 def update_tagrefs(self, mode='purge'): """Check tagrefs for deleted objects.""" defs = get_defs(self.document.getroot()) inkscape_tagrefs = defs.findall( "inkscape:tag/inkscape:tagref", namespaces=inkex.NSS) - if len(inkscape_tagrefs): + if len(inkscape_tagrefs) > 0: for tagref in inkscape_tagrefs: href = tagref.get(inkex.addNS('href', 'xlink'))[1:] if self.svg.getElementById(href) is None: From 2ab27300e0a3acd3314d95a43e8d089be2f268a3 Mon Sep 17 00:00:00 2001 From: leyghisbb Date: Fri, 9 Apr 2021 17:39:49 +0200 Subject: [PATCH 2/9] Added parabola extension --- extensions/fablabchemnitz/parabola.inx | 37 ++++ extensions/fablabchemnitz/parabola.py | 240 +++++++++++++++++++++++++ 2 files changed, 277 insertions(+) create mode 100644 extensions/fablabchemnitz/parabola.inx create mode 100644 extensions/fablabchemnitz/parabola.py diff --git a/extensions/fablabchemnitz/parabola.inx b/extensions/fablabchemnitz/parabola.inx new file mode 100644 index 00000000..cab4ea94 --- /dev/null +++ b/extensions/fablabchemnitz/parabola.inx @@ -0,0 +1,37 @@ + + + Parabola + fablabchemnitz.de.parabola + + + 120 + 120 + 25 + + + + + + + + + + true + true + true + true + + + + + all + + + + + + + + diff --git a/extensions/fablabchemnitz/parabola.py b/extensions/fablabchemnitz/parabola.py new file mode 100644 index 00000000..a9f66f10 --- /dev/null +++ b/extensions/fablabchemnitz/parabola.py @@ -0,0 +1,240 @@ +#!/usr/bin/env python3 +# coding=utf-8 +# +# 2/27/2021 - v.1.1.0 +# Copyright (C) 2021 Reginald Waters opensourcebear@nthebare.com +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" +This extension renders a wireframe shape and then draws lines to form a parabola +shape. + +The height and width are independently variable. The number of lines will change +the density of the end product. +""" +import inkex + +from inkex import turtle as pturtle + +class parabola(inkex.GenerateExtension): + container_label = 'Parabola' + def add_arguments(self, pars): + pars.add_argument("--height", type=int, default=300, help="Shape Height") + pars.add_argument("--width", type=int, default=300, help="Shape Width") + pars.add_argument("--seg_count", type=int, default=10, help="Number of line segments") + pars.add_argument("--shape", default="square") + pars.add_argument("--tab", default="common") + pars.add_argument("--c1", default="true") + pars.add_argument("--c2", default="false") + pars.add_argument("--c3", default="false") + pars.add_argument("--c4", default="false") + + def generate(self): + # Let's simplify the variable names + ht = int(self.options.height) + wt = int(self.options.width) + sc = int(self.options.seg_count) + shape = self.options.shape + c1 = self.options.c1 + c2 = self.options.c2 + c3 = self.options.c3 + c4 = self.options.c4 + + point = self.svg.namedview.center + style = inkex.Style({ + 'stroke-linejoin': 'miter', 'stroke-width': str(self.svg.unittouu('1px')), + 'stroke-opacity': '1.0', 'fill-opacity': '1.0', + 'stroke': '#000000', 'stroke-linecap': 'butt', + 'fill': 'none' + }) + + # Setting the amount to move across the horizontal and vertical + increaseht = (ht / sc) + increasewt = (wt / sc) + + tur = pturtle.pTurtle() + + tur.pu() # Pen up + tur.setpos(point) # start in the center + + if shape == "cross": + # We draw the cross shape and store the 4 points + # Can this be looped? + # Should I store the coordinates in an array/list? + tur.forward((ht / 2)) + toppoint = tur.getpos() + if c3 == 'true' or c4 == 'true': + tur.pd() + tur.backward((ht / 2)) + tur.pu() + if c1 == 'true' or c2 == 'true': + tur.pd() + tur.backward((ht / 2)) + bottompoint = tur.getpos() + tur.pu() + tur.setpos(point) + tur.right(90) + tur.forward((wt / 2)) + rightpoint = tur.getpos() + if c3 == 'true' or c2 == 'true': + tur.pd() + tur.backward((wt / 2)) + tur.pu() + if c1 == 'true' or c4 == 'true': + tur.pd() + tur.backward((wt / 2)) + leftpoint = tur.getpos() + + while sc > 0: + if c1 == 'true': + # Drawing the SE Corner based on SW coordinates + # We always draw this corner + tur.pu() + tur.setpos((bottompoint[0], bottompoint[1] - ( (increaseht / 2) * sc ) )) + tur.pd() + tur.setpos((bottompoint[0] + ( (increasewt / 2) * sc ), bottompoint[1] - (ht / 2) )) + + if c2 == 'true': # Drawing the SW Corner based on SE Coordinates + tur.pu() + tur.setpos((bottompoint[0], bottompoint[1] - ( (increaseht / 2) * sc ) )) + tur.pd() + tur.setpos((bottompoint[0] - ( (increasewt / 2) * sc ), bottompoint[1] - (ht / 2) )) + + if c3 == 'true': # Drawing the NW Corner based on NE Coordinates + tur.pu() + tur.setpos((toppoint[0], toppoint[1] + ( (increaseht / 2) * sc ) )) + tur.pd() + tur.setpos((toppoint[0] - ( (increasewt / 2) * sc ), toppoint[1] + (ht / 2) )) + + if c4 == 'true': # Drawing the NE Corner based on NW Coordinates + tur.pu() + tur.setpos((toppoint[0], toppoint[1] + ( (increaseht / 2) * sc ) )) + tur.pd() + tur.setpos((toppoint[0] + ( (increasewt / 2) * sc ), toppoint[1] + (ht / 2) )) + + sc = sc - 1 + + if shape == "triangle": + # We draw the triangle and store the 3 corner points + # Loopable? + tur.backward((ht / 2)) + tur.right(90) + tur.forward((wt /2)) + cornera = tur.getpos() + if c3 == 'true' or c2 == 'true': + tur.pd() + tur.backward((wt)) + cornerb = tur.getpos() + tur.pu() + if c2 == 'true' or c1 == 'true': + tur.pd() + tur.setpos((point[0], (cornera[1] - ht) )) + cornerc = tur.getpos() + tur.pu() + if c1 == 'true' or c3 == 'true': + tur.pd() + tur.setpos(cornera) + +# So.. The math below took a lot of trial and error to figure out... +# I probably need to take some geography classes... + + while sc > 0: + if c1 == 'true': + tur.pu() + tur.setpos(( (cornerb[0] + ((increasewt / 2) * (sc)) - (wt / 2)), cornerb[1] + (increaseht * sc) - ht )) + tur.pd() + tur.setpos(( (cornera[0] + (increasewt / 2) * (sc)), cornera[1] - (increaseht * sc) )) + + if c2 == 'true': + tur.pu() + tur.setpos((cornerb[0] - (increasewt * sc ) , cornerb[1] )) + tur.pd() + tur.setpos(( (cornerb[0] + ((increasewt / 2) * sc) - (wt / 2)), cornerb[1] + (increaseht * sc) - ht )) + + if c3 == 'true': + tur.pu() + tur.setpos((cornera[0] + (increasewt * sc ) , cornera[1] )) + tur.pd() + tur.setpos(( (cornera[0] - ((increasewt / 2) * sc) + (wt / 2)), cornera[1] + (increaseht * sc) - ht )) + + sc = sc - 1 + + + if shape == "square": + # We draw out the square shape and store the coordinates for each corner + # Can this be looped? + tur.right(90) + tur.forward((wt / 2)) + tur.right(90) + tur.forward((ht / 2)) + swcorner = tur.getpos() + if c4 == 'true' or c3 == 'true': # We only draw the 2 lines that are part of these corners + tur.pd() # Pen Down + tur.right(90) + tur.forward(wt) + secorner = tur.getpos() + tur.pu() + if c3 == 'true' or c2 == 'true': # We only draw the 2 lines that are part of these corners + tur.pd() + tur.right(90) + tur.forward(ht) + necorner = tur.getpos() + tur.pu() + if c1 == 'true' or c2 == 'true': # We only draw the 2 lines that are part of these corners + tur.pd() + tur.right(90) + tur.forward(wt) + nwcorner = tur.getpos() + tur.right(90) + tur.pu() + if c4 == 'true' or c1 == 'true': # We only draw the 2 lines that are part of these corners + tur.pd() + tur.forward(ht) + + while sc > 0: + if c1 == 'true': + # Drawing the NW Corner based on SW coordinates + # We always draw this corner + tur.pu() + tur.setpos((swcorner[0], swcorner[1] - ( increaseht * sc ) )) + tur.pd() + tur.setpos((swcorner[0] + ( increasewt * sc ), swcorner[1] - ht)) + + if c2 == 'true': # Drawing the NE Corner based on SE Coordinates + tur.pu() + tur.setpos((secorner[0], secorner[1] - ( increaseht * sc ) )) + tur.pd() + tur.setpos((secorner[0] - ( increasewt * sc ), secorner[1] - ht)) + + if c3 == 'true': # Drawing the SE Corner based on NE Coordinates + tur.pu() + tur.setpos((necorner[0], necorner[1] + ( increaseht * sc ) )) + tur.pd() + tur.setpos((necorner[0] - ( increasewt * sc ), necorner[1] + ht)) + + if c4 == 'true': # Drawing the SW Corner based on NW Coordinates + tur.pu() + tur.setpos((nwcorner[0], nwcorner[1] + ( increaseht * sc ) )) + tur.pd() + tur.setpos((nwcorner[0] + ( increasewt * sc ), nwcorner[1] + ht)) + + sc = sc - 1 + + return inkex.PathElement(d=tur.getPath(), style=str(style)) + +if __name__ == "__main__": + # execute only if run as a script + parabola().run() From 5dda2bedc32243b2fb029f1d527a606ebd0e6023 Mon Sep 17 00:00:00 2001 From: leyghisbb Date: Fri, 9 Apr 2021 18:16:33 +0200 Subject: [PATCH 3/9] Added flip extension --- extensions/fablabchemnitz/flip.inx | 16 +++++++++++ extensions/fablabchemnitz/flip.py | 38 ++++++++++++++++++++++++++ extensions/fablabchemnitz/parabola.inx | 5 ++-- 3 files changed, 56 insertions(+), 3 deletions(-) create mode 100644 extensions/fablabchemnitz/flip.inx create mode 100644 extensions/fablabchemnitz/flip.py diff --git a/extensions/fablabchemnitz/flip.inx b/extensions/fablabchemnitz/flip.inx new file mode 100644 index 00000000..c4a05daf --- /dev/null +++ b/extensions/fablabchemnitz/flip.inx @@ -0,0 +1,16 @@ + + + Flip + fablabchemnitz.de.Flip + + path + + + + + + + + \ No newline at end of file diff --git a/extensions/fablabchemnitz/flip.py b/extensions/fablabchemnitz/flip.py new file mode 100644 index 00000000..7ece6cea --- /dev/null +++ b/extensions/fablabchemnitz/flip.py @@ -0,0 +1,38 @@ +import math +from inkex import EffectExtension, PathElement, transforms as T + +# https://en.wikipedia.org/wiki/Rotations_and_reflections_in_two_dimensions +def reflection_matrix(theta): + theta2 = 2 * theta + return [ + [math.cos(theta2), math.sin(theta2), 0], + [math.sin(theta2), -math.cos(theta2), 0], + ] + +def svg_matrix_order(mat): + ((a, c, e), (b, d, f)) = mat + return a, b, c, d, e, f + +class FlipPath(EffectExtension): + """Extension to flip a path about the line from the start to end node""" + + def effect(self): + for node in self.svg.selection.filter(PathElement).values(): + points = list(node.path.end_points) + if len(points) < 2 or points[0] == points[-1]: + continue + start = points[0] + end = points[-1] + v = end - start + theta = math.atan2(v.y, v.x) + + # transforms go in reverse order + mat = T.Transform() + mat.add_translate(start) + mat.add_matrix(*svg_matrix_order(reflection_matrix(theta))) + mat.add_translate(-start) + + node.path = node.path.transform(mat) + +if __name__ == '__main__': + FlipPath().run() \ No newline at end of file diff --git a/extensions/fablabchemnitz/parabola.inx b/extensions/fablabchemnitz/parabola.inx index cab4ea94..2708e9a4 100644 --- a/extensions/fablabchemnitz/parabola.inx +++ b/extensions/fablabchemnitz/parabola.inx @@ -11,8 +11,7 @@ - - + @@ -34,4 +33,4 @@ - + \ No newline at end of file From b496df017de1fc96614fe384b67acae053109015 Mon Sep 17 00:00:00 2001 From: leyghisbb Date: Fri, 9 Apr 2021 19:13:32 +0200 Subject: [PATCH 4/9] added open closed path and export selection as svg --- .../fablabchemnitz/export_selection.inx | 19 ++++ extensions/fablabchemnitz/export_selection.py | 100 ++++++++++++++++++ extensions/fablabchemnitz/openClosedPath.inx | 15 +++ extensions/fablabchemnitz/openClosedPath.py | 37 +++++++ 4 files changed, 171 insertions(+) create mode 100644 extensions/fablabchemnitz/export_selection.inx create mode 100644 extensions/fablabchemnitz/export_selection.py create mode 100644 extensions/fablabchemnitz/openClosedPath.inx create mode 100644 extensions/fablabchemnitz/openClosedPath.py diff --git a/extensions/fablabchemnitz/export_selection.inx b/extensions/fablabchemnitz/export_selection.inx new file mode 100644 index 00000000..78e188bf --- /dev/null +++ b/extensions/fablabchemnitz/export_selection.inx @@ -0,0 +1,19 @@ + + + <_name>Export selection as SVG + fablabchemnitz.de.export_selection_as_svg + false + ./inkscape_export/ + + all + Export selection to separate SVG file. + + + + + + + + diff --git a/extensions/fablabchemnitz/export_selection.py b/extensions/fablabchemnitz/export_selection.py new file mode 100644 index 00000000..e3692440 --- /dev/null +++ b/extensions/fablabchemnitz/export_selection.py @@ -0,0 +1,100 @@ +# -*- coding: utf-8 -*- +from copy import deepcopy +from pathlib import Path +import logging +import math +import os + +import inkex +import inkex.command +from lxml import etree +from scour.scour import scourString + + +logger = logging.getLogger(__name__) + + +GROUP_ID = 'export_selection_transform' + + +class ExportObject(inkex.EffectExtension): + def add_arguments(self, pars): + pars.add_argument("--wrap_transform", type=inkex.Boolean, default=False, help="Wrap final document in transform") + pars.add_argument("--export_dir", default="~/inkscape_export/", help="Location to save exported documents") + + def effect(self): + if not self.svg.selected: + return + + export_dir = Path(self.absolute_href(self.options.export_dir)) + os.makedirs(export_dir, exist_ok=True) + + bbox = inkex.BoundingBox() + for elem in self.svg.selected.values(): + transform = inkex.Transform() + parent = elem.getparent() + if parent is not None and isinstance(parent, inkex.ShapeElement): + transform = parent.composed_transform() + try: + bbox += elem.bounding_box(transform) + except Exception: + logger.exception("Bounding box not computed") + logger.info("Skipping bounding box") + transform = elem.composed_transform() + x1, y1 = transform.apply_to_point([0, 0]) + x2, y2 = transform.apply_to_point([1, 1]) + bbox += inkex.BoundingBox((x1, x2), (y1, y2)) + + template = self.create_document() + filename = None + + group = etree.SubElement(template, '{http://www.w3.org/2000/svg}g') + group.attrib['id'] = GROUP_ID + group.attrib['transform'] = str(inkex.Transform(((1, 0, -bbox.left), (0, 1, -bbox.top)))) + + for elem in self.svg.selected.values(): + + elem_copy = deepcopy(elem) + elem_copy.attrib['transform'] = str(elem.composed_transform()) + group.append(elem_copy) + + width = math.ceil(bbox.width) + height = math.ceil(bbox.height) + template.attrib['viewBox'] = f'0 0 {width} {height}' + template.attrib['width'] = f'{width}' + template.attrib['height'] = f'{height}' + + if filename is None: + filename = elem.attrib.get('id', None) + if filename: + filename = filename.replace(os.sep, '_') + '.svg' + if not filename: + filename = 'element.svg' + + template.append(group) + + if not self.options.wrap_transform: + self.load(inkex.command.inkscape_command(template.tostring(), select=GROUP_ID, verbs=['SelectionUnGroup'])) + template = self.svg + for child in template.getchildren(): + if child.tag == '{http://www.w3.org/2000/svg}metadata': + template.remove(child) + + self.save_document(template, export_dir / filename) + + def create_document(self): + document = self.svg.copy() + for child in document.getchildren(): + if child.tag == '{http://www.w3.org/2000/svg}defs': + continue + document.remove(child) + return document + + def save_document(self, document, filename): + with open(filename, 'wb') as fp: + document = document.tostring() + fp.write(scourString(document).encode('utf8')) + + +if __name__ == '__main__': + ExportObject().run() \ No newline at end of file diff --git a/extensions/fablabchemnitz/openClosedPath.inx b/extensions/fablabchemnitz/openClosedPath.inx new file mode 100644 index 00000000..0233808a --- /dev/null +++ b/extensions/fablabchemnitz/openClosedPath.inx @@ -0,0 +1,15 @@ + + + Open closed Path + fablabchemnitz.de.openClosedPath + + + + + + + + + \ No newline at end of file diff --git a/extensions/fablabchemnitz/openClosedPath.py b/extensions/fablabchemnitz/openClosedPath.py new file mode 100644 index 00000000..1afb0335 --- /dev/null +++ b/extensions/fablabchemnitz/openClosedPath.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 +# coding=utf-8 +# +# Copyright (C) 2020 Ellen Wasboe, ellen@wasbo.net +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +""" +Remove all z-s from all selected paths +My purpose: to open paths of single line fonts with a temporary closing to fit into .ttf/.otf format files +""" + +import inkex, re +from inkex import PathElement + +class OpenClosedPath(inkex.EffectExtension): +# Extension to open a closed path by z or by last node + + def effect(self): + elements = self.svg.selection.filter(PathElement).values() + for elem in elements: + pp=elem.path.to_absolute() #remove transformation matrix + elem.path = re.sub(r"Z","",str(pp)) + +if __name__ == '__main__': + OpenClosedPath().run() From 2807b7b68802a3280271498fbee6babf04b564ba Mon Sep 17 00:00:00 2001 From: leyghisbb Date: Sat, 10 Apr 2021 12:32:59 +0200 Subject: [PATCH 5/9] removed some pre-developed string in vpypetools --- extensions/fablabchemnitz/vpypetools/vpypetools.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/fablabchemnitz/vpypetools/vpypetools.py b/extensions/fablabchemnitz/vpypetools/vpypetools.py index a44a6e57..a0f7c68a 100644 --- a/extensions/fablabchemnitz/vpypetools/vpypetools.py +++ b/extensions/fablabchemnitz/vpypetools/vpypetools.py @@ -340,8 +340,8 @@ class vpypetools (inkex.EffectExtension): # save the vpype document to new svg file and close it afterwards output_file = self.options.input_file + ".vpype.svg" output_fileIO = open(output_file, "w", encoding="utf-8") - vpype.write_svg(output_fileIO, doc, page_size=None, center=False, source_string='', layer_label_format='%d', show_pen_up=self.options.output_trajectories, color_mode='layer', single_path = True) - #vpype.write_svg(output_fileIO, doc, page_size=None, center=False, source_string='', layer_label_format='%d', show_pen_up=self.options.output_trajectories, color_mode='layer') + #vpype.write_svg(output_fileIO, doc, page_size=None, center=False, source_string='', layer_label_format='%d', show_pen_up=self.options.output_trajectories, color_mode='layer', single_path = True) + vpype.write_svg(output_fileIO, doc, page_size=None, center=False, source_string='', layer_label_format='%d', show_pen_up=self.options.output_trajectories, color_mode='layer') #vpype.write_svg(output_fileIO, doc, page_size=(self.svg.unittouu(self.document.getroot().get('width')), self.svg.unittouu(self.document.getroot().get('height'))), center=False, source_string='', layer_label_format='%d', show_pen_up=self.options.output_trajectories, color_mode='layer') output_fileIO.close() From b9f7cc314cdf8a1a3140a74bba547e6be7952087 Mon Sep 17 00:00:00 2001 From: leyghisbb Date: Mon, 12 Apr 2021 00:06:58 +0200 Subject: [PATCH 6/9] Patched Styles To Layers extension --- .../fablabchemnitz/styles_to_layers.inx | 7 +- extensions/fablabchemnitz/styles_to_layers.py | 232 ++++++++++-------- 2 files changed, 130 insertions(+), 109 deletions(-) diff --git a/extensions/fablabchemnitz/styles_to_layers.inx b/extensions/fablabchemnitz/styles_to_layers.inx index 01ef9c21..37575fcf 100644 --- a/extensions/fablabchemnitz/styles_to_layers.inx +++ b/extensions/fablabchemnitz/styles_to_layers.inx @@ -20,8 +20,11 @@ 1 false true - - + false + false + + + diff --git a/extensions/fablabchemnitz/styles_to_layers.py b/extensions/fablabchemnitz/styles_to_layers.py index a106e59c..a2713b1e 100644 --- a/extensions/fablabchemnitz/styles_to_layers.py +++ b/extensions/fablabchemnitz/styles_to_layers.py @@ -8,7 +8,7 @@ Features Author: Mario Voigt / FabLab Chemnitz Mail: mario.voigt@stadtfabrikanten.org Date: 19.08.2020 -Last patch: 05.04.2021 +Last patch: 11.04.2021 License: GNU GPL v3 """ import inkex @@ -40,23 +40,17 @@ class StylesToLayers(inkex.EffectExtension): return layer def __init__(self): - inkex.Effect.__init__(self) + inkex.EffectExtension.__init__(self) self.arg_parser.add_argument("--apply_transformations", type=inkex.Boolean, default=False, help="Run 'Apply Transformations' extension before running vpype. Helps avoiding geometry shifting") self.arg_parser.add_argument("--separateby", default = "stroke", help = "Separate by") self.arg_parser.add_argument("--parsecolors",default = "hexval", help = "Sort colors by") self.arg_parser.add_argument("--subdividethreshold", type=int, default = 1, help = "Threshold for splitting into sub layers") self.arg_parser.add_argument("--decimals", type=int, default = 1, help = "Decimal tolerance") self.arg_parser.add_argument("--cleanup", type=inkex.Boolean, default = True, help = "Decimal tolerance") + self.arg_parser.add_argument("--put_unfiltered", type=inkex.Boolean, default = False, help = "Put unfiltered elements to a separate layer") + self.arg_parser.add_argument("--show_info", type=inkex.Boolean, default = False, help = "Show elements which have no style attributes to filter") def effect(self): - applyTransformAvailable = False # at first we apply external extension - try: - import applytransform - applyTransformAvailable = True - except Exception as e: - # inkex.utils.debug(e) - inkex.utils.debug("Calling 'Apply Transformations' extension failed. Maybe the extension is not installed. You can download it from official InkScape Gallery. Skipping ...") - def colorsort(stroke_value): #this function applies to stroke or fill (hex colors) if self.options.parsecolors == "hexval": @@ -68,7 +62,15 @@ class StylesToLayers(inkex.EffectExtension): elif self.options.parsecolors == "luminance": return float(Color(stroke_value).to_hsl()[2]) return None - + + applyTransformAvailable = False # at first we apply external extension + try: + import applytransform + applyTransformAvailable = True + except Exception as e: + # inkex.utils.debug(e) + inkex.utils.debug("Calling 'Apply Transformations' extension failed. Maybe the extension is not installed. You can download it from official InkScape Gallery. Skipping ...") + layer_name = None layerNodeList = [] #list with layer, neutral_value, element and self.options.separateby type selected = [] #list of items to parse @@ -80,107 +82,123 @@ class StylesToLayers(inkex.EffectExtension): selected = self.svg.selected.values() for element in selected: + + # additional option to apply transformations. As we clear up some groups to form new layers, we might lose translations, rotations, etc. if self.options.apply_transformations is True and applyTransformAvailable is True: applytransform.ApplyTransform().recursiveFuseTransform(element) - style = element.get('style') - if style is not None: - #if no style attributes or stroke/fill are set as extra attribute - stroke = element.get('stroke') - stroke_width = element.get('stroke-width') - stroke_opacity = element.get('stroke-opacity') - fill = element.get('fill') - fill_opacity = element.get('fill-opacity') - - # possible values for fill are #HEXCOLOR (like #000000), color name (like purple, black, red) or gradients (URLs) - - neutral_value = None #we will use this value to slice the filter result into sub layers (threshold) - - if fill is not None: - style = 'fill:'+ fill + ";" - if stroke is not None: - style = style + 'stroke:' + stroke + ";" + + if isinstance(element, inkex.ShapeElement): # Elements which have a visible representation on the canvas (even without a style attribute but by their type); if we do not use that ifInstance Filter we provokate unkown InkScape fatal crashes + + style = element.get('style') + if style is not None: + #if no style attributes or stroke/fill are set as extra attribute + stroke = element.get('stroke') + stroke_width = element.get('stroke-width') + stroke_opacity = element.get('stroke-opacity') + fill = element.get('fill') + fill_opacity = element.get('fill-opacity') - #we don't want to destroy elements with gradients (they contain svg:stop elements which have a style too) and we don't want to mess with tspans (text) - #the Styles to Layers extension still might brick the gradients (some tests failed) - if style and element.tag != inkex.addNS('stop','svg') and element.tag != inkex.addNS('tspan','svg'): - - if self.options.separateby == "stroke": - stroke = re.search('(;|^)stroke:(.*?)(;|$)', style) - if stroke is not None: - stroke = stroke[0] - stroke_value = stroke.split("stroke:")[1].split(";")[0] - if stroke_value != "none": - stroke_converted = str(Color(stroke_value).to_rgb()) #the color can be hex code or clear name. we handle both the same - neutral_value = colorsort(stroke_converted) - layer_name = "stroke-" + self.options.parsecolors + "-" + stroke_converted - else: - layer_name = "stroke-" + self.options.parsecolors + "-none" - - elif self.options.separateby == "stroke_width": - stroke_width = re.search('stroke-width:(.*?)(;|$)', style) - if stroke_width is not None: - stroke_width = stroke_width[0] - neutral_value = self.svg.unittouu(stroke_width.split("stroke-width:")[1].split(";")[0]) - layer_name = stroke_width - else: - layer_name = "stroke-width-none" - - elif self.options.separateby == "stroke_hairline": - stroke_hairline = re.search('-inkscape-stroke:hairline(;|$)', style) - if stroke_hairline is not None: - neutral_value = 1 - layer_name = "stroke-hairline-yes" - else: - neutral_value = 0 - layer_name = "stroke-hairline-no" - - elif self.options.separateby == "stroke_opacity": - stroke_opacity = re.search('stroke-opacity:(.*?)(;|$)', style) - if stroke_opacity is not None: - stroke_opacity = stroke_opacity[0] - neutral_value = float(stroke_opacity.split("stroke-opacity:")[1].split(";")[0]) - layer_name = stroke_opacity - else: - layer_name = "stroke-opacity-none" - - elif self.options.separateby == "fill": - fill = re.search('fill:(.*?)(;|$)', style) - if fill is not None: - fill = fill[0] - fill_value = fill.split("fill:")[1].split(";")[0] - #check if the fill color is a real color or a gradient. if it's a gradient we skip the element - if fill_value != "none" and "url" not in fill_value: - fill_converted = str(Color(fill_value).to_rgb()) #the color can be hex code or clear name. we handle both the same - neutral_value = colorsort(fill_converted) - layer_name = "fill-" + self.options.parsecolors + "-" + fill_converted - elif "url" in fill_value: #okay we found a gradient. we put it to some group - layer_name = "fill-" + self.options.parsecolors + "-gradient" - else: - layer_name = "fill-" + self.options.parsecolors + "-none" - - elif self.options.separateby == "fill_opacity": - fill_opacity = re.search('fill-opacity:(.*?)(;|$)', style) - if fill_opacity is not None: - fill_opacity = fill_opacity[0] - neutral_value = float(fill_opacity.split("fill-opacity:")[1].split(";")[0]) - layer_name = fill_opacity - else: - layer_name = "fill-opacity-none" - - else: - inkex.utils.debug("No proper option selected.") - exit(1) + # possible values for fill are #HEXCOLOR (like #000000), color name (like purple, black, red) or gradients (URLs) + + neutral_value = None #we will use this value to slice the filter result into sub layers (threshold) + + if fill is not None: + style = 'fill:'+ fill + ";" + if stroke is not None: + style = style + 'stroke:' + stroke + ";" - if neutral_value is not None: #apply decimals filter - neutral_value = float(round(neutral_value, self.options.decimals)) - - if layer_name is not None: - layer_name = layer_name.split(";")[0] #cut off existing semicolons to avoid duplicated layers with/without semicolon - currentLayer = self.findLayer(layer_name) - if currentLayer is None: #layer does not exist, so create a new one - layerNodeList.append([self.createLayer(layerNodeList, layer_name), neutral_value, element, self.options.separateby]) + #we don't want to destroy elements with gradients (they contain svg:stop elements which have a style too) and we don't want to mess with tspans (text) + #the Styles to Layers extension still might brick the gradients (some tests failed) + if style and element.tag != inkex.addNS('stop','svg') and element.tag != inkex.addNS('tspan','svg'): + + if self.options.separateby == "stroke": + stroke = re.search('(;|^)stroke:(.*?)(;|$)', style) + if stroke is not None: + stroke = stroke[0] + stroke_value = stroke.split("stroke:")[1].split(";")[0] + if stroke_value != "none": + stroke_converted = str(Color(stroke_value).to_rgb()) #the color can be hex code or clear name. we handle both the same + neutral_value = colorsort(stroke_converted) + layer_name = "stroke-" + self.options.parsecolors + "-" + stroke_converted + else: + layer_name = "stroke-" + self.options.parsecolors + "-none" + + elif self.options.separateby == "stroke_width": + stroke_width = re.search('stroke-width:(.*?)(;|$)', style) + if stroke_width is not None: + stroke_width = stroke_width[0] + neutral_value = self.svg.unittouu(stroke_width.split("stroke-width:")[1].split(";")[0]) + layer_name = stroke_width + else: + layer_name = "stroke-width-none" + + elif self.options.separateby == "stroke_hairline": + stroke_hairline = re.search('-inkscape-stroke:hairline(;|$)', style) + if stroke_hairline is not None: + neutral_value = 1 + layer_name = "stroke-hairline-yes" + else: + neutral_value = 0 + layer_name = "stroke-hairline-no" + + elif self.options.separateby == "stroke_opacity": + stroke_opacity = re.search('stroke-opacity:(.*?)(;|$)', style) + if stroke_opacity is not None: + stroke_opacity = stroke_opacity[0] + neutral_value = float(stroke_opacity.split("stroke-opacity:")[1].split(";")[0]) + layer_name = stroke_opacity + else: + layer_name = "stroke-opacity-none" + + elif self.options.separateby == "fill": + fill = re.search('fill:(.*?)(;|$)', style) + if fill is not None: + fill = fill[0] + fill_value = fill.split("fill:")[1].split(";")[0] + #check if the fill color is a real color or a gradient. if it's a gradient we skip the element + if fill_value != "none" and "url" not in fill_value: + fill_converted = str(Color(fill_value).to_rgb()) #the color can be hex code or clear name. we handle both the same + neutral_value = colorsort(fill_converted) + layer_name = "fill-" + self.options.parsecolors + "-" + fill_converted + elif "url" in fill_value: #okay we found a gradient. we put it to some group + layer_name = "fill-" + self.options.parsecolors + "-gradient" + else: + layer_name = "fill-" + self.options.parsecolors + "-none" + + elif self.options.separateby == "fill_opacity": + fill_opacity = re.search('fill-opacity:(.*?)(;|$)', style) + if fill_opacity is not None: + fill_opacity = fill_opacity[0] + neutral_value = float(fill_opacity.split("fill-opacity:")[1].split(";")[0]) + layer_name = fill_opacity + else: + layer_name = "fill-opacity-none" + else: - layerNodeList.append([currentLayer, neutral_value, element, self.options.separateby]) #layer is existent. append items to this later + inkex.utils.debug("No proper option selected.") + exit(1) + + if neutral_value is not None: #apply decimals filter + neutral_value = float(round(neutral_value, self.options.decimals)) + + if layer_name is not None: + layer_name = layer_name.split(";")[0] #cut off existing semicolons to avoid duplicated layers with/without semicolon + currentLayer = self.findLayer(layer_name) + if currentLayer is None: #layer does not exist, so create a new one + layerNodeList.append([self.createLayer(layerNodeList, layer_name), neutral_value, element, self.options.separateby]) + else: + layerNodeList.append([currentLayer, neutral_value, element, self.options.separateby]) #layer is existent. append items to this later + else: #if no style attribute in element and not a group + if isinstance(element, inkex.Group) is False: + if self.options.show_info: + inkex.utils.debug(element.get('id') + ' has no style attribute') + if self.options.put_unfiltered: + layer_name = 'without-style-attribute' + currentLayer = self.findLayer(layer_name) + if currentLayer is None: #layer does not exist, so create a new one + layerNodeList.append([self.createLayer(layerNodeList, layer_name), None, element, None]) + else: + layerNodeList.append([currentLayer, None, element, None]) #layer is existent. append items to this later contentlength = 0 #some counter to track if there are layers inside or if it is just a list with empty children for layerNode in layerNodeList: From 0da493ace94f056a53549c432d19c1e88cc60213 Mon Sep 17 00:00:00 2001 From: leyghisbb Date: Mon, 12 Apr 2021 13:37:55 +0200 Subject: [PATCH 7/9] Added basic support for svg:use element --- extensions/fablabchemnitz/applytransform.py | 56 +++++++++++++++++++-- 1 file changed, 52 insertions(+), 4 deletions(-) diff --git a/extensions/fablabchemnitz/applytransform.py b/extensions/fablabchemnitz/applytransform.py index 9a8abb87..10b77737 100644 --- a/extensions/fablabchemnitz/applytransform.py +++ b/extensions/fablabchemnitz/applytransform.py @@ -4,8 +4,10 @@ # Copyright Mark "Klowner" Riedesel # https://github.com/Klowner/inkscape-applytransforms # -import inkex +import copy import math +from lxml import etree +import inkex from inkex.paths import CubicSuperPath, Path from inkex.transforms import Transform from inkex.styles import Style @@ -57,7 +59,7 @@ class ApplyTransform(inkex.EffectExtension): def recursiveFuseTransform(self, node, transf=[[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]): - transf = Transform(transf) * Transform(node.get("transform", None)) + transf = Transform(transf) * Transform(node.get("transform", None)) #a, b, c, d = linear transformations / e, f = translations if 'transform' in node.attrib: del node.attrib['transform'] @@ -139,10 +141,56 @@ class ApplyTransform(inkex.EffectExtension): else: node.set("r", edgex / 2) + elif node.tag == inkex.addNS("use", "svg"): + href = None + old_href_key = '{http://www.w3.org/1999/xlink}href' + new_href_key = 'href' + if node.attrib.has_key(old_href_key) is True: # {http://www.w3.org/1999/xlink}href (which gets displayed as 'xlink:href') attribute is deprecated. the newer attribute is just 'href' + href = node.attrib.get(old_href_key) + #node.attrib.pop(old_href_key) + if node.attrib.has_key(new_href_key) is True: + href = node.attrib.get(new_href_key) #we might overwrite the previous deprecated xlink:href but it's okay + #node.attrib.pop(new_href_key) + + #get the linked object from href attribute + linkedObject = self.document.getroot().xpath("//*[@id = '%s']" % href.lstrip('#')) #we must remove hashtag symbol + linkedObjectCopy = copy.copy(linkedObject[0]) + objectType = linkedObject[0].tag + + if objectType == inkex.addNS("image", "svg"): + mask = None #image might have an alpha channel + new_mask_id = self.svg.get_unique_id("mask") + newMask = None + if node.attrib.has_key('mask') is True: + mask = node.attrib.get('mask') + #node.attrib.pop('mask') + + #get the linked mask from mask attribute. We remove the old and create a new + if mask is not None: + linkedMask = self.document.getroot().xpath("//*[@id = '%s']" % mask.lstrip('url(#').rstrip(')')) #we must remove hashtag symbol + linkedMask[0].getparent().remove(linkedMask[0]) + maskAttributes = {'id': new_mask_id} + newMask = etree.SubElement(self.document.getroot(), inkex.addNS('mask', 'svg'), maskAttributes) + + width = float(linkedObjectCopy.get('width')) * transf.a + height = float(linkedObjectCopy.get('height')) * transf.d + linkedObjectCopy.set('width', '{:1.6f}'.format(width)) + linkedObjectCopy.set('height', '{:1.6f}'.format(height)) + linkedObjectCopy.set('x', '{:1.6f}'.format(transf.e)) + linkedObjectCopy.set('y', '{:1.6f}'.format(transf.f)) + if newMask is not None: + linkedObjectCopy.set('mask', 'url(#' + new_mask_id + ')') + maskRectAttributes = {'x': '{:1.6f}'.format(transf.e), 'y': '{:1.6f}'.format(transf.f), 'width': '{:1.6f}'.format(width), 'height': '{:1.6f}'.format(height), 'style':'fill:#ffffff;'} + maskRect = etree.SubElement(newMask, inkex.addNS('rect', 'svg'), maskRectAttributes) + else: + self.recursiveFuseTransform(linkedObjectCopy, transf) + + self.document.getroot().append(linkedObjectCopy) #for each svg:use we append a copy to the document root + node.getparent().remove(node) #then we remove the use object + elif node.tag in [inkex.addNS('rect', 'svg'), inkex.addNS('text', 'svg'), - inkex.addNS('image', 'svg'), - inkex.addNS('use', 'svg')]: + inkex.addNS('image', 'svg')]: inkex.utils.errormsg( "Shape %s (%s) not yet supported, try Object to path first" % (node.TAG, node.get("id")) From 89f19a339e083b95febf4675e53b185741793e52 Mon Sep 17 00:00:00 2001 From: leyghisbb Date: Mon, 12 Apr 2021 14:03:14 +0200 Subject: [PATCH 8/9] added new options to migrategroups extension --- extensions/fablabchemnitz/migrategroups.inx | 31 +++++++++++++-------- extensions/fablabchemnitz/migrategroups.py | 4 +++ 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/extensions/fablabchemnitz/migrategroups.inx b/extensions/fablabchemnitz/migrategroups.inx index 92bc8bd4..828934cb 100644 --- a/extensions/fablabchemnitz/migrategroups.inx +++ b/extensions/fablabchemnitz/migrategroups.inx @@ -39,17 +39,26 @@ - - - true - true - true - true - true - true - true - true - true + + + + + true + true + true + true + true + true + + + + true + true + true + true + true + + diff --git a/extensions/fablabchemnitz/migrategroups.py b/extensions/fablabchemnitz/migrategroups.py index 50898484..24ce7aab 100644 --- a/extensions/fablabchemnitz/migrategroups.py +++ b/extensions/fablabchemnitz/migrategroups.py @@ -47,6 +47,7 @@ class MigrateGroups(inkex.Effect): self.arg_parser.add_argument("--tspan", type=inkex.Boolean, default=True) self.arg_parser.add_argument("--linearGradient", type=inkex.Boolean, default=True) self.arg_parser.add_argument("--radialGradient", type=inkex.Boolean, default=True) + self.arg_parser.add_argument("--mask", type=inkex.Boolean, default=True) self.arg_parser.add_argument("--meshGradient", type=inkex.Boolean, default=True) self.arg_parser.add_argument("--meshRow", type=inkex.Boolean, default=True) self.arg_parser.add_argument("--meshPatch", type=inkex.Boolean, default=True) @@ -54,6 +55,7 @@ class MigrateGroups(inkex.Effect): self.arg_parser.add_argument("--script", type=inkex.Boolean, default=True) self.arg_parser.add_argument("--symbol", type=inkex.Boolean, default=True) self.arg_parser.add_argument("--stop", type=inkex.Boolean, default=True) + self.arg_parser.add_argument("--switch", type=inkex.Boolean, default=True) self.arg_parser.add_argument("--use", type=inkex.Boolean, default=True) self.arg_parser.add_argument("--flowRoot", type=inkex.Boolean, default=True) self.arg_parser.add_argument("--flowRegion", type=inkex.Boolean, default=True) @@ -85,8 +87,10 @@ class MigrateGroups(inkex.Effect): namespace.append("{http://www.w3.org/2000/svg}meshPatch") if self.options.meshPatch else "" namespace.append("{http://www.w3.org/2000/svg}script") if self.options.script else "" namespace.append("{http://www.w3.org/2000/svg}symbol") if self.options.symbol else "" + namespace.append("{http://www.w3.org/2000/svg}mask") if self.options.mask else "" namespace.append("{http://www.w3.org/2000/svg}metadata") if self.options.metadata else "" namespace.append("{http://www.w3.org/2000/svg}stop") if self.options.stop else "" + namespace.append("{http://www.w3.org/2000/svg}switch") if self.options.switch else "" namespace.append("{http://www.w3.org/2000/svg}use") if self.options.use else "" namespace.append("{http://www.w3.org/2000/svg}flowRoot") if self.options.flowRoot else "" namespace.append("{http://www.w3.org/2000/svg}flowRegion") if self.options.flowRegion else "" From 79603960349f261973dce1e1ea9f3a01d0ea551a Mon Sep 17 00:00:00 2001 From: leyghisbb Date: Mon, 12 Apr 2021 19:14:57 +0200 Subject: [PATCH 9/9] Massive patching in cleanup styles extension --- extensions/fablabchemnitz/cleanup.inx | 49 +++-- extensions/fablabchemnitz/cleanup.py | 202 +++++++++++------- .../fablabchemnitz/inventory_sticker.inx | 2 +- 3 files changed, 166 insertions(+), 87 deletions(-) diff --git a/extensions/fablabchemnitz/cleanup.inx b/extensions/fablabchemnitz/cleanup.inx index fc0c9065..89ea485f 100644 --- a/extensions/fablabchemnitz/cleanup.inx +++ b/extensions/fablabchemnitz/cleanup.inx @@ -2,20 +2,43 @@ Cleanup Styles fablabchemnitz.de.cleanup - 0.1000 - - - - - - + + + + + + + + false + 0.100 + + + + + + + + false + 100.0 + true + true + true + true + false + + + + + + + + + + + + + - 100.0 - true - true - true - true - all diff --git a/extensions/fablabchemnitz/cleanup.py b/extensions/fablabchemnitz/cleanup.py index b1b56375..e3590eb0 100644 --- a/extensions/fablabchemnitz/cleanup.py +++ b/extensions/fablabchemnitz/cleanup.py @@ -16,30 +16,52 @@ You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -Based on coloreffect.py by Jos Hirth and Aaron C. Spike +Based on +- coloreffect.py by Jos Hirth and Aaron C. Spike +- cleanup.py https://github.com/attoparsec/inkscape-extensions by attoparsec + +Author: Mario Voigt / FabLab Chemnitz +Mail: mario.voigt@stadtfabrikanten.org +Last Patch: 12.04.2021 +License: GNU GPL v3 + +Notes: + - This extension does not check if attributes contain duplicates properties like "opacity:1;fill:#393834;fill-opacity:1;opacity:1;fill:#393834;fill-opacity:1". We assume the SVG syntax is correct ''' import inkex import re class Cleanup(inkex.EffectExtension): + + groups = [] + def __init__(self): inkex.Effect.__init__(self) - self.arg_parser.add_argument("--stroke_width", type=float, default=0.1, help="Stroke width") - self.arg_parser.add_argument("--stroke_units", default="mm", help="Stroke unit") - self.arg_parser.add_argument("--opacity", type=float, default="100.0", help="Opacity") - self.arg_parser.add_argument("--reset_style_attributes", type=inkex.Boolean, help="Remove stroke style attributes like stroke-dasharray, stroke-dashoffset, stroke-linejoin, linecap, stroke-miterlimit") - self.arg_parser.add_argument("--reset_fill_attributes", type=inkex.Boolean, help="Sets 'fill:none;' to style attribute") + self.arg_parser.add_argument("--main_tabs") + self.arg_parser.add_argument("--dedicated_style_attributes", default="ignore", help="Handling of dedicated style attributes") + self.arg_parser.add_argument("--stroke_width_override", type=inkex.Boolean, default=False, help="Override stroke width") + self.arg_parser.add_argument("--stroke_width", type=float, default=0.100, help="Stroke width") + self.arg_parser.add_argument("--stroke_width_units", default="mm", help="Stroke width unit") + self.arg_parser.add_argument("--stroke_opacity_override", type=inkex.Boolean, default=False, help="Override stroke opacity") + self.arg_parser.add_argument("--stroke_opacity", type=float, default="100.0", help="Stroke opacity (%)") + self.arg_parser.add_argument("--reset_stroke_attributes", type=inkex.Boolean, help="Remove stroke style attributes 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linejoin', 'stroke-linecap', 'stroke-miterlimit'") + self.arg_parser.add_argument("--reset_fill_attributes", type=inkex.Boolean, help="Sets 'fill:none;fill-opacity:1;' to style attribute") self.arg_parser.add_argument("--apply_hairlines", type=inkex.Boolean, help="Adds 'vector-effect:non-scaling-stroke;' and '-inkscape-stroke:hairline;' Hint: stroke-width is kept in background. All hairlines still have a valued width.") self.arg_parser.add_argument("--apply_black_strokes", type=inkex.Boolean, help="Adds 'stroke:#000000;' to style attribute") - - + self.arg_parser.add_argument("--remove_group_styles", type=inkex.Boolean, help="Remove styles from groups") + def effect(self): if len(self.svg.selected) == 0: self.getAttribs(self.document.getroot()) else: for id, node in self.svg.selected.items(): self.getAttribs(node) + #finally remove the styles from collected groups (if enabled) + if self.options.remove_group_styles is True: + for group in self.groups: + if group.attrib.has_key('style') is True: + group.attrib.pop('style') def getAttribs(self, node): self.changeStyle(node) @@ -48,72 +70,106 @@ class Cleanup(inkex.EffectExtension): #stroke and fill styles can be included in style attribute or they can exist separately (can occure in older SVG files). We do not parse other attributes than style def changeStyle(self, node): - nodeDict = [] - nodeDict.append(inkex.addNS('line','svg')) - nodeDict.append(inkex.addNS('polyline','svg')) - nodeDict.append(inkex.addNS('polygon','svg')) - nodeDict.append(inkex.addNS('circle','svg')) - nodeDict.append(inkex.addNS('ellipse','svg')) - nodeDict.append(inkex.addNS('rect','svg')) - nodeDict.append(inkex.addNS('path','svg')) - nodeDict.append(inkex.addNS('g','svg')) - if node.tag in nodeDict: - if node.attrib.has_key('style'): - style = node.get('style') - if style: - #add missing style attributes if required - if style.endswith(';') is False: - style += ';' - if re.search('(;|^)stroke:(.*?)(;|$)', style) is None: #if "stroke" is None, add one. We need to distinguish because there's also attribute "-inkscape-stroke" that's why we check starting with ^ or ; - style += 'stroke:none;' - if "stroke-width:" not in style: - style += 'stroke-width:{:1.4f};'.format(self.svg.unittouu(str(self.options.stroke_width) + self.options.stroke_units)) - if "stroke-opacity:" not in style: - style += 'stroke-opacity:{:1.1f};'.format(self.options.opacity / 100) + + #we check/modify the style of all shapes (not groups) + if isinstance(node, inkex.ShapeElement) and not isinstance(node, inkex.Group): + # the final styles applied to this element (with influence from top level elements like groups) + composed_style = node.composed_style() + composedStyleAttributes = str(composed_style).split(';') #array + composedStyleAttributesDict = {} + if len(composed_style) > 0: #Style "composed_style" might contain just empty '' string which will lead to failing dict update + for composedStyleAttribute in composedStyleAttributes: + composedStyleAttributesDict.update({'{}'.format(composedStyleAttribute.split(':')[0]): composedStyleAttribute.split(':')[1]}) + + #three options to handle dedicated attributes (attributes not in the "style" attribute, but separate): + # - just delete all dedicated properties + # - merge dedicated properties, and prefer them over those from composed style + # - merge dedicated properties, but prefer properties from composed style + dedicatedStyleAttributesDict = {} + popDict = [] + popDict.extend(['stroke', 'stroke-opacity', 'stroke-width', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'fill', 'fill-opacity']) + for popItem in popDict: + if node.attrib.has_key(str(popItem)): + dedicatedStyleAttributesDict.update({'{}'.format(popItem): node.get(popItem)}) + node.attrib.pop(popItem) + + #inkex.utils.debug("composedStyleAttributesDict = " + str(composedStyleAttributesDict)) + #inkex.utils.debug("dedicatedStyleAttributesDict = " + str(dedicatedStyleAttributesDict)) + + if self.options.dedicated_style_attributes == 'prefer_dedicated': + composedStyleAttributesDict.update(dedicatedStyleAttributesDict) + node.set('style', composedStyleAttributesDict) + elif self.options.dedicated_style_attributes == 'prefer_composed': + dedicatedStyleAttributesDict.update(composedStyleAttributesDict) + node.set('style', dedicatedStyleAttributesDict) + elif self.options.dedicated_style_attributes == 'ignore': + pass - if self.options.apply_hairlines is True: - if "vector-effect:non-scaling-stroke" not in style: - style += 'vector-effect:non-scaling-stroke;' - if "-inkscape-stroke:hairline" not in style: - style += '-inkscape-stroke:hairline;' - - if re.search('fill:(.*?)(;|$)', style) is None: #if "fill" is None, add one. - style += 'fill:none;' - - #then parse the content and check what we need to adjust - declarations = style.split(';') - for i, decl in enumerate(declarations): - parts = decl.split(':', 2) - if len(parts) == 2: - (prop, val) = parts - prop = prop.strip().lower() - if prop == 'stroke-width': - new_val = self.svg.unittouu(str(self.options.stroke_width) + self.options.stroke_units) - declarations[i] = prop + ':{:1.4f}'.format(new_val) - if prop == 'stroke-opacity': - new_val = self.options.opacity / 100 - declarations[i] = prop + ':{:1.1f}'.format(new_val) - if self.options.reset_style_attributes is True: - if prop == 'stroke-dasharray': - declarations[i] = '' - if prop == 'stroke-dashoffset': - declarations[i] = '' - if prop == 'stroke-linejoin': - declarations[i] = '' - if prop == 'stroke-linecap': - declarations[i] = '' - if prop == 'stroke-miterlimit': - declarations[i] = '' - if self.options.apply_black_strokes is True: - if prop == 'stroke': - if val == 'none': - new_val = '#000000' - declarations[i] = prop + ':' + new_val - if self.options.reset_fill_attributes is True: - if prop == 'fill': - new_val = 'none' - declarations[i] = prop + ':' + new_val - node.set('style', ';'.join(declarations)) + # now parse the style with possibly merged dedicated attributes modded style attribute (dedicatedStyleAttributes) + if node.attrib.has_key('style') is False: + node.set('style', 'stroke:#000000;') #we add basic stroke color black. We cannot set to empty value or just ";" because it will not update properly + style = node.get('style') + + #add missing style attributes if required + if style.endswith(';') is False: + style += ';' + if re.search('(;|^)stroke:(.*?)(;|$)', style) is None: #if "stroke" is None, add one. We need to distinguish because there's also attribute "-inkscape-stroke" that's why we check starting with ^ or ; + style += 'stroke:none;' + if self.options.stroke_width_override is True and "stroke-width:" not in style: + style += 'stroke-width:{:1.4f};'.format(self.svg.unittouu(str(self.options.stroke_width) + self.options.stroke_width_units)) + if self.options.stroke_opacity_override is True and "stroke-opacity:" not in style: + style += 'stroke-opacity:{:1.1f};'.format(self.options.stroke_opacity / 100) + + if self.options.apply_hairlines is True: + if "vector-effect:non-scaling-stroke" not in style: + style += 'vector-effect:non-scaling-stroke;' + if "-inkscape-stroke:hairline" not in style: + style += '-inkscape-stroke:hairline;' + + if re.search('fill:(.*?)(;|$)', style) is None: #if "fill" is None, add one. + style += 'fill:none;' + + #then parse the content and check what we need to adjust + declarations = style.split(';') + for i, decl in enumerate(declarations): + parts = decl.split(':', 2) + if len(parts) == 2: + (prop, val) = parts + prop = prop.strip().lower() + if prop == 'stroke-width' and self.options.stroke_width_override is True: + new_val = self.svg.unittouu(str(self.options.stroke_width) + self.options.stroke_width_units) + declarations[i] = prop + ':{:1.4f}'.format(new_val) + if prop == 'stroke-opacity' and self.options.stroke_opacity_override is True: + new_val = self.options.stroke_opacity / 100 + declarations[i] = prop + ':{:1.1f}'.format(new_val) + if self.options.reset_stroke_attributes is True: + if prop == 'stroke-dasharray': + declarations[i] = '' + if prop == 'stroke-dashoffset': + declarations[i] = '' + if prop == 'stroke-linejoin': + declarations[i] = '' + if prop == 'stroke-linecap': + declarations[i] = '' + if prop == 'stroke-miterlimit': + declarations[i] = '' + if self.options.apply_black_strokes is True: + if prop == 'stroke': + if val == 'none': + new_val = '#000000' + declarations[i] = prop + ':' + new_val + if self.options.reset_fill_attributes is True: + if prop == 'fill': + new_val = 'none' + declarations[i] = prop + ':' + new_val + if prop == 'fill-opacity': + new_val = '1' + declarations[i] = prop + ':' + new_val + node.set('style', ';'.join(declarations)) + + # if element is group we add it to collection to remove it's style after parsing all selected items + elif isinstance(node, inkex.ShapeElement) and isinstance(node, inkex.Group): + self.groups.append(node) if __name__ == '__main__': Cleanup().run() \ No newline at end of file diff --git a/extensions/fablabchemnitz/inventory_sticker.inx b/extensions/fablabchemnitz/inventory_sticker.inx index d87fc822..701d74de 100644 --- a/extensions/fablabchemnitz/inventory_sticker.inx +++ b/extensions/fablabchemnitz/inventory_sticker.inx @@ -25,7 +25,7 @@ - +