From 13b8d3d2340c14453fbf7a1da00420c01779e760 Mon Sep 17 00:00:00 2001 From: Romain Testuz Date: Sun, 26 Nov 2017 09:00:26 +0100 Subject: [PATCH 1/5] improves the greedy algorithm by allowing paths to be reversed (some path commands like A are not supported), it now handles groups by considering them as a single path so it will not modify the order inside of the group and at the moment, it cannot reverse a group --- inkscape_driver/eggbot_reorder.inx | 26 ++-- inkscape_driver/eggbot_reorder.py | 217 ++++++++++++++++++++--------- 2 files changed, 166 insertions(+), 77 deletions(-) diff --git a/inkscape_driver/eggbot_reorder.inx b/inkscape_driver/eggbot_reorder.inx index fb89fae3..a5273901 100755 --- a/inkscape_driver/eggbot_reorder.inx +++ b/inkscape_driver/eggbot_reorder.inx @@ -6,27 +6,27 @@ eggbot_reorder.py inkex.py - - <_param name="Header" type="description" xml:space="preserve"> - This extension will perform simple optimizations - of selected paths. It will try to change the + <_param name="Header" type="description" xml:space="preserve"> + This extension will perform simple optimizations + of selected paths. It will try to change the order of plotting so as to reduce the amount of "pen-up" travel that occurs between paths. - + Solving for optimal plot order is a difficult problem, known in computer science as the - "traveling salesman problem," or just "TSP." - + "traveling salesman problem," or just "TSP". + This routine does not look for the best possible solution; that can be slow. Instead it tries a - few quick methods that often reduce pen-up - travel distance (and time) by 30% or more. + few quick methods that often reduce pen-up + travel distance (and time) by 30% or more. - Please note: This extension is still considered - experimental, and is only provided in case you - may find it useful. Be sure to save a copy of + Please note: This extension is still considered + experimental, and is only provided in case you + may find it useful. Be sure to save a copy of your document before running this routine. - + + true all From 951dd084ccb514f78244f085cafb9c2a1222f66a Mon Sep 17 00:00:00 2001 From: Romain Testuz Date: Sun, 10 Dec 2017 10:31:09 +0100 Subject: [PATCH 3/5] Create Path class --- inkscape_driver/eggbot_reorder.py | 74 ++++++++++++++++++++----------- 1 file changed, 48 insertions(+), 26 deletions(-) diff --git a/inkscape_driver/eggbot_reorder.py b/inkscape_driver/eggbot_reorder.py index b76b32b3..5b5b018a 100755 --- a/inkscape_driver/eggbot_reorder.py +++ b/inkscape_driver/eggbot_reorder.py @@ -32,55 +32,76 @@ def dist( x0, y0, x1, y1 ): return math.sqrt( ( x1 - x0 ) ** 2 + ( y1 - y0 ) ** 2 ) +def dist_t( x, y ): + return dist(x[0], x[1], y[0], y[1]) + +class Path: + def __init__(self, id, start, end, reversed=False): + self.id = id + self.start = start + self.end = end + self.reversed = reversed + + def __eq__(self, other): #ids must be unique + return self.id == other.id + + def get_start(self): + return self.start if not self.reversed else self.end + + def get_end(self): + return self.end if not self.reversed else self.start + + def reverse(self): + self.reversed = not self.reversed + + def dist_to_start(self, otherPath): + return dist_t(self.get_end(), otherPath.get_start()) + + def dist_to_end(self, otherPath): + return dist_t(self.get_end(), otherPath.get_end()) + def find_ordering( objlist, allowReverse ): """ - Takes a list of (id, (startX, startY, endX, endY)), and finds the best ordering. + Takes a list of Paths, and finds the best ordering. Uses a greedy algorithm which can reverse the path direction if necessary Returns a list of (id, reverse), as well as the original and optimized "air distance" which is just the distance traveled in the air. Reverse indicate if the path must be reversed """ - startX, startY = 0.0, 0.0 #Start point TODO 0,0 is not top left of the page + start = (0.0, 0.0) #Start point TODO 0,0 is not always top left of the page # let's figure out the default in-air length (this is not meaningful as we are using the dictionary ordering) air_length_default = 0 - oldx, oldy = startX, startY + old = start - for id, coords in objlist: - #inkex.debug(( id, oldx, oldy, coords[0], coords[1] )) - air_length_default += dist( oldx, oldy, coords[0], coords[1] ) - oldx = coords[2] - oldy = coords[3] - #fid.write("Default air distance: %d\n" % air_length_default) + for i, path in enumerate(objlist[1:]): + air_length_default += objlist[i-1].dist_to_start(path) air_length_ordered = 0 sort_list = [] - prevX, prevY = startX, startY + prev = Path(-1, (0,0), (0,0)) # for the previous end point, iterate over each remaining path and pick the closest starting point or ending point if allowed while len( objlist ) > 0: min_distance = sys.float_info.max #The biggest number possible for path in objlist: - dist_to_start = dist( prevX, prevY, path[1][0], path[1][1] ) - dist_to_end = dist( prevX, prevY, path[1][2], path[1][3] ) if allowReverse else -1 + dist_to_start = prev.dist_to_start(path) + dist_to_end = prev.dist_to_end(path) if dist_to_start < min_distance: min_distance = dist_to_start min_path = path - reverse = False if allowReverse and dist_to_end < min_distance: min_distance = dist_to_end + path.reverse() min_path = path - reverse = True air_length_ordered += min_distance - sort_list.append( (min_path[0], reverse) ) #Add (id, reverse) + sort_list.append( min_path ) objlist.remove( min_path ) - (prevX, prevY) = (min_path[1][0], min_path[1][1]) if reverse else (min_path[1][2], min_path[1][3]) - - #fid.write("optimized air distance: %d\n" % air_length_ordered) + prev = min_path return sort_list, air_length_default, air_length_ordered @@ -105,7 +126,7 @@ def reversePath( path ): #Adapted from https://github.com/Pomax/svg-path-reverse/blob/gh-pages/reverse.js #Unpack sublists into a single list - flattenedPath = [item for sublist in path for subsublist in sublist for item in subsublist]#Sorry + flattenedPath = [item for sublist in path for subsublist in sublist for item in subsublist] reversedPath = [] i = 0 @@ -220,30 +241,31 @@ def effect( self ): objlist = [] for id, node in self.selected.iteritems(): - item = ( id, self.get_start_end( node ) ) - objlist.append( item ) + (sx, sy, ex, ey) = self.get_start_end( node ) + path = Path( id, (sx, sy), (ex, ey) ) + objlist.append( path ) # sort / order the objects sort_order, air_distance_default, air_distance_ordered = find_ordering( objlist, self.options.allowReverse ) reverseCount = 0 - for id, reverse in sort_order: - node = self.selected[id] + for path in sort_order: + node = self.selected[path.id] if node.tag == inkex.addNS( 'path', 'svg' ): node_sp = simplepath.parsePath( node.get( 'd' ) ) - if(reverse): + if(path.reversed): node_sp_string = reversePath(node_sp) reverseCount += 1 else: node_sp_string = simplepath.formatPath(node_sp) node.set('d', node_sp_string) - elif node.tag == inkex.addNS( 'g', 'svg' ) and reverse: + elif node.tag == inkex.addNS('g', 'svg') and path.reversed: #TODO Every element of the group should be reversed inkex.errormsg("Reversing groups is currently not possible, please ungroup for better results") #keep in mind the different selected ids might have different parents - self.getParentNode( node ).append( node ) + self.getParentNode(node).append(node) inkex.errormsg("Reversed {} paths.".format(reverseCount)) #fid.close() From bf8104b4f42caa6796b5a3cf8d8942344f0f628a Mon Sep 17 00:00:00 2001 From: Romain Testuz Date: Sun, 10 Dec 2017 10:48:05 +0100 Subject: [PATCH 4/5] removed group support --- inkscape_driver/eggbot_reorder.py | 69 +++++++++++-------------------- 1 file changed, 23 insertions(+), 46 deletions(-) diff --git a/inkscape_driver/eggbot_reorder.py b/inkscape_driver/eggbot_reorder.py index 5b5b018a..2068de2e 100755 --- a/inkscape_driver/eggbot_reorder.py +++ b/inkscape_driver/eggbot_reorder.py @@ -176,51 +176,31 @@ def __init__( self ): def get_start_end( self, node ): """Given a node, return the start and end points""" - nodeStart = node - nodeEnd = node - transformStart = simpletransform.parseTransform( node.get( 'transform' ) ) - transformEnd = transformStart - - while nodeStart.tag == inkex.addNS( 'g', 'svg' ): - nodeStart = nodeStart[0] - if transformStart: - transformStart = simpletransform.parseTransform( nodeStart.get( 'transform' ), transformStart ) - - while nodeEnd.tag == inkex.addNS( 'g', 'svg' ): - nodeEnd = nodeEnd[-1] - if transformEnd: - transformEnd = simpletransform.parseTransform( nodeEnd.get( 'transform' ), transformEnd ) - - if nodeStart.tag == inkex.addNS( 'path', 'svg' ): - d_start = nodeStart.get( 'd' ) - sp_start = simplepath.parsePath( d_start ) - # simplepath converts coordinates to absolute and cleans them up, but - # these are still some big assumptions here, are they always valid? TODO - startX = sp_start[0][1][0] - startY = sp_start[0][1][1] + if node.tag != inkex.addNS( 'path', 'svg' ): + inkex.errormsg("Groups are not supported, please ungroup for better results") + return (0, 0, 0, 0) + + d = node.get( 'd' ) + sp = simplepath.parsePath( d ) + + # simplepath converts coordinates to absolute and cleans them up, but + # these are still some big assumptions here, are they always valid? TODO + startX = sp[0][1][0] + startY = sp[0][1][1] + if sp[-1][0] == 'Z': + # go back to start + endX = startX + endY = startY else: - inkex.errormsg("This script only work with paths and groups, please convert objects to paths") - startX = 0.0 - startY = 0.0 - - if nodeEnd.tag == inkex.addNS( 'path', 'svg' ): - d_end = nodeEnd.get( 'd' ) - sp_end = simplepath.parsePath( d_end ) - if sp_end[-1][0] == 'Z': - # go back to start - endX = sp_end[0][1][0] - endY = sp_end[0][1][1] - else: - endX = sp_end[-1][1][-2] - endY = sp_end[-1][1][-1] - else: - inkex.errormsg("This script only work with paths and groups, please convert objects to paths") - endX = 0.0 - endY = 0.0 + endX = sp[-1][1][-2] + endY = sp[-1][1][-1] + + transform = node.get( 'transform' ) + if transform: + transform = simpletransform.parseTransform( transform ) - sx, sy = conv( startX, startY, transformStart ) - ex, ey = conv( endX, endY, transformEnd ) - #inkex.debug(( sx, sy, ex, ey )) + sx, sy = conv( startX, startY, transform ) + ex, ey = conv( endX, endY, transform ) return ( sx, sy, ex, ey ) def effect( self ): @@ -260,9 +240,6 @@ def effect( self ): node_sp_string = simplepath.formatPath(node_sp) node.set('d', node_sp_string) - elif node.tag == inkex.addNS('g', 'svg') and path.reversed: - #TODO Every element of the group should be reversed - inkex.errormsg("Reversing groups is currently not possible, please ungroup for better results") #keep in mind the different selected ids might have different parents self.getParentNode(node).append(node) From 34f1ffbf899ed3cb447524f9b6a32a5c6f2b4b1c Mon Sep 17 00:00:00 2001 From: Romain Testuz Date: Sun, 10 Dec 2017 18:24:41 +0100 Subject: [PATCH 5/5] renamed Path attributes to be safer --- inkscape_driver/eggbot_reorder.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/inkscape_driver/eggbot_reorder.py b/inkscape_driver/eggbot_reorder.py index 2068de2e..d0a4f345 100755 --- a/inkscape_driver/eggbot_reorder.py +++ b/inkscape_driver/eggbot_reorder.py @@ -36,20 +36,23 @@ def dist_t( x, y ): return dist(x[0], x[1], y[0], y[1]) class Path: - def __init__(self, id, start, end, reversed=False): + def __init__(self, id, p1, p2, reversed=False): self.id = id - self.start = start - self.end = end + self.p1 = p1 + self.p2 = p2 self.reversed = reversed def __eq__(self, other): #ids must be unique return self.id == other.id + def __str__(self): + return "{}: {} {}".format(self.id, self.get_start(), self.get_end()) + def get_start(self): - return self.start if not self.reversed else self.end + return self.p1 if not self.reversed else self.p2 def get_end(self): - return self.end if not self.reversed else self.start + return self.p2 if not self.reversed else self.p1 def reverse(self): self.reversed = not self.reversed @@ -80,7 +83,7 @@ def find_ordering( objlist, allowReverse ): air_length_ordered = 0 sort_list = [] - prev = Path(-1, (0,0), (0,0)) + prev = Path("", start, start) # for the previous end point, iterate over each remaining path and pick the closest starting point or ending point if allowed while len( objlist ) > 0: