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: