kopia lustrzana https://github.com/evil-mad/EggBot
initial upload of source
git-svn-id: https://eggbotcode.googlecode.com/svn/trunk@71 72233254-1b6c-9e9c-5072-401df62706fbpull/47/head
rodzic
b8827f6278
commit
653f560bb6
|
@ -0,0 +1,123 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||
<_name>Eggbot </_name>
|
||||
<id>command.evilmadscience.plotd.eggbot</id>
|
||||
<dependency type="extension">org.inkscape.output.svg.inkscape</dependency>
|
||||
<dependency type="executable" location="extensions">eggbot.py</dependency>
|
||||
<dependency type="executable" location="extensions">inkex.py</dependency>
|
||||
<param name="tab" type="notebook">
|
||||
|
||||
<page name="splash" _gui-text="Plot">
|
||||
<_param name="splashpage" type="description" xml:space="preserve">
|
||||
Welcome to the Egg-Bot interface!
|
||||
|
||||
Press 'Apply' to begin plotting.
|
||||
|
||||
Or, look in the other tabs for
|
||||
configuration and other options.
|
||||
|
||||
</_param>
|
||||
</page>
|
||||
<page name="resume" _gui-text="Resume">
|
||||
<_param name="instructions_resume" type="description" xml:space="preserve">
|
||||
To pause a plot in progress, press the "PRG"
|
||||
button on the circuit board. After pausing, you
|
||||
can change the Eggbot settings or perform any
|
||||
manual adjustments that are needed.
|
||||
|
||||
To resume plotting-- or just cancel and return
|
||||
home --press 'Apply' with this tab selected.
|
||||
|
||||
Plot progress is stored in the inkscape file;
|
||||
if you need to quit inkscape and resume later,
|
||||
be sure to save the document first.
|
||||
</_param>
|
||||
<param name="cancelOnly" type="boolean" _gui-text="Cancel and return home only:">false</param>
|
||||
</page>
|
||||
|
||||
|
||||
<page name='timing' _gui-text='Timing'>
|
||||
<param name="penDownSpeed" type="int" min="1" max="10000" _gui-text="Speed when pen is down (steps/s):">500</param>
|
||||
<param name="penUpSpeed" type="int" min="1" max="10000" _gui-text="Speed when pen is up (step/s):">700</param>
|
||||
<param name="ServoSpeed" type="int" min="1" max="1600" _gui-text="Pen up/down speed (%/s):">50</param>
|
||||
<param name="penDownDelay" type="int" min="1" max="5000" _gui-text="Delay after lowering down (ms):">500</param>
|
||||
<param name="penUpDelay" type="int" min="1" max="5000" _gui-text="Delay after raising pen (ms):">500</param>
|
||||
<_param name="help_timing" type="description">
|
||||
(Pressing 'Apply' in this frame
|
||||
will save parameters, not plot.)</_param>
|
||||
</page>
|
||||
<page name='options' _gui-text='Options'>
|
||||
<param name="penUpPosition" type="int" min="0" max="100" _gui-text="Pen up position, 0-100%:">50</param>
|
||||
<param name="penDownPosition" type="int" min="0" max="100" _gui-text="Pen down position, 0-100%:">40</param>
|
||||
<param name="startCentered" type="boolean" _gui-text="Start with pen centered:">true</param>
|
||||
<param name="returnToHome" type="boolean" _gui-text="Return home when done:">true</param>
|
||||
<param name="wraparound" type="boolean" _gui-text="Egg (x) axis wraps around:">true</param> <param name="togglePenNow" type="boolean" _gui-text="Toggle pen up/down on 'Apply':">false</param>
|
||||
<param name="smoothness" type="float" _gui-text="Curve smoothing (lower for more):">.2</param>
|
||||
<_param name="help_options" type="description">
|
||||
(Pressing 'Apply' in this frame
|
||||
will save parameters, not plot.)</_param>
|
||||
</page>
|
||||
<page name="manual" _gui-text="Manual">
|
||||
<_param name="instructions_manual" type="description" xml:space="preserve">Eggbot Manual Control
|
||||
|
||||
You can use this frame to send "manual" commands
|
||||
to the Eggbot: Walk the stepper motors, raise or
|
||||
lower the pen, enable or disable the motors, or
|
||||
check the circuit board (EBB) firmware version.
|
||||
</_param>
|
||||
|
||||
<param name="manualType" type="enum" _gui-text=" Command: ">
|
||||
<item value="none" > -- Select -- </item>
|
||||
<item value="raise-pen" >Raise the Pen</item>
|
||||
<item value="lower-pen" >Lower the Pen</item>
|
||||
<item value="walk-egg-motor" >Walk Motor 2 (egg)</item>
|
||||
<item value="walk-pen-motor" >Walk Motor 1 (pen)</item>
|
||||
<item value="enable-motors" >Enable Motors</item>
|
||||
<item value="disable-motors" >Disable Motors</item>
|
||||
<item value="version-check" >Check EBB Version</item></param>
|
||||
|
||||
<param name="WalkDistance" type="int" min="-3200" max="3200"
|
||||
_gui-text=" Walk distance (steps):">5</param>
|
||||
<_param name="instructions_manual2" type="description" xml:space="preserve">Note: Walk distance can be positive or negative.
|
||||
|
||||
Press 'Apply' to execute the command.</_param>
|
||||
</page>
|
||||
<page name="layers" _gui-text="Layers">
|
||||
<_param name="instructions_layer" type="description" xml:space="preserve">
|
||||
Normally, we plot paths from all layers.
|
||||
You can also choose to plot a single layer
|
||||
or group of layers, for example to change
|
||||
pens between plotting layers.
|
||||
|
||||
Pressing 'Apply' from this frame will plot
|
||||
only layers whose names begin with the
|
||||
selected number, which can be up to 100.
|
||||
|
||||
</_param>
|
||||
<param name="layernumber" type="int" min="0" max="100" _gui-text=" Plot only layers beginning with: ">1</param>
|
||||
</page>
|
||||
<page name="Help" _gui-text="*">
|
||||
<_param name="instructions_general" type="description" xml:space="preserve">
|
||||
EggBot Inkscape extension
|
||||
http://www.egg-bot.com/
|
||||
(Preview version 7/30/2010)
|
||||
|
||||
*Motor wiring should be (L-R):
|
||||
Grey-Green-Yellow-Pink, for both motors
|
||||
|
||||
*Firmware 1.96 required for pen toggle,
|
||||
manual walks and plot-resume functions.
|
||||
|
||||
Known issues:
|
||||
* "Cancel" function does not work while plotting.
|
||||
(This is due to a known bug in Inkscape.)
|
||||
</_param>
|
||||
</page>
|
||||
</param>
|
||||
<effect needs-live-preview="false" needs-document="no">
|
||||
<object-type>all</object-type>
|
||||
</effect>
|
||||
<script>
|
||||
<command reldir="extensions" interpreter="python">eggbot.py</command>
|
||||
</script>
|
||||
</inkscape-extension>
|
|
@ -0,0 +1,889 @@
|
|||
'''
|
||||
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
|
||||
'''
|
||||
|
||||
#TODO: Restrict individual segments to 5 s maximum & reduce timeout value
|
||||
#TODO: Clean up serial search code, esp. for Windows
|
||||
#TODO: Store save/resume data in inkscape document itself (?)
|
||||
#TODO: Check why manual raise/lower pen don't use correct values ### Fix added, needs testing.
|
||||
|
||||
import inkex, cubicsuperpath, simplepath, cspsubdiv
|
||||
from simpletransform import *
|
||||
#import pathmodifier
|
||||
from bezmisc import *
|
||||
import gettext
|
||||
import sys
|
||||
import serial
|
||||
import time
|
||||
from math import sqrt
|
||||
import string
|
||||
|
||||
|
||||
|
||||
|
||||
F_DEFAULT_SPEED = 1
|
||||
N_PEN_DOWN_DELAY = 400 # delay (ms) for the pen to go down before the next move
|
||||
N_PEN_UP_DELAY = 400 # delay (ms) for the pen to up down before the next move
|
||||
N_PEN_AXIS_STEPS = 1000 # steps the pen motor can move
|
||||
N_EGG_AXIS_STEPS = 1000 # steps for the egg motor move in one revolution
|
||||
|
||||
N_PEN_UP_POS = 50 # Default pen-up position
|
||||
N_PEN_DOWN_POS = 40 # Default pen-down position
|
||||
N_SERVOSPEED = 50 # Default pen-lift speed
|
||||
N_WALK_DEFAULT = 10 # Default steps for walking stepper motors
|
||||
N_DEFAULT_LAYER = 1 # Default inkscape layer
|
||||
'''
|
||||
if bDebug = True, create an HPGL file to show what is being plotted.
|
||||
Pen up moves are shown in a different color if bDrawPenUpLines = True.
|
||||
Try viewing the .hpgl file in a shareware program or create a simple viewer.
|
||||
'''
|
||||
bDebug = False
|
||||
miscDebug = False
|
||||
bDrawPenUpLines = False
|
||||
bDryRun = False # write the commands to a text file instead of the serial port
|
||||
|
||||
platform = sys.platform.lower()
|
||||
if platform == 'win32':
|
||||
DEBUG_OUTPUT_FILE = 'C:/test.hpgl'
|
||||
DRY_RUN_OUTPUT_FILE = 'C:/dry_run.txt'
|
||||
MISC_OUTPUT_FILE = 'C:/misc.txt'
|
||||
#STR_DEFAULT_COM_PORT = 'COM6'
|
||||
else:
|
||||
import os #, tty
|
||||
DEBUG_OUTPUT_FILE = os.getenv('HOME') + '/test.hpgl'
|
||||
MISC_OUTPUT_FILE = os.getenv('HOME') + '/misc.txt'
|
||||
DRY_RUN_OUTPUT_FILE = os.getenv('HOME') + '/dry_run.txt'
|
||||
## if platform == 'darwin':
|
||||
## ''' There's no good value for OS X '''
|
||||
## STR_DEFAULT_COM_PORT = '/dev/cu.usbmodem1a21'
|
||||
## elif platform == 'sunos':
|
||||
## ''' Untested: YMMV '''
|
||||
## STR_DEFAULT_COM_PORT = '/dev/term/0'
|
||||
## else:
|
||||
## ''' Works fine on Ubuntu; YMMV '''
|
||||
## STR_DEFAULT_COM_PORT = '/dev/ttyACM0'
|
||||
|
||||
'''
|
||||
Break up a bezier curve into smaller curves, each of which
|
||||
is approximately a straight line within a given tolerance
|
||||
(the "smoothness" defined by [flat]).
|
||||
|
||||
This is a modified version of cspsubdiv.cspsubdiv(). I rewrote the recursive
|
||||
call because it caused recursion-depth errors on complicated line segments.
|
||||
'''
|
||||
def subdivideCubicPath(sp,flat,i=1):
|
||||
|
||||
while True:
|
||||
|
||||
#m = 0
|
||||
|
||||
while True:
|
||||
|
||||
if i >= len(sp):
|
||||
return
|
||||
|
||||
p0 = sp[i-1][1]
|
||||
p1 = sp[i-1][2]
|
||||
p2 = sp[i][0]
|
||||
p3 = sp[i][1]
|
||||
|
||||
b = (p0,p1,p2,p3)
|
||||
|
||||
if cspsubdiv.maxdist(b) > flat:
|
||||
break
|
||||
|
||||
i += 1
|
||||
|
||||
one, two = beziersplitatt(b,0.5)
|
||||
sp[i-1][2] = one[1]
|
||||
sp[i][0] = two[2]
|
||||
p = [one[2],one[3],two[1]]
|
||||
sp[i:1] = [p]
|
||||
|
||||
#class EggBot(pathmodifier.PathModifier):
|
||||
|
||||
class EggBot(inkex.Effect):
|
||||
def __init__(self):
|
||||
inkex.Effect.__init__(self)
|
||||
|
||||
self.OptionParser.add_option("--smoothness",
|
||||
action="store", type="float",
|
||||
dest="smoothness", default=.2,
|
||||
help="Smoothness of curves")
|
||||
## self.OptionParser.add_option( "--comPort",
|
||||
## action="store", type="string",
|
||||
## dest="comport", default=STR_DEFAULT_COM_PORT,
|
||||
## help="USB COM port to connect eggbot.")
|
||||
self.OptionParser.add_option("--startCentered",
|
||||
action="store", type="inkbool",
|
||||
dest="startCentered", default=True,
|
||||
help="Start plot with pen centered in the y-axis.")
|
||||
self.OptionParser.add_option("--returnToHome",
|
||||
action="store", type="inkbool",
|
||||
dest="returnToHome", default=True,
|
||||
help="Return to home at end of plot.")
|
||||
self.OptionParser.add_option("--wraparound",
|
||||
action="store", type="inkbool",
|
||||
dest="wraparound", default=True,
|
||||
help="Egg (x) axis wraps around-- take shortcuts!")
|
||||
self.OptionParser.add_option("--penUpSpeed",
|
||||
action="store", type="int",
|
||||
dest="penUpSpeed", default=F_DEFAULT_SPEED,
|
||||
help="Speed (step/sec) while pen is up.")
|
||||
self.OptionParser.add_option("--penDownSpeed",
|
||||
action="store", type="int",
|
||||
dest="penDownSpeed", default=F_DEFAULT_SPEED,
|
||||
help="Speed (step/sec) while pen is down.")
|
||||
self.OptionParser.add_option("--penDownDelay",
|
||||
action="store", type="int",
|
||||
dest="penDownDelay", default=N_PEN_DOWN_DELAY,
|
||||
help="Delay after pen down (msec).")
|
||||
self.OptionParser.add_option("--penUpDelay",
|
||||
action="store", type="int",
|
||||
dest="penUpDelay", default=N_PEN_UP_DELAY,
|
||||
help="Delay after pen up (msec).")
|
||||
self.OptionParser.add_option("--togglePenNow",
|
||||
action="store", type="inkbool",
|
||||
dest="togglePenNow", default=False,
|
||||
help="Toggle the pen up/down on Apply.")
|
||||
self.OptionParser.add_option("--tab",
|
||||
action="store", type="string",
|
||||
dest="tab", default="controls",
|
||||
help="The active tab when Apply was pressed")
|
||||
self.OptionParser.add_option("--penUpPosition",
|
||||
action="store", type="int",
|
||||
dest="penUpPosition", default=N_PEN_UP_POS,
|
||||
help="Position of pen when lifted")
|
||||
self.OptionParser.add_option("--ServoSpeed",
|
||||
action="store", type="int",
|
||||
dest="ServoSpeed", default=N_SERVOSPEED,
|
||||
help="Rate of lifting/lowering pen ")
|
||||
self.OptionParser.add_option("--penDownPosition",
|
||||
action="store", type="int",
|
||||
dest="penDownPosition", default=N_PEN_DOWN_POS,
|
||||
help="Position of pen when lowered")
|
||||
self.OptionParser.add_option("--layernumber",
|
||||
action="store", type="int",
|
||||
dest="layernumber", default=N_DEFAULT_LAYER,
|
||||
help="Selected layer for multilayer plotting")
|
||||
self.OptionParser.add_option("--manualType",
|
||||
action="store", type="string",
|
||||
dest="manualType", default="controls",
|
||||
help="The active tab when Apply was pressed")
|
||||
self.OptionParser.add_option("--WalkDistance",
|
||||
action="store", type="int",
|
||||
dest="WalkDistance", default=N_WALK_DEFAULT,
|
||||
help="Selected layer for multilayer plotting")
|
||||
self.OptionParser.add_option("--cancelOnly",
|
||||
action="store", type="inkbool",
|
||||
dest="cancelOnly", default=False,
|
||||
help="Cancel plot and return home only.")
|
||||
|
||||
self.bPenIsUp = True
|
||||
self.virtualPenIsUp = False #Keeps track of pen postion when stepping through plot before resuming
|
||||
self.fX = None
|
||||
self.fY = None
|
||||
self.fPrevX = None
|
||||
self.fPrevY = None
|
||||
self.ptFirst = None
|
||||
self.bStopped = False
|
||||
self.fSpeed = 1
|
||||
self.resumeMode = False
|
||||
self.nodeCount = int(0) #NOTE: python uses 32-bit ints.
|
||||
self.nodeTarget = int(0)
|
||||
self.pathcount = int(0)
|
||||
self.LayersPlotted = 0
|
||||
self.svgSerialPort = ''
|
||||
self.svgLayer = int(0)
|
||||
self.svgNodeCount = int(0)
|
||||
self.svgDataRead = False
|
||||
self.svgLastPath = int(0)
|
||||
self.svgLastPathNC = int(0)
|
||||
self.svgTotalDeltaX = int(0)
|
||||
self.svgTotalDeltaY = int(0)
|
||||
|
||||
self.nDeltaX = 0
|
||||
self.nDeltaY = 0
|
||||
self.DoubleStepSize = True
|
||||
|
||||
try:
|
||||
import motor1600
|
||||
except ImportError:
|
||||
self.DoubleStepSize = False
|
||||
|
||||
'''Main entry point: check to see which tab is selected, and act accordingly.'''
|
||||
def effect(self): #Pick main course of action, depending on active GUI tab when "Apply" was pressed.
|
||||
self.svg = self.document.getroot()
|
||||
self.CheckSVGforEggbotData()
|
||||
|
||||
if self.options.tab == '"splash"':
|
||||
self.allLayers = True
|
||||
self.plotCurrentLayer = True
|
||||
self.EggbotOpenSerial()
|
||||
self.svgNodeCount = 0
|
||||
self.svgLastPath = 0
|
||||
strButton = self.doRequest('QB\r') #Query if button pressed
|
||||
self.svgLayer = 12345; # indicate that we are plotting all layers.
|
||||
self.plotToEggBot()
|
||||
|
||||
|
||||
elif self.options.tab == '"resume"':
|
||||
self.EggbotOpenSerial()
|
||||
strButton = self.doRequest('QB\r') #Query if button pressed
|
||||
self.resumePlotSetup()
|
||||
if self.resumeMode:
|
||||
self.plotToEggBot()
|
||||
elif (self.options.cancelOnly == True):
|
||||
pass
|
||||
else:
|
||||
inkex.errormsg(gettext.gettext("Truly sorry, there does not seem to be any in-progress plot to resume."))
|
||||
|
||||
elif self.options.tab == '"layers"':
|
||||
self.allLayers = False
|
||||
self.plotCurrentLayer = False
|
||||
self.LayersPlotted = 0
|
||||
self.svgLastPath = 0
|
||||
self.EggbotOpenSerial()
|
||||
strButton = self.doRequest('QB\r') #Query if button pressed
|
||||
self.svgNodeCount = 0;
|
||||
self.svgLayer = self.options.layernumber
|
||||
self.plotToEggBot()
|
||||
if (self.LayersPlotted == 0):
|
||||
inkex.errormsg(gettext.gettext("Truly sorry, but I did not find any named layers to plot."))
|
||||
|
||||
elif self.options.tab == '"manual"':
|
||||
self.EggbotOpenSerial()
|
||||
self.manualCommand()
|
||||
|
||||
|
||||
elif self.options.tab == '"timing"':
|
||||
self.EggbotOpenSerial()
|
||||
if self.serialPort is not None:
|
||||
self.ServoSetupWrapper()
|
||||
|
||||
elif self.options.tab == '"options"':
|
||||
self.EggbotOpenSerial()
|
||||
if self.serialPort is not None:
|
||||
self.ServoSetupWrapper()
|
||||
if self.options.togglePenNow:
|
||||
self.doCommand('TP\r') #Toggle pen
|
||||
|
||||
self.svgDataRead = False
|
||||
self.UpdateSVGEggbotData(self.svg)
|
||||
self.EggbotCloseSerial()
|
||||
return
|
||||
|
||||
|
||||
def CheckSVGforEggbotData(self):
|
||||
self.svgDataRead = False
|
||||
self.recursiveEggbotDataScan(self.svg)
|
||||
if (self.svgDataRead == False): #if there is no eggbot data, add some:
|
||||
eggbotlayer = inkex.etree.SubElement(self.svg, 'eggbot')
|
||||
eggbotlayer.set('serialport', '')
|
||||
eggbotlayer.set('layer', str(0))
|
||||
eggbotlayer.set('node', str(0))
|
||||
eggbotlayer.set('lastpath', str(0))
|
||||
eggbotlayer.set('lastpathnc', str(0))
|
||||
eggbotlayer.set('totaldeltax', str(0))
|
||||
eggbotlayer.set('totaldeltay', str(0))
|
||||
|
||||
def recursiveEggbotDataScan(self, aNodeList):
|
||||
if (self.svgDataRead != True):
|
||||
for node in aNodeList:
|
||||
if node.tag == 'svg':
|
||||
self.recursiveEggbotDataScan(node)
|
||||
elif node.tag == inkex.addNS('eggbot','svg') or node.tag == 'eggbot':
|
||||
self.svgSerialPort = node.get('serialport')
|
||||
self.svgLayer = int(node.get('layer'))
|
||||
self.svgNodeCount = int(node.get('node'))
|
||||
|
||||
try:
|
||||
self.svgLastPath = int(node.get('lastpath'))
|
||||
self.svgLastPathNC = int(node.get('lastpathnc'))
|
||||
self.svgTotalDeltaX = int(node.get('totaldeltax'))
|
||||
self.svgTotalDeltaY = int(node.get('totaldeltay'))
|
||||
self.svgDataRead = True
|
||||
except:
|
||||
node.set('lastpath', str(0))
|
||||
node.set('lastpathnc', str(0))
|
||||
node.set('totaldeltax', str(0))
|
||||
node.set('totaldeltay', str(0))
|
||||
self.svgDataRead = True
|
||||
|
||||
def UpdateSVGEggbotData(self, aNodeList):
|
||||
if (self.svgDataRead != True):
|
||||
for node in aNodeList:
|
||||
if node.tag == 'svg':
|
||||
self.UpdateSVGEggbotData(node)
|
||||
elif node.tag == inkex.addNS('eggbot','svg') or node.tag == 'eggbot':
|
||||
node.set('serialport', self.svgSerialPort)
|
||||
node.set('layer', str(self.svgLayer))
|
||||
node.set('node', str(self.svgNodeCount))
|
||||
node.set('lastpath', str(self.svgLastPath))
|
||||
node.set('lastpathnc', str(self.svgLastPathNC))
|
||||
node.set('totaldeltax', str(self.svgTotalDeltaX))
|
||||
node.set('totaldeltay', str(self.svgTotalDeltaY))
|
||||
self.svgDataRead = True
|
||||
|
||||
def resumePlotSetup(self):
|
||||
self.LayerFound = False
|
||||
if (self.svgLayer < 101) and (self.svgLayer >= 0):
|
||||
self.options.layernumber = self.svgLayer
|
||||
self.allLayers = False
|
||||
self.plotCurrentLayer = False
|
||||
self.LayerFound = True
|
||||
elif (self.svgLayer == 12345): # Plot all layers
|
||||
self.allLayers = True
|
||||
self.plotCurrentLayer = True
|
||||
self.LayerFound = True
|
||||
if (self.LayerFound == True):
|
||||
if (self.svgNodeCount > 0):
|
||||
self.nodeTarget = self.svgNodeCount
|
||||
self.resumeMode = True
|
||||
if (self.options.cancelOnly == True):
|
||||
self.resumeMode = False
|
||||
self.fPrevX = self.svgTotalDeltaX
|
||||
self.fPrevY = self.svgTotalDeltaY
|
||||
self.fX = 0
|
||||
self.fY = 0
|
||||
self.plotLineAndTime()
|
||||
self.penUp() #Always end with pen-up
|
||||
self.svgLayer = 0
|
||||
self.svgNodeCount = 0
|
||||
self.svgLastPath = 0
|
||||
self.svgLastPathNC = 0
|
||||
self.svgTotalDeltaX = 0
|
||||
self.svgTotalDeltaY = 0
|
||||
|
||||
|
||||
|
||||
|
||||
def manualCommand(self): #execute commands from the "manual" tab
|
||||
#self.options.manualType
|
||||
|
||||
if self.options.manualType == "none":
|
||||
return
|
||||
|
||||
if self.serialPort is None:
|
||||
return
|
||||
|
||||
self.ServoSetup()
|
||||
#walks are done at pen-down speed.
|
||||
#TODO: Add auto-detect if pen is up or down.
|
||||
|
||||
if self.options.manualType == "raise-pen":
|
||||
self.ServoSetupWrapper()
|
||||
self.penUp()
|
||||
|
||||
elif self.options.manualType == "lower-pen":
|
||||
self.ServoSetupWrapper()
|
||||
self.penDown()
|
||||
|
||||
elif self.options.manualType == "enable-motors":
|
||||
self.sendEnableMotors()
|
||||
|
||||
elif self.options.manualType == "disable-motors":
|
||||
self.sendDisableMotors()
|
||||
|
||||
elif self.options.manualType == "version-check":
|
||||
strVersion = self.doRequest('v\r')
|
||||
inkex.errormsg('I asked the EBB for its version info, and it replied:\n ' + strVersion )
|
||||
|
||||
else: # self.options.manualType is "walk-egg-motor" or "walk-pen-motor":
|
||||
if self.options.manualType == "walk-egg-motor":
|
||||
self.nDeltaX = self.options.WalkDistance
|
||||
self.nDeltaY = 0
|
||||
elif self.options.manualType == "walk-pen-motor":
|
||||
self.nDeltaY = self.options.WalkDistance
|
||||
self.nDeltaX = 0
|
||||
else:
|
||||
return
|
||||
|
||||
strVersion = self.doRequest('QP\r') #Query pen position: 1 up, 0 down (followed by OK)
|
||||
if strVersion[0] == '0':
|
||||
#inkex.errormsg('Pen is down' )
|
||||
self.fSpeed = self.options.penDownSpeed
|
||||
if strVersion[0] == '1':
|
||||
#inkex.errormsg('Pen is up' )
|
||||
self.fSpeed = self.options.penUpSpeed
|
||||
|
||||
self.nTime = int(round(1000.0/self.fSpeed * distance(self.nDeltaX, self.nDeltaY)))
|
||||
strOutput = ','.join(['SM', str(self.nTime), str(self.nDeltaY), str(self.nDeltaX)]) + '\r'
|
||||
self.doCommand(strOutput)
|
||||
|
||||
|
||||
'''Perform the actual plotting, if selected in the interface:'''
|
||||
def plotToEggBot(self): #parse the svg data as a series of line segments and send each segment to be plotted
|
||||
|
||||
if self.serialPort is None:
|
||||
return
|
||||
|
||||
self.ServoSetup()
|
||||
|
||||
if bDebug:
|
||||
self.debugOut = open(DEBUG_OUTPUT_FILE, 'w')
|
||||
if bDrawPenUpLines:
|
||||
self.debugOut.write('IN;SP1;')
|
||||
else:
|
||||
self.debugOut.write('IN;PD;')
|
||||
try: # wrap everything in a try so we can for sure close the serial port
|
||||
#self.recursivelyTraverseSvg(self.document.getroot())
|
||||
self.recursivelyTraverseSvg(self.svg)
|
||||
self.penUp() #Always end with pen-up
|
||||
|
||||
''' return to home, if returnToHome = True '''
|
||||
if ((self.bStopped == False) and self.options.returnToHome and (self.ptFirst != None)):
|
||||
self.fX = self.ptFirst[0]
|
||||
self.fY = self.ptFirst[1]
|
||||
#self.penUp()
|
||||
self.nodeCount = self.nodeTarget # enablesfpx return-to-home only option
|
||||
self.plotLineAndTime()
|
||||
#inkex.errormsg('Final node count: ' + str(self.svgNodeCount)) #Node Count - Debug option
|
||||
if (self.bStopped == False):
|
||||
self.svgLayer = 0
|
||||
self.svgNodeCount = 0
|
||||
self.svgLastPath = 0
|
||||
self.svgLastPathNC = 0
|
||||
self.svgTotalDeltaX = 0
|
||||
self.svgTotalDeltaY = 0
|
||||
|
||||
finally:
|
||||
return
|
||||
|
||||
|
||||
## Recursively traverse the svg file to plot out all of the
|
||||
## paths. The function keeps track of the composite transformation
|
||||
## that should be applied to each path.
|
||||
##
|
||||
## This function handles path, group and rect elements. Most of these elements are
|
||||
## not used by Inkscape and aren't handled: clone, circle, ellipse, line, polyline,
|
||||
## polygon, text. Those should be converted to paths in Inkscape.
|
||||
|
||||
|
||||
def recursivelyTraverseSvg(self, aNodeList, matCurrent = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]):
|
||||
for node in aNodeList:
|
||||
|
||||
''' first apply the current matrix transform to this node's tranform '''
|
||||
matNew = composeTransform(matCurrent, parseTransform(node.get("transform")))
|
||||
if node.tag == inkex.addNS('g','svg') or node.tag == 'g':
|
||||
self.penUp()
|
||||
if (node.get(inkex.addNS('groupmode', 'inkscape')) == 'layer'):
|
||||
if self.allLayers == False:
|
||||
#inkex.errormsg('Plotting layer named: ' + node.get(inkex.addNS('label', 'inkscape')))
|
||||
self.DoWePlotLayer(node.get(inkex.addNS('label', 'inkscape')))
|
||||
self.recursivelyTraverseSvg(node, matNew)
|
||||
elif node.tag == inkex.addNS('use','svg') or node.tag == 'use':
|
||||
# HANDLE CLONES!
|
||||
pass
|
||||
elif node.tag == inkex.addNS('path','svg'):
|
||||
|
||||
self.pathcount += 1
|
||||
#if we're in resume mode AND self.pathcount < self.svgLastPath, then skip over this path.
|
||||
#if we're in resume mode and self.pathcount = self.svgLastPath, then start here, and set
|
||||
# self.nodeCount equal to self.svgLastPathNC
|
||||
|
||||
|
||||
|
||||
if self.resumeMode and (self.pathcount == self.svgLastPath):
|
||||
self.nodeCount = self.svgLastPathNC
|
||||
|
||||
if self.resumeMode and (self.pathcount < self.svgLastPath):
|
||||
pass
|
||||
else:
|
||||
self.plotPath(node, matNew)
|
||||
if (self.bStopped == False): #an "index" for resuming plots quickly-- record last complete path
|
||||
self.svgLastPath += 1
|
||||
self.svgLastPathNC = self.nodeCount
|
||||
|
||||
elif node.tag == inkex.addNS('pattern','svg') or node.tag == 'pattern':
|
||||
pass
|
||||
elif node.tag == inkex.addNS('metadata','svg') or node.tag == 'metadata':
|
||||
pass
|
||||
elif node.tag == inkex.addNS('defs','svg') or node.tag == 'defs':
|
||||
pass
|
||||
elif node.tag == inkex.addNS('namedview','sodipodi') or node.tag == 'namedview':
|
||||
pass
|
||||
elif node.tag == inkex.addNS('eggbot','svg') or node.tag == 'eggbot':
|
||||
pass
|
||||
elif node.tag == inkex.addNS('text','svg') or node.tag == 'text':
|
||||
inkex.errormsg('Warning: unable to draw text, please convert it to a path first.')
|
||||
pass
|
||||
else:
|
||||
inkex.errormsg('Warning: unable to draw object, please convert it to a path first.')
|
||||
pass
|
||||
|
||||
#We are only plotting *some* layers. Check to see
|
||||
# whether or not we're going to plot this one.
|
||||
#
|
||||
# First: scan first 4 chars of node id for first non-numeric character,
|
||||
# and scan the part before that (if any) into a number
|
||||
#
|
||||
# Then, see if the number matches the layer.
|
||||
|
||||
# self.plotCurrentLayer = False
|
||||
|
||||
def DoWePlotLayer(self, strLayerName):
|
||||
TempNumString = 'x'
|
||||
stringPos = 1
|
||||
CurrentLayerName = string.lstrip(strLayerName) #remove leading whitespace
|
||||
|
||||
#Look at layer name. Sample first character, then first two, and
|
||||
# so on, until the string ends or the string no longer consists of
|
||||
# digit characters only.
|
||||
|
||||
MaxLength = len(CurrentLayerName)
|
||||
if MaxLength > 0:
|
||||
while stringPos <= MaxLength:
|
||||
if str.isdigit( CurrentLayerName[:stringPos]):
|
||||
TempNumString = CurrentLayerName[:stringPos] # Store longest numeric string so far
|
||||
stringPos = stringPos + 1
|
||||
else:
|
||||
break
|
||||
|
||||
self.plotCurrentLayer = False #Temporarily assume that we aren't plotting the layer
|
||||
if (str.isdigit(TempNumString)):
|
||||
if (self.svgLayer == int(float(TempNumString))):
|
||||
self.plotCurrentLayer = True #We get to plot the layer!
|
||||
self.LayersPlotted += 1
|
||||
#Note: this function is only called if we are NOT plotting all layers.
|
||||
|
||||
'''
|
||||
Plot the path while applying the transformation defined
|
||||
by the matrix [matTransform].
|
||||
'''
|
||||
def plotPath(self, path, matTransform):
|
||||
''' turn this path into a cubicsuperpath (list of beziers)... '''
|
||||
d = path.get('d')
|
||||
|
||||
if len(simplepath.parsePath(d)) == 0:
|
||||
return
|
||||
|
||||
p = cubicsuperpath.parsePath(d)
|
||||
|
||||
''' ...and apply the transformation to each point '''
|
||||
applyTransformToPath(matTransform, p)
|
||||
|
||||
'''
|
||||
p is now a list of lists of cubic beziers [control pt1, control pt2, endpoint]
|
||||
where the start-point is the last point in the previous segment.
|
||||
'''
|
||||
for sp in p:
|
||||
|
||||
subdivideCubicPath(sp, self.options.smoothness)
|
||||
nIndex = 0
|
||||
|
||||
for csp in sp:
|
||||
|
||||
if self.bStopped:
|
||||
return
|
||||
|
||||
if self.plotCurrentLayer:
|
||||
if nIndex == 0:
|
||||
self.penUp()
|
||||
self.virtualPenIsUp = True
|
||||
elif nIndex == 1:
|
||||
self.penDown()
|
||||
self.virtualPenIsUp = False
|
||||
|
||||
nIndex += 1
|
||||
|
||||
if (self.DoubleStepSize == True):
|
||||
self.fX = float(csp[1][0]) / 2.0
|
||||
self.fY = float(csp[1][1]) / 2.0
|
||||
else:
|
||||
self.fX = float(csp[1][0])
|
||||
self.fY = float(csp[1][1])
|
||||
|
||||
''' store home '''
|
||||
if self.ptFirst is None:
|
||||
|
||||
''' if we should start at center, then the first line segment should draw from there '''
|
||||
if self.options.startCentered:
|
||||
self.fPrevX = self.fX
|
||||
#self.fPrevY = N_PEN_AXIS_STEPS / 2.0
|
||||
|
||||
if (self.DoubleStepSize == True):
|
||||
self.fPrevY = N_PEN_AXIS_STEPS / 4.0
|
||||
else:
|
||||
self.fPrevY = N_PEN_AXIS_STEPS / 2.0
|
||||
|
||||
self.ptFirst = (self.fPrevX, self.fPrevY)
|
||||
else:
|
||||
self.ptFirst = (self.fX, self.fY)
|
||||
|
||||
if self.plotCurrentLayer:
|
||||
self.plotLineAndTime()
|
||||
self.fPrevX = self.fX
|
||||
self.fPrevY = self.fY
|
||||
|
||||
def sendEnableMotors(self):
|
||||
self.doCommand('EM,1,1\r')
|
||||
|
||||
def sendDisableMotors(self):
|
||||
self.doCommand('EM,0,0\r')
|
||||
|
||||
def penUp(self):
|
||||
if ((self.resumeMode != True) or (self.virtualPenIsUp != True)):
|
||||
self.doCommand('SP,1\r')
|
||||
self.doCommand('SM,' + str(self.options.penUpDelay) + ',0,0\r') # pause for pen to go up
|
||||
self.bPenIsUp = True
|
||||
self.virtualPenIsUp = True
|
||||
|
||||
def penDown(self):
|
||||
self.virtualPenIsUp = False # Virtual pen keeps track of state for resuming plotting.
|
||||
if (self.resumeMode != True):
|
||||
self.doCommand('SP,0\r')
|
||||
self.doCommand('SM,' + str(self.options.penDownDelay) + ',0,0\r') # pause for pen to go down
|
||||
self.bPenIsUp = False
|
||||
|
||||
def ServoSetupWrapper(self):
|
||||
self.ServoSetup()
|
||||
strVersion = self.doRequest('QP\r') #Query pen position: 1 up, 0 down (followed by OK)
|
||||
if strVersion[0] == '0':
|
||||
#inkex.errormsg('Pen is down' )
|
||||
self.doCommand('SP,0\r') #Lower Pen
|
||||
else:
|
||||
self.doCommand('SP,1\r') #Raise pen
|
||||
|
||||
def ServoSetup(self):
|
||||
# Pen position units range from 0% to 100%, which correspond to
|
||||
# a timing range of 6000 - 30000 in units of 1/(12 MHz).
|
||||
# 1% corresponds to 20 us, or 240 units of 1/(12 MHz).
|
||||
|
||||
intTemp = 240 * (self.options.penUpPosition + 25)
|
||||
self.doCommand('SC,4,' + str(intTemp) + '\r')
|
||||
intTemp = 240 * (self.options.penDownPosition + 25)
|
||||
self.doCommand('SC,5,' + str(intTemp) + '\r')
|
||||
|
||||
# Servo speed units are in units of %/second, referring to the
|
||||
# percentages above. The EBB takes speeds in units of 1/(12 MHz) steps
|
||||
# per 21 ms. Scaling as above, 1% in 1 second corresponds to
|
||||
# 240 steps/s, which corresponds to 0.240 steps/ms, which corresponds
|
||||
# to 5.04 steps/21 ms. Rounding this to 5 steps/21 ms is correct
|
||||
# to within 1 %.
|
||||
|
||||
intTemp = 5 * self.options.ServoSpeed
|
||||
self.doCommand('SC,10,' + str(intTemp) + '\r')
|
||||
#inkex.errormsg('Setting up Servo Motors!')
|
||||
|
||||
|
||||
def stop(self):
|
||||
self.bStopped = True
|
||||
|
||||
'''
|
||||
Send commands out the com port as a line segment (dx, dy) and a time (ms) the segment
|
||||
should take to implement
|
||||
'''
|
||||
def plotLineAndTime(self):
|
||||
if self.bStopped:
|
||||
return
|
||||
if (self.fPrevX is None):
|
||||
return
|
||||
|
||||
self.nDeltaX = int(self.fX) - int(self.fPrevX)
|
||||
self.nDeltaY = int(self.fY) - int(self.fPrevY)
|
||||
|
||||
if self.bPenIsUp:
|
||||
self.fSpeed = self.options.penUpSpeed
|
||||
|
||||
if (self.options.wraparound == True):
|
||||
if (self.DoubleStepSize == True):
|
||||
if (self.nDeltaX > 800):
|
||||
self.nDeltaX -= 1600
|
||||
elif (self.nDeltaX < -800):
|
||||
self.nDeltaX += 1600
|
||||
else:
|
||||
if (self.nDeltaX > 1600):
|
||||
self.nDeltaX -= 3200
|
||||
elif (self.nDeltaX < -1600):
|
||||
self.nDeltaX += 3200
|
||||
|
||||
else:
|
||||
self.fSpeed = self.options.penDownSpeed
|
||||
|
||||
|
||||
if (distance(self.nDeltaX, self.nDeltaY) > 0):
|
||||
self.nodeCount += 1
|
||||
|
||||
if self.resumeMode:
|
||||
if (self.nodeCount > self.nodeTarget):
|
||||
self.resumeMode = False
|
||||
#inkex.errormsg('First node plotted will be number: ' + str(self.nodeCount))
|
||||
if (self.virtualPenIsUp != True):
|
||||
self.penDown()
|
||||
self.fSpeed = self.options.penDownSpeed
|
||||
|
||||
nTime = int(math.ceil(1000/self.fSpeed * distance(self.nDeltaX, self.nDeltaY)))
|
||||
|
||||
|
||||
while ((abs(self.nDeltaX) > 0) or (abs(self.nDeltaY) > 0)):
|
||||
if (nTime > 750):
|
||||
xd = int(round((750.0 * self.nDeltaX)/nTime))
|
||||
yd = int(round((750.0 * self.nDeltaY)/nTime))
|
||||
td = int(750)
|
||||
else:
|
||||
xd = self.nDeltaX
|
||||
yd = self.nDeltaY
|
||||
td = nTime
|
||||
if (td < 1):
|
||||
td = 1 # don't allow zero-time moves.
|
||||
|
||||
if (self.resumeMode != True):
|
||||
strOutput = ','.join(['SM', str(td), str(-yd), str(xd)]) + '\r'
|
||||
self.svgTotalDeltaX += xd
|
||||
self.svgTotalDeltaY += yd
|
||||
self.doCommand(strOutput)
|
||||
|
||||
self.nDeltaX -= xd
|
||||
self.nDeltaY -= yd
|
||||
nTime -= td
|
||||
|
||||
#self.doCommand('NI\r') #Increment node counter on EBB
|
||||
strButton = self.doRequest('QB\r') #Query if button pressed
|
||||
if strButton[0] == '0':
|
||||
pass #button not pressed
|
||||
else:
|
||||
self.svgNodeCount = self.nodeCount;
|
||||
inkex.errormsg('Plot paused by button press after segment number ' + str(self.nodeCount) + '.')
|
||||
inkex.errormsg('Use the "resume" feature to continue.' )
|
||||
#self.penUp() # Should be redundant...
|
||||
self.bStopped = True
|
||||
return
|
||||
|
||||
''' note: the pen-motor is first, and it corresponds to the y-axis on-screen '''
|
||||
|
||||
|
||||
def EggbotOpenSerial(self):
|
||||
if not bDryRun:
|
||||
self.serialPort = self.getSerialPort()
|
||||
else:
|
||||
self.serialPort = open(DRY_RUN_OUTPUT_FILE, 'w')
|
||||
|
||||
if self.serialPort is None:
|
||||
inkex.errormsg(gettext.gettext("Unable to find an Eggbot on any serial port. :("))
|
||||
|
||||
def EggbotCloseSerial(self):
|
||||
try:
|
||||
if self.serialPort != None:
|
||||
self.serialPort.flush()
|
||||
self.serialPort.close()
|
||||
if bDebug:
|
||||
self.debugOut.close()
|
||||
finally:
|
||||
self.serialPort = None
|
||||
return
|
||||
|
||||
|
||||
|
||||
'''
|
||||
look at COM1 to COM20 and return a SerialPort object
|
||||
for the first port with an EBB (eggbot board).
|
||||
|
||||
YOU are responsible for closing this serial port!
|
||||
'''
|
||||
def testSerialPort(self, strComPort):
|
||||
try:
|
||||
serialPort = serial.Serial(strComPort, timeout=1) # 1 second timeout!
|
||||
|
||||
serialPort.setRTS() # ??? remove
|
||||
serialPort.setDTR() # ??? remove
|
||||
serialPort.flushInput()
|
||||
serialPort.flushOutput()
|
||||
|
||||
time.sleep(0.1)
|
||||
|
||||
serialPort.write('v\r')
|
||||
strVersion = serialPort.readline()
|
||||
|
||||
if strVersion != None and strVersion.startswith('EBB'):
|
||||
# do version control here to check the firmware...
|
||||
return serialPort
|
||||
serialPort.close()
|
||||
except serial.SerialException:
|
||||
pass
|
||||
return None
|
||||
|
||||
def getSerialPort(self):
|
||||
if platform == 'win32':
|
||||
for i in range(1, 100):
|
||||
strComPort = 'COM' + str(i)
|
||||
serialPort = self.testSerialPort(strComPort)
|
||||
if serialPort != None:
|
||||
return serialPort
|
||||
else:
|
||||
if platform == 'darwin':
|
||||
strDir = '/dev'
|
||||
strPrefix = 'cu.usbmodem'
|
||||
elif platform == 'sunos5':
|
||||
strDir = '/dev/term'
|
||||
strPrefix = None
|
||||
else:
|
||||
strDir = '/dev'
|
||||
strPrefix = 'ttyACM'
|
||||
|
||||
device_list = os.listdir(strDir)
|
||||
# Before searching, first check to see if the
|
||||
# last known serial port is still good.
|
||||
if self.svgSerialPort in device_list:
|
||||
serialPort = self.testSerialPort(self.svgSerialPort)
|
||||
if serialPort != None:
|
||||
return serialPort
|
||||
|
||||
for device in device_list:
|
||||
if strPrefix != None:
|
||||
if not device.startswith(strPrefix):
|
||||
continue
|
||||
strComPort = strDir + '/' + device
|
||||
serialPort = self.testSerialPort(strComPort)
|
||||
if serialPort != None:
|
||||
self.svgSerialPort = strComPort
|
||||
return serialPort
|
||||
return None
|
||||
|
||||
def doCommand(self, cmd):
|
||||
try:
|
||||
self.serialPort.write(cmd)
|
||||
response = self.serialPort.readline()
|
||||
if (response != 'OK\r\n'):
|
||||
if (response != ''):
|
||||
inkex.errormsg('After command ' + cmd + ',')
|
||||
inkex.errormsg('Received bad response from EBB: ' + str(response) + '.')
|
||||
#inkex.errormsg('BTW:: Node number is ' + str(self.nodeCount) + '.')
|
||||
|
||||
else:
|
||||
inkex.errormsg('EBB Serial Timeout.')
|
||||
|
||||
except:
|
||||
pass
|
||||
|
||||
def doRequest(self, cmd):
|
||||
response = ''
|
||||
try:
|
||||
self.serialPort.write(cmd)
|
||||
response = self.serialPort.readline()
|
||||
responseDummy = self.serialPort.readline() #read in extra blank/OK line
|
||||
except:
|
||||
inkex.errormsg(gettext.gettext("Error reading serial data."))
|
||||
|
||||
return response
|
||||
'''
|
||||
Pythagorean theorem!
|
||||
'''
|
||||
def distance(x, y):
|
||||
return sqrt(x*x + y*y)
|
||||
|
||||
e = EggBot()
|
||||
#e.affect(output=False)
|
||||
e.affect()
|
Ładowanie…
Reference in New Issue