Fix issue #136
This commit is contained in:
parent
371d5f936d
commit
68e1dd9ac4
@ -12,6 +12,7 @@
|
|||||||
</param>
|
</param>
|
||||||
<param name="snap_ends" type="bool" gui-text="Snap connecting ends together" gui-description="This will deduplicate (merge) two nodes to one node">false</param>
|
<param name="snap_ends" type="bool" gui-text="Snap connecting ends together" gui-description="This will deduplicate (merge) two nodes to one node">false</param>
|
||||||
<param name="close_loops" type="bool" gui-text="Close loops (start/end of the same path)">true</param>
|
<param name="close_loops" type="bool" gui-text="Close loops (start/end of the same path)">true</param>
|
||||||
|
<param name="limit" type="int" min="0" max="99999" gui-text="Maximum items to process" gui-description="The more items at once are selected, the slower the process gets. Repeating in smaller steps is better. Set 0 for umlimited selection, else the selection gets cut off.">2000</param>
|
||||||
<param name="debug" type="bool" gui-text="Debug output">false</param>
|
<param name="debug" type="bool" gui-text="Debug output">false</param>
|
||||||
<!-- Keep in sync with chain_paths.py line 19 __version__ = ... -->
|
<!-- Keep in sync with chain_paths.py line 19 __version__ = ... -->
|
||||||
<label appearance="url">https://github.com/fablabnbg/inkscape-chain-paths</label>
|
<label appearance="url">https://github.com/fablabnbg/inkscape-chain-paths</label>
|
||||||
|
@ -37,247 +37,265 @@ from optparse import SUPPRESS_HELP
|
|||||||
|
|
||||||
class ChainPaths(inkex.EffectExtension):
|
class ChainPaths(inkex.EffectExtension):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
inkex.Effect.__init__(self)
|
inkex.Effect.__init__(self)
|
||||||
|
|
||||||
# For handling an SVG viewbox attribute, we will need to know the
|
# For handling an SVG viewbox attribute, we will need to know the
|
||||||
# values of the document's <svg> width and height attributes as well
|
# values of the document's <svg> width and height attributes as well
|
||||||
# as establishing a transform from the viewbox to the display.
|
# as establishing a transform from the viewbox to the display.
|
||||||
self.chain_epsilon = 0.01
|
self.chain_epsilon = 0.01
|
||||||
self.snap_ends = True
|
self.snap_ends = True
|
||||||
self.close_loops = True
|
self.close_loops = True
|
||||||
self.segments_done = {}
|
self.segments_done = {}
|
||||||
self.min_missed_distance_sq = None
|
self.min_missed_distance_sq = None
|
||||||
self.chained_count = 0
|
self.chained_count = 0
|
||||||
|
|
||||||
self.arg_parser.add_argument('-V', '--version', type=inkex.Boolean, default=False, help = 'Just print version number ("' + __version__ + '") and exit.')
|
self.arg_parser.add_argument('-V', '--version', type=inkex.Boolean, default=False, help = 'Just print version number ("' + __version__ + '") and exit.')
|
||||||
self.arg_parser.add_argument('-s', '--snap_ends', type=inkex.Boolean, default=True, help='snap end-points together when connecting')
|
self.arg_parser.add_argument('-s', '--snap_ends', type=inkex.Boolean, default=True, help='snap end-points together when connecting')
|
||||||
self.arg_parser.add_argument('-c', '--close_loops', type=inkex.Boolean, default=True, help='close loops (start/end of the same path)')
|
self.arg_parser.add_argument('-c', '--close_loops', type=inkex.Boolean, default=True, help='close loops (start/end of the same path)')
|
||||||
self.arg_parser.add_argument('-u', '--units', default="mm", help="measurement unit for epsilon")
|
self.arg_parser.add_argument('-l', '--limit', type=int, default=2000, help='Maximum items to process')
|
||||||
self.arg_parser.add_argument('-e', '--chain_epsilon', type=float, default=0.01, help="Max. distance to connect [mm]")
|
self.arg_parser.add_argument('-u', '--units', default="mm", help="measurement unit for epsilon")
|
||||||
self.arg_parser.add_argument('-d', '--debug', type=inkex.Boolean, default=False, help='Debug')
|
self.arg_parser.add_argument('-e', '--chain_epsilon', type=float, default=0.01, help="Max. distance to connect [mm]")
|
||||||
|
self.arg_parser.add_argument('-d', '--debug', type=inkex.Boolean, default=False, help='Debug')
|
||||||
|
|
||||||
def version(self):
|
def version(self):
|
||||||
return __version__
|
return __version__
|
||||||
def author(self):
|
def author(self):
|
||||||
return __author__
|
return __author__
|
||||||
|
|
||||||
def calc_unit_factor(self, units='mm'):
|
def calc_unit_factor(self, units='mm'):
|
||||||
""" return the scale factor for all dimension conversions.
|
""" return the scale factor for all dimension conversions.
|
||||||
- The document units are always irrelevant as
|
- The document units are always irrelevant as
|
||||||
everything in inkscape is expected to be in 90dpi pixel units
|
everything in inkscape is expected to be in 90dpi pixel units
|
||||||
"""
|
"""
|
||||||
dialog_units = self.svg.unittouu(str(1.0)+units)
|
dialog_units = self.svg.unittouu(str(1.0)+units)
|
||||||
self.unit_factor = 1.0 / dialog_units
|
self.unit_factor = 1.0 / dialog_units
|
||||||
return self.unit_factor
|
return self.unit_factor
|
||||||
|
|
||||||
def reverse_segment(self, seg):
|
def reverse_segment(self, seg):
|
||||||
r = []
|
r = []
|
||||||
for s in reversed(seg):
|
for s in reversed(seg):
|
||||||
# s has 3 elements: handle1, point, handle2
|
# s has 3 elements: handle1, point, handle2
|
||||||
# Swap handles.
|
# Swap handles.
|
||||||
s.reverse()
|
s.reverse()
|
||||||
r.append(s)
|
r.append(s)
|
||||||
return r
|
return r
|
||||||
|
|
||||||
def set_segment_done(self, so, id, n, msg=''):
|
def set_segment_done(self, so, id, n, msg=''):
|
||||||
if not id in self.segments_done:
|
if not id in self.segments_done:
|
||||||
self.segments_done[id] = {}
|
self.segments_done[id] = {}
|
||||||
self.segments_done[id][n] = True
|
self.segments_done[id][n] = True
|
||||||
if so.debug: inkex.utils.debug("done {} {} {}".format(id), n, msg)
|
if so.debug: inkex.utils.debug("done {} {} {}".format(id, n, msg))
|
||||||
|
|
||||||
def is_segment_done(self, id, n):
|
def is_segment_done(self, id, n):
|
||||||
if not id in self.segments_done:
|
if not id in self.segments_done:
|
||||||
return False
|
return False
|
||||||
if n in self.segments_done[id]:
|
if n in self.segments_done[id]:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def link_segments(self, seg1, seg2):
|
def link_segments(self, seg1, seg2):
|
||||||
if self.snap_ends:
|
if self.snap_ends:
|
||||||
seg = seg1[:-1]
|
seg = seg1[:-1]
|
||||||
p1 = seg1[-1]
|
p1 = seg1[-1]
|
||||||
p2 = seg2[0]
|
p2 = seg2[0]
|
||||||
# fuse p1 and p2 to create one new point:
|
# fuse p1 and p2 to create one new point:
|
||||||
# first handle from p1, point coordinates averaged, second handle from p2
|
# first handle from p1, point coordinates averaged, second handle from p2
|
||||||
seg.append([ [ p1[0][0] , p1[0][1] ],
|
seg.append([ [ p1[0][0] , p1[0][1] ],
|
||||||
[ (p1[1][0] + p2[1][0]) * .5, (p1[1][1] + p2[1][1]) * .5 ],
|
[ (p1[1][0] + p2[1][0]) * .5, (p1[1][1] + p2[1][1]) * .5 ],
|
||||||
[ p2[2][0] , p2[2][1] ] ])
|
[ p2[2][0] , p2[2][1] ] ])
|
||||||
seg.extend(seg2[1:])
|
seg.extend(seg2[1:])
|
||||||
else:
|
else:
|
||||||
seg = seg1[:]
|
seg = seg1[:]
|
||||||
seg.extend(seg2[:])
|
seg.extend(seg2[:])
|
||||||
self.chained_count += 1
|
self.chained_count += 1
|
||||||
return seg
|
return seg
|
||||||
|
|
||||||
def near_ends(self, end1, end2):
|
def near_ends(self, end1, end2):
|
||||||
""" requires self.eps_sq to be the square of the near distance """
|
""" requires self.eps_sq to be the square of the near distance """
|
||||||
dx = end1[0] - end2[0]
|
dx = end1[0] - end2[0]
|
||||||
dy = end1[1] - end2[1]
|
dy = end1[1] - end2[1]
|
||||||
d_sq = dx * dx + dy * dy
|
d_sq = dx * dx + dy * dy
|
||||||
if d_sq > self.eps_sq:
|
if d_sq > self.eps_sq:
|
||||||
if self.min_missed_distance_sq is None:
|
if self.min_missed_distance_sq is None:
|
||||||
self.min_missed_distance_sq = d_sq
|
self.min_missed_distance_sq = d_sq
|
||||||
elif self.min_missed_distance_sq > d_sq:
|
elif self.min_missed_distance_sq > d_sq:
|
||||||
self.min_missed_distance_sq = d_sq
|
self.min_missed_distance_sq = d_sq
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def effect(self):
|
def effect(self):
|
||||||
so = self.options
|
so = self.options
|
||||||
|
|
||||||
if so.version:
|
|
||||||
print(__version__)
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
self.calc_unit_factor(so.units)
|
if so.version:
|
||||||
|
print(__version__)
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
if so.snap_ends is not None: self.snap_ends = so.snap_ends
|
self.calc_unit_factor(so.units)
|
||||||
if so.close_loops is not None: self.close_loops = so.close_loops
|
|
||||||
if so.chain_epsilon is not None: self.chain_epsilon = so.chain_epsilon
|
|
||||||
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.svg.selected.items()):
|
if so.snap_ends is not None: self.snap_ends = so.snap_ends
|
||||||
inkex.errormsg("Please select one or more objects.")
|
if so.close_loops is not None: self.close_loops = so.close_loops
|
||||||
return
|
if so.chain_epsilon is not None: self.chain_epsilon = so.chain_epsilon
|
||||||
|
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
|
||||||
|
|
||||||
segments = []
|
selected = self.svg.selected.items()
|
||||||
for id, node in self.svg.selected.items():
|
|
||||||
if node.tag != inkex.addNS('path', 'svg'):
|
|
||||||
inkex.errormsg("Object id {} is not a path. Try\n - Path->Object to Path\n - Object->Ungroup".format(node.get('id')))
|
|
||||||
return
|
|
||||||
if so.debug: inkex.utils.debug("id={}, tag=".format(idnode.get('id'), node.tag))
|
|
||||||
path_d = CubicSuperPath(Path(node.get('d')))
|
|
||||||
sub_idx = -1
|
|
||||||
for sub in path_d:
|
|
||||||
sub_idx += 1
|
|
||||||
# sub = [[[200.0, 300.0], [200.0, 300.0], [175.0, 290.0]], [[175.0, 265.0], [220.37694, 256.99876], [175.0, 240.0]], [[175.0, 215.0], [200.0, 200.0], [200.0, 200.0]]]
|
|
||||||
# this is a path of three points. All the bezier handles are included. the Structure is:
|
|
||||||
# [[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 so.debug: inkex.utils.debug(" sub={}".format(sub))
|
|
||||||
end1 = [sub[ 0][1][0], sub[ 0][1][1]]
|
|
||||||
end2 = [sub[-1][1][0], sub[-1][1][1]]
|
|
||||||
|
|
||||||
# Remove trivial self reversal when building candidate segments list.
|
itemsCount = len(selected)
|
||||||
if ((len(sub) == 3) and self.near_ends(end1, end2)):
|
if not itemsCount:
|
||||||
if so.debug: inkex.utils.debug("dropping segment from self-reversing path, length: {}".format(len(sub)))
|
inkex.errormsg("Please select one or more objects.")
|
||||||
sub.pop()
|
return
|
||||||
end2 = [sub[-1][1][0], sub[-1][1][1]]
|
|
||||||
|
|
||||||
segments.append({'id': id, 'n': sub_idx, 'end1': end1, 'end2':end2, 'seg': sub})
|
#selected = dict(reversed(list(selected))) #reverse
|
||||||
if node.get(inkex.addNS('type', 'sodipodi')):
|
if so.limit > 0 and itemsCount > so.limit:
|
||||||
del node.attrib[inkex.addNS('type', 'sodipodi')]
|
inkex.utils.debug("Maximum items to process is set to {}. You selected {} items. We continue with processing until limit is reached.".format(so.limit, itemsCount))
|
||||||
if so.debug: inkex.utils.debug("-------- seen: ")
|
|
||||||
for s in segments:
|
|
||||||
if so.debug: inkex.utils.debug("{}, {}, {}, {}".format(s['id'], s['n'], s['end1'], s['end2']))
|
|
||||||
|
|
||||||
# chain the segments
|
segments = []
|
||||||
obsoleted = 0
|
workedon = 0
|
||||||
remaining = 0
|
for id, node in selected:
|
||||||
for id, node in self.svg.selected.items():
|
if node.tag != inkex.addNS('path', 'svg'):
|
||||||
path_d = CubicSuperPath(Path(node.get('d')))
|
inkex.errormsg("Object id {} is not a path. Try\n - Path->Object to Path\n - Object->Ungroup".format(node.get('id')))
|
||||||
# ATTENTION: for parsePath() it is the same, if first and last point coincide, or if the path is really closed.
|
return
|
||||||
path_closed = True if re.search(r'z\s*$', node.get('d')) else False
|
if so.debug: inkex.utils.debug("id={}, tag=".format(idnode.get('id'), node.tag))
|
||||||
new = []
|
path_d = CubicSuperPath(Path(node.get('d')))
|
||||||
cur_idx = -1
|
sub_idx = -1
|
||||||
for chain in path_d:
|
for sub in path_d:
|
||||||
cur_idx += 1
|
sub_idx += 1
|
||||||
if not self.is_segment_done(id, cur_idx):
|
# sub = [[[200.0, 300.0], [200.0, 300.0], [175.0, 290.0]], [[175.0, 265.0], [220.37694, 256.99876], [175.0, 240.0]], [[175.0, 215.0], [200.0, 200.0], [200.0, 200.0]]]
|
||||||
# quadratic algorithm: we check both ends of the current segment.
|
# this is a path of three points. All the bezier handles are included. the Structure is:
|
||||||
# If one of them is near another known end from the segments list, we
|
# [[handle0_OUT, point0, handle0_1], [handle1_0, point1, handle1_2], [handle2_1, point2, handle2_OUT]]
|
||||||
# chain this segment to the current segment and remove it from the
|
# the _OUT handles at the end of the path are ignored. The data structure has them identical to their points.
|
||||||
# list,
|
#
|
||||||
# end1-end1 or end2-end2: The new segment is reversed.
|
if so.debug: inkex.utils.debug(" sub={}".format(sub))
|
||||||
# end1-end2: The new segment is prepended to the current segment.
|
end1 = [sub[ 0][1][0], sub[ 0][1][1]]
|
||||||
# end2-end1: The new segment is appended to the current segment.
|
end2 = [sub[-1][1][0], sub[-1][1][1]]
|
||||||
self.set_segment_done(so, id, cur_idx, "output") # do not cross with ourselves.
|
|
||||||
end1 = [chain[ 0][1][0], chain[ 0][1][1]]
|
|
||||||
end2 = [chain[-1][1][0], chain[-1][1][1]]
|
|
||||||
|
|
||||||
# Remove trivial self revesal when doing the actual chain operation.
|
# Remove trivial self reversal when building candidate segments list.
|
||||||
if ((len(chain) == 3) and self.near_ends(end1, end2)):
|
if ((len(sub) == 3) and self.near_ends(end1, end2)):
|
||||||
chain.pop()
|
if so.debug: inkex.utils.debug("dropping segment from self-reversing path, length: {}".format(len(sub)))
|
||||||
end2 = [chain[-1][1][0], chain[-1][1][1]]
|
sub.pop()
|
||||||
|
end2 = [sub[-1][1][0], sub[-1][1][1]]
|
||||||
|
|
||||||
segments_idx = 0
|
segments.append({'id': id, 'n': sub_idx, 'end1': end1, 'end2':end2, 'seg': sub})
|
||||||
while segments_idx < len(segments):
|
if node.get(inkex.addNS('type', 'sodipodi')):
|
||||||
seg = segments[segments_idx]
|
del node.attrib[inkex.addNS('type', 'sodipodi')]
|
||||||
if self.is_segment_done(seg['id'], seg['n']):
|
workedon += 1
|
||||||
segments_idx += 1
|
if workedon >= so.limit and so.limit > 0:
|
||||||
continue
|
break
|
||||||
|
|
||||||
if (self.near_ends(end1, seg['end1']) or
|
if so.debug: inkex.utils.debug("-------- seen: ")
|
||||||
self.near_ends(end2, seg['end2'])):
|
for s in segments:
|
||||||
seg['seg'] = self.reverse_segment(seg['seg'])
|
if so.debug: inkex.utils.debug("{}, {}, {}, {}".format(s['id'], s['n'], s['end1'], s['end2']))
|
||||||
seg['end1'], seg['end2'] = seg['end2'], seg['end1']
|
|
||||||
if so.debug: inkex.utils.debug("reversed seg {}, {}".format(seg['id'], seg['n']))
|
|
||||||
|
|
||||||
if self.near_ends(end1, seg['end2']):
|
# chain the segments
|
||||||
# prepend seg to chain
|
obsoleted = 0
|
||||||
self.set_segment_done(so, seg['id'], seg['n'], 'prepended to {} {}'.format(id, cur_idx))
|
remaining = 0
|
||||||
chain = self.link_segments(seg['seg'], chain)
|
|
||||||
end1 = [chain[0][1][0], chain[0][1][1]]
|
|
||||||
segments_idx = 0 # this chain changed. re-visit all candidate
|
|
||||||
continue
|
|
||||||
|
|
||||||
if self.near_ends(end2, seg['end1']):
|
workedon = 0
|
||||||
# append seg to chain
|
for id, node in selected:
|
||||||
self.set_segment_done(so, seg['id'], seg['n'], 'appended to {} {}'.format(id, cur_idx))
|
path_d = CubicSuperPath(Path(node.get('d')))
|
||||||
chain = self.link_segments(chain, seg['seg'])
|
# ATTENTION: for parsePath() it is the same, if first and last point coincide, or if the path is really closed.
|
||||||
end2 = [chain[-1][1][0], chain[-1][1][1]]
|
path_closed = True if re.search(r'z\s*$', node.get('d')) else False
|
||||||
segments_idx = 0 # this chain changed. re-visit all candidate
|
new = []
|
||||||
continue
|
cur_idx = -1
|
||||||
|
for chain in path_d:
|
||||||
|
cur_idx += 1
|
||||||
|
if not self.is_segment_done(id, cur_idx):
|
||||||
|
# quadratic algorithm: we check both ends of the current segment.
|
||||||
|
# If one of them is near another known end from the segments list, we
|
||||||
|
# chain this segment to the current segment and remove it from the
|
||||||
|
# list,
|
||||||
|
# end1-end1 or end2-end2: The new segment is reversed.
|
||||||
|
# end1-end2: The new segment is prepended to the current segment.
|
||||||
|
# end2-end1: The new segment is appended to the current segment.
|
||||||
|
self.set_segment_done(so, id, cur_idx, "output") # do not cross with ourselves.
|
||||||
|
end1 = [chain[ 0][1][0], chain[ 0][1][1]]
|
||||||
|
end2 = [chain[-1][1][0], chain[-1][1][1]]
|
||||||
|
|
||||||
segments_idx += 1
|
# Remove trivial self revesal when doing the actual chain operation.
|
||||||
|
if ((len(chain) == 3) and self.near_ends(end1, end2)):
|
||||||
|
chain.pop()
|
||||||
|
end2 = [chain[-1][1][0], chain[-1][1][1]]
|
||||||
|
|
||||||
# Now all joinable segments are joined.
|
segments_idx = 0
|
||||||
# Finally, we can check, if the resulting path is a closed path:
|
while segments_idx < len(segments):
|
||||||
# Closing a path here, isolates it from the rest.
|
seg = segments[segments_idx]
|
||||||
# But as we prefer to make the chain as long as possible, we close late.
|
if self.is_segment_done(seg['id'], seg['n']):
|
||||||
if self.near_ends(end1, end2) and not path_closed and self.close_loops:
|
segments_idx += 1
|
||||||
if so.debug: inkex.utils.debug("closing closeable loop {}".format(id))
|
continue
|
||||||
if self.snap_ends:
|
|
||||||
# move first point to mid position
|
|
||||||
x1n = (chain[0][1][0] + chain[-1][1][0]) * 0.5
|
|
||||||
y1n = (chain[0][1][1] + chain[-1][1][1]) * 0.5
|
|
||||||
chain[0][1][0], chain[0][1][1] = x1n, y1n
|
|
||||||
# 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 so.debug: inkex.utils.debug("handle diff: {} {}".format(dx0e, 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
|
|
||||||
chain.pop()
|
|
||||||
end2 = [chain[-1][1][0], chain[-1][1][1]]
|
|
||||||
path_closed = True
|
|
||||||
self.chained_count +=1
|
|
||||||
|
|
||||||
new.append(chain)
|
if (self.near_ends(end1, seg['end1']) or
|
||||||
|
self.near_ends(end2, seg['end2'])):
|
||||||
|
seg['seg'] = self.reverse_segment(seg['seg'])
|
||||||
|
seg['end1'], seg['end2'] = seg['end2'], seg['end1']
|
||||||
|
if so.debug: inkex.utils.debug("reversed seg {}, {}".format(seg['id'], seg['n']))
|
||||||
|
|
||||||
if not len(new):
|
if self.near_ends(end1, seg['end2']):
|
||||||
# node.clear()
|
# prepend seg to chain
|
||||||
if node.getparent() is not None:
|
self.set_segment_done(so, seg['id'], seg['n'], 'prepended to {} {}'.format(id, cur_idx))
|
||||||
node.delete()
|
chain = self.link_segments(seg['seg'], chain)
|
||||||
obsoleted += 1
|
end1 = [chain[0][1][0], chain[0][1][1]]
|
||||||
if so.debug: inkex.utils.debug("Path node obsoleted: {}".format(id))
|
segments_idx = 0 # this chain changed. re-visit all candidate
|
||||||
else:
|
continue
|
||||||
remaining += 1
|
|
||||||
# BUG: All previously closed loops are open after we convert them back with cubicsuperpath.formatPath()
|
|
||||||
p_fmt = str(Path(CubicSuperPath(new).to_path().to_arrays()))
|
|
||||||
if path_closed: p_fmt += " z"
|
|
||||||
if so.debug: inkex.utils.debug("new path: {}".format(p_fmt))
|
|
||||||
node.set('d', p_fmt)
|
|
||||||
|
|
||||||
# statistics:
|
if self.near_ends(end2, seg['end1']):
|
||||||
if so.debug: inkex.utils.debug("Path nodes obsoleted: {}\nPath nodes remaining: {}".format(obsoleted, remaining))
|
# append seg to chain
|
||||||
if self.min_missed_distance_sq is not None:
|
self.set_segment_done(so, seg['id'], seg['n'], 'appended to {} {}'.format(id, cur_idx))
|
||||||
if so.debug: inkex.utils.debug("min_missed_distance: {} > {}".format(math.sqrt(float(self.min_missed_distance_sq))/self.unit_factor, self.chain_epsilon)+str(so.units))
|
chain = self.link_segments(chain, seg['seg'])
|
||||||
if so.debug: inkex.utils.debug("Successful link operations: {}".format(self.chained_count))
|
end2 = [chain[-1][1][0], chain[-1][1][1]]
|
||||||
|
segments_idx = 0 # this chain changed. re-visit all candidate
|
||||||
|
continue
|
||||||
|
|
||||||
|
segments_idx += 1
|
||||||
|
|
||||||
|
# Now all joinable segments are joined.
|
||||||
|
# Finally, we can check, if the resulting path is a closed path:
|
||||||
|
# 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 so.debug: inkex.utils.debug("closing closeable loop {}".format(id))
|
||||||
|
if self.snap_ends:
|
||||||
|
# move first point to mid position
|
||||||
|
x1n = (chain[0][1][0] + chain[-1][1][0]) * 0.5
|
||||||
|
y1n = (chain[0][1][1] + chain[-1][1][1]) * 0.5
|
||||||
|
chain[0][1][0], chain[0][1][1] = x1n, y1n
|
||||||
|
# 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 so.debug: inkex.utils.debug("handle diff: {} {}".format(dx0e, 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
|
||||||
|
chain.pop()
|
||||||
|
end2 = [chain[-1][1][0], chain[-1][1][1]]
|
||||||
|
path_closed = True
|
||||||
|
self.chained_count +=1
|
||||||
|
|
||||||
|
new.append(chain)
|
||||||
|
|
||||||
|
if not len(new):
|
||||||
|
# node.clear()
|
||||||
|
if node.getparent() is not None:
|
||||||
|
node.delete()
|
||||||
|
obsoleted += 1
|
||||||
|
if so.debug: inkex.utils.debug("Path node obsoleted: {}".format(id))
|
||||||
|
else:
|
||||||
|
remaining += 1
|
||||||
|
# BUG: All previously closed loops are open after we convert them back with cubicsuperpath.formatPath()
|
||||||
|
p_fmt = str(Path(CubicSuperPath(new).to_path().to_arrays()))
|
||||||
|
if path_closed: p_fmt += " z"
|
||||||
|
if so.debug: inkex.utils.debug("new path: {}".format(p_fmt))
|
||||||
|
node.set('d', p_fmt)
|
||||||
|
workedon += 1
|
||||||
|
if workedon >= so.limit and so.limit > 0:
|
||||||
|
break
|
||||||
|
|
||||||
|
# statistics:
|
||||||
|
if so.debug: inkex.utils.debug("Path nodes obsoleted: {}\nPath nodes remaining: {}".format(obsoleted, remaining))
|
||||||
|
if self.min_missed_distance_sq is not None:
|
||||||
|
if so.debug: inkex.utils.debug("min_missed_distance: {} > {}".format(math.sqrt(float(self.min_missed_distance_sq))/self.unit_factor, self.chain_epsilon)+str(so.units))
|
||||||
|
if so.debug: inkex.utils.debug("Successful link operations: {}".format(self.chained_count))
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
ChainPaths().run()
|
ChainPaths().run()
|
||||||
|
Loading…
Reference in New Issue
Block a user