kopia lustrzana https://github.com/evil-mad/EggBot
Added initial versions of the eggbot path reordering and optimizer extension.
git-svn-id: https://eggbotcode.googlecode.com/svn/trunk@87 72233254-1b6c-9e9c-5072-401df62706fbpull/47/head
rodzic
301b6594e8
commit
ebfeda3fe1
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||
<_name>Reorder Paths for Eggbot</_name>
|
||||
<id>command.evilmadscience.eggbot_reorder1.eggbot</id>
|
||||
<dependency type="extension">org.inkscape.output.svg.inkscape</dependency>
|
||||
<dependency type="executable" location="extensions">eggbot_reorder.py</dependency>
|
||||
<dependency type="executable" location="extensions">inkex.py</dependency>
|
||||
<!-- Written by Matthew Beckler for the Egg-Bot project. Email questions and comments to matthew at mbeckler dot org -->
|
||||
|
||||
<param name="reverse" type="boolean" _gui-text="Enable 'reverse path direction' optimizations (not yet implemented)">false</param>
|
||||
|
||||
<effect needs-live-preview="false" needs-document="no">
|
||||
<object-type>all</object-type>
|
||||
<effects-menu>
|
||||
<submenu _name="EggBot"/>
|
||||
</effects-menu>
|
||||
</effect>
|
||||
<script>
|
||||
<command reldir="extensions" interpreter="python">eggbot_reorder.py</command>
|
||||
</script>
|
||||
</inkscape-extension>
|
|
@ -0,0 +1,160 @@
|
|||
'''
|
||||
Egg-Bot 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 Egg-Bot 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 inkex
|
||||
import simplepath
|
||||
import simpletransform
|
||||
import gettext
|
||||
import sys
|
||||
import string
|
||||
import math
|
||||
import random
|
||||
|
||||
def dist(x0, y0, x1, y1):
|
||||
return math.sqrt( (x1 - x0)**2 + (y1 - y0)**2 )
|
||||
|
||||
# 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?
|
||||
def find_ordering_naive(objlist):
|
||||
|
||||
# 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
|
||||
|
||||
# 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
|
||||
def conv(x, y, trans_matrix=None):
|
||||
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=False, help="Enable 'reverse path direction' optimizations")
|
||||
|
||||
|
||||
# Given a node, return the start and end points
|
||||
def get_start_end(self, node, transform):
|
||||
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)
|
||||
|
||||
|
||||
# This is the main entry point
|
||||
def effect(self):
|
||||
# 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()
|
||||
|
||||
improvement_pct = 100 * ((air_distance_default - air_distance_ordered) / (air_distance_default))
|
||||
inkex.errormsg(gettext.gettext("Okay, this Inkscape file has 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)))
|
||||
|
||||
e = EggBotReorderPaths()
|
||||
e.affect()
|
||||
|
Ładowanie…
Reference in New Issue