several addons to filter and some other glitches removed

This commit is contained in:
Mario Voigt 2021-11-04 10:55:18 +01:00
parent 5935a95345
commit 07490f9349
5 changed files with 209 additions and 102 deletions

View File

@ -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:

View File

@ -2,40 +2,98 @@
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Filter By Length/Area</name>
<id>fablabchemnitz.de.filter_by_length_area</id>
<label>Applies to paths only! Rectangles and other elements are not supported. If your selection is empty, the whole document will be parsed.</label>
<param name="debug" type="bool" gui-text="Enable debug">false</param>
<param name="unit" gui-text="Unit" type="optiongroup" appearance="combo">
<option value="mm">mm</option>
<option value="cm">cm</option>
<option value="px">px</option>
<option value="pt">pt</option>
<option value="pc">pc</option>
<option value="in">in</option>
<param name="tab" type="notebook">
<page name="tab_settings" gui-text="Filter By Length/Area">
<label appearance="header">General Settings</label>
<param name="debug" type="bool" gui-text="Enable debug">false</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>
<hbox>
<vbox>
<label appearance="header">Threshold</label>
<param name="unit" gui-text="Unit" type="optiongroup" appearance="combo" gui-description="The unit applies to interval and thresholds">
<option value="mm">mm</option>
<option value="cm">cm</option>
<option value="px">px</option>
<option value="pt">pt</option>
<option value="pc">pc</option>
<option value="in">in</option>
</param>
<param name="nodes_interval" type="float" min="0.000" max="99999.000" precision="3" gui-text="Interval">10.000</param>
<separator/>
<param name="min_filter_enable" type="bool" gui-text="Enable filtering min.">false</param>
<param name="min_threshold" type="float" min="0.000" precision="3" max="10000000.000" gui-text="Min. length or area">1.000</param>
<param name="min_nodes" type="int" min="0" max="99999" gui-text="Min. nodes/&lt;interval&gt;">2</param>
<param name="max_filter_enable" type="bool" gui-text="Enable filtering max.">false</param>
<param name="max_threshold" type="float" min="0.000" precision="3" max="10000000.000" gui-text="Max. length or area">10000000.000</param>
<param name="max_nodes" type="int" min="0" max="99999" gui-text="Max. nodes/&lt;interval&gt;">10000000</param>
<param name="precision" type="int" min="0" max="16" gui-text="Precision">3</param>
</vbox>
<separator/>
<vbox>
<label appearance="header">Filter</label>
<param name="measure" type="optiongroup" appearance="combo" gui-text="By">
<option value="length">Length (Unit)</option>
<option value="nodes">Nodes per length (Unit)</option>
<option value="area">Area (Unit^2)</option>
</param>
<label appearance="header">Actions</label>
<param name="delete" type="bool" gui-text="Delete">false</param>
<hbox>
<param name="color_mode" type="optiongroup" appearance="combo" gui-text="Color mode">
<option value="none">None</option>
<option value="colorize_rainbow">Colorize (Rainbow effect)</option>
<option value="colorize_single">Colorize (Single color)</option>
</param>
<param name="color_single" type="color" appearance="colorbutton" gui-text="Single color">0xff00ffff</param>
</hbox>
<hbox>
<param name="sort_by_value" type="bool" gui-text="Sort by value">false</param>
<param name="reverse_sort_value" type="bool" gui-text="Reverse">false</param>
</hbox>
<hbox>
<param name="sort_by_id" type="bool" gui-text="Sort by Id">false</param>
<param name="reverse_sort_id" type="bool" gui-text="Reverse">false</param>
</hbox>
<param name="rename_ids" type="bool" gui-text="Rename (IDs)">false</param>
<hbox>
<param name="set_labels" type="bool" gui-text="Set labels" gui-description="Adds type and value to the element's label">false</param>
<param name="remove_labels" type="bool" gui-text="Remove labels" gui-description="Remove labels (cleaning option for previous applications)">false</param>
</hbox>
<param name="group" type="bool" gui-text="Group elements">false</param>
<param name="cleanup" type="bool" gui-text="Cleanup unused groups/layers" gui-description="This will call the extension 'Remove Empty Groups' if available">false</param>
</vbox>
</hbox>
<label appearance="header">Tips</label>
<label>Applies to paths only! Rectangles and other elements are not supported. If your selection is empty, the whole document will be parsed.
If you did not enable any filter, the actions are applied either to the whole selection or the complete document too.</label>
</page>
<page name="tab_about" gui-text="About">
<label appearance="header">Filter By Length/Area</label>
<label>A tool to filter for paths by different filters. Allows multiple actions to perform on.</label>
<label>2020 - 2021 / written by Mario Voigt (Stadtfabrikanten e.V. / FabLab Chemnitz)</label>
<spacer/>
<label appearance="header">Online Documentation</label>
<label appearance="url">https://y.stadtfabrikanten.org/filterbylengtharea</label>
<spacer/>
<label appearance="header">Contributing</label>
<label appearance="url">https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.X</label>
<label appearance="url">mailto:mario.voigt@stadtfabrikanten.org</label>
<spacer/>
<label appearance="header">MightyScape Extension Collection</label>
<label>This piece of software is part of the MightyScape for Inkscape Extension Collection and is licensed under GNU GPL v3</label>
<label appearance="url">https://y.stadtfabrikanten.org/mightyscape-overview</label>
</page>
<page name="tab_donate" gui-text="Donate">
<label appearance="header">Coffee + Pizza</label>
<label>We are the Stadtfabrikanten, running the FabLab Chemnitz since 2016. A FabLab is an open workshop that gives people access to machines and digital tools like 3D printers, laser cutters and CNC milling machines.</label>
<spacer/>
<label>You like our work and want to support us? You can donate to our non-profit organization by different ways:</label>
<label appearance="url">https://y.stadtfabrikanten.org/donate</label>
<spacer/>
<label>Thanks for using our extension and helping us!</label>
<image>../000_about_fablabchemnitz.svg</image>
</page>
</param>
<label>The unit applies to interval and thresholds</label>
<label appearance="header">Threshold</label>
<param name="nodes_interval" type="float" min="0.000" max="99999.000" precision="3" gui-text="Interval">10.000</param>
<separator/>
<param name="min_filter_enable" type="bool" gui-text="Enable filtering min.">false</param>
<param name="min_threshold" type="float" min="0.000" precision="3" max="10000000.000" gui-text="Min. length or area">1.000</param>
<param name="min_nodes" type="int" min="0" max="99999" gui-text="Min. nodes/&lt;interval&gt;">2</param>
<param name="max_filter_enable" type="bool" gui-text="Enable filtering max.">false</param>
<param name="max_threshold" type="float" min="0.000" precision="3" max="10000000.000" gui-text="Max. length or area">10000000.000</param>
<param name="max_nodes" type="int" min="0" max="99999" gui-text="Max. nodes/&lt;interval&gt;">10000000</param>
<param name="precision" type="int" min="0" max="16" gui-text="Precision">3</param>
<label>Filter</label>
<param name="measure" type="optiongroup" appearance="combo" gui-text="By">
<option value="length">Length (Unit)</option>
<option value="nodes">Nodes per length (Unit)</option>
<option value="area">Area (Unit^2)</option>
</param>
<label>Actions</label>
<param name="delete" type="bool" gui-text="Delete">false</param>
<param name="colorize" type="bool" gui-text="Colorize">false</param>
<hbox>
<param name="sort_by_asc" type="bool" gui-text="Sort by value">false</param>
<param name="reverse_sort" type="bool" gui-text="Reverse sorting">false</param>
</hbox>
<effect>
<object-type>all</object-type>
<effects-menu>

View File

@ -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:
# 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()

View File

@ -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",

View File

@ -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")