diff --git a/Cura/gui/projectPlanner.py b/Cura/gui/projectPlanner.py index 14002802..4de8b778 100644 --- a/Cura/gui/projectPlanner.py +++ b/Cura/gui/projectPlanner.py @@ -1,7 +1,7 @@ from __future__ import absolute_import import __init__ -import wx, os, platform, types, webbrowser, math, subprocess +import wx, os, platform, types, webbrowser, math, subprocess, threading, time import ConfigParser from wx import glcanvas @@ -24,10 +24,13 @@ from util import util3d from util import stl from util import sliceRun +class Action(): + pass + class projectPlanner(wx.Frame): "Main user interface window" def __init__(self): - super(projectPlanner, self).__init__(None, title='Cura') + super(projectPlanner, self).__init__(None, title='Cura - Project Planner') self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE)) wx.EVT_CLOSE(self, self.OnClose) @@ -68,9 +71,9 @@ class projectPlanner(wx.Frame): self.remButton = wx.Button(self, -1, "Remove") self.sliceButton = wx.Button(self, -1, "Slice") - sizer.Add(self.toolbar, (0,0), span=(1,1), flag=wx.EXPAND) - sizer.Add(self.preview, (1,0), span=(3,1), flag=wx.EXPAND) - sizer.Add(self.listbox, (0,1), span=(2,2), flag=wx.EXPAND) + sizer.Add(self.toolbar, (0,0), span=(1,3), flag=wx.EXPAND) + sizer.Add(self.preview, (1,0), span=(4,1), flag=wx.EXPAND) + sizer.Add(self.listbox, (1,1), span=(1,2), flag=wx.EXPAND) sizer.Add(self.addButton, (2,1), span=(1,1)) sizer.Add(self.remButton, (2,2), span=(1,1)) sizer.Add(self.sliceButton, (3,1), span=(1,1)) @@ -81,7 +84,25 @@ class projectPlanner(wx.Frame): self.remButton.Bind(wx.EVT_BUTTON, self.OnRemModel) self.sliceButton.Bind(wx.EVT_BUTTON, self.OnSlice) self.listbox.Bind(wx.EVT_LISTBOX, self.OnListSelect) + + panel = wx.Panel(self, -1) + sizer.Add(panel, (4,1), span=(1,2)) + sizer = wx.GridBagSizer(2,2) + panel.SetSizer(sizer) + + self.scaleCtrl = wx.TextCtrl(panel, -1, '') + self.rotateCtrl = wx.SpinCtrl(panel, -1, '', size=(21*4,21), style=wx.SP_ARROW_KEYS) + self.rotateCtrl.SetRange(0, 360) + + sizer.Add(wx.StaticText(panel, -1, 'Scale'), (0,0), flag=wx.ALIGN_CENTER_VERTICAL) + sizer.Add(self.scaleCtrl, (0,1), flag=wx.ALIGN_BOTTOM|wx.EXPAND) + sizer.Add(wx.StaticText(panel, -1, 'Rotate'), (1,0), flag=wx.ALIGN_CENTER_VERTICAL) + sizer.Add(self.rotateCtrl, (1,1), flag=wx.ALIGN_BOTTOM|wx.EXPAND) + + self.scaleCtrl.Bind(wx.EVT_TEXT, self.OnScaleChange) + self.rotateCtrl.Bind(wx.EVT_SPINCTRL, self.OnRotateChange) + self.SetSize((800,600)) def OnClose(self, e): @@ -162,7 +183,11 @@ class projectPlanner(wx.Frame): self.preview.Refresh() def OnListSelect(self, e): + if self.listbox.GetSelection() == -1: + return self.selection = self.list[self.listbox.GetSelection()] + self.scaleCtrl.SetValue(str(self.selection.scale)) + self.rotateCtrl.SetValue(int(self.selection.rotate)) self.preview.Refresh() def OnAddModel(self, e): @@ -201,6 +226,8 @@ class projectPlanner(wx.Frame): put('add_start_end_gcode', 'False') put('gcode_extension', 'project_tmp') + clearZ = 0 + actionList = [] for item in self.list: put('machine_center_x', item.centerX) put('machine_center_y', item.centerY) @@ -212,7 +239,14 @@ class projectPlanner(wx.Frame): put('swap_xz', item.swapXZ) put('swap_yz', item.swapYZ) - item.sliceCmd = sliceRun.getSliceCommand(item.filename) + action = Action() + action.sliceCmd = sliceRun.getSliceCommand(item.filename) + action.centerX = item.centerX + action.centerY = item.centerY + action.filename = item.filename + clearZ = max(clearZ, item.getMaximum().z * item.scale) + action.clearZ = clearZ + actionList.append(action) #Restore the old profile. profile.loadGlobalProfileFromString(oldProfile) @@ -222,44 +256,12 @@ class projectPlanner(wx.Frame): if dlg.ShowModal() != wx.ID_OK: dlg.Destroy() return - resultFile = open(dlg.GetPath(), "w") + resultFilename = dlg.GetPath() dlg.Destroy() - i = 1 - maxZ = 0 - prevItem = None - for item in self.list: - subprocess.call(item.sliceCmd) - - maxZ = max(maxZ, item.getMaximum().z * item.scale) - put('machine_center_x', item.centerX) - put('machine_center_y', item.centerY) - put('clear_z', maxZ) - - if prevItem == None: - resultFile.write(';TYPE:CUSTOM\n') - resultFile.write(profile.getAlterationFileContents('start.gcode')) - else: - #reset the extrusion length, and move to the next object center. - resultFile.write(';TYPE:CUSTOM\n') - resultFile.write(profile.getAlterationFileContents('nextobject.gcode')) - resultFile.write(';PRINTNR:%d\n' % (i)) - profile.loadGlobalProfileFromString(oldProfile) - - f = open(item.filename[: item.filename.rfind('.')] + "_export.project_tmp", "r") - data = f.read(4096) - while data != '': - resultFile.write(data) - data = f.read(4096) - f.close() - os.remove(item.filename[: item.filename.rfind('.')] + "_export.project_tmp") - i += 1 - - prevItem = item - - resultFile.write(';TYPE:CUSTOM\n') - resultFile.write(profile.getAlterationFileContents('end.gcode')) - resultFile.close() + pspw = ProjectSliceProgressWindow(actionList, resultFilename) + pspw.Centre() + pspw.Show(True) def loadModelFile(self, item): item.load(item.filename) @@ -283,12 +285,29 @@ class projectPlanner(wx.Frame): self.updateModelTransform(item) + item.centerX = -item.getMinimum().x + 5 + item.centerY = -item.getMinimum().y + 5 + + def OnScaleChange(self, e): + if self.selection == None: + return + try: + self.selection.scale = float(self.scaleCtrl.GetValue()) + except ValueError: + self.selection.scale = 1.0 + self.preview.Refresh() + + def OnRotateChange(self, e): + if self.selection == None: + return + self.selection.rotate = float(self.rotateCtrl.GetValue()) + self.updateModelTransform(self.selection) + def updateModelTransform(self, item): - scale = item.scale - rotate = item.rotate - scaleX = scale - scaleY = scale - scaleZ = scale + rotate = item.rotate / 180.0 * math.pi + scaleX = 1.0 + scaleY = 1.0 + scaleZ = 1.0 if item.flipX: scaleX = -scaleX if item.flipY: @@ -495,15 +514,15 @@ class PreviewGLCanvas(glcanvas.GLCanvas): glDisable(GL_LIGHTING) if self.parent.selection == item: - if item.gotHit: - glColor3f(1.0,0.3,0.0) - else: - glColor3f(1.0,1.0,0.0) - opengl.DrawBox(vMin, vMax) if item.gotHit: glColor3f(1.0,0.0,0.3) else: glColor3f(1.0,0.0,1.0) + opengl.DrawBox(vMin, vMax) + if item.gotHit: + glColor3f(1.0,0.3,0.0) + else: + glColor3f(1.0,1.0,0.0) opengl.DrawBox(vMinHead, vMaxHead) elif seenSelected: if item.gotHit: @@ -522,6 +541,129 @@ class PreviewGLCanvas(glcanvas.GLCanvas): glFlush() +class ProjectSliceProgressWindow(wx.Frame): + def __init__(self, actionList, resultFilename): + super(ProjectSliceProgressWindow, self).__init__(None, title='Cura') + self.actionList = actionList + self.resultFilename = resultFilename + self.abort = False + self.prevStep = 'start' + self.totalDoneFactor = 0.0 + self.startTime = time.time() + + #How long does each step take compared to the others. This is used to make a better scaled progress bar, and guess time left. + # TODO: Duplicate with sliceProgressPanel, move to sliceRun. + self.sliceStepTimeFactor = { + 'start': 3.3713991642, + 'slice': 15.4984838963, + 'preface': 5.17178297043, + 'inset': 116.362634182, + 'fill': 215.702672005, + 'multiply': 21.9536788464, + 'speed': 12.759510994, + 'raft': 31.4580039978, + 'skirt': 19.3436040878, + 'skin': 1.0, + 'joris': 1.0, + 'comb': 23.7805759907, + 'cool': 27.148763895, + 'dimension': 90.4914340973 + } + self.totalRunTimeFactor = 0 + for v in self.sliceStepTimeFactor.itervalues(): + self.totalRunTimeFactor += v + + self.sizer = wx.GridBagSizer(2, 2) + self.statusText = wx.StaticText(self, -1, "Building: %s" % (resultFilename)) + self.progressGauge = wx.Gauge(self, -1) + self.progressGauge.SetRange(10000) + self.progressGauge2 = wx.Gauge(self, -1) + self.progressGauge2.SetRange(len(self.actionList)) + self.abortButton = wx.Button(self, -1, "Abort") + self.sizer.Add(self.statusText, (0,0), flag=wx.ALIGN_CENTER) + self.sizer.Add(self.progressGauge, (1, 0), flag=wx.EXPAND) + self.sizer.Add(self.progressGauge2, (2, 0), flag=wx.EXPAND) + self.sizer.Add(self.abortButton, (3,0), flag=wx.ALIGN_CENTER) + + self.Bind(wx.EVT_BUTTON, self.OnAbort, self.abortButton) + self.SetSizer(self.sizer) + self.Layout() + self.Fit() + + threading.Thread(target=self.OnRun).start() + + def OnAbort(self, e): + self.abort = True + + def SetProgress(self, stepName, layer, maxLayer): + if self.prevStep != stepName: + self.totalDoneFactor += self.sliceStepTimeFactor[self.prevStep] + newTime = time.time() + #print "#####" + str(newTime-self.startTime) + " " + self.prevStep + " -> " + stepName + self.startTime = newTime + self.prevStep = stepName + + progresValue = ((self.totalDoneFactor + self.sliceStepTimeFactor[stepName] * layer / maxLayer) / self.totalRunTimeFactor) * 10000 + self.progressGauge.SetValue(int(progresValue)) + self.statusText.SetLabel(stepName + " [" + str(layer) + "/" + str(maxLayer) + "]") + + def OnRun(self): + resultFile = open(self.resultFilename, "w") + put = profile.putProfileSetting + for action in self.actionList: + p = subprocess.Popen(action.sliceCmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + line = p.stdout.readline() + + maxValue = 1 + self.progressLog = [] + while(len(line) > 0): + line = line.rstrip() + if line[0:9] == "Progress[" and line[-1:] == "]": + progress = line[9:-1].split(":") + if len(progress) > 2: + maxValue = int(progress[2]) + wx.CallAfter(self.SetProgress, progress[0], int(progress[1]), maxValue) + else: + print line + self.progressLog.append(line) + wx.CallAfter(self.statusText.SetLabel, line) + if self.abort: + p.terminate() + wx.CallAfter(self.statusText.SetLabel, "Aborted by user.") + return + line = p.stdout.readline() + self.returnCode = p.wait() + + oldProfile = profile.getGlobalProfileString() + put('machine_center_x', action.centerX) + put('machine_center_y', action.centerY) + put('clear_z', action.clearZ) + + if action == self.actionList[0]: + resultFile.write(';TYPE:CUSTOM\n') + resultFile.write(profile.getAlterationFileContents('start.gcode')) + else: + #reset the extrusion length, and move to the next object center. + resultFile.write(';TYPE:CUSTOM\n') + resultFile.write(profile.getAlterationFileContents('nextobject.gcode')) + resultFile.write(';PRINTNR:%d\n' % self.actionList.index(action)) + profile.loadGlobalProfileFromString(oldProfile) + + f = open(action.filename[: action.filename.rfind('.')] + "_export.project_tmp", "r") + data = f.read(4096) + while data != '': + resultFile.write(data) + data = f.read(4096) + f.close() + os.remove(action.filename[: action.filename.rfind('.')] + "_export.project_tmp") + + wx.CallAfter(self.progressGauge.SetValue, 10000) + wx.CallAfter(self.progressGauge2.SetValue, self.actionList.index(action) + 1) + + resultFile.write(';TYPE:CUSTOM\n') + resultFile.write(profile.getAlterationFileContents('end.gcode')) + resultFile.close() + def main(): app = wx.App(False) projectPlanner().Show(True) diff --git a/Cura/util/profile.py b/Cura/util/profile.py index 44d85994..55fc7f18 100644 --- a/Cura/util/profile.py +++ b/Cura/util/profile.py @@ -115,17 +115,16 @@ def getGlobalProfileString(): return '#'.join(ret) def getProfileSetting(name): - if name in profileDefaultSettings: - default = profileDefaultSettings[name] - else: - print "Missing default setting for: '" + name + "'" - profileDefaultSettings[name] = '' - default = '' - #Check if we have a configuration file loaded, else load the default. if not globals().has_key('globalProfileParser'): loadGlobalProfile(getDefaultProfilePath()) if not globalProfileParser.has_option('profile', name): + if name in profileDefaultSettings: + default = profileDefaultSettings[name] + else: + print "Missing default setting for: '" + name + "'" + profileDefaultSettings[name] = '' + default = '' if not globalProfileParser.has_section('profile'): globalProfileParser.add_section('profile') globalProfileParser.set('profile', name, str(default)) @@ -154,18 +153,17 @@ def getPreferencePath(): return os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "../preferences.ini")) def getPreference(name): - if name in preferencesDefaultSettings: - default = preferencesDefaultSettings[name] - else: - print "Missing default setting for: '" + name + "'" - preferencesDefaultSettings[name] = '' - default = '' - global globalPreferenceParser if globalPreferenceParser == None: globalPreferenceParser = ConfigParser.ConfigParser() globalPreferenceParser.read(getPreferencePath()) if not globalPreferenceParser.has_option('preference', name): + if name in preferencesDefaultSettings: + default = preferencesDefaultSettings[name] + else: + print "Missing default setting for: '" + name + "'" + preferencesDefaultSettings[name] = '' + default = '' if not globalPreferenceParser.has_section('preference'): globalPreferenceParser.add_section('preference') globalPreferenceParser.set('preference', name, str(default))