added purge pointy paths extension

This commit is contained in:
Mario Voigt 2021-04-21 15:09:36 +02:00
parent 1264a864bc
commit 8fcc0cbf64
5 changed files with 102 additions and 16 deletions

View File

@ -36,8 +36,8 @@ class DrawBBoxes(inkex.EffectExtension):
def effect(self): def effect(self):
if len(self.svg.selected) > 0: if len(self.svg.selected) > 0:
if self.options.split is False: if self.options.split is False:
for id, item in self.svg.selected.items(): for element in self.svg.selected.values():
self.drawBBox(item.bounding_box()) self.drawBBox(element.bounding_box())
else: else:
#self.drawBBox(self.svg.get_selected_bbox()) #works for InkScape (1:1.0+devel+202008292235+eff2292935) @ Linux and for Windows (but with deprecation) #self.drawBBox(self.svg.get_selected_bbox()) #works for InkScape (1:1.0+devel+202008292235+eff2292935) @ Linux and for Windows (but with deprecation)
self.drawBBox(self.svg.selection.bounding_box()) #works for InkScape 1.1dev (9b1fc87, 2020-08-27)) @ Windows self.drawBBox(self.svg.selection.bounding_box()) #works for InkScape 1.1dev (9b1fc87, 2020-08-27)) @ Windows

View File

@ -1,4 +1,4 @@
#!/usr/bin/env python #!/usr/bin/env python3
# coding=utf-8 # coding=utf-8
# #
# Copyright (C) 2005,2007 Aaron Spike, aaron@ekips.org # Copyright (C) 2005,2007 Aaron Spike, aaron@ekips.org
@ -139,7 +139,7 @@ class LinksCreator(inkex.EffectExtension):
if self.options.switch_pattern is True: if self.options.switch_pattern is True:
dashes = dashes[::-1] #reverse the array dashes = dashes[::-1] #reverse the array
#validate dashes. May not be negative. Otherwise Inkscape will freeze forever. Reason: rendering issue #validate dashes. May not be negative (dash or gap cannot be longer than the path itself). Otherwise Inkscape will freeze forever. Reason: rendering issue
if any(dash <= 0.0 for dash in dashes) == True: if any(dash <= 0.0 for dash in dashes) == True:
if self.options.show_info is True: self.msg("node " + node.get('id') + ": Error! Dash array may not contain negative numbers: " + ' '.join(format(dash, "1.3f") for dash in dashes) + ". Path skipped. Maybe it's too short. Adjust your link count, multiplicator and length accordingly, or set to unit '%'") if self.options.show_info is True: self.msg("node " + node.get('id') + ": Error! Dash array may not contain negative numbers: " + ' '.join(format(dash, "1.3f") for dash in dashes) + ". Path skipped. Maybe it's too short. Adjust your link count, multiplicator and length accordingly, or set to unit '%'")
return False if self.options.skip_errors is True else exit(1) return False if self.options.skip_errors is True else exit(1)
@ -267,11 +267,10 @@ class LinksCreator(inkex.EffectExtension):
dash = dash - length dash = dash - length
length = bezier.cspseglength(new[-1][-1], sub[i]) length = bezier.cspseglength(new[-1][-1], sub[i])
while dash < length: while dash < length:
new[-1][-1], nxt, sub[i] = \ new[-1][-1], nxt, sub[i] = bezier.cspbezsplitatlength(new[-1][-1], sub[i], dash/length)
bezier.cspbezsplitatlength(new[-1][-1], sub[i], dash/length) if idash % 2: # create a gap
if idash % 2: # create a gap
new.append([nxt[:]]) new.append([nxt[:]])
else: # splice the curve else: # splice the curve
new[-1].append(nxt[:]) new[-1].append(nxt[:])
length = length - dash length = length - dash
idash = (idash + 1) % len(dashes) idash = (idash + 1) % len(dashes)
@ -283,22 +282,44 @@ class LinksCreator(inkex.EffectExtension):
i += 1 i += 1
style.pop('stroke-dasharray') style.pop('stroke-dasharray')
node.pop('sodipodi:type') node.pop('sodipodi:type')
csp = CubicSuperPath(new)
node.path = CubicSuperPath(new) node.path = CubicSuperPath(new)
node.style = style node.style = style
# break apart the combined path to have multiple elements # break apart the combined path to have multiple elements
if self.options.breakapart is True: if self.options.breakapart is True:
breakOutputNodes = self.breakContours(node) breakOutputNodes = None
breakOutputNodes = self.breakContours(node, breakOutputNodes)
breakApartGroup = nodeParent.add(inkex.Group()) breakApartGroup = nodeParent.add(inkex.Group())
for breakOutputNode in breakOutputNodes: for breakOutputNode in breakOutputNodes:
breakApartGroup.append(breakOutputNode) breakApartGroup.append(breakOutputNode)
#self.msg(replacedNode.get('id')) #self.msg(replacedNode.get('id'))
#self.svg.selection.set(replacedNode.get('id')) #update selection to split paths segments (does not work, so commented out) #self.svg.selection.set(replacedNode.get('id')) #update selection to split paths segments (does not work, so commented out)
#cleanup useless points
p = breakOutputNode.path
commandsCoords = p.to_arrays()
# "m 45.250809,91.692739" - this path contains onyl one command - a single point
if len(commandsCoords) == 1:
breakOutputNode.delete()
# "m 45.250809,91.692739 z" - this path contains two commands, but only one coordinate.
# It's a single point, the path is closed by a Z command
elif len(commandsCoords) == 2 and commandsCoords[0][1] == commandsCoords[1][1]:
breakOutputNode.delete()
# "m 45.250809,91.692739 l 45.250809,91.692739" - this path contains two commands,
# but the first and second coordinate are the same. It will render als point
elif len(commandsCoords) == 2 and commandsCoords[-1][0] == 'Z':
breakOutputNode.delete()
# "m 45.250809,91.692739 l 45.250809,91.692739 z" - this path contains three commands,
# but the first and second coordinate are the same. It will render als point, the path is closed by a Z command
elif len(commandsCoords) == 3 and commandsCoords[0][1] == commandsCoords[1][1] and commandsCoords[2][1] == 'Z':
breakOutputNode.delete()
if len(self.svg.selected) > 0: if len(self.svg.selected) > 0:
for node in self.svg.selection.values(): for node in self.svg.selection.values():
#at first we need to break down combined nodes to single path, otherwise dasharray cannot properly be applied #at first we need to break down combined nodes to single path, otherwise dasharray cannot properly be applied
breakInputNodes = self.breakContours(node) breakInputNodes = None
breakInputNodes = self.breakContours(node, breakInputNodes)
for breakInputNode in breakInputNodes: for breakInputNode in breakInputNodes:
createLinks(breakInputNode) createLinks(breakInputNode)
else: else:

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Purge Pointy Paths</name>
<id>fablabchemnitz.de.purge_pointy_paths</id>
<effect>
<object-type>path</object-type>
<effects-menu>
<submenu name="FabLab Chemnitz">
<submenu name="Nesting/Cut Optimization"/>
</submenu>
</effects-menu>
</effect>
<script>
<command location="inx" interpreter="python">purge_pointy_paths.py</command>
</script>
</inkscape-extension>

View File

@ -0,0 +1,49 @@
#!/usr/bin/env python3
"""
This filter deletes paths which render as point only
More usesless and/or redundant filters for removing duplicate nodes and segments are provided by the following extension:
- https://stadtfabrikanten.org/display/IFM/Purge+Duplicate+Path+Segments
- https://stadtfabrikanten.org/display/IFM/Purge+Duplicate+Path+Nodes
Extension for InkScape 1.X
Author: Mario Voigt / FabLab Chemnitz
Mail: mario.voigt@stadtfabrikanten.org
Date: 21.04.2021
Last patch: 21.04.2021
License: GNU GPL v3
"""
import inkex
from lxml import etree
class PurgeInvalidPaths(inkex.EffectExtension):
def effect(self):
if len(self.svg.selected) > 0:
for element in self.svg.selected.values():
if isinstance(element, inkex.PathElement):
p = element.path
commandsCoords = p.to_arrays()
# "m 45.250809,91.692739" - this path contains onyl one command - a single point
if len(commandsCoords) == 1:
element.delete()
# "m 45.250809,91.692739 z" - this path contains two commands, but only one coordinate.
# It's a single point, the path is closed by a Z command
elif len(commandsCoords) == 2 and commandsCoords[0][1] == commandsCoords[1][1]:
element.delete()
# "m 45.250809,91.692739 l 45.250809,91.692739" - this path contains two commands,
# but the first and second coordinate are the same. It will render als point
elif len(commandsCoords) == 2 and commandsCoords[-1][0] == 'Z':
element.delete()
# "m 45.250809,91.692739 l 45.250809,91.692739 z" - this path contains three commands,
# but the first and second coordinate are the same. It will render als point, the path is closed by a Z command
elif len(commandsCoords) == 3 and commandsCoords[0][1] == commandsCoords[1][1] and commandsCoords[2][1] == 'Z':
element.delete()
else:
inkex.errormsg('Please select some objects first.')
return
if __name__ == '__main__':
PurgeInvalidPaths().run()

View File

@ -59,9 +59,9 @@ class RemoveRedundant(inkex.EffectExtension):
seenSegments = set() seenSegments = set()
coordsCache = FixedRadiusSearch() coordsCache = FixedRadiusSearch()
for id, node in self.svg.selected.items(): for element in self.svg.selected.values():
if node.tag == inkex.addNS('path','svg'): if element.tag == inkex.addNS('path','svg'):
d = node.get('d') d = element.get('d')
path = paths.CubicSuperPath(d).to_path().to_arrays() path = paths.CubicSuperPath(d).to_path().to_arrays()
newPath = [] newPath = []
start = prev = None start = prev = None
@ -117,7 +117,7 @@ class RemoveRedundant(inkex.EffectExtension):
newPath.append([command, coords]) newPath.append([command, coords])
while len(newPath) and newPath[-1][0] == 'M': while len(newPath) and newPath[-1][0] == 'M':
newPath = newPath[:-1] newPath = newPath[:-1]
node.set('d',str(paths.Path(newPath))) element.set('d',str(paths.Path(newPath)))
if __name__ == '__main__': if __name__ == '__main__':
RemoveRedundant().run() RemoveRedundant().run()