예제 #1
0
class GEditDlg(wx.Frame):
	def __init__(self, parent):
		self.parent = parent
		self.history = parent.history
		wx.Frame.__init__(self, None, wx.ID_ANY, TITLE_PREFIX, size=(600, 600))
		self.Show()
		ico = wx.Icon(os.path.join(cmdFolder, "images", "geditico.png"), wx.BITMAP_TYPE_PNG)
		self.SetIcon(ico)
		
		self.Bind(wx.EVT_CLOSE, self.onClose)
		self.settings = Settings(cmdFolder)
		self.propDlg = None
		self.legend = None
		
		self.log = self.parent.log
		
		self.images = Images(os.path.join(cmdFolder, "images"))
		
		self.shiftX = 0
		self.shiftY = 0
		self.modified = False
		self.filename = None
		self.importFileName = None
		self.okToImport = False
		
		self.gObj = self.loadGCode(self.filename)
		if self.gObj is not None:
			self.updateTitle()

		self.gcFrame = GcFrame(self, self.gObj, self.settings)
		self.stLayerText = wx.StaticText(self, wx.ID_ANY, "Layer Height:   0.00")

		ht = self.gcFrame.GetSize().Get()[1] - 2*BUTTONDIM[1] - 20
		
		if self.gObj is None:
			lmax = 1
		else:
			lmax = self.gObj.layerCount()-1
		
		self.slLayers = wx.Slider(
			self, wx.ID_ANY, 0, 0, 1000, size=(-1, ht), 
			style=wx.SL_VERTICAL | wx.SL_AUTOTICKS | wx.SL_LABELS | wx.SL_INVERSE)
		self.Bind(wx.EVT_SCROLL, self.onLayerScroll, self.slLayers)
		if self.gObj is None:
			self.slLayers.Enable(False)
			
		self.lcGCode = GcodeListCtrl(self, self.gcode, self.images)
		self.lcGCode.setLineNumbers(self.settings.uselinenbrs)
		self.currentLayer = 0
		self.setLayerText()
		if self.gObj is not None:
			self.lcGCode.setLayerBounds(self.gObj.getGCodeLines(0))
			
		self.bShift = wx.BitmapButton(self, wx.ID_ANY, self.images.pngShift, size=BUTTONDIM)
		self.bShift.SetToolTip("Move model in x/y direction")
		self.Bind(wx.EVT_BUTTON, self.doShiftModel, self.bShift)
		self.bShift.Enable(False)

		self.bModTemp = wx.BitmapButton(self, wx.ID_ANY, self.images.pngModtemp, size=BUTTONDIM)
		self.bModTemp.SetToolTip("Modify Temperatures")
		self.Bind(wx.EVT_BUTTON, self.onModTemps, self.bModTemp)
		self.bModTemp.Enable(False)
				
		self.bModSpeed = wx.BitmapButton(self, wx.ID_ANY, self.images.pngModspeed, size=BUTTONDIM)
		self.bModSpeed.SetToolTip("Modify Speed")
		self.Bind(wx.EVT_BUTTON, self.onModSpeed, self.bModSpeed)
		self.bModSpeed.Enable(False)
		
		self.bFilChange = wx.BitmapButton(self, wx.ID_ANY, self.images.pngFilchange, size=BUTTONDIM)
		self.bFilChange.SetToolTip("Insert G Code to assist with changing filament")
		self.Bind(wx.EVT_BUTTON, self.onFilChange, self.bFilChange)
		self.bFilChange.Enable(False)
		
		self.bEdit = wx.BitmapButton(self, wx.ID_ANY, self.images.pngEdit, size=BUTTONDIM)
		self.bEdit.SetToolTip("Free edit G Code")
		self.Bind(wx.EVT_BUTTON, self.onEditGCode, self.bEdit)
		self.bEdit.Enable(False)
		
		self.bUp = wx.BitmapButton(self, wx.ID_ANY, self.images.pngUp, size=BUTTONDIM)
		self.bUp.SetToolTip("Move up one layer")
		self.Bind(wx.EVT_BUTTON, self.onUp, self.bUp)
		self.bUp.Enable(False)
		
		self.bDown = wx.BitmapButton(self, wx.ID_ANY, self.images.pngDown, size=BUTTONDIM)
		self.bDown.SetToolTip("Move down one layer")
		self.Bind(wx.EVT_BUTTON, self.onDown, self.bDown)
		self.bDown.Enable(False)
		
		self.bInfo = wx.BitmapButton(self, wx.ID_ANY, self.images.pngInfo, size=BUTTONDIM)
		self.bInfo.SetToolTip("Information")
		self.Bind(wx.EVT_BUTTON, self.onInfo, self.bInfo)
		self.bInfo.Enable(False)
		
		self.bLegend = wx.BitmapButton(self, wx.ID_ANY, self.images.pngLegend, size=BUTTONDIM)
		self.bLegend.SetToolTip("Display a color legend")
		self.Bind(wx.EVT_BUTTON, self.onLegend, self.bLegend)
		self.bLegend.Enable(True)
		
		self.bSaveLayers = wx.BitmapButton(self, wx.ID_ANY, self.images.pngSavelayers, size=BUTTONDIM)
		self.bSaveLayers.SetToolTip("Save specific layers to a file")
		self.Bind(wx.EVT_BUTTON, self.onSaveLayers, self.bSaveLayers)
		self.bSaveLayers.Enable(False)

		self.bOpen = wx.BitmapButton(self, wx.ID_ANY, self.images.pngFileopen, size=BUTTONDIM)
		self.bOpen.SetToolTip("Open a G Code file")
		self.Bind(wx.EVT_BUTTON, self.onOpen, self.bOpen)
		
		self.bImport = wx.BitmapButton(self, wx.ID_ANY, self.images.pngImport, size=BUTTONDIM)
		self.bImport.SetToolTip("Import the current toolbox G Code file")
		self.Bind(wx.EVT_BUTTON, self.onImport, self.bImport)
		self.bImport.Enable(False)
		
		self.bImportQ = wx.BitmapButton(self, wx.ID_ANY, self.images.pngNext, size=BUTTONDIM)
		self.bImportQ.SetToolTip("Import the next G Code file from the queue")
		self.Bind(wx.EVT_BUTTON, self.onImportFromQueue, self.bImportQ)
		self.bImportQ.Enable(False)
		
		self.bExport = wx.BitmapButton(self, wx.ID_ANY, self.images.pngExport, size=BUTTONDIM)
		self.bExport.SetToolTip("Export the current toolbox G Code file")
		self.Bind(wx.EVT_CHECKBOX, self.onExport, self.bExport)
		self.bExport.Enable(not self.settings.autoexport)
		
		self.cbExport = wx.CheckBox(self, wx.ID_ANY, "Auto-export")
		self.cbExport.SetToolTip("Auto-export the current G Code file when saving")
		self.Bind(wx.EVT_CHECKBOX, self.onCbExport, self.cbExport)
		
		self.cbEnqueue = wx.CheckBox(self, wx.ID_ANY, "Add to queue")
		self.cbEnqueue.SetToolTip("Enqueue the current G Code file on the end of the G Code queue when exporting")
		self.Bind(wx.EVT_BUTTON, self.onEnqueue, self.cbEnqueue)
		
		self.bSave = wx.BitmapButton(self, wx.ID_ANY, self.images.pngFilesave, size=BUTTONDIM)
		self.bSave.SetToolTip("Save G Code to the current file")
		self.Bind(wx.EVT_BUTTON, self.onSave, self.bSave)
		self.bSave.Enable(False)
		
		self.bSaveAs = wx.BitmapButton(self, wx.ID_ANY, self.images.pngFilesaveas, size=BUTTONDIM)
		self.bSaveAs.SetToolTip("Save G Code to a different file")
		self.Bind(wx.EVT_BUTTON, self.onSaveAs, self.bSaveAs)
		self.bSaveAs.Enable(False)
		
		self.cbShowMoves = wx.CheckBox(self, wx.ID_ANY, "Show Moves")
		self.cbShowMoves.SetToolTip("Show/Hide non-extrusion moves")
		self.cbShowMoves.SetValue(self.settings.showmoves)
		self.Bind(wx.EVT_CHECKBOX, self.onCbShowMoves, self.cbShowMoves)

		self.cbShowPrevious = wx.CheckBox(self, wx.ID_ANY, "Show Previous Layer")
		self.cbShowPrevious.SetToolTip("Show/Hide the previous layer")
		self.cbShowPrevious.SetValue(self.settings.showprevious)
		self.Bind(wx.EVT_CHECKBOX, self.onCbShowPrevious, self.cbShowPrevious)

		self.cbShowRetractions = wx.CheckBox(self, wx.ID_ANY, "Show Retractions")
		self.cbShowRetractions.SetToolTip("Show/Hide retractions")
		self.cbShowRetractions.SetValue(self.settings.showretractions)
		self.Bind(wx.EVT_CHECKBOX, self.onCbShowRetractions, self.cbShowRetractions)

		self.cbShowRevRetractions = wx.CheckBox(self, wx.ID_ANY, "Show Reverse Retractions")
		self.cbShowRevRetractions.SetToolTip("Show/Hide reverse retractions")
		self.cbShowRevRetractions.SetValue(self.settings.showrevretractions)
		self.Bind(wx.EVT_CHECKBOX, self.onCbShowRevRetractions, self.cbShowRevRetractions)
		
		self.cmbTool = wx.ComboBox(self, wx.ID_ANY, "None", choices = ["None", "0", "1", "2", "3"],
			style = wx.CB_DROPDOWN + wx.CB_READONLY)
		self.cmbTool.SetToolTip("Choose which tool, if any, is highlighted in the display")
		self.Bind(wx.EVT_COMBOBOX, self.onCmbTool, self.cmbTool)

		self.cbLineNbrs = wx.CheckBox(self, wx.ID_ANY, "Line Numbers")
		self.cbLineNbrs.SetToolTip("Use G Code line numbers")
		self.cbLineNbrs.SetValue(self.settings.uselinenbrs)
		self.Bind(wx.EVT_CHECKBOX, self.onCbLineNbrs, self.cbLineNbrs)
		
		self.bBracketStart = wx.BitmapButton(self, wx.ID_ANY, self.images.pngBracketopen, size=BUTTONDIM)
		self.bBracketStart.SetToolTip("Mark the beginning of a block of G code")
		self.Bind(wx.EVT_BUTTON, self.onBracketStart, self.bBracketStart)
		self.bBracketStart.Enable(False)
		
		self.bBracketEnd = wx.BitmapButton(self, wx.ID_ANY, self.images.pngBracketclose, size=BUTTONDIM)
		self.bBracketEnd.SetToolTip("Mark the end of a block of G code")
		self.Bind(wx.EVT_BUTTON, self.onBracketEnd, self.bBracketEnd)
		self.bBracketEnd.Enable(False)
		
		self.bBracketDel = wx.BitmapButton(self, wx.ID_ANY, self.images.pngBracketdel, size=BUTTONDIM)
		self.bBracketDel.SetToolTip("Delete the marked block of G code")
		self.Bind(wx.EVT_BUTTON, self.onBracketDel, self.bBracketDel)
		self.bBracketDel.Enable(False)
		
			
		btnszr = wx.BoxSizer(wx.HORIZONTAL)
		btnszr.AddSpacer(20)
		btnszr.Add(self.bShift)
		btnszr.AddSpacer(10)
		btnszr.Add(self.bModTemp)
		btnszr.AddSpacer(10)
		btnszr.Add(self.bModSpeed)
		btnszr.AddSpacer(10)
		btnszr.Add(self.bFilChange)
		btnszr.AddSpacer(10)
		btnszr.Add(self.bEdit)
		btnszr.AddSpacer(10)
		btnszr.Add(self.bInfo)
		btnszr.AddSpacer(10)
		btnszr.Add(self.bLegend)
		btnszr.AddSpacer(50)
		btnszr.Add(self.bSaveLayers)
		btnszr.AddSpacer(10)
		btnszr.Add(self.bOpen)
		btnszr.AddSpacer(10)
		btnszr.Add(self.bImport)
		btnszr.AddSpacer(10)
		btnszr.Add(self.bImportQ)
		btnszr.AddSpacer(10)
		btnszr.Add(self.bExport)
		btnszr.AddSpacer(5)
		
		optszr = wx.BoxSizer(wx.VERTICAL)
		optszr.AddSpacer(1)
		optszr.Add(self.cbExport)
		optszr.AddSpacer(1)
		optszr.Add(self.cbEnqueue)
		btnszr.Add(optszr)
		
		btnszr.AddSpacer(10)
		btnszr.Add(self.bSave)
		btnszr.AddSpacer(10)
		btnszr.Add(self.bSaveAs)
		btnszr.AddSpacer(10)

		hszr = wx.BoxSizer(wx.HORIZONTAL)
		hszr.AddSpacer(20)
		
		vszr = wx.BoxSizer(wx.VERTICAL)
		vszr.Add(self.gcFrame)
		vszr.Add(self.stLayerText, 1, wx.ALIGN_CENTER_HORIZONTAL, 1)
		vszr.AddSpacer(10)

		opthszr = wx.BoxSizer(wx.HORIZONTAL)
		optszr = wx.BoxSizer(wx.VERTICAL)
		optszr.AddSpacer(1)
		optszr.Add(self.cbShowMoves)
		optszr.AddSpacer(1)
		optszr.Add(self.cbShowPrevious)
		opthszr.Add(optszr)
		opthszr.AddSpacer(10)
		optszr = wx.BoxSizer(wx.VERTICAL)
		optszr.AddSpacer(1)
		optszr.Add(self.cbShowRetractions)
		optszr.AddSpacer(1)
		optszr.Add(self.cbShowRevRetractions)
		opthszr.Add(optszr)
		opthszr.AddSpacer(10)
		optszr = wx.BoxSizer(wx.VERTICAL)
		optszr.AddSpacer(1)
		hsz = wx.BoxSizer(wx.HORIZONTAL)
		hsz.Add(wx.StaticText(self, wx.ID_ANY, "Tool to Hi-light: "), 1, wx.TOP, 5)
		hsz.AddSpacer(5)
		hsz.Add(self.cmbTool)
		optszr.Add(hsz)
		opthszr.Add(optszr)
		vszr.Add(opthszr)
		hszr.Add(vszr)

		szNav = wx.BoxSizer(wx.VERTICAL)
		szNav.Add(self.bUp, 1, wx.ALIGN_CENTER_HORIZONTAL, 1)
		szNav.AddSpacer(10)
		szNav.Add(self.slLayers)
		szNav.AddSpacer(10)
		szNav.Add(self.bDown, 1, wx.ALIGN_CENTER_HORIZONTAL, 1)

		hszr.Add(szNav)
		hszr.AddSpacer(20)
		
		listszr = wx.BoxSizer(wx.VERTICAL)
		listszr.Add(self.lcGCode)
		listszr.AddSpacer(10)
		listszr.Add(self.cbLineNbrs, 1, wx.ALIGN_CENTER_HORIZONTAL, 1)
		
		brksz = wx.BoxSizer(wx.HORIZONTAL)
		brksz.Add(self.bBracketStart)
		brksz.AddSpacer(20)
		brksz.Add(self.bBracketDel)
		brksz.AddSpacer(20)
		brksz.Add(self.bBracketEnd)
		listszr.AddSpacer(10)
		listszr.Add(brksz, 0, wx.ALIGN_CENTER_HORIZONTAL, 1)
		
		hszr.Add(listszr)
		hszr.AddSpacer(20)
		
		vszr = wx.BoxSizer(wx.VERTICAL)
		vszr.AddSpacer(20)
		vszr.Add(btnszr)
		vszr.AddSpacer(10)
		vszr.Add(hszr)
		vszr.AddSpacer(20)
		
		
		self.SetSizer(vszr)
		self.Layout()
		self.Fit()
		
		self.slLayers.SetRange(0, lmax)
		self.slLayers.SetPageSize(int(lmax/10))
		
		if self.gObj is not None:
			self.enableButtons()
			
	def onLegend(self, evt):
		if self.legend is None:
			self.legend = LegendDlg(self)
			self.legend.Show()
		else:
			self.legend.Show()
			self.legend.Raise()
			
	def legendClosed(self):
		self.legend = None
			
	def setImportFile(self, fn):
		self.importFileName = fn
		if fn is None:
			self.bImport.SetToolTip("")
			self.bImport.Enable(False)
		else:
			self.bImport.SetToolTip("Import G Code file (%s)" % fn)
			self.bImport.Enable(True)
			
	def onBracketStart(self, evt):
		b = self.lcGCode.setBracketStart()
		self.gcFrame.setBracket(b)
		self.enableBracketDel(b)

	def onBracketEnd(self, evt):
		b = self.lcGCode.setBracketEnd()
		self.gcFrame.setBracket(b)
		self.enableBracketDel(b)

	def enableBracketDel(self, b=None):
		if b is None:
			b = self.lcGCode.getBracket()
			
		if b[0] is None or b[1] is None:
			self.bBracketDel.Enable(False)
		else:
			self.bBracketDel.Enable(True)
		
	def onBracketDel(self, evt):
		b = self.lcGCode.getBracket()
		if b[0] is None or b[1] is None:
			return
		
		self.gcode = self.gcode[:b[0]] + self.gcode[b[1]+1:]
		self.setModified(True)
		self.gObj = self.buildModel()
		self.modGcSuffixTemps(self.gObj.getTemps())
		l = self.gcFrame.getCurrentLayer()
		self.gcFrame.loadModel(self.gObj, l, self.gcFrame.getZoom())
		lmax = self.gObj.layerCount()-1
		self.slLayers.SetRange(0, lmax)
		self.slLayers.SetPageSize(int(lmax/10))
		self.lcGCode.setGCode(self.gcode)
		self.lcGCode.setLayerBounds(self.gObj.getGCodeLines(l))
		self.bBracketDel.Enable(False)
		self.updateInfoDlg(self.currentLayer)
	
	def updateTitle(self):
		if self.filename is None:
			self.SetTitle("%s" % TITLE_PREFIX)
		else:
			txt = TITLE_PREFIX + " - "
			if self.modified:
				txt += "* "
			txt += self.filename
			self.SetTitle(txt)

			
	def setModified(self, flag=True):
		self.modified = flag
		self.updateTitle()
		
	def onExport(self, evt):
		if self.modified:
			dlg = wx.MessageDialog(self,
				"You have unsaved changes.\nAre you sure you want to export?",
				"Confirm Export With Pending Changes",
				wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION)
			rc = dlg.ShowModal()
			dlg.Destroy()
			if rc != wx.ID_YES:
				return
		
		self.parent.exportGcFile(self.filename, True, self.settings.autoenqueue)
		
	def onCbExport(self, evt):
		self.settings.autoexport = evt.IsChecked()
		self.bExport.Enable(not self.settings.autoexport)
		
	def onEnqueue(self, evt):
		self.settings.autoenqueue = evt.IsChecked()
		
	def onImport(self, evt):
		fn = self.parent.importGcFile()
		if fn is None:
			return
		
		self.loadGFile(fn)
		
	def onImportFromQueue(self, evt):
		fn = self.parent.importGcFromQueue()
		if fn is None:
			return
		
		self.loadGFile(fn)
		
	def setImportButton(self, msg):
		if msg is None:
			self.okToImport = False
			self.bImportQ.SetToolTip("")
			self.bImportQ.Enable(False)
		else:
			self.okToImport = True
			self.bImportQ.SetToolTip(msg)
			self.bImportQ.Enable(self.bOpen.IsEnabled())
		
	def onOpen(self, evt):
		if self.modified:
			dlg = wx.MessageDialog(self,
				"You have unsaved changes.\nAre you sure you want to open a different file?",
				"Confirm Open With Pending Changes",
				wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION)
			rc = dlg.ShowModal()
			dlg.Destroy()
			if rc != wx.ID_YES:
				return

		self.gcodeFileDialog()
		
	def onInfo(self, evt):
		if self.propDlg is not None:
			return
		
		self.propDlg = PropertiesDlg(self, self, None, cb=self.onInfoClose)
		
		self.showFileProperties()
		self.showLayerProperties(self.currentLayer)
		self.propDlg.Show()
		
	def onInfoClose(self):
		self.propDlg = None
		
	def updateInfoDlg(self, lx):
		if self.propDlg is None:
			return
		
		self.showFileProperties()
		self.showLayerProperties(lx)
		
	def showFileProperties(self):
		slCfg, filSiz, tempsHE, tempsBed = parseGCSuffix(self.gcode)
		ftime = time.strftime('%y/%m/%d-%H:%M:%S', time.localtime(os.path.getmtime(self.filename)))
		if len(self.filename) > 50:
			self.propDlg.setProperty(PropertyEnum.fileName, os.path.basename(self.filename))
		else:
			self.propDlg.setProperty(PropertyEnum.fileName, self.filename)
		self.propDlg.setProperty(PropertyEnum.slicerCfg, slCfg)
		self.propDlg.setProperty(PropertyEnum.filamentSize, filSiz)
		self.propDlg.setProperty(PropertyEnum.temperatures, "HE:%s  BED:%s" % (tempsHE, tempsBed))
		self.propDlg.setProperty(PropertyEnum.sliceTime, ftime)
		self.propDlg.setProperty(PropertyEnum.printEstimate, self.totalTimeStr)
		
	def showLayerProperties(self, lx):
		if self.propDlg is None:
			return
		
		self.propDlg.setProperty(PropertyEnum.layerNum, "%d" % lx)
		x0, y0, xn, yn = self.gObj.getLayerMinMaxXY(lx)
		if x0 is None:
			self.propDlg.setProperty(PropertyEnum.minMaxXY, "")
		else:
			self.propDlg.setProperty(PropertyEnum.minMaxXY, "(%.2f, %.2f) - (%.2f, %.2f)" % (x0, y0, xn, yn))
		
		le, prior, after = self.gObj.getLayerFilament(lx)
		eUsed = self.gObj.getFilament()
		s = []
		for i in range(self.settings.nextruders):
			s.append("%.2f/%.2f    <: %.2f    >: %.2f" % (le[i], eUsed[i], prior[i], after[i]))
		self.propDlg.setProperty(PropertyEnum.filamentUsed, s)

		f, l = self.gObj.getGCodeLines(lx)
		if f is None:
			self.propDlg.setProperty(PropertyEnum.gCodeRange, "")
		else:
			self.propDlg.setProperty(PropertyEnum.gCodeRange, "%d - %d" % (f, l))
		
		self.propDlg.setProperty(PropertyEnum.layerPrintTime, self.layerTimeStr[lx])
		if lx == 0:
			self.propDlg.setProperty(PropertyEnum.timeUntil, "")
		else:
			t = sum(self.layerTimes[:lx])
			self.propDlg.setProperty(PropertyEnum.timeUntil, formatElapsed(t))
		
	def gcodeFileDialog(self):
		wildcard = "GCode (*.gcode)|*.gcode;*.GCODE|"	 \
			"All files (*.*)|*.*"
			
		dlg = wx.FileDialog(
			self, message="Choose a GCode file",
			defaultDir=self.settings.lastdirectory, 
			defaultFile="",
			wildcard=wildcard,
			style=wx.FD_OPEN)

		rc = dlg.ShowModal()
		if rc == wx.ID_OK:
			path = dlg.GetPath().encode('ascii','ignore')
		dlg.Destroy()
		if rc != wx.ID_OK:
			return
		
		self.loadGFile(path)
		
	def loadGFile(self, path):
		self.settings.lastdirectory = os.path.dirname(path)
		
		self.gObj = self.loadGCode(path)
		if self.gObj is None:
			lmax = 1
			self.slLayers.Enable(False)
			self.bUp.Enable(False)
			self.bDown.Enable(False)
			self.filename = None
		else:
			lmax = self.gObj.layerCount()-1
			self.slLayers.Enable(True)
			self.bUp.Enable(True)
			self.bDown.Enable(True)
			self.filename = path
			
		self.updateTitle()
			
		self.slLayers.SetRange(0, lmax)
		self.slLayers.SetPageSize(int(lmax/10))
		
		self.gcFrame.loadModel(self.gObj)
		self.lcGCode.setGCode(self.gcode)
		self.currentLayer = 0
		self.setLayerText()
		self.slLayers.SetValue(0)
		self.updateInfoDlg(0)

		self.setModified(False)
		if self.gObj is not None:
			self.lcGCode.setLayerBounds(self.gObj.getGCodeLines(0))
			self.enableButtons()
		else:
			self.enableButtons(False)
			
	def enableButtons(self, flag=True, openButtons=False):
		self.bShift.Enable(flag)
		self.bModTemp.Enable(flag)
		self.bModSpeed.Enable(flag)
		self.bEdit.Enable(flag)
		self.bInfo.Enable(flag)
		self.bUp.Enable(flag)
		self.bDown.Enable(flag)
		self.bSaveLayers.Enable(flag)
		self.bSave.Enable(flag)
		self.bSaveAs.Enable(flag)
		self.bExport.Enable(flag and (not self.settings.autoexport))
		self.bFilChange.Enable(flag)
		self.bBracketStart.Enable(flag)
		self.bBracketEnd.Enable(flag)
		self.enableBracketDel()
		if openButtons:
			if flag and self.importFileName is not None:
				self.bImport.Enable(True)
			else:
				self.bImport.Enable(False)
			if flag and self.okToImport:
				self.bImportQ.Enable(True)
			else:
				self.bImportQ.Enable(False)
			self.bOpen.Enable(flag)
			
	def doShiftModel(self, evt):
		dlg = ShiftModelDlg(self, self.gObj, self.settings.buildarea)
		dlg.CenterOnScreen()
		rc = dlg.ShowModal()
		dlg.Destroy()
		if rc == wx.ID_OK:
			self.applyShift()
			self.setModified()
		else:
			self.setShift(0, 0)
		
	def setShift(self, sx, sy):
		self.shiftX = sx
		self.shiftY = sy
		self.gcFrame.setShift(sx, sy)

	def applyShift(self):
		self.gcode = [self.applyAxisShift(self.applyAxisShift(l, 'y', self.shiftY), 'x', self.shiftX) for l in self.gcode]
		self.shiftX = 0
		self.shiftY = 0
		self.gObj = self.buildModel()
		self.gcFrame.loadModel(self.gObj, self.gcFrame.getCurrentLayer(), self.gcFrame.getZoom())
		self.lcGCode.setGCode(self.gcode)
		self.lcGCode.refreshList()
		self.updateInfoDlg(self.currentLayer)

	def applyAxisShift(self, s, axis, shift):
		if "m117" in s or "M117" in s:
			return s

		if axis == 'x':
			m = reX.match(s)
			maxv = self.settings.buildarea[0]
		elif axis == 'y':
			m = reY.match(s)
			maxv = self.settings.buildarea[1]
		elif axis == 'z':
			m = reZ.match(s)
			maxv = self.settings.buildarea[1]
		else:
			return s
		
		if m is None or m.lastindex != 3:
			return s
		
		value = float(m.group(2)) + float(shift)
		if value < 0:
			value = 0.0
		elif value > maxv:
			value = float(maxv)
			
		return "%s%s%s" % (m.group(1), str(value), m.group(3))
	
	def onModTemps(self, evt):
		dlg = ModifyTempsDlg(self, self.gObj, self.settings.platemps, self.settings.abstemps)
		dlg.CenterOnScreen()
		rc = dlg.ShowModal()
		if rc == wx.ID_OK:
			bed, hes = dlg.getResult()
			
		dlg.Destroy()
		if rc != wx.ID_OK:
			return
		
		self.applyTempChange(bed, hes)

	def applyTempChange(self, bed, hes):
		self.currentTool = 0
		self.gcode = [self.applySingleTempChange(l, bed, hes) for l in self.gcode]
		self.setModified(True)
		self.gObj = self.buildModel()
		self.modGcSuffixTemps(self.gObj.getTemps())
		self.gcFrame.loadModel(self.gObj, self.gcFrame.getCurrentLayer(), self.gcFrame.getZoom())
		self.lcGCode.setGCode(self.gcode)
		self.lcGCode.refreshList()
		self.updateInfoDlg(self.currentLayer)
		
	def modGcSuffixTemps(self, nTemps):
		bstr = "%.1f" % nTemps[0]
		
		h = []
		nct = 0
		for x in nTemps[1]:
			if x is None:
				nct += 1
			else:
				if nct != 0:
					h.extend([None]*nct)
					nct = 0
				h.append("%.1f" % x)
		hestr = ",".join(h)
		
		modifyGCSuffix(self.gcode, None, None, hestr, bstr)
		
	def applySingleTempChange(self, s, bed, hes):
		if "m104" in s.lower() or "m109" in s.lower():
			m = reS.match(s)
			difference = hes[self.currentTool]
		elif "m140" in s.lower() or "m190" in s.lower():
			m = reS.match(s)
			difference = bed
		elif s.startswith("T"):
			try:
				t = int(s[1:])
			except:
				t = None

			if t is not None:
				self.currentTool = t
			return s
		else:
			return s

		if m is None or m.lastindex != 3:
			return s

		value = float(m.group(2))
		if value == 0.0:
			return s

		value += float(difference)
		return "%s%s%s" % (m.group(1), str(value), m.group(3))
	
	def onModSpeed(self, evt):
		dlg = ModifySpeedDlg(self)
		dlg.CenterOnScreen()
		val = dlg.ShowModal()

		if val == wx.ID_OK:
			modSpeeds = dlg.getResult()
			
		dlg.Destroy()
		if val != wx.ID_OK:
			return
		
		self.applySpeedChange([float(x)/100.0 for x in modSpeeds])

	def applySpeedChange(self, speeds):
		self.gcode = [self.applySingleSpeedChange(l, speeds) for l in self.gcode]
		self.setModified(True)
		self.gObj = self.buildModel()
		self.gcFrame.loadModel(self.gObj, self.gcFrame.getCurrentLayer(), self.gcFrame.getZoom())
		self.lcGCode.setGCode(self.gcode)
		self.lcGCode.refreshList()
		self.updateInfoDlg(self.currentLayer)

	def applySingleSpeedChange(self, s, speeds):
		if "m117" in s or "M117" in s:
			return s

		m = reF.match(s)
		if m is None or m.lastindex != 3:
			return s

		e = reE.match(s)
		if e is None: #no extrusion - must  be a move
			factor = speeds[1]
		else:
			factor = speeds[0]

		value = float(m.group(2)) * float(factor)
		return "%s%s%s" % (m.group(1), str(value), m.group(3))
	
	def onFilChange(self, evt):
		insertPoint = self.lcGCode.getSelectedLine()
		dlg = FilamentChangeDlg(self, self.gcode, self.gObj,
				insertPoint,
				self.gObj[self.currentLayer].printHeight())
		rc = dlg.ShowModal()
		if rc == wx.ID_OK:
			ngc = dlg.getValues()
			
		dlg.Destroy()
		if rc != wx.ID_OK:
			return
			
		if insertPoint == 0:
			self.gcode = ngc + self.gcode
		else:
			self.gcode = self.gcode[:insertPoint] + ngc + self.gcode[insertPoint:]
		
		self.setModified(True)
		self.enableButtons()
		self.gObj = self.buildModel()
				
		lmax = self.gObj.layerCount()-1
		self.slLayers.SetRange(0, lmax)
		self.slLayers.SetPageSize(int(lmax/10))

		self.gcFrame.loadModel(self.gObj, self.currentLayer, None)
		self.lcGCode.setGCode(self.gcode)
		self.lcGCode.setLayerBounds(self.gObj.getGCodeLines(self.currentLayer))
		self.updateInfoDlg(self.currentLayer)
	
	def onEditGCode(self, evt):
		self.editDlg = EditGCodeDlg(self, self.gcode, "<live buffer>", self.editClosed)
		self.editDlg.CenterOnScreen()
		self.editDlg.Show()
		self.enableButtons(flag=False, openButtons=True)
		
	def editClosed(self, rc):
		self.enableButtons(flag=True, openButtons=True)
		if rc == wx.ID_OK:
			data = self.editDlg.getData()
			
		self.editDlg.Destroy()
		if rc != wx.ID_OK:
			return

		self.gcode = data[:]
		self.setModified(True)
		self.gObj = self.buildModel()
		self.modGcSuffixTemps(self.gObj.getTemps())
		self.gcFrame.loadModel(self.gObj, 0, 1)
		self.currentLayer = 0
		self.setLayerText()
		self.slLayers.SetValue(0)
		
		lmax = self.gObj.layerCount()-1
		self.slLayers.SetRange(0, lmax)
		self.slLayers.SetPageSize(int(lmax/10))

		self.lcGCode.setGCode(self.gcode)
		self.lcGCode.setLayerBounds(self.gObj.getGCodeLines(0))
		self.lcGCode.refreshList()
		self.updateInfoDlg(0)
			
	def onCbShowMoves(self, evt):
		self.settings.showmoves = self.cbShowMoves.GetValue()
		self.gcFrame.setShowMoves(self.settings.showmoves)
	
	def onCbShowPrevious(self, evt):
		self.settings.showprevious = self.cbShowPrevious.GetValue()
		self.gcFrame.setShowPrevious(self.settings.showprevious)
	
	def onCbShowRetractions(self, evt):
		self.settings.showretractions = self.cbShowRetractions.GetValue()
		self.gcFrame.setShowRetractions(self.settings.showretractions)
	
	def onCbShowRevRetractions(self, evt):
		self.settings.showrevretractions = self.cbShowRevRetractions.GetValue()
		self.gcFrame.setShowRevRetractions(self.settings.showrevretractions)
		
	def onCmbTool(self, evt):
		sel = self.cmbTool.GetStringSelection()
		if sel == "" or sel == "None":
			sel = None
		else:
			try:
				sel = int(sel)
			except:
				sel = None
				
		self.gcFrame.setHilightTool(sel)
		
	def onCbLineNbrs(self, evt):
		self.settings.uselinenbrs = self.cbLineNbrs.GetValue()
		self.lcGCode.setLineNumbers(self.settings.uselinenbrs)
		
	def onLayerScroll(self, evt):
		v = self.slLayers.GetValue()
		if v == self.currentLayer:
			return
		
		self.changeLayer(v)
		
	def onUp(self, evt):
		lmax = self.slLayers.GetRange()[1]
		if self.currentLayer >= lmax:
			return
		
		v = self.currentLayer + 1
		self.changeLayer(v)
	
	def onDown(self, evt):
		if self.currentLayer <= 0:
			return
		
		v = self.currentLayer - 1
		self.changeLayer(v)
		
	def changeLayer(self, v):
		self.currentLayer = v
		self.gcFrame.setLayer(v)
		self.slLayers.SetValue(v)
		self.setLayerText()
		self.lcGCode.setLayerBounds(self.gObj.getGCodeLines(v))
		self.showLayerProperties(v)
		
	def setLayerText(self):
		if self.gObj is None:
			ht = 0.0
		else:
			ht = self.gObj[self.currentLayer].printHeight()
		self.stLayerText.SetLabel("Layer Height: %0.3f" % ht)
		
	def reportSelectedLine(self, ln):
		self.gcFrame.reportSelectedLine(ln)
					
	def onClose(self, evt):
		self.settings.save()
		if self.modified:
			dlg = wx.MessageDialog(self,
				"You have unsaved changes.\nAre you sure you want to exit?",
				"Confirm Exit With Pending Changes",
				wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION)
			rc = dlg.ShowModal()
			dlg.Destroy()
			if rc == wx.ID_YES:
				self.parent.GEditClosed()
		else:
			self.parent.GEditClosed()
		
	def loadGCode(self, fn):
		if fn is None:
			self.gcode = []
			return None
		
		try:
			self.gcode = list(open(fn))
		except:
			print "Error opening file %s" % fn
			return None
		
		return self.buildModel()
		
	def buildModel(self):
		rgcode = [s.rstrip() for s in self.gcode]
		
		cnc = CNC(self.settings.acceleration, self.settings.layerheight)
		
		ln = -1
		for gl in rgcode:
			ln += 1
			if ";" in gl:
				gl = gl.split(";")[0]
			if gl.strip() == "":
				continue
			
			p = re.split("\\s+", gl, 1)
			
			params = {}
			if not (p[0].strip() in ["M117", "m117"]):
				if len(p) >= 2:
					self.paramStr = p[1]
					
					if "X" in self.paramStr:
						params["X"] = self._get_float("X")
						
					if "Y" in self.paramStr:
						params["Y"] = self._get_float("Y")
			
					if "Z" in self.paramStr:
						params["Z"] = self._get_float("Z")
			
					if "E" in self.paramStr:
						params["E"] = self._get_float("E")
			
					if "F" in self.paramStr:
						params["F"] = self._get_float("F")
			
					if "S" in self.paramStr:
						params["S"] = self._get_float("S")
			
			cnc.execute(p[0], params, ln)
			
		gobj = cnc.getGObject()
		gobj.setMaxLine(ln)
		self.totalTime, self.layerTimes = cnc.getTimes()

		self.totalTimeStr = formatElapsed(self.totalTime)
		self.layerTimeStr = [formatElapsed(s) for s in self.layerTimes]

		return gobj
				
	def _get_float(self,which):
		try:
			return float(gcRegex.findall(self.paramStr.split(which)[1])[0])
		except:
			print "exception: ", self.paramStr
	
	def onSaveAs(self, evt):
		wildcard = "GCode (*.gcode)|*.gcode;*.GCODE"

		dlg = wx.FileDialog(
			self, message="Save as ...", defaultDir=self.settings.lastdirectory, 
			defaultFile="", wildcard=wildcard, style=wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT)
		
		val = dlg.ShowModal()

		if val != wx.ID_OK:
			dlg.Destroy()
			return
		
		path = dlg.GetPath()
		dlg.Destroy()

		ext = os.path.splitext(os.path.basename(path))[1]
		if ext == "":
			path += ".gcode"
			
		self.saveFile(path)
		
	def onSave(self, evt):
		if self.filename is None:
			self.onSaveAs(evt)
		else:
			self.saveFile(self.filename)
		
	def saveFile(self, path):			
		fp = file(path, 'w')
		
		for ln in self.gcode:
			fp.write("%s\n" % ln.rstrip())
			
		self.setModified(False)
			
		fp.close()
		
		self.filename = path
		self.parent.exportGcFile(path, self.settings.autoexport, self.settings.autoenqueue)
		self.updateTitle()
		
		dlg = wx.MessageDialog(self, "G Code file\n" + path + "\nwritten.",
			'Save Successful', wx.OK | wx.ICON_INFORMATION)
		dlg.ShowModal()
		dlg.Destroy()
		
	def onSaveLayers(self, evt):
		dlg = SaveLayerDlg(self, self.gObj)
		rc = dlg.ShowModal()
		if rc == wx.ID_OK:
			sx, ex, ereset, zmodify, zdelta = dlg.getValues()
			
		dlg.Destroy()
		if rc != wx.ID_OK:
			return
		
		startLine = self.gObj.getGCodeLines(sx)[0]
		endLine = self.gObj.getGCodeLines(ex)[1]
		
		wildcard = "GCode (*.gcode)|*.gcode;*.GCODE"

		dlg = wx.FileDialog(
			self, message="Save as ...", defaultDir=self.settings.lastdirectory, 
			defaultFile="", wildcard=wildcard, style=wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT)
		
		val = dlg.ShowModal()

		if val != wx.ID_OK:
			dlg.Destroy()
			return
		
		path = dlg.GetPath()
		dlg.Destroy()

		ext = os.path.splitext(os.path.basename(path))[1]
		if ext == "":
			path += ".gcode"
			
		fp = file(path, 'w')
		
		if ereset:
			fp.write("G92 E%0.5f\n" % self.gObj[sx].startingE())
			
		if zmodify:
			fp.write("\n".join([self.applyAxisShift(ln, 'z', zdelta).rstrip() for ln in self.gcode[startLine:endLine+1]]))
		else:
			fp.write("\n".join([ln.rstrip() for ln in self.gcode[startLine:endLine+1]]))
			
		fp.close()
		
		dlg = wx.MessageDialog(self, "G Code file\n" + path + "\nwritten.",
			'Save Layers Successful', wx.OK | wx.ICON_INFORMATION)
		dlg.ShowModal()
		dlg.Destroy()
		
	def applyZMod(self, ln, modflag):
		if not modflag:
			return ln
		
		return ln
예제 #2
0
class GCodeDlg(wx.Frame):
	def __init__(self, parent, server, gcode, filenm, pname, settings, images, cbexit):
		wx.Frame.__init__(self, None, wx.ID_ANY, self.formatTitle(pname, filenm))
		self.SetBackgroundColour("white")
		self.Bind(wx.EVT_CLOSE, self.onClose)

		self.parent = parent
		self.server = server
		self.gcode = gcode
		self.filenm = filenm
		self.pname = pname
		self.settings = settings
		self.images = images
		self.exitDlg = cbexit
		self.nExtr = self.parent.nExtr
		self.layerCount = 0
		if self.gcode:
			self.sTotalTime = " / " + formatElapsed(self.gcode.getPrintTime())
			self.filament = self.gcode.getFilament()
		else:
			self.sTotalTime = ""
			self.filament = [[0.0, 0.0]]

		self.printPosition = 0
		self.followPrint = True

		self.lx = 0

		lbFont = wx.Font(10, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD)

		self.gcf = GcFrame(self, self.pname, self.gcode, self.settings)
		self.stHeight = wx.StaticText(self, wx.ID_ANY, "")
		self.stHeight.SetFont(lbFont)
		self.stTime = wx.StaticText(self, wx.ID_ANY, "")
		self.stTime.SetFont(lbFont)
		self.stFilament = wx.StaticText(self, wx.ID_ANY, "")
		self.stFilament.SetFont(lbFont)
		self.slLayer = wx.Slider(self, wx.ID_ANY, 1, 1, 10, style=wx.SL_VERTICAL+wx.SL_LABELS+wx.SL_INVERSE)
		self.Bind(wx.EVT_SLIDER, self.onSlLayer, self.slLayer)
		self.bUp =  wx.BitmapButton(self, wx.ID_ANY, self.images.pngUp, size=BTNDIM, style=wx.NO_BORDER)
		self.bUp.SetBackgroundColour("white")
		self.Bind(wx.EVT_BUTTON, self.onBUp, self.bUp)
		self.bDown =  wx.BitmapButton(self, wx.ID_ANY, self.images.pngDown, size=BTNDIM, style=wx.NO_BORDER)
		self.bDown.SetBackgroundColour("white")
		self.Bind(wx.EVT_BUTTON, self.onBDown, self.bDown)

		self.setSliderRange()

		self.cbSync = wx.CheckBox(self, wx.ID_ANY, "Sync with print")
		self.cbSync.SetValue(True)
		self.Bind(wx.EVT_CHECKBOX, self.onCbSync, self.cbSync)
		self.cbPrintedOnly = wx.CheckBox(self, wx.ID_ANY, "Only show printed")
		self.cbPrintedOnly.SetValue(self.settings.getSetting("showprintedonly", self.pname, "False"))
		self.Bind(wx.EVT_CHECKBOX, self.onCbShowPrintedOnly, self.cbPrintedOnly)
		self.cbShowPrev = wx.CheckBox(self, wx.ID_ANY, "Show previous layer")
		self.cbShowPrev.SetValue(self.settings.getSetting("showprevious", self.pname, "False"))
		self.Bind(wx.EVT_CHECKBOX, self.onCbShowPrev, self.cbShowPrev)
		self.cbShowMoves = wx.CheckBox(self, wx.ID_ANY, "Show moves")
		self.cbShowMoves.SetValue(self.settings.getSetting("showmoves", self.pname, "False"))
		self.Bind(wx.EVT_CHECKBOX, self.onCbShowMoves, self.cbShowMoves)
		self.cbShowRetr = wx.CheckBox(self, wx.ID_ANY, "Show retractions")
		self.cbShowRetr.SetValue(self.settings.getSetting("showretractions", self.pname, "False"))
		self.Bind(wx.EVT_CHECKBOX, self.onCbShowRetr, self.cbShowRetr)
		self.cbShowRevRetr = wx.CheckBox(self, wx.ID_ANY, "Show reverse retractions")
		self.cbShowRevRetr.SetValue(self.settings.getSetting("showrevretractions", self.pname, "False"))
		self.Bind(wx.EVT_CHECKBOX, self.onCbShowRevRetr, self.cbShowRevRetr)

		sznavgc = wx.BoxSizer(wx.VERTICAL)
		sznavgc.Add(self.bUp, 0, wx.LEFT, 12 if os.name == 'posix' else 25)
		sznavgc.Add(self.slLayer, 1, wx.GROW)
		sznavgc.Add(self.bDown, 0, wx.LEFT, 12 if os.name == 'posix' else 25)

		szgcf = wx.BoxSizer(wx.VERTICAL)
		szgcf.Add(self.gcf)
		szgcf.AddSpacer(5)
		szgcf.Add(self.stHeight, 0, wx.ALIGN_CENTER)
		szgcf.AddSpacer(5)
		szgcf.Add(self.stTime, 0, wx.ALIGN_CENTER)
		szgcf.AddSpacer(5)
		szgcf.Add(self.stFilament, 0, wx.ALIGN_CENTER)

		szgc = wx.BoxSizer(wx.HORIZONTAL)
		szgc.AddSpacer(15)
		szgc.Add(szgcf)
		if os.name == 'posix':
			szgc.AddSpacer(10)
		szgc.Add(sznavgc, 1, wx.GROW)
		szgc.AddSpacer(15)

		szopt1 = wx.BoxSizer(wx.VERTICAL)
		szopt1.Add(self.cbSync, 1, wx.EXPAND)
		szopt1.Add(self.cbPrintedOnly, 1, wx.EXPAND)

		szopt2 = wx.BoxSizer(wx.VERTICAL)
		szopt2.Add(self.cbShowPrev, 1, wx.EXPAND)
		szopt2.Add(self.cbShowMoves, 1, wx.EXPAND)

		szopt3 = wx.BoxSizer(wx.VERTICAL)
		szopt3.Add(self.cbShowRetr, 1, wx.EXPAND)
		szopt3.Add(self.cbShowRevRetr, 1, wx.EXPAND)

		szoptions = wx.BoxSizer(wx.HORIZONTAL)
		szoptions.AddSpacer(20)
		szoptions.Add(szopt1, 1, wx.EXPAND)
		szoptions.AddSpacer(5)
		szoptions.Add(szopt2, 1, wx.EXPAND)
		szoptions.AddSpacer(5)
		szoptions.Add(szopt3, 1, wx.EXPAND)
		szoptions.AddSpacer(10)

		sz = wx.BoxSizer(wx.VERTICAL)
		sz.AddSpacer(10)
		sz.Add(szgc)
		sz.AddSpacer(5)
		sz.Add(szoptions)
		sz.AddSpacer(10)

		self.showLayerInfo()

		self.SetSizer(sz)
		self.Fit()
		self.Layout()

	@staticmethod
	def formatTitle(pname, filenm):

		if filenm is None:
			return "%s - GCode Monitor - (no file loaded)" % pname
		else:
			return "%s - GCode monitor - %s" % (pname, filenm)

	def reloadGCode(self, gcode, filenm):
		self.filenm = filenm
		self.SetTitle(self.formatTitle(self.pname, filenm))
		self.gcode = gcode
		if self.gcode:
			self.sTotalTime = " / " + formatElapsed(self.gcode.getPrintTime())
			self.filament = self.gcode.getFilament()
		else:
			self.sTotalTime = ""
			self.filament = 0.0

		self.setSliderRange()

		# send new gcode to gcf
		self.printPosition = 0
		self.gcf.loadGCode(gcode)
		self.showLayerInfo()

	def setSliderRange(self):
		n = self.gcode.layerCount()
		self.slLayer.SetRange(1, 10 if n == 0 else n)
		self.slLayer.SetValue(1)
		self.slLayer.Enable(n != 0)
		self.bUp.Enable(n != 0)
		self.bDown.Enable(n != 0)
		self.layerCount = n

	def setPrintPosition(self, pos):
		if pos is None:
			return False, None, None
		self.printPosition = 0 if pos is None else pos
		self.gcf.setPrintPosition(self.printPosition)

		pLayer, lpct = self.gcode.findLayerByOffset(self.printPosition)
		cLayer = self.gcf.getCurrentLayerNum()

		if not self.followPrint:
			return cLayer != pLayer, pLayer, lpct
		
		if cLayer != pLayer:
			self.gcf.setLayer(pLayer)
			self.slLayer.SetValue(pLayer+1)
			self.showLayerInfo()
			return True, pLayer, lpct

		return False, pLayer, lpct

	def showLayerInfo(self):
		l = self.gcf.getCurrentLayer()
		if l is None:
			lblHt = ""
			lblTime = ""
			lblFilament = ""
		else:
			lx = self.gcf.getCurrentLayerNum()
			lblHt, lblTime, lblFilament = self.formatLayerInfo(l, lx)
			
		self.stHeight.SetLabel(lblHt)
		self.stTime.SetLabel(lblTime)
		self.stFilament.SetLabel(lblFilament)
		
	def formatLayerInfo(self, l, lx):
		sHt = "Height: {:.2f}  Layer: {:d} / {:d}".format(l.getHeight(), lx+1, self.layerCount)
		
		sTm = "  Print time: {:s}{:s}".format(formatElapsed(l.getLayerTime()), self.sTotalTime)
		o = l.getOffsets()
		if self.printPosition < o[0]:
			lyrs = self.gcode.getLayersBetweenOffsets(self.printPosition, o[0])
			untilTime = 0.0
			for ly in lyrs:
				untilTime += ly.getLayerTime()
			sTm += " ({:s} until)".format(formatElapsed(untilTime))

		sFi = "  Filament: "
		lf = l.getFilament()
		for i in range(self.nExtr):
			if i > 0:
				sFi += " / "
				
			if self.nExtr > 1:
				if i > 0:
					sFi += " - "
				sFi += "{:d}: ".format(i)
			sFi += "{:.2f}mm ({:.2f}cm3) / {:.2f}mm".format(lf[i][0], lf[i][1], self.filament[i][0])
			
		return sHt, sTm, sFi

	def onSlLayer(self, _):
		self.followPrintOff()
		v = self.slLayer.GetValue()-1
		self.gcf.setLayer(v)
		self.showLayerInfo()

	def onBUp(self, _):
		v = self.slLayer.GetValue()
		if v < self.gcode.layerCount():
			self.followPrintOff()
			v += 1
			self.slLayer.SetValue(v)
			self.gcf.setLayer(v-1)
			self.showLayerInfo()

	def onBDown(self, _):
		v = self.slLayer.GetValue()
		if v > 1:
			self.followPrintOff()
			v -= 1
			self.slLayer.SetValue(v)
			self.gcf.setLayer(v-1)
			self.showLayerInfo()

	def followPrintOff(self):
		if self.followPrint:
			self.cbSync.SetValue(False)
			self.followPrint = False
			self.gcf.setFollowPrint(False)

	def onCbSync(self, _):
		self.followPrint = self.cbSync.GetValue()
		self.gcf.setFollowPrint(self.followPrint)

	def onCbShowPrintedOnly(self, _):
		v = self.cbPrintedOnly.GetValue()
		self.settings.setSetting("showprintedonly", str(v), self.pname)
		self.gcf.setShowPrintedOnly(v)

	def onCbShowPrev(self, _):
		v = self.cbShowPrev.GetValue()
		self.settings.setSetting("showprevious", str(v), self.pname)
		self.gcf.setShowPrevious(v)

	def onCbShowMoves(self, _):
		v = self.cbShowMoves.GetValue()
		self.settings.setSetting("showmoves", str(v), self.pname)
		self.gcf.setShowMoves(v)

	def onCbShowRetr(self, _):
		v = self.cbShowRetr.GetValue()
		self.settings.setSetting("showretractions", str(v), self.pname)
		self.gcf.setShowRetractions(v)

	def onCbShowRevRetr(self, _):
		v = self.cbShowRevRetr.GetValue()
		self.settings.setSetting("showrevretractions", str(v), self.pname)
		self.gcf.setShowRevRetractions(v)

	def onClose(self, _):
		self.exitDlg()