From 07490f9349a3636ec9cf00e69329e0246eeb831c Mon Sep 17 00:00:00 2001 From: Mario Voigt Date: Thu, 4 Nov 2021 10:55:18 +0100 Subject: [PATCH] several addons to filter and some other glitches removed --- .../create_links/create_links.py | 56 ++------ .../filter_by_length_area.inx | 124 +++++++++++++----- .../filter_by_length_area.py | 124 +++++++++++++++--- .../filter_by_length_area/meta.json | 5 +- .../styles_to_layers/styles_to_layers.py | 2 +- 5 files changed, 209 insertions(+), 102 deletions(-) diff --git a/extensions/fablabchemnitz/create_links/create_links.py b/extensions/fablabchemnitz/create_links/create_links.py index 8e2306b9..719c40df 100644 --- a/extensions/fablabchemnitz/create_links/create_links.py +++ b/extensions/fablabchemnitz/create_links/create_links.py @@ -196,60 +196,22 @@ class LinksCreator(inkex.EffectExtension): # check if the element has a style attribute. If not we create a blank one with a black stroke and without fill style = None default_fill = 'none' - default_stroke_width = '1px' default_stroke = '#000000' + default_stroke_width = str(self.svg.unittouu('1px')) if element.attrib.has_key('style'): - style = element.get('style') - if style.endswith(';') is False: - style += ';' - - # if has style attribute and dasharray and/or dashoffset are present we modify it accordingly - declarations = style.split(';') # parse the style content and check what we need to adjust - for i, decl in enumerate(declarations): - parts = decl.split(':', 2) - if len(parts) == 2: - (prop, val) = parts - prop = prop.strip().lower() - #if prop == 'fill': - # declarations[i] = prop + ':{}'.format(default_fill) - #if prop == 'stroke': - # declarations[i] = prop + ':{}'.format(default_stroke) - #if prop == 'stroke-width': - # declarations[i] = prop + ':{}'.format(default_stroke_width) - if prop == 'stroke-dasharray': #comma separated list of one or more float values - declarations[i] = prop + ':{}'.format(stroke_dasharray) - if prop == 'stroke-dashoffset': - declarations[i] = prop + ':{}'.format(stroke_dashoffset) - element.set('style', ';'.join(declarations)) #apply new style to element - + element.style['stroke-dasharray'] = stroke_dasharray + element.style['stroke-dashoffset'] = stroke_dashoffset #if has style attribute but the style attribute does not contain fill, stroke, stroke-width, stroke-dasharray or stroke-dashoffset yet - style = element.style - if re.search('fill:(.*?)(;|$)', str(style)) is None: - style += 'fill:{};'.format(default_fill) - if re.search('(;|^)stroke:(.*?)(;|$)', str(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:{};'.format(default_stroke) - if not 'stroke-width' in style: - style += 'stroke-width:{};'.format(default_stroke_width) - if not 'stroke-dasharray' in style: - style += 'stroke-dasharray:{};'.format(stroke_dasharray) - if not 'stroke-dashoffset' in style: - style += 'stroke-dashoffset:{};'.format(stroke_dashoffset) - element.set('style', style) + if element.style.get('fill') is None: element.style['fill'] = default_fill + if element.style.get('stroke') is None: element.style['stroke'] = default_stroke + if element.style.get('stroke-width') is None: element.style['stroke-width'] = default_stroke_width else: - style = 'fill:{};stroke:{};stroke-width:{};stroke-dasharray:{};stroke-dashoffset:{};'.format(default_fill, default_stroke, default_stroke_width, stroke_dasharray, stroke_dashoffset) - element.set('style', style) + element.style = 'fill:{};stroke:{};stroke-width:{};stroke-dasharray:{};stroke-dashoffset:{};'.format( + default_fill, default_stroke, default_stroke_width, stroke_dasharray, stroke_dashoffset) #if enabled, we override stroke color with blue (now, as the element definitely has a style) if self.options.weakening_mode is True and self.options.switch_pattern is True: - declarations = element.get('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': - declarations[i] = prop + ':{}'.format("#0000ff") - element.set('style', ';'.join(declarations)) #apply new style to element + element.style['stroke'] = "#0000ff" # Print some info about values if self.options.show_info is True: diff --git a/extensions/fablabchemnitz/filter_by_length_area/filter_by_length_area.inx b/extensions/fablabchemnitz/filter_by_length_area/filter_by_length_area.inx index 948a13b7..a9b9956d 100644 --- a/extensions/fablabchemnitz/filter_by_length_area/filter_by_length_area.inx +++ b/extensions/fablabchemnitz/filter_by_length_area/filter_by_length_area.inx @@ -2,40 +2,98 @@ Filter By Length/Area fablabchemnitz.de.filter_by_length_area - - false - - - - - - - + + + + false + false + + + + + + + + + + + + 10.000 + + false + 1.000 + 2 + false + 10000000.000 + 10000000 + 3 + + + + + + + + + + + false + + + + + + + 0xff00ffff + + + false + false + + + false + false + + false + + false + false + + false + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ../000_about_fablabchemnitz.svg + - - - 10.000 - - false - 1.000 - 2 - false - 10000000.000 - 10000000 - 3 - - - - - - - - false - false - - false - false - all diff --git a/extensions/fablabchemnitz/filter_by_length_area/filter_by_length_area.py b/extensions/fablabchemnitz/filter_by_length_area/filter_by_length_area.py index c0ac6070..bdfea872 100644 --- a/extensions/fablabchemnitz/filter_by_length_area/filter_by_length_area.py +++ b/extensions/fablabchemnitz/filter_by_length_area/filter_by_length_area.py @@ -1,25 +1,35 @@ #!/usr/bin/env python3 ''' -Extension for InkScape 1.0 -Features -- Filter paths which are smaller/bigger than a given length or area +Extension for InkScape 1.0+ Author: Mario Voigt / FabLab Chemnitz Mail: mario.voigt@stadtfabrikanten.org Date: 03.08.2020 -Last patch: 21.10.2021 +Last patch: 04.11.2021 License: GNU GPL v3 + +ToDo: + - id sorting: handle ids with/without numbers and sort by number + ''' +import sys import colorsys import inkex +from inkex import Color from inkex.bezier import csplength, csparea +sys.path.append("../remove_empty_groups") +sys.path.append("../apply_transformations") + class FilterByLengthArea(inkex.EffectExtension): def add_arguments(self, pars): + pars.add_argument('--tab') pars.add_argument('--debug', type=inkex.Boolean, default=False) + pars.add_argument("--apply_transformations", type=inkex.Boolean, default=False, help="Run 'Apply Transformations' extension before running vpype. Helps avoiding geometry shifting") + pars.add_argument("--cleanup", type=inkex.Boolean, default = True, help = "Cleanup all unused groups/layers (requires separate extension)") pars.add_argument('--unit') pars.add_argument('--min_filter_enable', type=inkex.Boolean, default=True, help='Enable filtering min.') pars.add_argument('--min_threshold', type=float, default=0.000, help='Remove paths with an threshold smaller than this value') @@ -31,15 +41,30 @@ class FilterByLengthArea(inkex.EffectExtension): pars.add_argument('--precision', type=int, default=3, help='Precision') pars.add_argument('--measure', default="length") pars.add_argument('--delete', type=inkex.Boolean, default=False) - pars.add_argument('--colorize', type=inkex.Boolean, default=False) - pars.add_argument('--sort_by_asc', type=inkex.Boolean, default=False) - pars.add_argument('--reverse_sort', type=inkex.Boolean, default=False) + pars.add_argument('--color_mode', default="none") + pars.add_argument('--color_single', type=Color, default='0xff00ffff') + pars.add_argument('--sort_by_value', type=inkex.Boolean, default=False) + pars.add_argument('--reverse_sort_value', type=inkex.Boolean, default=False) + pars.add_argument('--sort_by_id', type=inkex.Boolean, default=False) + pars.add_argument('--reverse_sort_id', type=inkex.Boolean, default=False) + pars.add_argument('--rename_ids', type=inkex.Boolean, default=False) + pars.add_argument('--set_labels', type=inkex.Boolean, default=False, help="Adds type and value to the element's label") + pars.add_argument('--remove_labels', type=inkex.Boolean, default=False, help="Remove labels (cleaning option for previous applications)") + pars.add_argument('--group', type=inkex.Boolean, default=False) def effect(self): global to_sort, so to_sort = [] so = self.options + applyTransformationsAvailable = False # at first we apply external extension + try: + import apply_transformations + applyTransformationsAvailable = True + except Exception as e: + # self.msg(e) + self.msg("Calling 'Apply Transformations' extension failed. Maybe the extension is not installed. You can download it from official InkScape Gallery. Skipping ...") + so.min_threshold = self.svg.unittouu(str(so.min_threshold) + self.svg.unit) so.max_threshold = self.svg.unittouu(str(so.max_threshold) + self.svg.unit) unit_factor = 1.0 / self.svg.uutounit(1.0, so.unit) @@ -48,13 +73,18 @@ class FilterByLengthArea(inkex.EffectExtension): return if len(self.svg.selected) > 0: - elements = self.svg.selection.filter(inkex.PathElement).values() + elements = self.svg.selection.filter().values() else: elements = self.document.xpath("//svg:path", namespaces=inkex.NSS) if so.debug is True: inkex.utils.debug("Collecting elements ...") - for element in elements: + + for element in elements: + # additional option to apply transformations. As we clear up some groups to form new layers, we might lose translations, rotations, etc. + if so.apply_transformations is True and applyTransformationsAvailable is True: + apply_transformations.ApplyTransformations().recursiveFuseTransform(element) + try: csp = element.path.transform(element.composed_transform()).to_superpath() @@ -65,7 +95,7 @@ class FilterByLengthArea(inkex.EffectExtension): (so.min_filter_enable is False and so.max_filter_enable is False): #complete selection if so.debug is True: inkex.utils.debug("id={}, area={:0.3f}{}^2".format(element.get('id'), area, so.unit)) - to_sort.append({'element': element, 'value': area}) + to_sort.append({'element': element, 'value': area, 'type': 'area'}) elif so.measure == "length": slengths, stotal = csplength(csp) #get segment lengths and total length of path in document's internal unit @@ -75,7 +105,7 @@ class FilterByLengthArea(inkex.EffectExtension): (so.min_filter_enable is False and so.max_filter_enable is False): #complete selection if so.debug is True: inkex.utils.debug("id={}, length={:0.3f}{}".format(element.get('id'), self.svg.uutounit(str(stotal), so.unit), so.unit)) - to_sort.append({'element': element, 'value': stotal}) + to_sort.append({'element': element, 'value': stotal, 'type': 'length'}) elif so.measure == "nodes": slengths, stotal = csplength(csp) #get segment lengths and total length of path in document's internal unit @@ -86,7 +116,7 @@ class FilterByLengthArea(inkex.EffectExtension): (so.min_filter_enable is False and so.max_filter_enable is False): #complete selection if so.debug is True: inkex.utils.debug("id={}, length={:0.3f}{}, nodes={}".format(element.get('id'), self.svg.uutounit(str(stotal), so.unit), so.unit, nodes)) - to_sort.append({'element': element, 'value': nodes}) + to_sort.append({'element': element, 'value': nodes, 'type': 'nodes'}) except Exception as e: #inkex.utils.debug(e) @@ -96,23 +126,77 @@ class FilterByLengthArea(inkex.EffectExtension): element = to_sort[i].get('element') if so.delete is True: element.delete() + if so.delete is True: return #quit here - if so.sort_by_asc is True: + if so.sort_by_value is True: to_sort.sort(key=lambda x: x.get('value')) #sort by target value + if so.sort_by_id is True: + to_sort.sort(key=lambda x: x.get('element').get('id')) #sort by id. will override previous value sort + + if so.group is True: + group = inkex.Group(id=self.svg.get_unique_id("filtered")) + self.svg.get_current_layer().add(group) + + allIds = self.svg.get_ids() + newIds = [] #we pre-populate this + for i in range(0, len(to_sort)): + newIds.append("{}{}".format(element.tag.replace('{http://www.w3.org/2000/svg}',''), i)) #should be element tag 'path' + for i in range(0, len(to_sort)): element = to_sort[i].get('element') - if so.reverse_sort is True: - idx = len(element.getparent()) - else: - idx = 0 - element.getparent().insert(idx, element) - if so.colorize is True: + + if so.rename_ids is True: + if newIds[i] in allIds: #already exist. lets rename that one before using it's id for the recent element + try: + renameIdPre = element.get('id') + "-" + renameId = self.svg.get_unique_id(renameIdPre) + #inkex.utils.debug("Trying to rename {} to {}".format(element.get('id'), renameId)) + originalElement = self.svg.getElementById(newIds[i]) + originalElement.set('id', renameId) + except Exception as e: + pass + #inkex.utils.debug(e) + element.set('id', newIds[i]) + + if so.sort_by_value is True: + if so.reverse_sort_value is True: + idx = len(element.getparent()) + else: + idx = 0 + element.getparent().insert(idx, element) + + if so.sort_by_id is True: + if so.reverse_sort_id is True: + idx = len(element.getparent()) + else: + idx = 0 + element.getparent().insert(idx, element) + + if so.color_mode == "colorize_rainbow": color = colorsys.hsv_to_rgb(i / float(len(to_sort)), 1.0, 1.0) element.style['stroke'] = '#%02x%02x%02x' % tuple(int(x * 255) for x in color) - + + if so.color_mode == "colorize_single": + element.style['stroke'] = so.color_single + + if so.set_labels is True: + element.set('inkscape:label', "{}={}".format(to_sort[i].get('type'), to_sort[i].get('value'))) + + if so.remove_labels is True: + element.pop('inkscape:label') + + if so.group is True: + group.append(element) + + if so.cleanup == True: + try: + import remove_empty_groups + remove_empty_groups.RemoveEmptyGroups.effect(self) + except: + self.msg("Calling 'Remove Empty Groups' extension failed. Maybe the extension is not installed. You can download it from official InkScape Gallery. Skipping ...") if __name__ == '__main__': FilterByLengthArea().run() \ No newline at end of file diff --git a/extensions/fablabchemnitz/filter_by_length_area/meta.json b/extensions/fablabchemnitz/filter_by_length_area/meta.json index c240c708..f93f4d88 100644 --- a/extensions/fablabchemnitz/filter_by_length_area/meta.json +++ b/extensions/fablabchemnitz/filter_by_length_area/meta.json @@ -3,7 +3,10 @@ "name": "Filter By Length/Area", "id": "fablabchemnitz.de.filter_by_length_area", "path": "filter_by_length_area", - "dependent_extensions": null, + "dependent_extensions": [ + "apply_transformations", + "remove_empty_groups" + ], "original_name": "Filter By Length/Area", "original_id": "com.filter_by_length_area", "license": "GNU GPL v3", diff --git a/extensions/fablabchemnitz/styles_to_layers/styles_to_layers.py b/extensions/fablabchemnitz/styles_to_layers/styles_to_layers.py index 2ab229dc..0c7b3bec 100644 --- a/extensions/fablabchemnitz/styles_to_layers/styles_to_layers.py +++ b/extensions/fablabchemnitz/styles_to_layers/styles_to_layers.py @@ -50,7 +50,7 @@ class StylesToLayers(inkex.EffectExtension): pars.add_argument("--parsecolors",default = "hexval", help = "Sort colors by") pars.add_argument("--subdividethreshold", type=int, default = 1, help = "Threshold for splitting into sub layers") pars.add_argument("--decimals", type=int, default = 1, help = "Decimal tolerance") - pars.add_argument("--cleanup", type=inkex.Boolean, default = True, help = "Decimal tolerance") + pars.add_argument("--cleanup", type=inkex.Boolean, default = True, help = "Cleanup all unused groups/layers (requires separate extension)") pars.add_argument("--put_unfiltered", type=inkex.Boolean, default = False, help = "Put unfiltered elements to a separate layer") pars.add_argument("--show_info", type=inkex.Boolean, default = False, help = "Show elements which have no style attributes to filter")