Bugfix move path node, extend it
This commit is contained in:
parent
3d315df0d6
commit
bfebcb246b
@ -17,7 +17,7 @@
|
|||||||
<page name="tab_about" gui-text="About">
|
<page name="tab_about" gui-text="About">
|
||||||
<label appearance="header">Move Path Node</label>
|
<label appearance="header">Move Path Node</label>
|
||||||
<label>Extension to change starting / end node of a path and visualize it by dots and numbers. You can also use this extension as a trimmer for open paths.</label>
|
<label>Extension to change starting / end node of a path and visualize it by dots and numbers. You can also use this extension as a trimmer for open paths.</label>
|
||||||
<label>2021 - 2023 / written by Mario Voigt (Stadtfabrikanten e.V. / FabLab Chemnitz)</label>
|
<label>2021 - 2025 / written by Mario Voigt (Stadtfabrikanten e.V. / FabLab Chemnitz)</label>
|
||||||
<spacer />
|
<spacer />
|
||||||
<label appearance="header">Online Documentation</label>
|
<label appearance="header">Online Documentation</label>
|
||||||
<label appearance="url">https://y.stadtfabrikanten.org/movepathnode</label>
|
<label appearance="url">https://y.stadtfabrikanten.org/movepathnode</label>
|
||||||
|
@ -1,21 +1,32 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
This extension changes the order of the nodes without changing the shape of the path. It's required to modify paths
|
||||||
|
like this for example when projecting text to path or applying bezier envelope transformation.
|
||||||
|
|
||||||
Author: Mario Voigt / FabLab Chemnitz
|
Author: Mario Voigt / FabLab Chemnitz
|
||||||
Mail: mario.voigt@stadtfabrikanten.org
|
Mail: mario.voigt@stadtfabrikanten.org
|
||||||
Date: 19.05.2021
|
Date: 19.05.2021
|
||||||
Last patch: 19.05.2021
|
Last patch: 28.05.2025
|
||||||
License: GNU GPL v3
|
License: GNU GPL v3
|
||||||
'''
|
'''
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
import inkex
|
import inkex
|
||||||
|
import sys
|
||||||
from inkex import Circle, TextElement, PathElement
|
from inkex import Circle, TextElement, PathElement
|
||||||
from inkex.paths import CubicSuperPath, Path
|
from inkex.paths import CubicSuperPath, Path
|
||||||
|
|
||||||
class MovePathNode(inkex.EffectExtension):
|
class MovePathNode(inkex.EffectExtension):
|
||||||
|
|
||||||
def modify(self, element):
|
def modify(self, element):
|
||||||
|
|
||||||
|
if self.options.debug is True:
|
||||||
|
inkex.utils.debug("raw root path:")
|
||||||
|
inkex.utils.debug(element.path)
|
||||||
|
inkex.utils.debug("-"*25)
|
||||||
|
|
||||||
raw = element.path.to_arrays()
|
raw = element.path.to_arrays()
|
||||||
subpaths, prev = [], 0
|
subpaths, prev = [], 0
|
||||||
for i in range(len(raw)): # Breaks compound paths into simple paths
|
for i in range(len(raw)): # Breaks compound paths into simple paths
|
||||||
@ -25,9 +36,9 @@ class MovePathNode(inkex.EffectExtension):
|
|||||||
subpaths.append(raw[prev:])
|
subpaths.append(raw[prev:])
|
||||||
if self.options.debug is True:
|
if self.options.debug is True:
|
||||||
if len(subpaths) == 0:
|
if len(subpaths) == 0:
|
||||||
self.msg("{} has no subpaths").format(element.get('id'))
|
inkex.utils.debug("{} has no subpaths").format(element.get('id'))
|
||||||
else:
|
else:
|
||||||
self.msg("{} has {} subpath(s)".format(element.get('id'), len(subpaths)))
|
inkex.utils.debug("{} has {} subpath(s)".format(element.get('id'), len(subpaths)))
|
||||||
|
|
||||||
subpathNr = 0
|
subpathNr = 0
|
||||||
for path in subpaths:
|
for path in subpaths:
|
||||||
@ -38,18 +49,19 @@ class MovePathNode(inkex.EffectExtension):
|
|||||||
if path[-1][0] == 'Z' or \
|
if path[-1][0] == 'Z' or \
|
||||||
(path[-1][0] == 'L' and path[0][1] == path[-1][1]) or \
|
(path[-1][0] == 'L' and path[0][1] == path[-1][1]) or \
|
||||||
(path[-1][0] == 'C' and path[0][1] == [path[-1][1][-2], path[-1][1][-1]]) \
|
(path[-1][0] == 'C' and path[0][1] == [path[-1][1][-2], path[-1][1][-1]]) \
|
||||||
: #if first is last point the path is also closed. The "Z" command is not required
|
: #if first is last point the path is also kind of closed, but not cleanly. We assume that matching of start and end targets to be closed
|
||||||
pathIsClosed = True
|
pathIsClosed = True
|
||||||
|
|
||||||
if self.options.debug is True:
|
if self.options.debug is True:
|
||||||
self.msg("pathIsClosed = " + str(pathIsClosed))
|
nodeCountInitial = len(path)
|
||||||
self.msg("nodes = " + str(len(path)))
|
inkex.utils.debug("pathIsClosed = " + str(pathIsClosed))
|
||||||
|
inkex.utils.debug("initial nodes = " + str(nodeCountInitial))
|
||||||
|
|
||||||
if self.options.closed_only is True and pathIsClosed is False:
|
if self.options.closed_only is True and pathIsClosed is False:
|
||||||
if len(subpaths) == 0:
|
if len(subpaths) == 0:
|
||||||
self.msg("{}/subpath {} is not closed! Skipping ...".format(element.get('id'), subpathNr))
|
inkex.utils.debug("{}/subpath {} is not closed! Skipping ...".format(element.get('id'), subpathNr))
|
||||||
else:
|
else:
|
||||||
self.msg("{} is not closed! Skipping ...".format(element.get('id')))
|
inkex.utils.debug("{} is not closed! Skipping ...".format(element.get('id')))
|
||||||
continue #skip this open path
|
continue #skip this open path
|
||||||
|
|
||||||
if len(path) == 2:
|
if len(path) == 2:
|
||||||
@ -63,68 +75,91 @@ class MovePathNode(inkex.EffectExtension):
|
|||||||
moves = (self.options.movenode) % len(path)
|
moves = (self.options.movenode) % len(path)
|
||||||
if pathIsClosed is True: #if closed start and end collapse and "duplicate"
|
if pathIsClosed is True: #if closed start and end collapse and "duplicate"
|
||||||
moves = (self.options.movenode) % (len(path) - 1)
|
moves = (self.options.movenode) % (len(path) - 1)
|
||||||
if self.options.movenode == 0: #special handling for 0 is required
|
if self.options.movenode == 0: #special handling for 0 is required - means "do nothing"
|
||||||
moves = 0
|
moves = 0
|
||||||
|
if moves < 1:
|
||||||
|
if self.options.debug is True: inkex.utils.debug("Nothing to do (0 moves) ...")
|
||||||
|
else:
|
||||||
|
if self.options.debug is True:
|
||||||
|
inkex.utils.debug("moves to perform = " + str(moves))
|
||||||
|
inkex.utils.debug("raw root path:")
|
||||||
|
inkex.utils.debug(path)
|
||||||
|
inkex.utils.debug("-"*25)
|
||||||
|
|
||||||
if self.options.debug is True:
|
for i in range(moves):
|
||||||
self.msg("moves to perform = " + str(moves))
|
if len(path) > 2: #the path needs at least more than two segments, else we might just get a "pointy path" on an open path
|
||||||
self.msg("root path:")
|
|
||||||
self.msg(path)
|
|
||||||
self.msg("-"*25)
|
|
||||||
|
|
||||||
for i in range(moves):
|
#special case for rare paths: check if first node and last node match and if there is a Z between, which doubles up, while showing 1 node less than exspected
|
||||||
if len(path) > 2: #the path needs at least more than two segments, else we might just get a "pointy path" on an open path
|
doubleClosed = False
|
||||||
|
if [path[0][1][-2], path[0][1][-1]] == [path[-2][1][-2], path[-2][1][-1]] and pathIsClosed is True:
|
||||||
#we move the first segment to the end of the list
|
doubleClosed = True
|
||||||
move = path[0]
|
if self.options.debug is True: inkex.utils.debug("doubleClosed = " + str(doubleClosed))
|
||||||
del path[0]
|
|
||||||
path.append(move)
|
#we move the first segment to the end of the list
|
||||||
oldseg = copy.deepcopy(path[0]) #if we assign like "oldseg = path[0]", it will get overwritten. So we need copy
|
move = path[0]
|
||||||
|
del path[0]
|
||||||
if self.options.debug is True:
|
path.append(move)
|
||||||
self.msg("moved path (move no. {}):".format(i+1))
|
oldseg = copy.deepcopy(path[0]) #if we assign like "oldseg = path[0]", it will get overwritten. So we need copy
|
||||||
self.msg(path)
|
|
||||||
self.msg("-"*25)
|
|
||||||
|
|
||||||
#Now we messed the integrity of the path. It does not begin with 'M' now. But we need an 'M'.
|
|
||||||
#It now either starts with L or C. H, V, Z cannot occure here.
|
|
||||||
if path[0][0] == 'C': #and path[-1][0] == 'M':
|
|
||||||
#self.msg("C to M")
|
|
||||||
path[0][1] = [path[0][1][-2], path[0][1][-1]]
|
|
||||||
elif path[0][0] == 'L': #and path[-1][0] == 'M':
|
|
||||||
#self.msg("L to M")
|
|
||||||
path[0][1] = [path[0][1][0], path[0][1][1]]
|
|
||||||
#else:
|
|
||||||
# self.msg("no idea")
|
|
||||||
path[0][0] = 'M' #we really need M. Does not matter if 'L' or 'C'.
|
|
||||||
|
|
||||||
if pathIsClosed is True:
|
|
||||||
if path[-1][0] == 'M' and len(oldseg[1]) == 2: #data of an 'L' command
|
|
||||||
path[-1][0] = 'L'
|
|
||||||
path[-1][1] = path[0][1]
|
|
||||||
elif path[-1][0] == 'M' and len(oldseg[1]) > 2: #data of an 'C' command
|
|
||||||
path[-1][0] = 'C'
|
|
||||||
path[-1][1] = oldseg[1]
|
|
||||||
else:
|
|
||||||
if path[-1][0] == 'M': #if open path we just drop the dangling 'M' command completely
|
|
||||||
del path[-1]
|
|
||||||
|
|
||||||
if self.options.debug is True:
|
if self.options.debug is True:
|
||||||
self.msg("final path:")
|
inkex.utils.debug("moved path (move no. {}):".format(i+1))
|
||||||
self.msg(path)
|
inkex.utils.debug(path)
|
||||||
self.msg("-"*25)
|
inkex.utils.debug("-"*25)
|
||||||
|
|
||||||
newSubpaths[subpathNr - 1] = path
|
#Now we messed the integrity of the path. It does not begin with 'M' now. But we need an 'M'.
|
||||||
else:
|
#It now either starts with L or C. H, V, Z cannot occure here.
|
||||||
inkex.utils.debug("More moves entered than possible to apply. Path result would be a point, not a line")
|
if path[0][0] == 'C': #and path[-1][0] == 'M':
|
||||||
#return
|
#inkex.utils.debug("C to M")
|
||||||
|
path[0][1] = [path[0][1][-2], path[0][1][-1]]
|
||||||
|
elif path[0][0] == 'L': #and path[-1][0] == 'M':
|
||||||
|
#inkex.utils.debug("L to M")
|
||||||
|
path[0][1] = [path[0][1][0], path[0][1][1]]
|
||||||
|
#else:
|
||||||
|
# inkex.utils.debug("no idea")
|
||||||
|
|
||||||
|
path[0][0] = 'M' #we really need M. Does not matter if 'L' or 'C'.
|
||||||
|
|
||||||
|
if doubleClosed is True:
|
||||||
|
del path[-2]
|
||||||
|
|
||||||
|
if pathIsClosed is True:
|
||||||
|
if path[-1][0] == 'M' and len(oldseg[1]) == 2: #data of an 'L' command
|
||||||
|
path[-1][0] = 'L'
|
||||||
|
path[-1][1] = path[0][1]
|
||||||
|
if self.options.debug is True: inkex.utils.debug("modified M command to L command")
|
||||||
|
elif path[-1][0] == 'M' and len(oldseg[1]) > 2: #data of an 'C' command
|
||||||
|
path[-1][0] = 'C'
|
||||||
|
path[-1][1] = oldseg[1]
|
||||||
|
if self.options.debug is True: inkex.utils.debug("modified M command to C command")
|
||||||
|
else:
|
||||||
|
if path[-1][0] == 'M': #if open path we just drop the dangling 'M' command completely
|
||||||
|
del path[-1]
|
||||||
|
if self.options.debug is True: inkex.utils.debug("delete dangling M command")
|
||||||
|
|
||||||
|
if pathIsClosed is True and path[-1] != (['Z', []]):
|
||||||
|
path.append(['Z', []])
|
||||||
|
|
||||||
|
if self.options.debug is True:
|
||||||
|
nodeCountFinal = len(path)
|
||||||
|
inkex.utils.debug("final nodes = " + str(nodeCountFinal)) #that count must match the inital count!
|
||||||
|
inkex.utils.debug("final path:")
|
||||||
|
inkex.utils.debug(path)
|
||||||
|
inkex.utils.debug("-"*25)
|
||||||
|
if nodeCountInitial != nodeCountFinal:
|
||||||
|
inkex.utils.debug("Warning! Node count changed from {} to {}".format(nodeCountInitial, nodeCountFinal))
|
||||||
|
|
||||||
|
newSubpaths[subpathNr - 1] = path
|
||||||
|
else:
|
||||||
|
if self.options.debug is True:
|
||||||
|
inkex.utils.debug("More moves entered than possible to apply. Path result would be a point, not a line")
|
||||||
|
#return
|
||||||
|
|
||||||
composedPath = inkex.Path()
|
composedPath = inkex.Path()
|
||||||
for newSubpath in newSubpaths:
|
for newSubpath in newSubpaths:
|
||||||
composedPath.extend(newSubpath)
|
composedPath.extend(newSubpath)
|
||||||
|
|
||||||
if self.options.debug is True:
|
if self.options.debug is True:
|
||||||
self.msg("Composed path = " + str(composedPath))
|
inkex.utils.debug("Composed path = " + str(composedPath))
|
||||||
|
|
||||||
element.path = composedPath
|
element.path = composedPath
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user