Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrade to Inkscape 1.0 #5

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 35 additions & 43 deletions chain_paths.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@
# 2020-04-10 jw, V0.6 -- Close paths correctly. Self reversing path hack was too eager.
# Workaround for cubicsuperpath.parsePath/formatPath limitation.
# Started python3 compatibility.
# 2020-05-27 vi, V0.7 -- Upgrade to Inkscape 1.0. Write debug info to inkex debug dialog.

from __future__ import print_function

__version__ = '0.6' # Keep in sync with chain_paths.inx ca line 22
__version__ = '0.7' # Keep in sync with chain_paths.inx ca line 22
__author__ = 'Juergen Weigert <[email protected]>'
__credits__ = ['Juergen Weigert', 'Veronika Irvine']

import sys, os, shutil, time, logging, tempfile, math
import re
Expand All @@ -44,22 +46,22 @@

# inkscape libraries
import inkex
import cubicsuperpath

inkex.localize()
inkex.localization.localize()

from optparse import SUPPRESS_HELP

def uutounit(self, nn, uu):
try:
return self.uutounit(nn, uu) # inkscape 0.91
return self.svg.unittouu(str(nn)+uu)
except:
return inkex.uutounit(nn, uu) # inkscape 0.48
return inkex.unittouu(str(nn)+uu)

class ChainPaths(inkex.Effect):
"""
Inkscape Extension make long continuous paths from smaller parts
"""

def __init__(self):
# Call the base class constructor.
inkex.Effect.__init__(self)
Expand All @@ -74,23 +76,11 @@ def __init__(self):
self.min_missed_distance_sq = None
self.chained_count = 0

try:
self.tty = open("/dev/tty", 'w')
except:
try:
self.tty = open("CON:", 'w') # windows. Does this work???
except:
self.tty = open(os.devnull, 'w') # '/dev/null' for POSIX, 'nul' for Windows.
if debug: print("__init__", file=self.tty)

self.OptionParser.add_option('-V', '--version',
action = 'store_const', const=True, dest='version', default=False,
help = 'Just print version number ("' + __version__ + '") and exit.')
self.OptionParser.add_option('-s', '--snap', action='store', dest='snap_ends', type='inkbool', default=True, help='snap end-points together when connecting')
self.OptionParser.add_option('-c', '--close', action='store', dest='close_loops', type='inkbool', default=True, help='close loops (start/end of the same path)')
self.OptionParser.add_option('-u', '--units', action='store', dest="units", type="string", default="mm", help="measurement unit for epsilon")
self.OptionParser.add_option('-e', '--epsilon', action='store',
type='float', dest='chain_epsilon', default=0.01, help="Max. distance to connect [mm]")
self.arg_parser.add_argument('-V', '--version', dest='version', type=inkex.Boolean, default=False, help = 'Just print version number ("' + __version__ + '") and exit.')
self.arg_parser.add_argument('-s', '--snap', dest='snap_ends', type=inkex.Boolean, default=True, help='snap end-points together when connecting')
self.arg_parser.add_argument('-c', '--close', dest='close_loops', type=inkex.Boolean, default=True, help='close loops (start/end of the same path)')
self.arg_parser.add_argument('-u', '--units', dest="units", type=str, default="mm", help="measurement unit for epsilon")
self.arg_parser.add_argument('-e', '--epsilon', type=float, dest='chain_epsilon', default=0.01, help="Max. distance to connect [mm]")

def version(self):
return __version__
Expand Down Expand Up @@ -119,7 +109,7 @@ def set_segment_done(self, id, n, msg=''):
if not id in self.segments_done:
self.segments_done[id] = {}
self.segments_done[id][n] = True
if debug: print("done", id, n, msg, file=self.tty)
if debug: inkex.utils.debug("done "+str(id)+" "+str(n)+" "+msg)

def is_segment_done(self, id, n):
if not id in self.segments_done:
Expand Down Expand Up @@ -174,17 +164,18 @@ def effect(self):
if self.chain_epsilon < 0.001: self.chain_epsilon = 0.001 # keep a minimum.
self.eps_sq = self.chain_epsilon * self.unit_factor * self.chain_epsilon * self.unit_factor

if not len(self.selected.items()):
if not len(self.svg.selected.items()):
inkex.errormsg(_("Please select one or more objects."))
return

segments = []
for id, node in self.selected.iteritems():
for id in self.svg.selected:
node = self.svg.selected[id]
if node.tag != inkex.addNS('path', 'svg'):
inkex.errormsg(_("Object " + id + " is not a path. Try\n - Path->Object to Path\n - Object->Ungroup"))
return
if debug: print("id=" + str(id), "tag=" + str(node.tag), file=self.tty)
path_d = cubicsuperpath.parsePath(node.get('d'))
if debug: inkex.utils.debug("id=" + str(id) + ", tag=" + str(node.tag))
path_d = inkex.paths.CubicSuperPath(inkex.paths.Path(node.get('d')))
sub_idx = -1
for sub in path_d:
sub_idx += 1
Expand All @@ -193,29 +184,30 @@ def effect(self):
# [[handle0_OUT, point0, handle0_1], [handle1_0, point1, handle1_2], [handle2_1, point2, handle2_OUT]]
# the _OUT handles at the end of the path are ignored. The data structure has them identical to their points.
#
if debug: print(" sub=" + str(sub), file=self.tty)
if debug: inkex.utils.debug(" sub=" + str(sub))
end1 = [sub[ 0][1][0], sub[ 0][1][1]]
end2 = [sub[-1][1][0], sub[-1][1][1]]

# Remove trivial self revesal when building candidate segments list.
if ((len(sub) == 3) and self.near_ends(end1, end2)):
if debug: print("dropping segment from self-reversing path, length:", len(sub), file=self.tty)
if debug: inkex.utils.debug("dropping segment from self-reversing path, length:"+str(len(sub)))
sub.pop()
end2 = [sub[-1][1][0], sub[-1][1][1]]

segments.append({'id': id, 'n': sub_idx, 'end1': end1, 'end2':end2, 'seg': sub})
if node.get(inkex.addNS('type', 'sodipodi')):
del node.attrib[inkex.addNS('type', 'sodipodi')]
if debug: print("-------- seen:", file=self.tty)
if debug: inkex.utils.debug("-------- seen: ")
for s in segments:
if debug: print(s['id'], s['n'], s['end1'], s['end2'], file=self.tty)
if debug: inkex.utils.debug(str(s['id'])+", "+str(s['n'])+", "+str(s['end1'])+", "+str(s['end2']))

# chain the segments
obsoleted = 0
remaining = 0
for id, node in self.selected.iteritems():
for id in self.svg.selected:
node = self.svg.selected[id]
# path_style = simplestyle.parseStyle(node.get('style'))
path_d = cubicsuperpath.parsePath(node.get('d'))
path_d = inkex.paths.CubicSuperPath(inkex.paths.Path(node.get('d')))
# ATTENTION: for parsePath() it is the same, if first and last point coincide, or if the path is really closed.
path_closed = True if re.search("z\s*$", node.get('d')) else False
new = []
Expand Down Expand Up @@ -250,7 +242,7 @@ def effect(self):
self.near_ends(end2, seg['end2'])):
seg['seg'] = self.reverse_segment(seg['seg'])
seg['end1'], seg['end2'] = seg['end2'], seg['end1']
if debug: print("reversed seg", seg['id'], seg['n'], file=self.tty)
if debug: inkex.utils.debug("reversed seg " + str(seg['id']) + ", "+str(seg['n']))

if self.near_ends(end1, seg['end2']):
# prepend seg to chain
Expand All @@ -275,7 +267,7 @@ def effect(self):
# Closing a path here, isolates it from the rest.
# But as we prefer to make the chain as long as possible, we close late.
if self.near_ends(end1, end2) and not path_closed and self.close_loops:
if debug: print("closing closeable loop", id, file=self.tty)
if debug: inkex.utils.debug("closing closeable loop " +str(id))
if self.snap_ends:
# move first point to mid position
x1n = (chain[0][1][0] + chain[-1][1][0]) * 0.5
Expand All @@ -284,7 +276,7 @@ def effect(self):
# merge handle of the last point to the handle of the first point
dx0e = chain[-1][0][0] - chain[-1][1][0]
dy0e = chain[-1][0][1] - chain[-1][1][1]
if debug: print("handle diff: ", dx0e, dy0e, file=self.tty)
if debug: inkex.utils.debug("handle diff: "+ str(dx0e) + str(dy0e))
# FIXME: this does not work. cubicsuperpath.formatPath() ignores this handle.
chain[0][0][0], chain[0][0][1] = x1n+dx0e, y1n+dy0e
# drop last point
Expand All @@ -299,23 +291,23 @@ def effect(self):
# node.clear()
node.getparent().remove(node)
obsoleted += 1
if debug: print("Path node obsoleted:", id, file=self.tty)
if debug: inkex.utils.debug("Path node obsoleted: " +str(id))
else:
remaining += 1
# BUG: All previously closed loops, are open, after we convert them back with cubicsuperpath.formatPath()
p_fmt = cubicsuperpath.formatPath(new)
p_fmt = str(inkex.paths.Path(inkex.paths.CubicSuperPath(new).to_path().to_arrays()))
if path_closed: p_fmt += " z"
if debug: print("new path :", p_fmt, file=self.tty)
if debug: inkex.utils.debug("new path: "+str(p_fmt))
node.set('d', p_fmt)

# statistics:
if debug: print("Path nodes obsoleted:", obsoleted, "\nPath nodes remaining:", remaining, file=self.tty)
if debug: inkex.utils.debug("Path nodes obsoleted: "+str(obsoleted) + "\nPath nodes remaining:"+str(remaining))
if self.min_missed_distance_sq is not None:
if debug: print("min_missed_distance:", math.sqrt(float(self.min_missed_distance_sq))/self.unit_factor, '>', self.chain_epsilon, self.options.units, file=self.tty)
if debug: print("Successful link operations: ", self.chained_count, file=self.tty)
if debug: inkex.utils.debug("min_missed_distance: "+str(math.sqrt(float(self.min_missed_distance_sq))/self.unit_factor)+'>'+str(self.chain_epsilon)+str(self.options.units))
if debug: inkex.utils.debug("Successful link operations: "+str(self.chained_count))

if __name__ == '__main__':
e = ChainPaths()

e.affect()
e.run()
sys.exit(0) # helps to keep the selection