removed obsolete extension
This commit is contained in:
parent
d0752fbcef
commit
34d9fca962
@ -1,33 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
|
||||||
<_name>Clean Path</_name>
|
|
||||||
<id>fablabchemnitz.de.clean_path</id>
|
|
||||||
<dependency type="extension">org.inkscape.output.svg.inkscape</dependency>
|
|
||||||
<dependency type="executable" location="extensions">inkex.py</dependency>
|
|
||||||
<dependency type="executable" location="extensions">fablabchemnitz_clean_path.py</dependency>
|
|
||||||
|
|
||||||
<param name="epsilon" type="float" min="0.001" max="999.999" precision="3" _gui-text="Max. distance to connect">0.01</param>
|
|
||||||
<param name="units" type="optiongroup" appearance="minimal" _gui-text=" ">
|
|
||||||
<option value="mm">mm</option>
|
|
||||||
<option value="cm">cm</option>
|
|
||||||
<option value="in">in</option>
|
|
||||||
<option value="pt">pt</option>
|
|
||||||
<option value="px">px</option></param>
|
|
||||||
<param name="snap" type="boolean" _gui-text="Snap connecting ends together">false</param>
|
|
||||||
<!-- Keep in sync with clean_path.py line 19 __version__ = ... -->
|
|
||||||
<param name="about_version" type="description">
|
|
||||||
|
|
||||||
|
|
||||||
https://github.com/jnweiger/inkscape-clean-path
|
|
||||||
Version 0.1</param>
|
|
||||||
|
|
||||||
<effect needs-live-preview="false" >
|
|
||||||
<object-type>path</object-type>
|
|
||||||
<effects-menu>
|
|
||||||
<submenu _name="FabLab Chemnitz Fix/Optimize/Modify"/>
|
|
||||||
</effects-menu>
|
|
||||||
</effect>
|
|
||||||
<script>
|
|
||||||
<command reldir="extensions" interpreter="python">fablabchemnitz_clean_path.py</command>
|
|
||||||
</script>
|
|
||||||
</inkscape-extension>
|
|
@ -1,275 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
#
|
|
||||||
# Inkscape extension making long continuous paths from shorter pieces.
|
|
||||||
# (C) 2015 juewei@fabmail.org
|
|
||||||
#
|
|
||||||
# code snippets visited to learn the extension 'effect' interface:
|
|
||||||
# - convert2dashes.py
|
|
||||||
# - http://github.com/jnweiger/inkscape-silhouette
|
|
||||||
# - http://github.com/jnweiger/inkscape-gears-dev
|
|
||||||
# - http://sourceforge.net/projects/inkcut/
|
|
||||||
# - http://code.google.com/p/inkscape2tikz/
|
|
||||||
# - http://code.google.com/p/eggbotcode/
|
|
||||||
#
|
|
||||||
# 2015-11-30 jw, V0.1 -- initial draught
|
|
||||||
|
|
||||||
__version__ = '0.1' # Keep in sync with clean_path.inx ca line 22
|
|
||||||
__author__ = 'Juergen Weigert <juergen@fabmail.org>'
|
|
||||||
|
|
||||||
import sys, os, shutil, time, logging, tempfile, math
|
|
||||||
|
|
||||||
debug = True
|
|
||||||
#debug = False
|
|
||||||
|
|
||||||
# search path, so that inkscape libraries are found when we are standalone.
|
|
||||||
sys_platform = sys.platform.lower()
|
|
||||||
if sys_platform.startswith('win'): # windows
|
|
||||||
sys.path.append('C:\Program Files\Inkscape\share\extensions')
|
|
||||||
elif sys_platform.startswith('darwin'): # mac
|
|
||||||
sys.path.append('/Applications/Inkscape.app/Contents/Resources/extensions')
|
|
||||||
else: # linux
|
|
||||||
# if sys_platform.startswith('linux'):
|
|
||||||
sys.path.append('/usr/share/inkscape/extensions')
|
|
||||||
|
|
||||||
# inkscape libraries
|
|
||||||
import inkex
|
|
||||||
import cubicsuperpath
|
|
||||||
|
|
||||||
inkex.localize()
|
|
||||||
|
|
||||||
from optparse import SUPPRESS_HELP
|
|
||||||
|
|
||||||
def uutounit(self,nn,uu):
|
|
||||||
try:
|
|
||||||
return self.uutounit(nn,uu) # inkscape 0.91
|
|
||||||
except:
|
|
||||||
return inkex.uutounit(nn,uu) # inkscape 0.48
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
# For handling an SVG viewbox attribute, we will need to know the
|
|
||||||
# values of the document's <svg> width and height attributes as well
|
|
||||||
# as establishing a transform from the viewbox to the display.
|
|
||||||
self.chain_epsilon = 0.01
|
|
||||||
self.snap_ends = True
|
|
||||||
self.segments_done = {}
|
|
||||||
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 >>self.tty, "__init__"
|
|
||||||
|
|
||||||
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('-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]")
|
|
||||||
|
|
||||||
def version(self):
|
|
||||||
print "hiuhu"
|
|
||||||
return __version__
|
|
||||||
def author(self):
|
|
||||||
return __author__
|
|
||||||
|
|
||||||
def calc_unit_factor(self, units='mm'):
|
|
||||||
""" return the scale factor for all dimension conversions.
|
|
||||||
- The document units are always irrelevant as
|
|
||||||
everything in inkscape is expected to be in 90dpi pixel units
|
|
||||||
"""
|
|
||||||
dialog_units = uutounit(self, 1.0, units)
|
|
||||||
self.unit_factor = 1.0 / dialog_units
|
|
||||||
return self.unit_factor
|
|
||||||
|
|
||||||
def reverse_segment(self, seg):
|
|
||||||
r = []
|
|
||||||
for s in reversed(seg):
|
|
||||||
# s has 3 elements: handle1, point, handle2
|
|
||||||
# Swap handles.
|
|
||||||
s.reverse()
|
|
||||||
r.append(s)
|
|
||||||
return r
|
|
||||||
|
|
||||||
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 >>self.tty, "done", id, n, msg
|
|
||||||
|
|
||||||
def is_segment_done(self, id, n):
|
|
||||||
if not id in self.segments_done:
|
|
||||||
return False
|
|
||||||
if n in self.segments_done[id]:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def link_segments(self, seg1, seg2):
|
|
||||||
if self.snap_ends:
|
|
||||||
seg = seg1[:-1]
|
|
||||||
p1 = seg1[-1]
|
|
||||||
p2 = seg2[0]
|
|
||||||
# fuse p1 and p2 to create one new point:
|
|
||||||
# first handle from p1, point coordinates averaged, second handle from p2
|
|
||||||
seg.append([ [ p1[0][0] , p1[0][1] ],
|
|
||||||
[(p1[1][0]+p2[1][0])*.5, (p1[1][1]+p2[1][1])*.5],
|
|
||||||
[ p2[2][0] , p2[2][1] ] ])
|
|
||||||
seg.extend(seg2[1:])
|
|
||||||
else:
|
|
||||||
seg = seg1[:]
|
|
||||||
seg.extend(seg2[:])
|
|
||||||
self.chained_count += 1
|
|
||||||
return seg
|
|
||||||
|
|
||||||
|
|
||||||
def near_ends(self, end1, end2):
|
|
||||||
""" requires self.eps_sq to be the square of the near distance """
|
|
||||||
dx = end1[0] - end2[0]
|
|
||||||
dy = end1[1] - end2[1]
|
|
||||||
d_sq = dx * dx + dy * dy
|
|
||||||
if d_sq > self.eps_sq:
|
|
||||||
if self.min_missed_distance_sq is None:
|
|
||||||
self.min_missed_distance_sq = d_sq
|
|
||||||
elif self.min_missed_distance_sq > d_sq:
|
|
||||||
self.min_missed_distance_sq = d_sq
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def effect(self):
|
|
||||||
if self.options.version:
|
|
||||||
print __version__
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
self.calc_unit_factor(self.options.units)
|
|
||||||
|
|
||||||
if self.options.snap_ends is not None: self.snap_ends = self.options.snap_ends
|
|
||||||
if self.options.chain_epsilon is not None: self.chain_epsilon = self.options.chain_epsilon
|
|
||||||
if self.chain_epsilon < 0.0001: self.chain_epsilon = 0.0001 # keep a minimum.
|
|
||||||
self.eps_sq = self.chain_epsilon * self.unit_factor * self.chain_epsilon * self.unit_factor
|
|
||||||
|
|
||||||
if not len(self.selected.items()):
|
|
||||||
inkex.errormsg(_("Please select one or more objects."))
|
|
||||||
return
|
|
||||||
|
|
||||||
segments = []
|
|
||||||
for id, node in self.selected.iteritems():
|
|
||||||
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 >>self.tty, "id="+str(id), "tag="+str(node.tag)
|
|
||||||
path_d = cubicsuperpath.parsePath(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_x, point0, handle0_1], [handle1_0, point1, handle1_2], [handle2_1, point2, handle2_x]]
|
|
||||||
if debug: print >>self.tty, " sub="+str(sub)
|
|
||||||
end1=[sub[0][1][0],sub[0][1][1]]
|
|
||||||
end2=[sub[-1][1][0],sub[-1][1][1]]
|
|
||||||
|
|
||||||
while ((len(sub) > 1) and self.near_ends(end1, end2)):
|
|
||||||
# FIXME: this also splits any closed path. eg. a rectangle.
|
|
||||||
if debug: print >>self.tty, "splitting self-reversing path, length:", len(sub)
|
|
||||||
## We split the path and generate more snippets.
|
|
||||||
splitp=[sub[-2][1][0],sub[-2][1][1]]
|
|
||||||
segments.append({'id': id, 'n': sub_idx, 'end1': splitp, 'end2':end2, 'seg': [sub[-2],sub[-1]]})
|
|
||||||
sub_idx += 1
|
|
||||||
sub.pop()
|
|
||||||
end2=splitp
|
|
||||||
|
|
||||||
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 >>self.tty, "-------- seen:"
|
|
||||||
for s in segments:
|
|
||||||
if debug: print >>self.tty, s['id'],s['n'],s['end1'],s['end2']
|
|
||||||
|
|
||||||
# chain the segments
|
|
||||||
obsoleted = 0
|
|
||||||
remaining = 0
|
|
||||||
for id, node in self.selected.iteritems():
|
|
||||||
# path_style = simplestyle.parseStyle(node.get('style'))
|
|
||||||
path_d = cubicsuperpath.parsePath(node.get('d'))
|
|
||||||
new=[]
|
|
||||||
cur_idx = -1
|
|
||||||
for cur 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(id, cur_idx, "output") # do not cross with ourselves.
|
|
||||||
end1=[cur[0][1][0],cur[0][1][1]]
|
|
||||||
end2=[cur[-1][1][0],cur[-1][1][1]]
|
|
||||||
segments_idx = 0
|
|
||||||
while segments_idx < len(segments):
|
|
||||||
seg = segments[segments_idx]
|
|
||||||
if self.is_segment_done(seg['id'], seg['n']):
|
|
||||||
segments_idx += 1
|
|
||||||
continue
|
|
||||||
|
|
||||||
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 debug: print >>self.tty, "reversed seg", seg['id'], seg['n']
|
|
||||||
|
|
||||||
if self.near_ends(end1, seg['end2']):
|
|
||||||
# prepend seg to cur
|
|
||||||
self.set_segment_done(seg['id'], seg['n'], 'prepended to '+id+' '+str(cur_idx))
|
|
||||||
cur = self.link_segments(seg['seg'], cur)
|
|
||||||
end1=[cur[0][1][0],cur[0][1][1]]
|
|
||||||
segments_idx = 0
|
|
||||||
continue
|
|
||||||
|
|
||||||
if self.near_ends(end2, seg['end1']):
|
|
||||||
# append seg to cur
|
|
||||||
self.set_segment_done(seg['id'], seg['n'], 'appended to '+id+' '+str(cur_idx))
|
|
||||||
cur = self.link_segments(cur, seg['seg'])
|
|
||||||
end2=[cur[-1][1][0],cur[-1][1][1]]
|
|
||||||
segments_idx = 0
|
|
||||||
continue
|
|
||||||
|
|
||||||
segments_idx += 1
|
|
||||||
|
|
||||||
new.append(cur)
|
|
||||||
|
|
||||||
if not len(new):
|
|
||||||
# node.clear()
|
|
||||||
node.getparent().remove(node)
|
|
||||||
obsoleted += 1
|
|
||||||
if debug: print >>self.tty, "Path node obsoleted:", id
|
|
||||||
else:
|
|
||||||
remaining += 1
|
|
||||||
node.set('d',cubicsuperpath.formatPath(new))
|
|
||||||
|
|
||||||
# statistics:
|
|
||||||
print >>self.tty, "Path nodes obsoleted:", obsoleted, "\nPath nodes remaining:", remaining
|
|
||||||
if self.min_missed_distance_sq is not None:
|
|
||||||
print >>self.tty, "min_missed_distance:", math.sqrt(float(self.min_missed_distance_sq))/self.unit_factor, '>', self.chain_epsilon, self.options.units
|
|
||||||
print >>self.tty, "Successful link operations: ", self.chained_count
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
e = ChainPaths()
|
|
||||||
|
|
||||||
e.affect()
|
|
||||||
sys.exit(0) # helps to keep the selection
|
|
Reference in New Issue
Block a user