diff --git a/.gitignore b/.gitignore index a965e14..2cded6d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +*.pyc *.hex build *~ diff --git a/config.py b/config.py new file mode 100644 index 0000000..bd7ad34 --- /dev/null +++ b/config.py @@ -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() diff --git a/config_helptext.py b/config_helptext.py new file mode 100644 index 0000000..11abc24 --- /dev/null +++ b/config_helptext.py @@ -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", +}