#!/usr/bin/python3 ''' migrate.py ''' import sys import os from shutil import * from tkinter import * from tkinter import filedialog import configparser import gettext if os.path.dirname(__file__).startswith('/usr/share'): localeDir = '/usr/share/locale' INSTYPE = ['pkg', 'linuxcnc'] HALLIB = '/usr/share/linuxcnc/hallib' else: path = os.path.dirname(__file__).split('configs')[0] localeDir = os.path.join(path, 'share/locale') INSTYPE = ['rip', os.path.join(path, 'scripts/linuxcnc')] HALLIB = os.path.join(path, 'lib/hallib') iconPath = os.path.join(os.path.dirname(__file__), 'lib/images/chips_plasma.png') gettext.install("linuxcnc", localedir=localeDir) _ = gettext.gettext class Dialog(Toplevel): def __init__(self, parent, title, text, buttons=1, insType=False, entry=None): super().__init__() # self.transient(parent) self.grab_set() self.attributes('-type', 'popup_menu') self.overrideredirect(True) self.resizable(False, False) self.reply = False self.value = StringVar() self.entry = entry self.parent = parent self.focus_set() justify = 'left' if 'Migration Log' in title else 'center' f = Frame(self, relief='ridge', borderwidth=4) f.grid(row=0, column=1, padx=1, pady=1) t = Label(f, text=title) t.grid(row=0, column=0, columnspan=3, sticky='ew') l = Label(f, text=f"{text}\n", justify=justify) l.grid(row=1, column=0, columnspan=3, padx=8, pady=(8,0)) if entry: e = Entry(f, textvariable=self.value, justify='center') e.grid(row=2, column=0, columnspan=3, sticky='ew', padx=8, pady=(0,8)) e.insert(END, entry) e.icursor(END) e.focus_set() if insType: text = 'Copy Command' key = '' else: text = 'Cancel' key = '' b1 = Button(f, text='OK', width=12, command=lambda:self.on_button(1)) self.bb1 = self.bind('', lambda e:self.on_button(1)) if buttons == 3: b2 = Button(f, text='View Log', width=12, command=lambda:self.on_button(2)) self.bb2 = self.bind('', lambda e:self.on_button(2)) b2.grid(row=3, column=1, pady=(0,8)) if buttons > 1: b0 = Button(f, text=text, width=12, command=lambda:self.on_button(0)) self.bb0 = self.bind(key, lambda e:self.on_button(0)) b1.grid(row=3, column=0, sticky='w', padx=(8,0), pady=(0,8)) b0.grid(row=3, column=2, sticky='e', padx=(0,8), pady=(0,8)) else: b1.grid(row=3, column=1, padx=(0,8), pady=(0,8)) parent.eval(f"tk::PlaceWindow {self} center") def on_button(self, button): if button == 2: title = 'plasmac2 Migration Log' text = '' with open(self.parent.logFile, 'r') as inFile: for line in inFile: text += line Dialog(self.parent, title, text).show() self.reply = 'log' elif button and self.entry: self.reply = self.value.get() else: self.reply = button self.parent.focus_set() self.destroy() def show(self): self.wm_deiconify() self.wait_window() return self.reply class Migrate(Tk): def __init__(self): super().__init__() icon = PhotoImage(file=iconPath) self.eval(f"wm iconphoto . {icon}") self.title('plasmac2 Migrate') text = _('Migration utility for plasmac2') l = Label(self, text=text) self.label = Label(self, justify='left') self.migrate_text() b = Frame(self) bm = Button(b, text=_('Migrate'), width=10, command=self.migrate, padx=0) bq = Button(b, text=_('Quit'), width=10, command=self.shutdown, padx=0) bm.grid(row=0, column=0) bq.grid(row=0, column=2) l.grid(row=0, column=0, sticky='ew', padx=8, pady=(8,0)) self.label.grid(row=1, column=0, sticky='ew', padx=8, pady=(8,0)) b.grid(row=3, column=0, sticky='ew', padx=8, pady=(16,8)) b.columnconfigure((0,1,2), weight=1) self.lcnc = os.path.expanduser('~/linuxcnc') if not os.path.isdir(self.lcnc): title = _('Directory Error') msg1 = _('The directory ~/linuxcnc does not exist') msg2 = _('It needs to have been created by LinuxCNC') msg3 = _('and have a working QtPlasmaC configuration') msg4 = _('to ensure that the structure is correct') Dialog(self, title, f"{msg1}.\n{msg2}\n{msg3}\n{msg4}.\n").show() raise SystemExit if 'root' in self.lcnc: title = _('User Error') msg = _('plasmac2 setup can not be run as a root user') Dialog(self, title, f"{msg}.\n").show() raise SystemExit self.configDir = os.path.join(self.lcnc, 'configs') self.currentDir = os.path.dirname(__file__) self.prefs = configparser.ConfigParser() self.prefs.optionxform=str self.eval(f"tk::PlaceWindow {self} center") self.bind('', lambda e:self.migrate()) self.bind('', lambda e:self.shutdown()) def migrate(self): self.migrate_text() ini = filedialog.askopenfilename( title='QtPlasmaC INI File', initialdir=self.configDir, filetypes=(('ini files', '*.ini'),)) if not ini: return iniFile = os.path.basename(ini) self.oldDir = os.path.dirname(ini) path, base = self.oldDir.rsplit('/',1) newBase = f"{base}_plasmac2" title = _('Directory Name') msg1 = _('The new directory name will be') msg2 = _('If you change it to the same name as the QtPlasmaC config') msg3 = _('then the QtPlasmaC config directory will be renamed to:') msg4 = _('Accept it or change it then accept') reply = Dialog(self, title, f"{msg1}: {newBase}\n\n{msg2}\n{msg3}\n{base}_qtplasmac\n\n{msg4}\n", buttons=2, entry=newBase).show() if reply == base: try: orgDir = self.oldDir self.oldDir = f"{self.oldDir}_qtplasmac" os.rename(orgDir, self.oldDir) except Exception as e: title = 'Directory Error' msg1 = _('Error while renaming') Dialog(self, title, f"{msg1}: {orgDir}\n\nto: {self.oldDir}\n\n{e}").show() return elif not reply: return self.newDir = os.path.join(path, reply) self.newIni = os.path.join(self.newDir, iniFile) self.logFile = os.path.join(self.newDir, 'migrate.log') self.log = [] if os.path.isfile(self.logFile): os.remove(self.logFile) if os.path.exists(self.newDir): title = _('Directory Exists') msg1 = _('already exists') msg2 = _('Overwrite') reply = Dialog(self, title, f"{self.newDir} {msg1}\n\n{msg2}?\n", buttons=2).show() if not reply: return if os.path.isfile(self.newDir) or os.path.islink(self.newDir): os.remove(self.newDir) elif os.path.isdir(self.newDir): rmtree(self.newDir) try: ignores = ignore_patterns('qtplasmac','backups','machine_log*.txt', \ '*.qss','*.py','*.bak','qtvcp.prefs','M190', \ 'qtplasmac.prefs') # copy required files p2Path = os.path.join(self.newDir, 'plasmac2') copytree(self.oldDir, self.newDir, ignore=ignores) # remove any existing plasmac2 if os.path.islink(p2Path): os.remove(p2Path) elif os.path.exists(p2Path): os.remove(p2Path) # create a link to plasmac2 os.symlink(self.currentDir, p2Path) # copy user custom files self.copy_custom_files(self.newDir) self.do_ini_file() self.do_hal_files() self.do_prefs_file() # all done... path = os.path.join(self.newDir, iniFile) title = _('Migration Complete') msg1 = _('The INI file for the plasmac2 config is') msg2 = _('Copy Command will copy the complete command required to run LinuxCNC using this config') msg = f"{msg1}:\n\n{path}\n\n{msg2}.\n" clip = False btns = 2 if self.log: with open(self.logFile, 'w') as outFile: for line in self.log: outFile.write(line) msg1 = '-------------------------------------------------' msg2 = _('Some hal files contain references to qtplasmac and they were commented out in the file') msg3 = _('There is a log of these files in') msg4 = _('in the new config directory') msg += f"\n{msg1}\n\n{msg2}.\n\n{msg3} {os.path.basename(self.logFile)} {msg4}.\n" btns = 3 while 1: reply = Dialog(self, title, msg, buttons=btns, insType=INSTYPE[0]).show() if reply == 1: if clip: text1 = _('Warning') text2 = _('The copied command will be removed from the') text3 = _('system clipboard when this window is closed') self.label['text'] = f"{text1}:\n{text2}\n{text3}.\n" break elif reply == 0: clip = True self.clipboard_clear() self.clipboard_append(f"{INSTYPE[1]} {path}") self.update() except Exception as e: title = _('Migration error') line = sys.exc_info()[-1].tb_lineno msg1 = _('Migration was unsuccessful') msg2 = _('Error in line') Dialog(self, title, f"{msg1}.\n\n{msg2}: {line}\n{str(e)}").show() def do_ini_file(self): with open(self.newIni, 'r') as inFile: config = inFile.readlines() # [DISPLAY] section section = {} for lNum in range(config.index('[DISPLAY]\n') + 1, len(config)): if config[lNum].startswith('['): break section[lNum] = config[lNum] insert = lNum - 1 if config[lNum - 1] == '\n' else lNum done = [] for lNum in section: if section[lNum].startswith('DISPLAY') and 'd' not in done: config[lNum] = 'DISPLAY = axis\n' done.append('d') elif section[lNum].startswith('OPEN_FILE') and 'of' not in done: config[lNum] = 'OPEN_FILE = ""\n' done.append('of') elif section[lNum].startswith('TOOL_EDITOR') and 'te' not in done: config[lNum] = 'TOOL_EDITOR = tooledit x y\n' done.append('te') elif section[lNum].startswith('CYCLE_TIME') and 'ct' not in done: config[lNum] = 'CYCLE_TIME = 100\n' done.append('ct') elif section[lNum].startswith('USER_COMMAND_FILE') and 'uc' not in done: config[lNum] = 'USER_COMMAND_FILE = ./plasmac2/plasmac2.py\n' done.append('uc') elif section[lNum].startswith('POSITION_OFFSET') and 'po' not in done: config[lNum] = 'POSITION_OFFSET = RELATIVE\n' done.append('po') elif section[lNum].startswith('POSITION_FEEDBACK') and 'pf' not in done: config[lNum] = 'POSITION_FEEDBACK = ACTUAL\n' done.append('pf') for option in ['d','of','te','ct','uc','po','pf']: if option == 'd' and option not in done: config.insert(insert,'DISPLAY = axis\n') elif option == 'of' and option not in done: config.insert(insert,'OPEN_FILE = ""\n') elif option == 'te' and option not in done: config.insert(insert,'TOOL_EDITOR = tooledit x y\n') elif option == 'ct' and option not in done: config.insert(insert,'CYCLE_TIME = 100\n') elif option == 'uc' and option not in done: config.insert(insert,'USER_COMMAND_FILE = ./plasmac2/plasmac2.py\n') elif option == 'po' and option not in done: config.insert(insert,'POSITION_OFFSET = RELATIVE\n') elif option == 'pf' and option not in done: config.insert(insert,'POSITION_FEEDBACK = ACTUAL\n') # [RS274NGC] section section = {} for lNum in range(config.index('[RS274NGC]\n') + 1, len(config)): if config[lNum].startswith('['): break section[lNum] = config[lNum] insert = lNum - 1 if config[lNum - 1] == '\n' else lNum done = [] for lNum in section: if section[lNum].startswith('USER_M_PATH') and 'u' not in done: mPath = config[lNum].split('=')[1].strip() config[lNum] = f"USER_M_PATH = ./plasmac2:{mPath}\n" done.append('u') for option in ['u']: if option == 'u' and option not in done: config.insert(insert,'USER_M_PATH = ./plasmac2:./\n') # [EMC] section - to get machine name section = {} for lNum in range(config.index('[EMC]\n') + 1, len(config)): if config[lNum].startswith('['): break section[lNum] = config[lNum] insert = lNum - 1 if config[lNum - 1] == '\n' else lNum done = [] for lNum in section: if section[lNum].startswith('MACHINE'): self.machineName = config[lNum].split('=')[1].strip() # [FILTER] section section = {} for lNum in range(config.index('[FILTER]\n') + 1, len(config)): if config[lNum].startswith('['): break section[lNum] = config[lNum] insert = lNum - 1 if config[lNum - 1] == '\n' else lNum done = [] for lNum in section: if section[lNum].startswith('PROGRAM_EXTENSION') and 'p' not in done: config[lNum] = 'PROGRAM_EXTENSION = .ngc,.nc,.tap (filter gcode files)\n' done.append('p') if section[lNum].startswith('ngc') and 'g' not in done: config[lNum] = 'ngc = qtplasmac_gcode\n' done.append('g') if section[lNum].startswith('nc') and 'c' not in done: config[lNum] = 'nc = qtplasmac_gcode\n' done.append('c') if section[lNum].startswith('tap') and 'a' not in done: config[lNum] = 'tap = qtplasmac_gcode\n' done.append('a') for option in ['a','c','g','p']: if option == 'a' and option not in done: config.insert(insert,'tap = qtplasmac_gcode\n') elif option == 'c' and option not in done: config.insert(insert,'nc = qtplasmac_gcode\n') elif option == 'g' and option not in done: config.insert(insert,'ngc = qtplasmac_gcode\n') elif option == 'p' and option not in done: config.insert(insert,'PROGRAM_EXTENSION = .ngc,.nc,.tap (filter gcode files)\n') # [HAL] section section = {} self.halList = [] sim = False self.qtplasmacHal = [] for lNum in range(config.index('[HAL]\n') + 1, len(config)): if config[lNum].startswith('['): break section[lNum] = config[lNum] for lNum in section: if section[lNum].startswith('HALFILE') or \ section[lNum].startswith('POSTGUI_HALFILE') or \ section[lNum].startswith('SHUTDOWN'): halFileRaw = config[lNum].split('=')[1].strip() if os.path.isfile(os.path.join(HALLIB, halFileRaw)): halFile = os.path.join(HALLIB, halFileRaw) else: halFile = os.path.realpath(os.path.join(self.oldDir, halFileRaw)) self.halList.append(halFileRaw) # if existing config is a sim if 'sim_postgui' in halFile: sim = True config[lNum] = '' elif 'sim_no_stepgen.tcl' in halFile: config[lNum] = 'HALFILE = plasmac2/lib/sim/sim_no_stepgen.tcl\n' sim = True elif 'sim_stepgen.tcl' in halFile: config[lNum] = 'HALFILE = plasmac2/lib/sim/sim_stepgen.tcl\n' sim = True elif 'sim_' in halFile: sim = True # add sim panel if required if sim: app1 = '[APPLICATIONS]' app2 = 'DELAY = 2' app3 = 'APP = plasmac2/lib/sim/sim_panel.py' config.insert(0, f"{app1}\n{app2}\n{app3}\n\n") # write the ini file with open(self.newIni, 'w') as outFile: for line in config: outFile.write(line) def do_hal_files(self): validPins = ['out_0', 'out_1', 'out_2', 'abort','frame-job','pause', \ 'power','probe','pulse','run','run-pause','touchoff'] for file in self.halList: if not os.path.dirname(file): halFile = os.path.join(self.newDir, file) edit = False logged = False with open(halFile, 'r') as inFile: config = inFile.readlines() for lNum in range(len(config)): if 'qtplasmac.ext_' in config[lNum]: for pin in validPins: if f"qtplasmac.ext_{pin}" in config[lNum]: config[lNum] = config[lNum].replace(f"qtplasmac.ext_{pin}", f"axisui.ext.{pin}") edit = True if 'qtplasmac' in config[lNum] or 'sim-torch' in config[lNum]: config[lNum] = f"# MIGRATION: {config[lNum]}" edit = True if not logged: self.log.append(f"\n{file}\n") logged = True self.log.append(f"{lNum+1} - {config[lNum]}") # write the hal fil if edit: with open(halFile, 'w') as outFile: for line in config: outFile.write(line) def do_prefs_file(self): prefsFile = os.path.join(self.newDir, f"{self.machineName}.prefs") with open(prefsFile, 'r') as inFile: config = inFile.readlines() # [BUTTONS] section edit = False section = {} for lNum in range(config.index('[BUTTONS]\n') + 1, len(config)): if config[lNum].startswith('['): break section[lNum] = config[lNum] for lNum in section: if 'Name =' in section[lNum]: name, value = config[lNum].split('=') value = value.replace('\\',' ').replace('&&', '& ') config[lNum] = f"{name}={value}" edit = True if 'Code =' in section[lNum] and 'offsets-view' in section[lNum]: config[lNum-1] = f"{config[lNum-1].split('=')[0]}= \n" config[lNum] = f"{config[lNum].split('=')[0]}= \n" self.log.append(f"\n{self.machineName}.prefs\n") text1 = _('is not available in plasmac2') text2 = _('it has been removed from the prefs file') self.log.append(f"{lNum} & {lNum+1} - offsets-view {text1}, {text2}.\n") edit = True elif 'qtplasmac.ext_' in config[lNum]: config[lNum] = config[lNum].replace('qtplasmac.ext_','axisui.ext.') edit = True # write the prefs file if edit: with open(prefsFile, 'w') as outFile: for line in config: outFile.write(line) def copy_custom_files(self, dir): for f in ['commands', 'hal', 'periodic']: src = os.path.join(self.currentDir, 'lib/custom', f"user_{f}.py") copy(src, os.path.join(dir, '.')) def migrate_text(self): text1 = _('Makes a copy of an existing working QtPlasmaC config') text2 = _('Then converts that copy to a working plasmac2 config') self.label['text'] = f"{text1}.\n\n{text2}.\n" def shutdown(self): raise SystemExit if __name__ == "__main__": migrate = Migrate() migrate.mainloop()