Added applytransforms option to Styles To Layers; fixed layers being

children of other layers; added hairline option
This commit is contained in:
Mario Voigt 2021-04-05 21:47:05 +02:00
parent 0c3f0009a1
commit 93a1009130
2 changed files with 119 additions and 85 deletions

View File

@ -5,6 +5,7 @@
<param name="separateby" gui-text="Separate by" type="optiongroup" appearance="combo"> <param name="separateby" gui-text="Separate by" type="optiongroup" appearance="combo">
<option value="stroke">Stroke color</option> <option value="stroke">Stroke color</option>
<option value="stroke_width">Stroke width</option> <option value="stroke_width">Stroke width</option>
<option value="stroke_hairline">Stroke hairline</option>
<option value="stroke_opacity">Stroke opacity</option> <option value="stroke_opacity">Stroke opacity</option>
<option value="fill">Fill color</option> <option value="fill">Fill color</option>
<option value="fill_opacity">Fill opacity</option> <option value="fill_opacity">Fill opacity</option>
@ -15,9 +16,10 @@
<option value="saturation">Saturation</option> <option value="saturation">Saturation</option>
<option value="luminance">Luminance</option> <option value="luminance">Luminance</option>
</param> </param>
<param name="subdividethreshold" gui-text="Number of sub layers" gui-description="A min/max range of the selected style type value will be calculated and you retrieve a set of layer (coarse grouping) with sub-layers (fine grouping). If you have less calculated sub layers than this threshold it will be limited automatically." type="int" min="1" max="9999">1</param> <param name="subdividethreshold" type="int" min="1" max="9999" gui-text="Number of sub layers" gui-description="A min/max range of the selected style type value will be calculated and you retrieve a set of layer (coarse grouping) with sub-layers (fine grouping). If you have less calculated sub layers than this threshold it will be limited automatically.">1</param>
<param name="decimals" gui-text="Decimal tolerance" gui-description="The more decimals the more distinct layers you will get. This only applies for the sub layers (threshold > 1)" type="int" min="0" max="10">1</param> <param name="decimals" type="int" min="0" max="10" gui-text="Decimal tolerance" gui-description="The more decimals the more distinct layers you will get. This only applies for the sub layers (threshold > 1)">1</param>
<param name="cleanup" gui-text="Cleanup all unused groups (requires separate extension)" type="boolean" gui-description="This will call the extension 'Remove Empty Groups' if available">true</param> <param name="apply_transformations" type="bool" gui-text="Apply transformations (requires separate extension)" gui-description="This will call the extension 'Apply Transformations'. Helps avoiding geometry shifting">false</param>
<param name="cleanup" type="bool" gui-text="Cleanup all unused groups/layers (requires separate extension)" gui-description="This will call the extension 'Remove Empty Groups' if available">true</param>
<label>This extension will re-layer your selected items or the whole document according to their style (stroke or fill)</label> <label>This extension will re-layer your selected items or the whole document according to their style (stroke or fill)</label>
<label>Tinkered by Mario Voigt / Stadtfabrikanten e.V. (2020)</label> <label>Tinkered by Mario Voigt / Stadtfabrikanten e.V. (2020)</label>

View File

@ -8,7 +8,7 @@ Features
Author: Mario Voigt / FabLab Chemnitz Author: Mario Voigt / FabLab Chemnitz
Mail: mario.voigt@stadtfabrikanten.org Mail: mario.voigt@stadtfabrikanten.org
Date: 19.08.2020 Date: 19.08.2020
Last patch: 10.09.2020 Last patch: 05.04.2021
License: GNU GPL v3 License: GNU GPL v3
""" """
import inkex import inkex
@ -18,7 +18,7 @@ import math
from operator import itemgetter from operator import itemgetter
from inkex.colors import Color from inkex.colors import Color
class StylesToLayers(inkex.Effect): class StylesToLayers(inkex.EffectExtension):
def findLayer(self, layerName): def findLayer(self, layerName):
svg_layers = self.document.xpath('//svg:g[@inkscape:groupmode="layer"]', namespaces=inkex.NSS) svg_layers = self.document.xpath('//svg:g[@inkscape:groupmode="layer"]', namespaces=inkex.NSS)
@ -41,6 +41,7 @@ class StylesToLayers(inkex.Effect):
def __init__(self): def __init__(self):
inkex.Effect.__init__(self) inkex.Effect.__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("--separateby", default = "stroke", help = "Separate by")
self.arg_parser.add_argument("--parsecolors",default = "hexval", help = "Sort colors 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("--subdividethreshold", type=int, default = 1, help = "Threshold for splitting into sub layers")
@ -48,6 +49,14 @@ class StylesToLayers(inkex.Effect):
self.arg_parser.add_argument("--cleanup", type=inkex.Boolean, default = True, help = "Decimal tolerance") self.arg_parser.add_argument("--cleanup", type=inkex.Boolean, default = True, help = "Decimal tolerance")
def effect(self): 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) def colorsort(stroke_value): #this function applies to stroke or fill (hex colors)
if self.options.parsecolors == "hexval": if self.options.parsecolors == "hexval":
@ -71,8 +80,10 @@ class StylesToLayers(inkex.Effect):
selected = self.svg.selected.values() selected = self.svg.selected.values()
for element in selected: for element in selected:
if self.options.apply_transformations is True and applyTransformAvailable is True:
applytransform.ApplyTransform().recursiveFuseTransform(element)
style = element.get('style') style = element.get('style')
if style is not None:
#if no style attributes or stroke/fill are set as extra attribute #if no style attributes or stroke/fill are set as extra attribute
stroke = element.get('stroke') stroke = element.get('stroke')
stroke_width = element.get('stroke-width') stroke_width = element.get('stroke-width')
@ -90,9 +101,11 @@ class StylesToLayers(inkex.Effect):
style = style + 'stroke:' + stroke + ";" style = style + 'stroke:' + stroke + ";"
#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) #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 style and element.tag != inkex.addNS('stop','svg') and element.tag != inkex.addNS('tspan','svg'):
if self.options.separateby == "stroke": if self.options.separateby == "stroke":
stroke = re.search('stroke:(.*?)(;|$)', style) stroke = re.search('^stroke:(.*?)(;|$)', style) #we use ^ to exlucde "-inkscape-stroke" which can be "hairline" since InkScape 1.1+
if stroke is not None: if stroke is not None:
stroke = stroke[0] stroke = stroke[0]
stroke_value = stroke.split("stroke:")[1].split(";")[0] stroke_value = stroke.split("stroke:")[1].split(";")[0]
@ -102,6 +115,7 @@ class StylesToLayers(inkex.Effect):
layer_name = "stroke-" + self.options.parsecolors + "-" + stroke_converted layer_name = "stroke-" + self.options.parsecolors + "-" + stroke_converted
else: else:
layer_name = "stroke-" + self.options.parsecolors + "-none" layer_name = "stroke-" + self.options.parsecolors + "-none"
elif self.options.separateby == "stroke_width": elif self.options.separateby == "stroke_width":
stroke_width = re.search('stroke-width:(.*?)(;|$)', style) stroke_width = re.search('stroke-width:(.*?)(;|$)', style)
if stroke_width is not None: if stroke_width is not None:
@ -110,6 +124,16 @@ class StylesToLayers(inkex.Effect):
layer_name = stroke_width layer_name = stroke_width
else: else:
layer_name = "stroke-width-none" 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": elif self.options.separateby == "stroke_opacity":
stroke_opacity = re.search('stroke-opacity:(.*?)(;|$)', style) stroke_opacity = re.search('stroke-opacity:(.*?)(;|$)', style)
if stroke_opacity is not None: if stroke_opacity is not None:
@ -118,6 +142,7 @@ class StylesToLayers(inkex.Effect):
layer_name = stroke_opacity layer_name = stroke_opacity
else: else:
layer_name = "stroke-opacity-none" layer_name = "stroke-opacity-none"
elif self.options.separateby == "fill": elif self.options.separateby == "fill":
fill = re.search('fill:(.*?)(;|$)', style) fill = re.search('fill:(.*?)(;|$)', style)
if fill is not None: if fill is not None:
@ -132,6 +157,7 @@ class StylesToLayers(inkex.Effect):
layer_name = "fill-" + self.options.parsecolors + "-gradient" layer_name = "fill-" + self.options.parsecolors + "-gradient"
else: else:
layer_name = "fill-" + self.options.parsecolors + "-none" layer_name = "fill-" + self.options.parsecolors + "-none"
elif self.options.separateby == "fill_opacity": elif self.options.separateby == "fill_opacity":
fill_opacity = re.search('fill-opacity:(.*?)(;|$)', style) fill_opacity = re.search('fill-opacity:(.*?)(;|$)', style)
if fill_opacity is not None: if fill_opacity is not None:
@ -140,6 +166,7 @@ class StylesToLayers(inkex.Effect):
layer_name = fill_opacity layer_name = fill_opacity
else: else:
layer_name = "fill-opacity-none" layer_name = "fill-opacity-none"
else: else:
inkex.utils.debug("No proper option selected.") inkex.utils.debug("No proper option selected.")
exit(1) exit(1)
@ -163,6 +190,11 @@ class StylesToLayers(inkex.Effect):
except: except:
continue continue
# we do some cosmetics with layers. Sometimes it can happen that one layer includes another. We don't want that. We move all layers to the top level
for newLayerNode in layerNodeList:
self.document.getroot().append(newLayerNode[0])
# Additionally if threshold was defined re-arrange the previously created layers by putting them into sub layers # Additionally if threshold was defined re-arrange the previously created layers by putting them into sub layers
if self.options.subdividethreshold > 1 and contentlength > 0: #check if we need to subdivide and if there are items we could rearrange into sub layers if self.options.subdividethreshold > 1 and contentlength > 0: #check if we need to subdivide and if there are items we could rearrange into sub layers
@ -236,7 +268,7 @@ class StylesToLayers(inkex.Effect):
import cleangroups import cleangroups
cleangroups.CleanGroups.effect(self) cleangroups.CleanGroups.effect(self)
except: except:
inkex.utils.debug("Calling 'Remove Empty Groups' extension failed. Maybe the extension is not installed. You can download it from official InkScape Gallery.") inkex.utils.debug("Calling 'Remove Empty Groups' extension failed. Maybe the extension is not installed. You can download it from official InkScape Gallery. Skipping ...")
if __name__ == '__main__': if __name__ == '__main__':
StylesToLayers().run() StylesToLayers().run()