diff --git a/Cura/gui/gcodeTextArea.py b/Cura/gui/gcodeTextArea.py index 3c023325..68d05249 100644 --- a/Cura/gui/gcodeTextArea.py +++ b/Cura/gui/gcodeTextArea.py @@ -19,6 +19,8 @@ class GcodeTextArea(wx.stc.StyledTextCtrl): self.IndicatorSetForeground(0, "#0000FF") self.IndicatorSetStyle(1, wx.stc.STC_INDIC_SQUIGGLE) self.IndicatorSetForeground(1, "#FF0000") + self.SetWrapMode(wx.stc.STC_WRAP_NONE) + self.SetScrollWidth(1000) #GCodes and MCodes as supported by Marlin #GCode 21 is not really supported by Marlin, but we still do not report it as error as it's often used. diff --git a/Cura/gui/projectPlanner.py b/Cura/gui/projectPlanner.py index 5f77025e..b24721e5 100644 --- a/Cura/gui/projectPlanner.py +++ b/Cura/gui/projectPlanner.py @@ -25,9 +25,108 @@ from util import stl from util import sliceRun from util import gcodeInterpreter -class Action(): +class Action(object): pass +class ProjectObject(stl.stlModel): + def __init__(self, filename): + super(ProjectObject, self).__init__() + + self.load(filename) + + self.filename = filename + self.scale = 1.0 + self.rotate = 0.0 + self.flipX = False + self.flipY = False + self.flipZ = False + self.swapXZ = False + self.swapYZ = False + self.extruder = 0 + + self.modelDisplayList = None + self.modelDirty = False + + self.origonalVertexes = list(self.vertexes) + for i in xrange(0, len(self.origonalVertexes)): + self.origonalVertexes[i] = self.origonalVertexes[i].copy() + self.getMinimumZ() + + self.centerX = -self.getMinimum().x + 5 + self.centerY = -self.getMinimum().y + 5 + + self.updateModelTransform() + + self.centerX = -self.getMinimum().x + 5 + self.centerY = -self.getMinimum().y + 5 + + def updateModelTransform(self): + rotate = self.rotate / 180.0 * math.pi + scaleX = 1.0 + scaleY = 1.0 + scaleZ = 1.0 + if self.flipX: + scaleX = -scaleX + if self.flipY: + scaleY = -scaleY + if self.flipZ: + scaleZ = -scaleZ + swapXZ = self.swapXZ + swapYZ = self.swapYZ + mat00 = math.cos(rotate) * scaleX + mat01 =-math.sin(rotate) * scaleY + mat10 = math.sin(rotate) * scaleX + mat11 = math.cos(rotate) * scaleY + + for i in xrange(0, len(self.origonalVertexes)): + x = self.origonalVertexes[i].x + y = self.origonalVertexes[i].y + z = self.origonalVertexes[i].z + if swapXZ: + x, z = z, x + if swapYZ: + y, z = z, y + self.vertexes[i].x = x * mat00 + y * mat01 + self.vertexes[i].y = x * mat10 + y * mat11 + self.vertexes[i].z = z * scaleZ + + for face in self.faces: + v1 = face.v[0] + v2 = face.v[1] + v3 = face.v[2] + face.normal = (v2 - v1).cross(v3 - v1) + face.normal.normalize() + + minZ = self.getMinimumZ() + minV = self.getMinimum() + maxV = self.getMaximum() + for v in self.vertexes: + v.z -= minZ + v.x -= minV.x + (maxV.x - minV.x) / 2 + v.y -= minV.y + (maxV.y - minV.y) / 2 + self.getMinimumZ() + self.modelDirty = True + + def clone(self): + p = ProjectObject(self.filename) + + p.centerX = self.centerX + 5 + p.centerY = self.centerY + 5 + + p.filename = self.filename + p.scale = self.scale + p.rotate = self.rotate + p.flipX = self.flipX + p.flipY = self.flipY + p.flipZ = self.flipZ + p.swapXZ = self.swapXZ + p.swapYZ = self.swapYZ + p.extruder = self.extruder + + p.updateModelTransform() + + return p + class projectPlanner(wx.Frame): "Main user interface window" def __init__(self): @@ -62,6 +161,14 @@ class projectPlanner(wx.Frame): toolbarUtil.NormalButton(self.toolbar, self.OnQuit, 'exit.png', 'Close project planner') self.toolbar.Realize() + + self.toolbar2 = toolbarUtil.Toolbar(self) + toolbarUtil.NormalButton(self.toolbar2, self.OnAddModel, 'object-add.png', 'Add model') + toolbarUtil.NormalButton(self.toolbar2, self.OnRemModel, 'object-remove.png', 'Remove model') + toolbarUtil.NormalButton(self.toolbar2, self.OnMoveUp, 'move-up.png', 'Move model up in print list') + toolbarUtil.NormalButton(self.toolbar2, self.OnMoveDown, 'move-down.png', 'Move model down in print list') + toolbarUtil.NormalButton(self.toolbar2, self.OnCopy, 'copy.png', 'Make a copy of the current selected object') + self.toolbar2.Realize() sizer = wx.GridBagSizer(2,2) self.SetSizer(sizer) @@ -72,7 +179,8 @@ class projectPlanner(wx.Frame): self.sliceButton = wx.Button(self, -1, "Slice") self.autoPlaceButton = wx.Button(self, -1, "Auto Place") - sizer.Add(self.toolbar, (0,0), span=(1,3), flag=wx.EXPAND) + sizer.Add(self.toolbar, (0,0), span=(1,1), flag=wx.EXPAND) + sizer.Add(self.toolbar2, (0,1), span=(1,2), 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)) @@ -156,9 +264,7 @@ class projectPlanner(wx.Frame): while cp.has_section('model_%d' % (i)): section = 'model_%d' % (i) - item = stl.stlModel() - item.filename = unicode(cp.get(section, 'filename'), "utf-8") - self.loadModelFile(item) + item = ProjectObject(unicode(cp.get(section, 'filename'), "utf-8")) item.centerX = float(cp.get(section, 'centerX')) item.centerY = float(cp.get(section, 'centerY')) item.scale = float(cp.get(section, 'scale')) @@ -170,6 +276,7 @@ class projectPlanner(wx.Frame): item.swapYZ = cp.get(section, 'swapYZ') == 'True' if cp.has_option(section, 'extruder'): item.extuder = int(cp.get(section, 'extruder'))-1 + item.updateModelTransform() i += 1 self.list.append(item) @@ -177,6 +284,7 @@ class projectPlanner(wx.Frame): self.listbox.SetSelection(len(self.list)-1) self.OnListSelect(None) + self.preview.Refresh() dlg.Destroy() @@ -209,32 +317,67 @@ class projectPlanner(wx.Frame): dlg.SetWildcard("STL files (*.stl)|*.stl;*.STL") if dlg.ShowModal() == wx.ID_OK: for filename in dlg.GetPaths(): - item = stl.stlModel() - item.filename=filename + item = ProjectObject(filename) profile.putPreference('lastFile', item.filename) - if not(os.path.exists(item.filename)): - return - self.loadModelFile(item) self.list.append(item) - self.listbox.AppendAndEnsureVisible(os.path.split(item.filename)[1]) - self.listbox.SetSelection(len(self.list)-1) - self.OnListSelect(None) + self.selection = item + self._updateListbox() + self.preview.Refresh() dlg.Destroy() def OnRemModel(self, e): if self.selection == None: return self.list.remove(self.selection) - i = self.listbox.GetSelection() - self.listbox.Delete(i) - if len(self.list) > i: - self.listbox.SetSelection(i) - elif len(self.list) > 0: - self.listbox.SetSelection(len(self.list) - 1) - self.selection = None - self.OnListSelect(None) + self._updateListbox() self.preview.Refresh() + def OnMoveUp(self, e): + if self.selection == None: + return + i = self.listbox.GetSelection() + if i == 0: + return + self.list.remove(self.selection) + self.list.insert(i-1, self.selection) + self._updateListbox() + self.preview.Refresh() + + def OnMoveDown(self, e): + if self.selection == None: + return + i = self.listbox.GetSelection() + if i == len(self.list) - 1: + return + self.list.remove(self.selection) + self.list.insert(i+1, self.selection) + self._updateListbox() + self.preview.Refresh() + + def OnCopy(self, e): + if self.selection == None: + return + + item = self.selection.clone() + self.list.append(item) + self.selection = item + + self._updateListbox() + self.preview.Refresh() + + def _updateListbox(self): + self.listbox.Clear() + for item in self.list: + self.listbox.AppendAndEnsureVisible(os.path.split(item.filename)[1]) + if self.selection in self.list: + self.listbox.SetSelection(self.list.index(self.selection)) + elif len(self.list) > 0: + self.selection = self.list[0] + self.listbox.SetSelection(0) + else: + self.selection = None + self.listbox.SetSelection(-1) + def OnAutoPlace(self, e): bestAllowedSize = int(self.machineSize.y) bestArea = self._doAutoPlace(bestAllowedSize) @@ -333,32 +476,6 @@ class projectPlanner(wx.Frame): pspw.Centre() pspw.Show(True) - def loadModelFile(self, item): - item.load(item.filename) - item.origonalVertexes = list(item.vertexes) - for i in xrange(0, len(item.origonalVertexes)): - item.origonalVertexes[i] = item.origonalVertexes[i].copy() - item.getMinimumZ() - - item.centerX = -item.getMinimum().x + 5 - item.centerY = -item.getMinimum().y + 5 - item.scale = 1.0 - item.rotate = 0.0 - item.flipX = False - item.flipY = False - item.flipZ = False - item.swapXZ = False - item.swapYZ = False - item.extruder = 0 - - item.modelDisplayList = None - item.modelDirty = False - - self.updateModelTransform(item) - - item.centerX = -item.getMinimum().x + 5 - item.centerY = -item.getMinimum().y + 5 - def OnScaleChange(self, e): if self.selection == None: return @@ -372,7 +489,8 @@ class projectPlanner(wx.Frame): if self.selection == None: return self.selection.rotate = float(self.rotateCtrl.GetValue()) - self.updateModelTransform(self.selection) + self.selection.updateModelTransform() + self.preview.Refresh() def OnExtruderChange(self, e): if self.selection == None: @@ -380,57 +498,6 @@ class projectPlanner(wx.Frame): self.selection.extruder = int(self.extruderCtrl.GetValue()) - 1 self.preview.Refresh() - def updateModelTransform(self, item): - rotate = item.rotate / 180.0 * math.pi - scaleX = 1.0 - scaleY = 1.0 - scaleZ = 1.0 - if item.flipX: - scaleX = -scaleX - if item.flipY: - scaleY = -scaleY - if item.flipZ: - scaleZ = -scaleZ - swapXZ = item.swapXZ - swapYZ = item.swapYZ - mat00 = math.cos(rotate) * scaleX - mat01 =-math.sin(rotate) * scaleY - mat10 = math.sin(rotate) * scaleX - mat11 = math.cos(rotate) * scaleY - - for i in xrange(0, len(item.origonalVertexes)): - x = item.origonalVertexes[i].x - y = item.origonalVertexes[i].y - z = item.origonalVertexes[i].z - if swapXZ: - x, z = z, x - if swapYZ: - y, z = z, y - item.vertexes[i].x = x * mat00 + y * mat01 - item.vertexes[i].y = x * mat10 + y * mat11 - item.vertexes[i].z = z * scaleZ - - for face in item.faces: - v1 = face.v[0] - v2 = face.v[1] - v3 = face.v[2] - face.normal = (v2 - v1).cross(v3 - v1) - face.normal.normalize() - - self.moveModel(item) - - def moveModel(self, item): - minZ = item.getMinimumZ() - min = item.getMinimum() - max = item.getMaximum() - for v in item.vertexes: - v.z -= minZ - v.x -= min.x + (max.x - min.x) / 2 - v.y -= min.y + (max.y - min.y) / 2 - item.getMinimumZ() - item.modelDirty = True - self.preview.Refresh() - class PreviewGLCanvas(glcanvas.GLCanvas): def __init__(self, parent): attribList = (glcanvas.WX_GL_RGBA, glcanvas.WX_GL_DOUBLEBUFFER, glcanvas.WX_GL_DEPTH_SIZE, 24, glcanvas.WX_GL_STENCIL_SIZE, 8) @@ -637,28 +704,6 @@ class ProjectSliceProgressWindow(wx.Frame): self.startTime = time.time() self.sliceStartTime = 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) @@ -690,13 +735,13 @@ class ProjectSliceProgressWindow(wx.Frame): def SetProgress(self, stepName, layer, maxLayer): if self.prevStep != stepName: - self.totalDoneFactor += self.sliceStepTimeFactor[self.prevStep] + self.totalDoneFactor += sliceRun.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 + progresValue = ((self.totalDoneFactor + sliceRun.sliceStepTimeFactor[stepName] * layer / maxLayer) / sliceRun.totalRunTimeFactor) * 10000 self.progressGauge.SetValue(int(progresValue)) self.statusText.SetLabel(stepName + " [" + str(layer) + "/" + str(maxLayer) + "]") diff --git a/Cura/gui/sliceProgessPanel.py b/Cura/gui/sliceProgessPanel.py index 4eed4712..9e49e5b3 100644 --- a/Cura/gui/sliceProgessPanel.py +++ b/Cura/gui/sliceProgessPanel.py @@ -15,27 +15,6 @@ class sliceProgessPanel(wx.Panel): self.filelist = filelist self.abort = False - #How long does each step take compared to the others. This is used to make a better scaled progress bar, and guess time left. - 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 - box = wx.StaticBox(self, -1, filelist[0]) self.sizer = wx.StaticBoxSizer(box, wx.HORIZONTAL) @@ -125,13 +104,13 @@ class sliceProgessPanel(wx.Panel): def SetProgress(self, stepName, layer, maxLayer): if self.prevStep != stepName: - self.totalDoneFactor += self.sliceStepTimeFactor[self.prevStep] + self.totalDoneFactor += sliceRun.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 + progresValue = ((self.totalDoneFactor + sliceRun.sliceStepTimeFactor[stepName] * layer / maxLayer) / sliceRun.totalRunTimeFactor) * 10000 self.progressGauge.SetValue(int(progresValue)) self.statusText.SetLabel(stepName + " [" + str(layer) + "/" + str(maxLayer) + "]") diff --git a/Cura/gui/toolbarUtil.py b/Cura/gui/toolbarUtil.py index eba0a0b0..1ec088ef 100644 --- a/Cura/gui/toolbarUtil.py +++ b/Cura/gui/toolbarUtil.py @@ -180,6 +180,7 @@ class NormalButton(buttons.GenBitmapButton): super(NormalButton, self).__init__(parent, id, self.bitmap, size=size) self.helpText = helpText + self.callback = callback self.SetBezelWidth(1) self.SetUseFocusIndicator(False) @@ -187,10 +188,14 @@ class NormalButton(buttons.GenBitmapButton): self.Bind(wx.EVT_ENTER_WINDOW, self.OnMouseEnter) self.Bind(wx.EVT_LEAVE_WINDOW, self.OnMouseLeave) - self.Bind(wx.EVT_BUTTON, callback) + self.Bind(wx.EVT_BUTTON, self.OnButton) parent.AddControl(self) + def OnButton(self, event): + self.GetParent().OnPopupHide(event) + self.callback(event) + def OnMouseEnter(self, event): self.GetParent().OnPopupDisplay(event) event.Skip() diff --git a/Cura/gui/validators.py b/Cura/gui/validators.py index b1da7783..f29e8d1e 100644 --- a/Cura/gui/validators.py +++ b/Cura/gui/validators.py @@ -11,7 +11,7 @@ SUCCESS = 0 WARNING = 1 ERROR = 2 -class validFloat(): +class validFloat(object): def __init__(self, setting, minValue = None, maxValue = None): self.setting = setting self.setting.validators.append(self) @@ -29,7 +29,7 @@ class validFloat(): except (ValueError, SyntaxError): return ERROR, '"' + str(self.setting.GetValue()) + '" is not a valid number or expression' -class validInt(): +class validInt(object): def __init__(self, setting, minValue = None, maxValue = None): self.setting = setting self.setting.validators.append(self) @@ -47,7 +47,7 @@ class validInt(): except (ValueError, SyntaxError): return ERROR, '"' + str(self.setting.GetValue()) + '" is not a valid whole number or expression' -class warningAbove(): +class warningAbove(object): def __init__(self, setting, minValueForWarning, warningMessage): self.setting = setting self.setting.validators.append(self) @@ -68,7 +68,7 @@ class warningAbove(): #We already have an error by the int/float validator in this case. return SUCCESS, '' -class wallThicknessValidator(): +class wallThicknessValidator(object): def __init__(self, setting): self.setting = setting self.setting.validators.append(self) @@ -94,7 +94,7 @@ class wallThicknessValidator(): #We already have an error by the int/float validator in this case. return SUCCESS, '' -class printSpeedValidator(): +class printSpeedValidator(object): def __init__(self, setting): self.setting = setting self.setting.validators.append(self) diff --git a/Cura/images/copy.png b/Cura/images/copy.png new file mode 100644 index 00000000..5cdeb5fc Binary files /dev/null and b/Cura/images/copy.png differ diff --git a/Cura/images/move-down.png b/Cura/images/move-down.png new file mode 100644 index 00000000..5891219e Binary files /dev/null and b/Cura/images/move-down.png differ diff --git a/Cura/images/move-up.png b/Cura/images/move-up.png new file mode 100644 index 00000000..b3d9cef9 Binary files /dev/null and b/Cura/images/move-up.png differ diff --git a/Cura/images/object-add.png b/Cura/images/object-add.png new file mode 100644 index 00000000..1b362dab Binary files /dev/null and b/Cura/images/object-add.png differ diff --git a/Cura/images/object-remove.png b/Cura/images/object-remove.png new file mode 100644 index 00000000..064e523c Binary files /dev/null and b/Cura/images/object-remove.png differ diff --git a/Cura/util/gcodeInterpreter.py b/Cura/util/gcodeInterpreter.py index 3af69bb4..5714972c 100644 --- a/Cura/util/gcodeInterpreter.py +++ b/Cura/util/gcodeInterpreter.py @@ -9,13 +9,13 @@ import os from util import util3d from util import profile -class gcodePath(): +class gcodePath(object): def __init__(self, newType, pathType, startPoint): self.type = newType self.pathType = pathType self.list = [startPoint] -class gcode(): +class gcode(object): def __init__(self): self.regMatch = {} self.layerList = [] diff --git a/Cura/util/sliceRun.py b/Cura/util/sliceRun.py index edc304d3..4bc302a0 100644 --- a/Cura/util/sliceRun.py +++ b/Cura/util/sliceRun.py @@ -5,6 +5,28 @@ import platform, os, subprocess, sys from cura_sf.skeinforge_application.skeinforge_utilities import skeinforge_craft from util import profile +#How long does each step take compared to the others. This is used to make a better scaled progress bar, and guess time left. +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 +} + +totalRunTimeFactor = 0 +for v in sliceStepTimeFactor.itervalues(): + totalRunTimeFactor += v + def getPyPyExe(): "Return the path to the pypy executable if we can find it. Else return False" if platform.system() == "Windows": diff --git a/Cura/util/stl.py b/Cura/util/stl.py index 9b1323a2..bf7558b4 100644 --- a/Cura/util/stl.py +++ b/Cura/util/stl.py @@ -9,11 +9,11 @@ import struct from util import util3d -class stlFace(): +class stlFace(object): def __init__(self, v0, v1, v2): self.v = [v0, v1, v2] -class stlModel(): +class stlModel(object): def __init__(self): self.faces = [] self.vertexes = [] diff --git a/Cura/util/util3d.py b/Cura/util/util3d.py index 30b25159..d5411fc9 100644 --- a/Cura/util/util3d.py +++ b/Cura/util/util3d.py @@ -1,7 +1,7 @@ import math -class Vector3(): +class Vector3(object): def __init__(self, x=0.0, y=0.0, z=0.0): self.x = x self.y = y