Configtool: first steps for a user friendly configuration tool.

Contribution by jbernardis.

This attempt uses a rarther simple tree-type dialog. Also only
half done, which is entirely sufficient for evaluation.
This commit is contained in:
Markus Hitter 2015-01-03 14:16:06 +01:00
parent 86f1fc418f
commit 0d956511f9
3 changed files with 467 additions and 0 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
*.pyc
*.hex
build
*~

371
config.py Normal file
View File

@ -0,0 +1,371 @@
#!/bin/env python
import wx
import re
try:
from agw import customtreectrl as CT
except ImportError:
import wx.lib.agw.customtreectrl as CT
from config_helptext import helpText
BSIZE = (90, 60)
class MyFrame(wx.Frame):
def __init__(self):
self.reDefine = re.compile("\s*#define\s+(\S+)\s+(\S+)")
self.reDefQS = re.compile("\s*#define\s+(\S+)\s+(\"[^\"]*\")")
self.reDefTS = re.compile("\s*#define\s+(DEFINE_TEMP_SENSOR\\([^)]*\\))")
self.reDefHT = re.compile("\s*#define\s+(DEFINE_HEATER\\([^)]*\\))")
self.reDefBool = re.compile("\s*#define\s+(\S+)\s+")
self.t = 0
self.seq = 1
wx.Frame.__init__(self, None, -1, "Teacup Firmware Configurator",
size = (450, 600))
self.Bind(wx.EVT_CLOSE, self.onClose)
self.cfgValues = {}
panel = wx.Panel(self, -1)
sz = wx.BoxSizer(wx.HORIZONTAL)
bsz = wx.BoxSizer(wx.VERTICAL)
b = wx.Button(panel, wx.ID_ANY, "Load\nConfig", size = BSIZE)
panel.Bind(wx.EVT_BUTTON, self.onLoadConfig, b)
bsz.Add(b)
style = wx.SUNKEN_BORDER | wx.WANTS_CHARS
agwStyle = CT.TR_HAS_BUTTONS | CT.TR_HAS_VARIABLE_ROW_HEIGHT | CT.TR_ALIGN_WINDOWS
self.tree = CT.CustomTreeCtrl(panel, wx.ID_ANY, wx.DefaultPosition,
(100, 100), style, agwStyle)
self.tree.Bind(CT.EVT_TREE_ITEM_CHECKED, self.onItemChecked)
self.tree.Bind(CT.EVT_TREE_ITEM_GETTOOLTIP, self.onToolTip)
self.root = self.tree.AddRoot("Teacup Configuration")
self.stMech = self.mechSubTree(self.tree, self.root)
self.stAcc = self.accSubTree(self.tree, self.root)
pins = self.tree.AppendItem(self.root, "3. Pinouts",
data = "Pinouts")
sensors = self.tree.AppendItem(self.root, "4. Temperature Sensors",
data = "Sensors")
heaters = self.tree.AppendItem(self.root, "5. Heaters",
data = "Heaters")
comm = self.tree.AppendItem(self.root, "6. Communications",
data = "Communications")
misc = self.tree.AppendItem(self.root, "7. Miscellaneous",
data = "Miscellaneous")
sz.Add(self.tree, 1, wx.EXPAND + wx.ALL, 5)
sz.Add(bsz, 0, wx.ALL, 5)
panel.SetSizer(sz)
def mechSubTree(self, tree, root):
spmKeys = ['SMX', 'SMY', 'SMZ', 'SME']
mfrKeys = ['MFRX', 'MFRY', 'MFRZ', 'MFRE']
msrKeys = ['MSRX', 'MSRY', 'MSRZ']
eclKeys = ['ECX', 'ECY', 'ECZ']
minmaxKeys = ['MINX', 'MAXX', 'MINY', 'MAXY', 'MINZ', 'MAXZ']
mechLabels = {'SPM': "Steps/Meter",
'SMX': "X:", 'SMY': "Y:", 'SMZ': "Z:", 'SME' : "E:",
'MFR': "Max Feed Rate",
'MFRX': "X:", 'MFRY': "Y:", 'MFRZ': "Z:", 'MFRE': "E:",
'MSR': "Search Feed Rate",
'MSRX': "X:", 'MSRY': "Y:", 'MSRZ': "Z:",
'ECL': "Endstop Clearance",
'ECX': "X:", 'ECY': "Y:", 'ECZ': "Z:",
'MINMAX': "Minimum/Maximum X/Y/Z",
'MINX': "Min X:", 'MAXX': "Max X:", 'MINY': "Min Y:",
'MAXY': "Max Y:", 'MINZ': "Min Z:", 'MAXZ': "Max Z:",
'ABSE': "Absolute E Coordinates"}
self.mechCfgKeys = {'SMX': "STEPS_PER_M_X", 'SMY': "STEPS_PER_M_Y",
'SMZ': "STEPS_PER_M_Z", 'SME' : "STEPS_PER_M_E",
'MFRX': "MAXIMUM_FEEDRATE_X",
'MFRY': "MAXIMUM_FEEDRATE_Y",
'MFRZ': "MAXIMUM_FEEDRATE_Z",
'MFRE': "MAXIMUM_FEEDRATE_E",
'MSRX': "SEARCH_FEEDRATE_X",
'MSRY': "SEARCH_FEEDRATE_Y",
'MSRZ': "SEARCH_FEEDRATE_Z",
'ECX': "ENDSTOP_CLEARANCE_X",
'ECY': "ENDSTOP_CLEARANCE_Y",
'ECZ': "ENDSTOP_CLEARANCE_Z",
'MINX': "X_MIN", 'MAXX': "X_MAX", 'MINY': "Y_MIN",
'MAXY': "Y_MAX", 'MINZ': "Z_MIN", 'MAXZ': "Z_MAX",
'ABSE': "E_ABSOLUTE"}
self.tcMech = {}
self.brMech = {}
mech = tree.AppendItem(root, "1. Mechanical/Hardware", data = "Mechanical")
for tag, tbl, cttype in [('SPM', spmKeys, 0), ('MFR', mfrKeys, 0),
('MSR', msrKeys, 0), ('ECL', eclKeys, 0),
('MINMAX', minmaxKeys, 1)]:
st = tree.AppendItem(mech, mechLabels[tag], ct_type = cttype, data = tag)
self.brMech[tag] = st
for k in tbl:
ck = self.mechCfgKeys[k]
if ck in self.cfgValues.keys():
v = self.cfgValues[ck]
else:
v = ""
tc = wx.TextCtrl(tree, wx.ID_ANY, v, style = wx.TE_RIGHT)
if k in helpText.keys():
tc.SetToolTipString(helpText[k])
tc.Bind(wx.EVT_CHAR, self.onTextCtrl)
self.tcMech[k] = tc
self.brMech[k] = tree.AppendItem(st, mechLabels[k], ct_type = 0,
wnd = tc, data = k)
allAbsent = True
for i in minmaxKeys:
k = self.mechCfgKeys[i]
if k in self.cfgValues.keys():
allAbsent = False
break
if allAbsent:
self.tree.CheckItem(self.brMech['MINMAX'], False)
else:
self.tree.CheckItem(self.brMech['MINMAX'], True)
br = tree.AppendItem(mech, mechLabels['ABSE'], ct_type = 1, data = "ABSE")
ck = self.mechCfgKeys['ABSE']
if ck in self.cfgValues.keys() and self.cfgValues[ck]:
self.tree.CheckItem(br, True)
else:
self.tree.CheckItem(br, False)
self.brMech['ABSE'] = br
return mech
def insertMechValues(self):
for k in self.mechCfgKeys.keys():
if k in self.tcMech.keys():
ck = self.mechCfgKeys[k]
if ck in self.cfgValues.keys():
self.tcMech[k].SetValue(self.cfgValues[ck])
allAbsent = True
for i in ['MINX', 'MAXX', 'MINY', 'MAXY', 'MINZ', 'MAXZ']:
k = self.mechCfgKeys[i]
if k in self.cfgValues.keys():
allAbsent = False
break
if allAbsent:
self.tree.CheckItem(self.brMech['MINMAX'], False)
else:
self.tree.CheckItem(self.brMech['MINMAX'], True)
br = self.brMech['ABSE']
ck = self.mechCfgKeys['ABSE']
if ck in self.cfgValues.keys() and self.cfgValues[ck]:
self.tree.CheckItem(br, True)
else:
self.tree.CheckItem(br, False)
def accSubTree(self, tree, root):
accLabels = {'ACTYPE': "Acceleration Type:",
'ACRR': "RepRap", 'ACRP': "Ramping", 'ACTP': "Temporal",
'ACCEL' : "Acceleration", 'LKAH': "Look Ahead",
'JERK': "Maximum Jerk:",
'JERKX': "X", 'JERKY': "Y", 'JERKZ': "Z", 'JERKE': "E"}
self.accCfgKeys = {'ACRR': "ACCELERATION_REPRAP",
'ACRP': "ACCELERATION_RAMPING",
'ACTP': "ACCELERATION_TEMPORAL",
'ACCEL': "ACCELERATION", 'LKAH': "LOOKAHEAD",
'JERKX': "MAX_JERK_X", 'JERKY': "MAX_JERK_Y",
'JERKZ': "MAX_JERK_Z", 'JERKE': "MAX_JERK_E"}
self.tcAcc = {}
self.brAcc = {}
acc = tree.AppendItem(root, "2. Acceleration", data = "Acceleration")
st = tree.AppendItem(acc, accLabels['ACTYPE'], data = "ACTYPE")
self.brAcc['ACTYPE'] = st
for tag in ['ACRR', 'ACRP', 'ACTP']:
br = tree.AppendItem(st, accLabels[tag], ct_type = 2, data = tag)
self.brAcc[tag] = br
ck = self.accCfgKeys[tag]
if ck in self.cfgValues.keys() and self.cfgValues[ck]:
self.tree.CheckItem(br, True)
tag = 'ACRP'
ck = self.accCfgKeys['ACCEL']
if ck in self.cfgValues.keys():
v = self.cfgValues[ck]
else:
v = ""
tc = wx.TextCtrl(tree, wx.ID_ANY, v, style = wx.TE_RIGHT)
tc.SetToolTipString(helpText['ACCEL'])
tc.Bind(wx.EVT_CHAR, self.onTextCtrl)
self.tcAcc['ACCEL'] = tc
br = self.brAcc[tag]
self.brAcc['ACCEL'] = tree.AppendItem(br, accLabels['ACCEL'], ct_type = 0,
wnd = self.tcAcc['ACCEL'],
data = 'ACCEL')
self.brAcc['LKAH'] = tree.AppendItem(br, accLabels['LKAH'], ct_type = 1,
data = 'LKAH')
br = self.brAcc['LKAH']
ck = self.accCfgKeys['LKAH']
if ck in self.cfgValues.keys() and self.cfgValues[ck]:
self.tree.CheckItem(br, True)
else:
self.tree.CheckItem(br, False)
st = tree.AppendItem(acc, accLabels['JERK'], data = 'JERK')
self.brAcc['JERK'] = st
for k in ['JERKX', 'JERKY', 'JERKZ', 'JERKE']:
ck = self.accCfgKeys[k]
if ck in self.cfgValues.keys():
v = self.cfgValues[ck]
else:
v = ""
tc = wx.TextCtrl(tree, wx.ID_ANY, v, style = wx.TE_RIGHT)
if k in helpText.keys():
tc.SetToolTipString(helpText[k])
tc.Bind(wx.EVT_CHAR, self.onTextCtrl)
self.tcAcc[k] = tc
self.brAcc[k] = tree.AppendItem(st, accLabels[k], ct_type = 0,
wnd = tc, data = k)
return acc
def insertAccValues(self):
for k in self.accCfgKeys.keys():
if k in self.tcAcc.keys():
ck = self.accCfgKeys[k]
if ck in self.cfgValues.keys():
self.tcAcc[k].SetValue(self.cfgValues[ck])
for tag in ['ACRR', 'ACRP', 'ACTP']:
br = self.brAcc[tag]
ck = self.accCfgKeys[tag]
if ck in self.cfgValues.keys() and self.cfgValues[ck]:
self.tree.CheckItem(br, True)
br = self.brAcc['LKAH']
ck = self.accCfgKeys['LKAH']
if ck in self.cfgValues.keys() and self.cfgValues[ck]:
self.tree.CheckItem(br, True)
else:
self.tree.CheckItem(br, False)
def onItemChecked(self, evt):
item = evt.GetItem()
match = None
for k in self.brMech.keys():
if item == self.brMech[k]:
match = k
break
if match is None:
return
if match == 'MINMAX':
self.tree.EnableChildren(item, self.tree.IsItemChecked(item))
def onToolTip(self, evt):
item = evt.GetItem()
if item:
data = self.tree.GetPyData(item)
if data and data in helpText.keys():
evt.SetToolTip(wx.ToolTip(helpText[data]))
def onClose(self, evt):
self.Destroy()
def onTextCtrl(self, event):
char = chr(event.GetKeyCode())
event.Skip()
def onLoadConfig(self, evt):
wildcard = "C Header files (*.h)|*.h"
dlg = wx.FileDialog(self, message = "Choose a Config file",
defaultDir = ".", defaultFile = "",
wildcard = wildcard, style = wx.OPEN | wx.CHANGE_DIR)
path = None
if dlg.ShowModal() == wx.ID_OK:
path = dlg.GetPath()
dlg.Destroy()
if path is None:
return
self.loadConfigFile(path)
self.insertMechValues()
self.insertAccValues()
def loadConfigFile(self, fn):
try:
lst = list(open(fn))
except:
return False
self.cfgValues = {}
self.cfgValues['E_ABSOLUTE'] = False
for ln in lst:
if ln.lstrip().startswith("#define"):
m = self.reDefTS.search(ln)
if m:
t = m.groups()
if len(t) == 1:
print "TSkey (%s)" % t[0]
continue
m = self.reDefHT.search(ln)
if m:
t = m.groups()
if len(t) == 1:
print "HTkey (%s)" % t[0]
continue
m = self.reDefQS.search(ln)
if m:
t = m.groups()
if len(t) == 2:
self.cfgValues[t[0]] = t[1]
continue
m = self.reDefine.search(ln)
if m:
t = m.groups()
if len(t) == 2:
self.cfgValues[t[0]] = t[1]
continue
m = self.reDefBool.search(ln)
if m:
t = m.groups()
if len(t) == 1:
self.cfgValues[t[0]] = True
return True
if __name__ == '__main__':
app = wx.PySimpleApp()
frame = MyFrame()
frame.Show(True)
app.MainLoop()

95
config_helptext.py Normal file
View File

@ -0,0 +1,95 @@
helpText = {
'SPM': "steps per meter ( = steps per mm * 1000 ) \
calculate these values appropriate for your machine.\n\
for threaded rods, this is:\n\n\
\t(steps motor per turn) / (pitch of the thread) * 1000\n\n\
for belts, this is\n\n\
\t(steps per motor turn) / (number of gear teeth) / (belt module) * 1000\n\n\
half-stepping doubles the number, quarter stepping requires * 4, etc.\n\
valid range = 20 to 4,0960,000 (0.02 to 40960 steps/mm). \
all numbers are integers, so no decimal point",
'SMX': "steps per meter for the X axis",
'SMY': "steps per meter for the Y axis",
'SMZ': "steps per meter for the Z axis",
'SME': "steps per meter for the E axis",
'MFR': "maximum feed rate - in mm/min - for G0 rapid moves and as a cap for \
all other feedrates",
'MFRX': "maximum feed rate for the X axis (mm/min)",
'MFRY': "maximum feed rate for the Y axis (mm/min)",
'MFRZ': "maximum feed rate for the Z axis (mm/min)",
'MFRE': "maximum feed rate for the E axis (mm/min)",
'MSR': "search feed rate - in mm/min - used when doing precision endstop \
search and as a default feed rate",
'MSRX': "search feed rate for the X axis (mm/min)",
'MSRY': "search feed rate for the Y axis (mm/min)",
'MSRZ': "search feed rate for the Z axis (mm/min)",
'ECL': "When hitting an endstop, Teacup properly decelerates instead of \
doing an abrupt stop\nto save your mechanics. Ineviteably, this means it \
overshoots the endstop trigger point by some distance.\n\n\
To deal with this, Teacup adapts homing movement speeds to what your endstops \
can deal with.\nThe higher the allowed acceleration and the more clearance \
the endstop comes with, the faster Teacup\nwill do homing movements.\n\n\
Set here how many micrometers (mm * 1000) your endstop allows the carriage to \
overshoot the\ntrigger point. Typically 1000 or 2000 for mechanical endstops, \
more for optical ones.\nYou can set it to zero, in which case \
SEARCH_FEEDRATE_{XYZ} is used, but expect very slow\nhoming movements.\n\n\
Units: micrometers\n\
Sane values: 0 to 20000 (0 to 20 mm)\n\
Valid range: 0 to 1000000",
'ECX': "endstop clearance for the X axis (mm * 1000)",
'ECY': "endstop clearance for the Y axis (mm * 1000)",
'ECZ': "endstop clearance for the Z axis (mm * 1000)",
'MINMAX': "soft axis limits, in mm.\n\ndefine them to your machine's size \
relative to what your host considers to be the origin.",
'MINX': "Minimum limit for the X axis:",
'MAXX': "maximum limit for the X axis:",
'MINY': "minimum limit for the Y axis:",
'MAXY': "maximum limit for the Y axis:",
'MINZ': "minimum limit for the Z axis",
'MAXZ': "maximum limit for the Z axis",
'ABSE': "some G-code creators produce relative length commands for the \
extruder,\nothers absolute ones. G-code using absolute lengths can be \
recognized when there\nare G92 E0 commands from time to time. if you have \
G92 E0 in your G-code, check this box.",
'ACTYPE': "Acceleration algorithm",
'ACRR': "acceleration, reprap style.\n\n\
Each movement starts at the speed of the previous command and accelerates or \
decelerates\nlinearly to reach target speed at the end of the movement.",
'ACRP': "acceleration and deceleration ramping.\n\n\
Each movement starts at (almost) no speed, linearly accelerates to target \
speed and decelerates\njust in time to smoothly stop at the target.",
'ACTP': "This algorithm causes the timer to fire when any axis needs to step, \
instead of\nsynchronising to the axis with the most steps ala bresenham",
'ACCEL' : "how fast to accelerate when using acceleration ramping.\n\n\
given in mm/s^2, decimal allowed, useful range 1. to 10,000.\n\
Start with 10. for milling (high precision) or 1000. for printing",
'LKAH': "Define this to enable look-ahead during *ramping* acceleration to \
smoothly transition\nbetween moves instead of performing a dead stop every \
move. Enabling look-ahead requires about\n3600 bytes of flash memory.",
'JERK': "When performing look-ahead, we need to decide what an acceptable \
jerk to the\nmechanics is. Look-ahead attempts to instantly change direction \
at movement\ncrossings, which means instant changes in the speed of the axes \
participating\nin the movement. Define here how big the speed bumps on each \
of the axes is\nallowed to be.\n\n\
If you want a full stop before and after moving a specific axis, define\n\
maximum jerk of this axis to 0. This is often wanted for the Z axis. If you want\n\
to ignore jerk on an axis, define it to twice the maximum feedrate of this axis.\n\n\
Having these values too low results in more than neccessary slowdown at\n\
movement crossings, but is otherwise harmless. Too high values can result\n\
in stepper motors suddenly stalling. If angles between movements in your\n\
G-code are small and your printer runs through entire curves full speed,\n\
there's no point in raising the values.\n\n\
Units: mm/min\n\
Sane values: 0 to 400\n\
Valid range: 0 to 65535",
'JERKX': "maximum jerk for the X axis",
'JERKY': "maximum jerk for the Y axis",
'JERKZ': "maximum jerk for the Z axis",
'JERKE': "maximum jerk for the E axis",
}