Tuned "Colors to Layers" > renamed to "Styles To Layers"
This commit is contained in:
parent
30cb0b42c8
commit
5f624d6692
@ -1,23 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||
<name>Colors To Layers</name>
|
||||
<id>org.inkscape.color.colors_to_layers</id>
|
||||
<param name="separateby" gui-text="Separate by" type="optiongroup" appearance="combo">
|
||||
<option value="stroke">Stroke color</option>
|
||||
<option value="fill">Fill color</option>
|
||||
</param>
|
||||
<label>This extension will re-layer your selected items or the whole document according to their color (stroke or fill)</label>
|
||||
<label>Tinkered by Mario Voigt / Stadtfabrikanten e.V. (2020)</label>
|
||||
<label appearance="url">https://fablabchemnitz.de</label>
|
||||
<effect>
|
||||
<object-type>all</object-type>
|
||||
<effects-menu>
|
||||
<submenu name="FabLab Chemnitz">
|
||||
<submenu name="Groups and Layers"/>
|
||||
</submenu>
|
||||
</effects-menu>
|
||||
</effect>
|
||||
<script>
|
||||
<command location="inx" interpreter="python">fablabchemnitz_colors_to_layers.py</command>
|
||||
</script>
|
||||
</inkscape-extension>
|
@ -1,86 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
Extension for InkScape 1.0
|
||||
Features
|
||||
- filters the current selection or the whole document for fill or stroke style. Each style will be put onto it's own layer. This way you can devide elements by their colors.
|
||||
|
||||
Author: Mario Voigt / FabLab Chemnitz
|
||||
Mail: mario.voigt@stadtfabrikanten.org
|
||||
Date: 19.08.2020
|
||||
Last patch: 19.08.2020
|
||||
License: GNU GPL v3
|
||||
"""
|
||||
|
||||
import inkex
|
||||
import re
|
||||
from lxml import etree
|
||||
|
||||
class LayerGroup(inkex.Effect):
|
||||
|
||||
def findLayer(self, layerName):
|
||||
svg_layers = self.document.xpath('//svg:g[@inkscape:groupmode="layer"]', namespaces=inkex.NSS)
|
||||
for layer in svg_layers:
|
||||
#inkex.utils.debug(str(layer.get('inkscape:label')) + " == " + layerName)
|
||||
if layer.get('inkscape:label') == layerName:
|
||||
return layer
|
||||
return None
|
||||
|
||||
def createLayer(self, layerNodeList, layerName):
|
||||
svg = self.document.xpath('//svg:svg',namespaces=inkex.NSS)[0]
|
||||
for layer in layerNodeList:
|
||||
#inkex.utils.debug(str(layer[0].get('inkscape:label')) + " == " + layerName)
|
||||
if layer[0].get('inkscape:label') == layerName:
|
||||
return layer[0] #already exists. Do not create duplicate
|
||||
layer = etree.SubElement(svg, 'g')
|
||||
layer.set(inkex.addNS('label', 'inkscape'), '%s' % layerName)
|
||||
layer.set(inkex.addNS('groupmode', 'inkscape'), 'layer')
|
||||
return layer
|
||||
|
||||
def __init__(self):
|
||||
inkex.Effect.__init__(self)
|
||||
self.arg_parser.add_argument("--separateby", default = "stroke", help = "Separate by")
|
||||
|
||||
def effect(self):
|
||||
|
||||
layer_name = None
|
||||
layerNodeList = [] #list with layer and node
|
||||
selected = [] #list of items to parse
|
||||
|
||||
if len(self.svg.selected) == 0:
|
||||
for element in self.document.getroot().iter("*"):
|
||||
selected.append(element)
|
||||
else:
|
||||
selected = self.svg.selected.values()
|
||||
|
||||
for element in selected:
|
||||
style = element.get('style')
|
||||
fill = element.get('fill') #if no style attribute or fill is set as extra attribute
|
||||
stroke = element.get('stroke') #if no style attribute or stroke is set as extra attribute
|
||||
if fill is not None or stroke is not None:
|
||||
style = 'fill:'+ fill + ';stroke:' + stroke + ";"
|
||||
|
||||
if style:
|
||||
if self.options.separateby == "stroke":
|
||||
stroke = re.search('stroke:(.*?)(;|$)', style)
|
||||
if stroke is not None:
|
||||
stroke = stroke[0][8:-1]
|
||||
layer_name = "stroke-" + stroke
|
||||
elif self.options.separateby == "fill":
|
||||
fill = re.search('fill:(.*?)(;|$)', style)
|
||||
if fill is not None:
|
||||
fill = fill[0][6:-1]
|
||||
layer_name = "fill-" + fill
|
||||
|
||||
if layer_name is not None:
|
||||
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), element])
|
||||
else:
|
||||
layerNodeList.append([currentLayer, element]) #layer is existent. append items to this later
|
||||
|
||||
for layerNode in layerNodeList:
|
||||
layerNode[0].append(layerNode[1])
|
||||
|
||||
LayerGroup().run()
|
35
extensions/fablabchemnitz_styles_to_layers.inx
Normal file
35
extensions/fablabchemnitz_styles_to_layers.inx
Normal file
@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||
<name>Styles To Layers</name>
|
||||
<id>fablabchemnitz.de.styles_to_layers</id>
|
||||
<param name="separateby" gui-text="Separate by" type="optiongroup" appearance="combo">
|
||||
<option value="stroke">Stroke color</option>
|
||||
<option value="stroke_width">Stroke width</option>
|
||||
<option value="stroke_opacity">Stroke opacity</option>
|
||||
<option value="fill">Fill color</option>
|
||||
<option value="fill_opacity">Fill opacity</option>
|
||||
</param>
|
||||
<param name="parsecolor" gui-text="Sort colors by" type="optiongroup" appearance="combo">
|
||||
<option value="hexval">Hex value</option>
|
||||
<option value="hue">Hue</option>
|
||||
<option value="saturation">Saturation</option>
|
||||
<option value="luminance">Luminance</option>
|
||||
</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="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>
|
||||
|
||||
<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 appearance="url">https://fablabchemnitz.de</label>
|
||||
<effect>
|
||||
<object-type>all</object-type>
|
||||
<effects-menu>
|
||||
<submenu name="FabLab Chemnitz">
|
||||
<submenu name="Groups and Layers"/>
|
||||
</submenu>
|
||||
</effects-menu>
|
||||
</effect>
|
||||
<script>
|
||||
<command location="inx" interpreter="python">fablabchemnitz_styles_to_layers.py</command>
|
||||
</script>
|
||||
</inkscape-extension>
|
210
extensions/fablabchemnitz_styles_to_layers.py
Normal file
210
extensions/fablabchemnitz_styles_to_layers.py
Normal file
@ -0,0 +1,210 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
Extension for InkScape 1.0
|
||||
Features
|
||||
- filters the current selection or the whole document for fill or stroke style. Each style will be put onto it's own layer. This way you can devide elements by their colors.
|
||||
|
||||
Author: Mario Voigt / FabLab Chemnitz
|
||||
Mail: mario.voigt@stadtfabrikanten.org
|
||||
Date: 19.08.2020
|
||||
Last patch: 22.08.2020
|
||||
License: GNU GPL v3
|
||||
"""
|
||||
import inkex
|
||||
import re
|
||||
from lxml import etree
|
||||
import math
|
||||
from operator import itemgetter
|
||||
from inkex.colors import Color
|
||||
|
||||
class LayerGroup(inkex.Effect):
|
||||
|
||||
def findLayer(self, layerName):
|
||||
svg_layers = self.document.xpath('//svg:g[@inkscape:groupmode="layer"]', namespaces=inkex.NSS)
|
||||
for layer in svg_layers:
|
||||
#inkex.utils.debug(str(layer.get('inkscape:label')) + " == " + layerName)
|
||||
if layer.get('inkscape:label') == layerName:
|
||||
return layer
|
||||
return None
|
||||
|
||||
def createLayer(self, layerNodeList, layerName):
|
||||
svg = self.document.xpath('//svg:svg',namespaces=inkex.NSS)[0]
|
||||
for layer in layerNodeList:
|
||||
#inkex.utils.debug(str(layer[0].get('inkscape:label')) + " == " + layerName)
|
||||
if layer[0].get('inkscape:label') == layerName:
|
||||
return layer[0] #already exists. Do not create duplicate
|
||||
layer = etree.SubElement(svg, 'g')
|
||||
layer.set(inkex.addNS('label', 'inkscape'), '%s' % layerName)
|
||||
layer.set(inkex.addNS('groupmode', 'inkscape'), 'layer')
|
||||
return layer
|
||||
|
||||
def __init__(self):
|
||||
inkex.Effect.__init__(self)
|
||||
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")
|
||||
|
||||
def effect(self):
|
||||
|
||||
def colorsort(stroke_value): #this function applies to stroke or fill (hex colors)
|
||||
if self.options.parsecolors == "hexval":
|
||||
return float(int(stroke_value[1:], 16))
|
||||
elif self.options.parsecolors == "hue":
|
||||
return float(Color(stroke_value).to_hsl()[0])
|
||||
elif self.options.parsecolors == "saturation":
|
||||
return float(Color(stroke_value).to_hsl()[1])
|
||||
elif self.options.parsecolors == "luminance":
|
||||
return float(Color(stroke_value).to_hsl()[2])
|
||||
return None
|
||||
|
||||
layer_name = None
|
||||
layerNodeList = [] #list with layer, neutral_value, element and self.options.separateby type
|
||||
selected = [] #list of items to parse
|
||||
|
||||
if len(self.svg.selected) == 0:
|
||||
for element in self.document.getroot().iter("*"):
|
||||
selected.append(element)
|
||||
else:
|
||||
selected = self.svg.selected.values()
|
||||
|
||||
for element in selected:
|
||||
style = element.get('style')
|
||||
|
||||
#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')
|
||||
|
||||
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 style:
|
||||
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": neutral_value = colorsort(stroke_value)
|
||||
layer_name = self.options.parsecolors + "-" + stroke
|
||||
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
|
||||
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
|
||||
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]
|
||||
if fill_value != "none": neutral_value = colorsort(fill_value)
|
||||
layer_name = self.options.parsecolors + "-" + fill
|
||||
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:
|
||||
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
|
||||
|
||||
contentlength = 0 #some counter to track if there layers inside or if it is just a list with empty children
|
||||
for layerNode in layerNodeList:
|
||||
layerNode[0].append(layerNode[2]) #append element to created layer
|
||||
if layerNode[1] is not None: contentlength += 1 #for each found layer we increment +1
|
||||
|
||||
# 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 there if we need to subdivide and if there items we could rearrange into sub layers
|
||||
|
||||
#disabled sorting because it can return NoneType values which will kill the algorithm
|
||||
#layerNodeList.sort(key=itemgetter(1)) #sort by neutral values from min to max to put them with ease into parent layers
|
||||
|
||||
topLevelLayerNodeList = [] #list with new layers and sub layers (mapping)
|
||||
minmax_range = []
|
||||
for layerNode in layerNodeList:
|
||||
if layerNode[1] is not None:
|
||||
if layerNode[1] not in minmax_range:
|
||||
minmax_range.append(layerNode[1]) #get neutral_value
|
||||
|
||||
if len(minmax_range) >= 3: #if there are less than 3 distinct values a sub-layering will make no sense
|
||||
#adjust the subdividethreshold if there are less layers than division threshold value dictates
|
||||
if len(minmax_range) - 1 < self.options.subdividethreshold:
|
||||
self.options.subdividethreshold = len(minmax_range)-1
|
||||
#calculate the sub layer slices (sub ranges)
|
||||
minval = min(minmax_range)
|
||||
maxval = max(minmax_range)
|
||||
sliceinterval = (maxval - minval) / self.options.subdividethreshold
|
||||
|
||||
#inkex.utils.debug("neutral values (sorted) = " + str(minmax_range))
|
||||
#inkex.utils.debug("min neutral_value = " + str(minval))
|
||||
#inkex.utils.debug("max neutral_value = " + str(maxval))
|
||||
#inkex.utils.debug("slice value (divide step size) = " + str(sliceinterval))
|
||||
#inkex.utils.debug("subdivides (parent layers) = " + str(self.options.subdividethreshold))
|
||||
|
||||
for layerNode in layerNodeList:
|
||||
for x in range(0, self.options.subdividethreshold): #loop through the sorted neutral_values and determine to which layer they should belong
|
||||
|
||||
if layerNode[1] is None:
|
||||
layer_name = str(layerNode[3]) + "#parent:unfilterable"
|
||||
currentLayer = self.findLayer(layer_name)
|
||||
if currentLayer is None: #layer does not exist, so create a new one
|
||||
topLevelLayerNodeList.append([self.createLayer(topLevelLayerNodeList, layer_name), layerNode[0]])
|
||||
else:
|
||||
topLevelLayerNodeList.append([currentLayer, layerNode[0]]) #layer is existent. append items to this later
|
||||
break
|
||||
else:
|
||||
layer_name = str(layerNode[3]) + "#parent" + str(x+1)
|
||||
currentLayer = self.findLayer(layer_name)
|
||||
#value example for arranging:
|
||||
#min neutral_value = 0.07
|
||||
#max neutral_value = 2.50
|
||||
#slice value = 0.81
|
||||
#subdivides = 3
|
||||
#
|
||||
#that finally should generate:
|
||||
# layer #1: (from 0.07) to (0.07 + 0.81 = 0.88)
|
||||
# layer #2: (from 0.88) to (0.88 + 0.81 = 1.69)
|
||||
# layer #3: (from 1.69) to (1.69 + 0.81 = 2.50)
|
||||
#
|
||||
#now check layerNode[1] (neutral_value) and sort it into the correct layer
|
||||
if (layerNode[1] >= minval + sliceinterval * x) and (layerNode[1] <= minval + sliceinterval + sliceinterval * x):
|
||||
if currentLayer is None: #layer does not exist, so create a new one
|
||||
topLevelLayerNodeList.append([self.createLayer(topLevelLayerNodeList, layer_name), layerNode[0]])
|
||||
else:
|
||||
topLevelLayerNodeList.append([currentLayer, layerNode[0]]) #layer is existent. append items to this later
|
||||
break
|
||||
|
||||
#finally append the sublayers to the slices
|
||||
#for layer in topLevelLayerNodeList:
|
||||
#inkex.utils.debug(layer[0].get('inkscape:label'))
|
||||
#inkex.utils.debug(layer[1])
|
||||
for newLayerNode in topLevelLayerNodeList:
|
||||
newLayerNode[0].append(newLayerNode[1]) #append newlayer to layer
|
||||
|
||||
LayerGroup().run()
|
Reference in New Issue
Block a user