evil-mad-EggBot/inkscape_driver/eggbot_reorder.py

178 wiersze
7.0 KiB
Python
Executable File

# coding=utf-8
# EggBot Path Ordering extension
# This extension tries to re-order the document's paths to improve
# the plotting time by plotting nearby paths consecutively.
#
# Written by Matthew Beckler for the EggBot project.
# Email questions and comments to matthew at mbeckler dot org
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
import gettext
import math
import random
import sys
import inkex
import simplepath
import simpletransform
def dist(x0, y0, x1, y1):
return math.sqrt((x1 - x0) ** 2 + (y1 - y0) ** 2)
def find_ordering_naive(objlist):
"""
Takes a list of (id, (startX, startY, endX, endY)), and finds the best ordering.
Doesn't handle anything fancy, like reversing the ordering, but it's useful for now.
Returns a list of JUST THE IDs, in a better order, as well as the original and optimized
"air distance" which is just the distance traveled in the air. Perhaps we want to make
these comparison distances into something more relevant such as degrees traveled?
"""
# let's figure out the default in-air length, so we know how much we improved
air_length_default = 0
try:
oldx = objlist[0][1][2]
oldy = objlist[0][1][3]
except:
inkex.errormsg(gettext.gettext(str(objlist[0])))
sys.exit(1)
for id, coords in objlist[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)
air_length_ordered = 0
# for now, start with a random one:
sort_list = []
random_index = random.randint(0, len(objlist) - 1)
sort_list.append(objlist[random_index])
objlist.remove(objlist[random_index])
# for now, do this in the most naive way:
# for the previous end point, iterate over each remaining path and pick the closest starting point
while len(objlist) > 0:
min_distance = 100000000 # TODO put something else here better?
for path in objlist:
# instead of having a prevX, prevY, we just look at the last item in sort_list
this_distance = dist(sort_list[-1][1][2], sort_list[-1][1][3], path[1][0], path[1][1])
# this is such a common thing to do, you'd think there would be a name for it...
if this_distance < min_distance:
min_distance = this_distance
min_path = path
air_length_ordered += min_distance
sort_list.append(min_path)
objlist.remove(min_path)
# fid.write("optimized air distance: %d\n" % air_length_ordered)
# remove the extraneous info from the list order
sort_order = [id for id, coords in sort_list]
return sort_order, air_length_default, air_length_ordered
def conv(x, y, trans_matrix=None):
"""
not used currently, but can be used to apply a translation matrix to an (x, y) pair
I'm sure there is a better way to do this using simpletransform or it's ilk
"""
if trans_matrix:
xt = trans_matrix[0][0] * x + trans_matrix[0][1] * y + trans_matrix[0][2]
yt = trans_matrix[1][0] * x + trans_matrix[1][1] * y + trans_matrix[1][2]
return xt, yt
else:
return x, y
class EggBotReorderPaths(inkex.Effect):
def __init__(self):
inkex.Effect.__init__(self)
# self.OptionParser.add_option( '-r', '--reverse', action='store', type="inkbool",
# dest="reverse", default=True, help="Enable 'reverse path direction' optimizations" )
# self.OptionParser.add_option( '-w', '--wrap', action='store', type="inkbool",
# dest="wrap", default=True, help="Enable 'wrap egg axis' optimizations" )
def get_start_end(self, node, transform):
"""Given a node, return the start and end points"""
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:
endX = sp[-1][1][-2]
endY = sp[-1][1][-1]
sx, sy = conv(startX, startY, transform)
ex, ey = conv(endX, endY, transform)
return sx, sy, ex, ey
def effect(self):
"""This is the main entry point"""
# based partially on the restack.py extension
if len(self.selected) > 0:
svg = self.document.getroot()
# TODO check for non-path elements?
# TODO it seems like the order of selection is not consistent
# fid = open("/home/matthew/debug.txt", "w")
# for each selected item - TODO make this be all objects, everywhere
# I can think of two options:
# 1. Iterate over all paths in root, then iterate over all layers, and their paths
# 2. Some magic with xpath? (would this limit us to specific node types?)
objlist = []
for id, node in self.selected.iteritems():
transform = node.get('transform')
if transform:
transform = simpletransform.parseTransform(transform)
item = (id, self.get_start_end(node, transform))
objlist.append(item)
# sort / order the objects
sort_order, air_distance_default, air_distance_ordered = find_ordering_naive(objlist)
for id in sort_order:
# There's some good magic here, that you can use an
# object id to index into self.selected. Brilliant!
self.current_layer.append(self.selected[id])
# fid.close()
if air_distance_default > 0: # don't divide by zero. :P
improvement_pct = 100 * ((air_distance_default - air_distance_ordered) / air_distance_default)
inkex.errormsg(gettext.gettext("Selected paths have been reordered and optimized for quicker EggBot plotting.\n\nOriginal air-distance: %d\nOptimized air-distance: %d\nDistance reduced by: %1.2d%%\n\nHave a nice day!" % (air_distance_default, air_distance_ordered, improvement_pct)))
else:
inkex.errormsg(gettext.gettext("Unable to start. Please select multiple distinct paths. :)"))
e = EggBotReorderPaths()
e.affect()