Module skeinforge_engine_gui
[hide private]
[frames] | no frames]

Source Code for Module skeinforge_engine_gui

  1  #!/usr/bin/env python 
  2   
  3  from threading import Thread 
  4  import ConfigParser 
  5  import logging 
  6  import os 
  7  import skeinforge_engine 
  8  import sys 
  9  import shutil 
 10  import StringIO 
 11  try: 
 12      import wx 
 13  except: 
 14      print "WX is not installed. This program requires WX to run." 
 15      raise 
 16   
 17  import printrun_utilities.gviz as gviz 
 18  import printrun_utilities.SimpleEditor as SimpleEditor 
 19   
 20  guiConfig = ConfigParser.ConfigParser(allow_no_value=True) 
 21  guiConfigFilename="skeinforge_engine_gui.cfg" 
 22  runtimeConfig = ConfigParser.ConfigParser(allow_no_value=True) 
 23  runtimeConfigFilename="skeinforge_engine_gui.runtime" 
 24  engineConfig = ConfigParser.ConfigParser(allow_no_value=True) 
 25  engineConfigFilename="skeinforge_engine.cfg" 
 26   
27 -class GuiFrame(wx.Frame):
28 - def __init__(self, *args, **kwds):
29 kwds["style"] = wx.DEFAULT_DIALOG_STYLE | wx.MAXIMIZE_BOX | wx.MINIMIZE_BOX | wx.RESIZE_BORDER 30 wx.Frame.__init__(self, *args, **kwds) 31 self.SetIcon(wx.Icon("SkeinforgeEngine.ico", wx.BITMAP_TYPE_ICO)) 32 self.SetBackgroundColour(wx.WHITE) 33 self.dialogs = [] 34 self.lastProfileName = runtimeConfig.get('runtime', 'last.profile') 35 self.lastShowGcode = runtimeConfig.getboolean('runtime', 'last.show.gcode') 36 self.openGcodeFilesVisualisation = guiConfig.getboolean('general', 'open.gcode.files.visualisation') 37 self.lastProfileIndex = 0 38 39 lastPath = runtimeConfig.get('runtime','last.path') 40 self.fileTxt = wx.TextCtrl(self, -1, lastPath, size=(300, -1)) 41 42 self.logTxtCtrl = wx.TextCtrl(self, -1, "", style=wx.TE_MULTILINE | wx.TE_READONLY | wx.HSCROLL, size=(-1, 200)) 43 44 self.profilesDirectory = guiConfig.get('general', 'profiles.location') 45 46 self.profilesCB = wx.ComboBox(self, -1, size=(300, -1), style=wx.CB_READONLY) 47 self.profilesCB.SetSelection(self.lastProfileIndex) 48 49 if not os.path.exists(self.profilesDirectory): 50 self.logTxtCtrl.WriteText("Invalid profiles directory: %s" % self.profilesDirectory) 51 else: 52 self.updateProfileList() 53 54 self.profileLbl = wx.StaticText(self, -1, "Profile", size=(-1, -1)) 55 self.fileLbl = wx.StaticText(self, -1, "File", size=(-1, -1)) 56 self.logLbl = wx.StaticText(self, -1, "Log", size=(-1, -1)) 57 58 self.fileBtn = wx.Button(self, -1, "&Open...", size=(100, -1)) 59 self.skeinBtn = wx.Button(self, -1, "&Skein", size=(100, -1)) 60 self.editProfileBtn = wx.Button(self, -1, "Edit...", size=(100, -1)) 61 62 self.editProfileBtn.Enable(self.profilesCB.GetSelection() > 0) 63 64 self.showGcodeCheckBox = wx.CheckBox(self, -1, 'Show Gcode', (10, 10)) 65 self.showGcodeCheckBox.SetValue(self.lastShowGcode) 66 67 self.Bind(wx.EVT_BUTTON, self.onOpenFile, self.fileBtn) 68 self.Bind(wx.EVT_BUTTON, self.onEditProfile, self.editProfileBtn) 69 self.Bind(wx.EVT_BUTTON, self.onSkein, self.skeinBtn) 70 71 self.Bind(wx.EVT_COMBOBOX, self.onProfileChange, self.profilesCB) 72 self.Bind(wx.EVT_CHECKBOX, self.onShowGcodeChange, self.showGcodeCheckBox) 73 74 self.Bind(wx.EVT_CLOSE, self.onExit) 75 76 skein_id = wx.NewId() 77 self.Bind(wx.EVT_MENU, self.onSkein, id=skein_id) 78 79 openFile_id = wx.NewId() 80 self.Bind(wx.EVT_MENU, self.onOpenFile, id=openFile_id) 81 82 self.accel_tbl = wx.AcceleratorTable([(wx.ACCEL_CTRL, ord('S'), skein_id), (wx.ACCEL_CTRL, ord('O'), openFile_id)]) 83 84 self.SetAcceleratorTable(self.accel_tbl) 85 86 self.skeinforgeEngine = skeinforge_engine 87 logHandler = WxLog(self.logTxtCtrl) 88 logHandler.setLevel(logging.getLevelName(guiConfig.get('general','skeinforge.engine.log.handler.level'))) 89 self.skeinforgeEngine.logger.addHandler(logHandler) 90 91 self.prepMenu() 92 93 self.__set_properties() 94 self.__do_layout()
95
96 - def updateProfileList(self):
97 self.profilesCB.Clear() 98 self.profilesCB.Append("None (Default)") 99 index = 1 100 self.lastProfileIndex = None 101 for f in os.listdir(self.profilesDirectory): 102 profilePath = os.path.join(self.profilesDirectory, f) 103 if os.path.isfile(profilePath): 104 if f == self.lastProfileName: 105 self.lastProfileIndex = index 106 self.profilesCB.Append(f) 107 index += 1 108 if self.lastProfileIndex == None: 109 self.lastProfileIndex = 0 110 self.profilesCB.SetSelection(self.lastProfileIndex)
111
112 - def __set_properties(self):
113 self.SetTitle("SkeinforgeEngine")
114
115 - def __do_layout(self):
116 mainSizer = wx.GridBagSizer(hgap=5, vgap=5) 117 mainSizer.AddGrowableCol(1, 0) 118 mainSizer.AddGrowableRow(2, 0) 119 mainSizer.Add(self.fileLbl, pos=(0, 0), flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL, border=5) 120 mainSizer.Add(self.fileTxt, pos=(0, 1), flag=wx.ALL | wx.EXPAND, border=5) 121 mainSizer.Add(self.fileBtn, pos=(0, 2), flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL, border=5) 122 123 mainSizer.Add(self.profileLbl, pos=(1, 0), flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL, border=5) 124 mainSizer.Add(self.profilesCB, pos=(1, 1), flag=wx.ALL | wx.EXPAND, border=5) 125 mainSizer.Add(self.editProfileBtn, pos=(1, 2), flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL, border=5) 126 127 mainSizer.Add(self.logLbl, pos=(2, 0), flag=wx.ALL | wx.ALIGN_TOP, border=5) 128 mainSizer.Add(self.logTxtCtrl, pos=(2, 1), span=wx.GBSpan(1, 2), flag=wx.EXPAND | wx.ALL, border=5) 129 130 131 mainSizer.Add(self.showGcodeCheckBox, pos=(3, 1), flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL, border=5) 132 mainSizer.Add(self.skeinBtn, pos=(3, 2), flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL, border=5) 133 134 self.SetSizer(mainSizer) 135 self.SetMinSize((500, 400)) 136 mainSizer.Fit(self) 137 self.Center() 138 self.Layout()
139
140 - def prepMenu(self):
141 self.menustrip = wx.MenuBar() 142 143 fileMenu = wx.Menu() 144 self.Bind(wx.EVT_MENU, self.onOpenFile, fileMenu.Append(-1, "&Open...", " Opens file")) 145 self.Bind(wx.EVT_MENU, self.onClearOutput, fileMenu.Append(-1, "Clear log", " Clear log")) 146 self.Bind(wx.EVT_MENU, self.onExit, fileMenu.Append(wx.ID_EXIT, "E&xit", " Closes the Window")) 147 self.menustrip.Append(fileMenu, "&File") 148 149 profilesMenu = wx.Menu() 150 self.Bind(wx.EVT_MENU, self.onEditProfile, profilesMenu.Append(-1, "&Edit...", " Edit currently selected profile")) 151 self.Bind(wx.EVT_MENU, self.onCopyProfile, profilesMenu.Append(-1, "&Copy", " Make a new copy of the currently selected profile")) 152 self.Bind(wx.EVT_MENU, self.onRefreshProfilesList, profilesMenu.Append(-1, "&Refresh", " Refresh list of profiles")) 153 self.Bind(wx.EVT_MENU, self.onViewEffectiveProfile, profilesMenu.Append(-1, "&View effective profile", " View the complete profile the engine would use")) 154 self.Bind(wx.EVT_MENU, self.onCopyEffectiveProfile, profilesMenu.Append(-1, "Copy effective profile", " Make a new copy of the currently effective profile")) 155 self.menustrip.Append(profilesMenu, "&Profiles") 156 157 configMenu = wx.Menu() 158 self.Bind(wx.EVT_MENU, self.onEditEngineConfig, configMenu.Append(-1, "&Edit skeinforge_engine.cfg...", " Edit engine config")) 159 self.Bind(wx.EVT_MENU, self.onEditEngineGuiConfig, configMenu.Append(-1, "&Edit skeinforge_engine_gui.cfg...", " Edit engine gui config")) 160 self.menustrip.Append(configMenu, "&Config") 161 162 self.SetMenuBar(self.menustrip)
163
164 - def onViewEffectiveProfile(self, event):
165 se = SimpleEditor("Effective Profile", self.getEffectiveProfileString(self.getEffectiveProfile()).getvalue(), None, True) 166 self.dialogs.append(se)
167
168 - def getEffectiveProfileString(self, effectiveConfig):
169 effectiveConfigString = StringIO.StringIO() 170 for section in effectiveConfig.sections(): 171 effectiveConfigString.write("[%s]\n" % section) 172 for option in effectiveConfig.options(section): 173 value = effectiveConfig.get(section, option) 174 effectiveConfigString.write("%s=%s\n" % (option, value)) 175 effectiveConfigString.write("\n") 176 return effectiveConfigString
177
178 - def getEffectiveProfile(self):
179 defaultProfile = engineConfig.get('general', 'default.profile') 180 effectiveConfig = ConfigParser.ConfigParser(allow_no_value=True) 181 effectiveConfig.read(defaultProfile) 182 183 profileSelectionIndex = self.profilesCB.GetSelection() 184 if profileSelectionIndex > 0: 185 profileName = self.profilesCB.GetString(profileSelectionIndex).encode() 186 profilePath = os.path.join(self.profilesDirectory, profileName) 187 effectiveConfig.read(profilePath) 188 return effectiveConfig
189
190 - def onCopyEffectiveProfile(self, event):
191 profileSelectionIndex = self.profilesCB.GetSelection() 192 if profileSelectionIndex > 0: 193 profileName = self.profilesCB.GetString(profileSelectionIndex).encode() 194 else: 195 profileName = "default.profile" 196 dlg = wx.TextEntryDialog(self, 'New profile name:', 'Copy Effective Profile', "%s.copy" % profileName) 197 txtCtrl = dlg.FindWindowById(3000) 198 txtCtrl.Validator = NewFileNameValidator(self.profilesDirectory, dlg) 199 if dlg.ShowModal() == wx.ID_OK: 200 newProfileName = txtCtrl.GetValue() 201 newProfilePath = os.path.join(self.profilesDirectory, newProfileName) 202 try: 203 fd = os.open(newProfilePath, os.O_RDWR | os.O_CREAT) 204 f = os.fdopen(fd, "w+") 205 effectiveProfile = self.getEffectiveProfile() 206 effectiveProfile.set('profile', 'name', newProfileName) 207 f.write (self.getEffectiveProfileString(effectiveProfile).getvalue()) 208 f.close() 209 self.lastProfileName = newProfileName 210 self.updateProfileList() 211 self.GetEventHandler().ProcessEvent(wx.PyCommandEvent(wx.EVT_COMBOBOX.typeId, self.profilesCB.GetId())) 212 except (IOError, os.error), why: 213 msgDlg = wx.MessageDialog(dlg, 'Unable to copy effective profile: %s.' % str(why), 'Error', wx.OK | wx.ICON_ERROR) 214 msgDlg.ShowModal() 215 msgDlg.Destroy() 216 dlg.Destroy()
217
218 - def onEditEngineGuiConfig(self, event):
219 self.editFile('skeinforge_engine_gui.cfg')
220
221 - def onEditEngineConfig(self, event):
222 self.editFile('skeinforge_engine.cfg')
223
224 - def onEditProfile(self, event):
225 '''Opens the profile for editing.''' 226 profileSelectionIndex = self.profilesCB.GetSelection() 227 if profileSelectionIndex < 1: 228 return 229 profileName = self.profilesCB.GetString(profileSelectionIndex).encode() 230 profilePath = os.path.join(self.profilesDirectory, profileName) 231 self.editFile(profilePath)
232
233 - def editFile(self, filePath):
234 f2 = open(filePath) 235 def saveFileCallback(text): 236 f = open(filePath, 'w+') 237 if len(text.strip()) == 0: 238 return 239 f.write(text) 240 f.close()
241 se = SimpleEditor(os.path.basename(filePath), f2.read(), saveFileCallback) 242 self.dialogs.append(se) 243
244 - def onCopyProfile(self, event):
245 profileSelectionIndex = self.profilesCB.GetSelection() 246 if profileSelectionIndex < 1: 247 return 248 profileName = self.profilesCB.GetString(profileSelectionIndex).encode() 249 profilePath = os.path.join(self.profilesDirectory, profileName) 250 dlg = wx.TextEntryDialog(self, 'New profile name:', 'Copy Profile', "%s.copy" % profileName) 251 252 txtCtrl = dlg.FindWindowById(3000) 253 txtCtrl.Validator = NewFileNameValidator(self.profilesDirectory, dlg) 254 if dlg.ShowModal() == wx.ID_OK: 255 newProfileName = txtCtrl.GetValue() 256 newProfilePath = os.path.join(self.profilesDirectory, newProfileName) 257 try: 258 shutil.copyfile(profilePath, newProfilePath) 259 self.lastProfileName = newProfileName 260 self.updateProfileList() 261 self.GetEventHandler().ProcessEvent(wx.PyCommandEvent(wx.EVT_COMBOBOX.typeId, self.profilesCB.GetId())) 262 except (IOError, os.error), why: 263 msgDlg = wx.MessageDialog(dlg, 'Unable to copy profile: %s.' % str(why), 'Error', wx.OK | wx.ICON_ERROR) 264 msgDlg.ShowModal() 265 msgDlg.Destroy() 266 dlg.Destroy()
267
268 - def onExit(self, event):
269 for d in self.dialogs: 270 try: 271 d.Destroy() 272 except: 273 pass 274 self.Destroy()
275
276 - def onRefreshProfilesList(self, event):
277 self.updateProfileList() 278 self.GetEventHandler().ProcessEvent(wx.PyCommandEvent(wx.EVT_COMBOBOX.typeId, self.profilesCB.GetId()))
279
280 - def onClearOutput(self, event):
281 self.logTxtCtrl.Clear()
282
283 - def onSkein(self, event):
284 '''Calls skeinforge_engine and optionally shows the created gcode afterwards.''' 285 if self.fileTxt.GetValue() == "": 286 self.logTxtCtrl.WriteText("No file given.\n") 287 return 288 289 args = [] 290 profileSelectionIndex = self.profilesCB.GetSelection() 291 292 if profileSelectionIndex > 0: 293 profileName = self.profilesCB.GetString(profileSelectionIndex).encode() 294 profilePath = os.path.join(self.profilesDirectory, profileName) 295 args.append("-p") 296 args.append(profilePath) 297 298 args.append(self.fileTxt.GetValue().encode()) 299 300 self.logTxtCtrl.WriteText("Skeining...\n") 301 302 slicedModel = self.skeinforgeEngine.main(args) 303 304 if self.showGcodeCheckBox.GetValue(): 305 if slicedModel.runtimeParameters.outputFilename == None: 306 self.logTxtCtrl.WriteText("Unable to find output filename in sliced model. Cannot preview Gcode.\n") 307 else: 308 self.showGcodeVisualisation(slicedModel.runtimeParameters.outputFilename) 309 310 self.logTxtCtrl.WriteText("Done!\n\n")
311
312 - def showGcodeVisualisation(self, filename):
313 gwindow = gviz.window([], title=os.path.basename(filename)) 314 self.dialogs.append(gwindow) 315 f = open(filename) 316 for i in f: 317 gwindow.p.addgcode(i) 318 gwindow.Show()
319
320 - def onOpenFile(self, event):
321 '''Shows the file choice dialog.''' 322 if runtimeConfig.get('runtime', 'last.path') != None: 323 startFolder = os.path.dirname(runtimeConfig.get('runtime', 'last.path')) 324 else: 325 startFolder = os.getcwd() 326 dlg = wx.FileDialog(self, "Choose a file", startFolder, "", "*.*", wx.OPEN) 327 if dlg.ShowModal() == wx.ID_OK: 328 path = dlg.GetPath() 329 330 if path.endswith('.gcode') and self.openGcodeFilesVisualisation: 331 self.showGcodeVisualisation(path) 332 else: 333 self.saveRuntimeParameter('last.path', path) 334 self.fileTxt.SetValue(path) 335 dlg.Destroy()
336
337 - def onProfileChange(self, event):
338 self.lastProfileIndex = self.profilesCB.GetSelection() 339 self.editProfileBtn.Enable(self.lastProfileIndex > 0) 340 self.lastProfileName = self.profilesCB.GetString(self.lastProfileIndex).encode() 341 if self.lastProfileIndex > 0: 342 self.saveRuntimeParameter('last.profile', self.lastProfileName) 343 else: 344 self.saveRuntimeParameter('last.profile', '')
345
346 - def onShowGcodeChange(self, event):
347 self.saveRuntimeParameter('last.show.gcode', self.showGcodeCheckBox.GetValue())
348
349 - def saveRuntimeParameter(self, option, value):
350 '''Saves the options in the cfg file under the [runtime] section.''' 351 runtimeConfig.set('runtime', option, value) 352 with open(runtimeConfigFilename, 'wb') as configfile: 353 runtimeConfig.write(configfile)
354
355 -class WxLog(logging.Handler):
356 - def __init__(self, ctrl):
357 logging.Handler.__init__(self) 358 self.ctrl = ctrl
359 - def emit(self, record):
360 self.ctrl.AppendText(self.format(record) + "\n") 361 self.ctrl.Update()
362
363 -class NewFileNameValidator(wx.PyValidator):
364 - def __init__(self, directory, parentDialog):
365 wx.PyValidator.__init__(self) 366 self.directory = directory 367 self.parentDialog = parentDialog 368
369 - def Clone(self):
370 '''Every validator must implement Clone() method!''' 371 return NewFileNameValidator(self.directory, self.parentDialog) 372
373 - def Validate(self, win):
374 txtCtrl = self.Window 375 newFilename = txtCtrl.GetValue() 376 newPath = os.path.join(self.directory, newFilename) 377 if os.path.exists(newPath): 378 msgDlg = wx.MessageDialog(self.parentDialog, 'File already exists: %s' % newPath, 'Error', wx.OK | wx.ICON_ERROR) 379 msgDlg.ShowModal() 380 msgDlg.Destroy() 381 return False 382 else: 383 return True 384
385 - def TransferToWindow(self):
386 return True 387
388 - def TransferFromWindow(self):
389 return True 390
391 -class SkeinforgeEngineGui(wx.App):
392 - def OnInit(self):
393 wx.InitAllImageHandlers() 394 frame_1 = GuiFrame(None, -1, "") 395 self.SetTopWindow(frame_1) 396 frame_1.Show() 397 return 1
398 399 if __name__ == "__main__": 400 guiConfig.read(guiConfigFilename) 401 runtimeConfig.read(runtimeConfigFilename) 402 engineConfig.read(engineConfigFilename) 403 skeinforgeEngineGui = SkeinforgeEngineGui(0) 404 skeinforgeEngineGui.MainLoop() 405