diff --git a/Cura/cura_sf/fabmetheus_utilities/geometry/solids/triangle_mesh.py b/Cura/cura_sf/fabmetheus_utilities/geometry/solids/triangle_mesh.py index 32398305..550f4006 100644 --- a/Cura/cura_sf/fabmetheus_utilities/geometry/solids/triangle_mesh.py +++ b/Cura/cura_sf/fabmetheus_utilities/geometry/solids/triangle_mesh.py @@ -824,7 +824,7 @@ class TriangleMesh( group.Group ): halfHeight = 0.5 * self.layerHeight self.zoneArrangement = ZoneArrangement(self.layerHeight, self.getTransformedVertexes()) layerTop = self.cornerMaximum.z - halfHeight * 0.5 - z = self.cornerMinimum.z + halfHeight + z = halfHeight layerCount = int((layerTop - z) / self.layerHeight) + 1 while z < layerTop: getLoopLayerAppend(self.loopLayers, layerCount, z).loops = self.getLoopsFromMesh(self.zoneArrangement.getEmptyZ(z)) diff --git a/Cura/cura_sf/fabmetheus_utilities/settings.py b/Cura/cura_sf/fabmetheus_utilities/settings.py index 08ad268d..fa79a29c 100644 --- a/Cura/cura_sf/fabmetheus_utilities/settings.py +++ b/Cura/cura_sf/fabmetheus_utilities/settings.py @@ -113,6 +113,9 @@ def getProfileInformation(): 'SwapYZ': storedSetting("swap_yz"), 'Scale': storedSettingFloat("model_scale"), 'Rotate': storedSettingFloat("model_rotate_base"), + 'CenterX': storedSettingFloat("machine_center_x"), + 'CenterY': storedSettingFloat("machine_center_y"), + 'AlternativeCenterFile': storedSetting("alternative_center"), },'scale': { 'Activate_Scale': "False", 'XY_Plane_Scale_ratio': DEFSET, @@ -171,7 +174,7 @@ def getProfileInformation(): 'Surrounding_Angle_degrees': DEFSET, 'Thread_Sequence_Choice': storedSetting('sequence'), },'multiply': { - 'Activate_Multiply': "True", + 'Activate_Multiply': "False", 'Center_X_mm': storedSettingFloat("machine_center_x"), 'Center_Y_mm': storedSettingFloat("machine_center_y"), 'Number_of_Columns_integer': storedSetting('model_multiply_x'), diff --git a/Cura/cura_sf/skeinforge_application/skeinforge_plugins/craft_plugins/carve.py b/Cura/cura_sf/skeinforge_application/skeinforge_plugins/craft_plugins/carve.py index ae949b3d..b3ad845e 100644 --- a/Cura/cura_sf/skeinforge_application/skeinforge_plugins/craft_plugins/carve.py +++ b/Cura/cura_sf/skeinforge_application/skeinforge_plugins/craft_plugins/carve.py @@ -185,8 +185,11 @@ class CarveRepository: self.flipZ = settings.BooleanSetting().getFromValue('FlipZ', self, False) self.swapXZ = settings.BooleanSetting().getFromValue('SwapXZ', self, False) self.swapYZ = settings.BooleanSetting().getFromValue('SwapYZ', self, False) + self.centerX = settings.FloatSpin().getFromValue(0.0, 'CenterX', self, 1000.0, 0.0) + self.centerY = settings.FloatSpin().getFromValue(0.0, 'CenterY', self, 1000.0, 0.0) self.scale = settings.FloatSpin().getFromValue( 0.1, 'Scale', self, 10.0, 1.0 ) self.rotate = settings.FloatSpin().getFromValue( -180.0, 'Rotate', self, 180.0, 0.0 ) + self.alternativeCenter = settings.StringSetting().getFromValue('AlternativeCenterFile', self, '') def execute(self): @@ -219,16 +222,6 @@ class CarveSkein: mat10 = math.sin(rotate) * scaleX mat11 = math.cos(rotate) * scaleY - minZ = carving.getMinimumZ() - minSize = carving.getCarveCornerMinimum() - maxSize = carving.getCarveCornerMaximum() - for v in carving.vertexes: - v.z -= minZ - v.x -= minSize.x + (maxSize.x - minSize.x) / 2 - v.y -= minSize.y + (maxSize.y - minSize.y) / 2 - #v.x += self.machineCenter.x - #v.y += self.machineCenter.y - for i in xrange(0, len(carving.vertexes)): x = carving.vertexes[i].x y = carving.vertexes[i].y @@ -242,6 +235,34 @@ class CarveSkein: x * mat10 + y * mat11, z * scaleZ) + if repository.alternativeCenter.value != '': + carving2 = svg_writer.getCarving(repository.alternativeCenter.value) + for i in xrange(0, len(carving2.vertexes)): + x = carving2.vertexes[i].x + y = carving2.vertexes[i].y + z = carving2.vertexes[i].z + if swapXZ: + x, z = z, x + if swapYZ: + y, z = z, y + carving2.vertexes[i] = Vector3( + x * mat00 + y * mat01, + x * mat10 + y * mat11, + z * scaleZ) + minZ = carving2.getMinimumZ() + minSize = carving2.getCarveCornerMinimum() + maxSize = carving2.getCarveCornerMaximum() + else: + minZ = carving.getMinimumZ() + minSize = carving.getCarveCornerMinimum() + maxSize = carving.getCarveCornerMaximum() + for v in carving.vertexes: + v.z -= minZ + v.x -= minSize.x + (maxSize.x - minSize.x) / 2 + v.y -= minSize.y + (maxSize.y - minSize.y) / 2 + v.x += repository.centerX.value + v.y += repository.centerY.value + layerHeight = repository.layerHeight.value edgeWidth = repository.edgeWidth.value carving.setCarveLayerHeight(layerHeight) diff --git a/Cura/cura_sf/skeinforge_application/skeinforge_plugins/craft_plugins/cool.py b/Cura/cura_sf/skeinforge_application/skeinforge_plugins/craft_plugins/cool.py index 5a4f5491..eb363a8c 100644 --- a/Cura/cura_sf/skeinforge_application/skeinforge_plugins/craft_plugins/cool.py +++ b/Cura/cura_sf/skeinforge_application/skeinforge_plugins/craft_plugins/cool.py @@ -223,6 +223,8 @@ class CoolSkein: def addCoolTemperature(self, remainingOrbitTime): 'Parse a gcode line and add it to the cool skein.' + if self.repository.minimumLayerTime.value < 0.0001: + return layerCool = self.repository.maximumCool.value * remainingOrbitTime / self.repository.minimumLayerTime.value if self.isBridgeLayer: layerCool = max(self.repository.bridgeCool.value, layerCool) @@ -404,9 +406,10 @@ class CoolSkein: def setMultiplier(self, remainingOrbitTime): 'Set the feed and flow rate multiplier.' layerTimeActive = self.getLayerTimeActive() - self.multiplier = min(1.0, layerTimeActive / (remainingOrbitTime + layerTimeActive)) - - + if remainingOrbitTime + layerTimeActive > 0.00001: + self.multiplier = min(1.0, layerTimeActive / (remainingOrbitTime + layerTimeActive)) + else: + self.multiplier = 1.0 def main(): 'Display the cool dialog.' diff --git a/Cura/cura_sf/skeinforge_application/skeinforge_plugins/craft_plugins/skirt.py b/Cura/cura_sf/skeinforge_application/skeinforge_plugins/craft_plugins/skirt.py index 4a87d471..51f3549c 100644 --- a/Cura/cura_sf/skeinforge_application/skeinforge_plugins/craft_plugins/skirt.py +++ b/Cura/cura_sf/skeinforge_application/skeinforge_plugins/craft_plugins/skirt.py @@ -171,6 +171,8 @@ class SkirtSkein: def addSkirt(self, z): 'At skirt at z to gcode output.' + if len(self.outsetLoops) < 1 or len(self.outsetLoops[0]) < 1: + return self.setSkirtFeedFlowTemperature() self.distanceFeedRate.addLine('()') oldTemperature = self.oldTemperatureInput diff --git a/Cura/gui/alterationPanel.py b/Cura/gui/alterationPanel.py index c102f19b..5f5098d6 100644 --- a/Cura/gui/alterationPanel.py +++ b/Cura/gui/alterationPanel.py @@ -1,6 +1,7 @@ import wx import sys,math,threading,os +from gui import gcodeTextArea from util import profile class alterationPanel(wx.Panel): @@ -10,8 +11,9 @@ class alterationPanel(wx.Panel): self.alterationFileList = ['start.gcode', 'end.gcode', 'support_start.gcode', 'support_end.gcode', 'nextobject.gcode', 'replace.csv'] self.currentFile = None - self.textArea = wx.TextCtrl(self, style=wx.TE_MULTILINE|wx.TE_DONTWRAP|wx.TE_PROCESS_TAB) - self.textArea.SetFont(wx.Font(wx.SystemSettings.GetFont(wx.SYS_ANSI_VAR_FONT).GetPointSize(), wx.FONTFAMILY_MODERN, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) + #self.textArea = wx.TextCtrl(self, style=wx.TE_MULTILINE|wx.TE_DONTWRAP|wx.TE_PROCESS_TAB) + #self.textArea.SetFont(wx.Font(wx.SystemSettings.GetFont(wx.SYS_ANSI_VAR_FONT).GetPointSize(), wx.FONTFAMILY_MODERN, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) + self.textArea = gcodeTextArea.GcodeTextArea(self) self.list = wx.ListBox(self, choices=self.alterationFileList, style=wx.LB_SINGLE) self.list.SetSelection(0) self.Bind(wx.EVT_LISTBOX, self.OnSelect, self.list) diff --git a/Cura/gui/gcodeTextArea.py b/Cura/gui/gcodeTextArea.py new file mode 100644 index 00000000..68d05249 --- /dev/null +++ b/Cura/gui/gcodeTextArea.py @@ -0,0 +1,103 @@ +import wx, wx.stc +import sys,math,os + +from util import profile + +class GcodeTextArea(wx.stc.StyledTextCtrl): + def __init__(self, parent): + super(GcodeTextArea, self).__init__(parent) + + self.SetLexer(wx.stc.STC_LEX_CONTAINER) + self.Bind(wx.stc.EVT_STC_STYLENEEDED, self.OnStyle) + + fontSize = wx.SystemSettings.GetFont(wx.SYS_ANSI_VAR_FONT).GetPointSize() + fontName = wx.Font(wx.SystemSettings.GetFont(wx.SYS_ANSI_VAR_FONT).GetPointSize(), wx.FONTFAMILY_MODERN, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL).GetFaceName() + self.SetStyleBits(5) + self.StyleSetSpec(0, "face:%s,size:%d" % (fontName, fontSize)) + self.StyleSetSpec(1, "fore:#006000,face:%s,size:%d" % (fontName, fontSize)) + self.IndicatorSetStyle(0, wx.stc.STC_INDIC_TT) + 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. + self.supportedGCodes = [0,1,2,3,4,21,28,90,91,92] + self.supportedMCodes = [17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,42,80,81,82,83,84,85,92,104,105,106,107,109,114,115,117,119,140,190,201,202,203,204,205,206,220,221,240,301,302,303,400,500,501,502,503,999] + + def OnStyle(self, e): + lineNr = self.LineFromPosition(self.GetEndStyled()) + while self.PositionFromLine(lineNr) > -1: + line = self.GetLine(lineNr) + start = self.PositionFromLine(lineNr) + length = self.LineLength(lineNr) + self.StartStyling(start, 255) + self.SetStyling(length, 0) + if ';' in line: + pos = line.index(';') + self.StartStyling(start + pos, 31) + self.SetStyling(length - pos, 1) + length = pos + + pos = 0 + while pos < length: + if line[pos] in " \t\n\r": + while pos < length and line[pos] in " \t\n\r": + pos += 1 + else: + end = pos + while end < length and not line[end] in " \t\n\r": + end += 1 + if self.checkGCodePart(line[pos:end], start + pos): + self.StartStyling(start + pos, 0x20) + self.SetStyling(end - pos, 0x20) + pos = end + lineNr += 1 + + def checkGCodePart(self, part, pos): + if len(part) < 2: + self.StartStyling(pos, 0x40) + self.SetStyling(1, 0x40) + return True + if not part[0] in "GMXYZFESTBPIDCJ": + self.StartStyling(pos, 0x40) + self.SetStyling(1, 0x40) + return True + if part[1] == '{': + if part[-1] != '}': + return True + tag = part[2:-1] + if not profile.isProfileSetting(tag) and not profile.isPreference(tag): + self.StartStyling(pos + 2, 0x40) + self.SetStyling(len(tag), 0x40) + return True + elif part[0] in "GM": + try: + code = int(part[1:]) + except (ValueError): + self.StartStyling(pos + 1, 0x40) + self.SetStyling(len(part) - 1, 0x40) + return True + if part[0] == 'G': + if not code in self.supportedGCodes: + return True + if part[0] == 'M': + if not code in self.supportedMCodes: + return True + else: + try: + float(part[1:]) + except (ValueError): + self.StartStyling(pos + 1, 0x40) + self.SetStyling(len(part) - 1, 0x40) + return True + return False + + def GetValue(self): + return self.GetText() + + def SetValue(self, s): + self.SetText(s) + diff --git a/Cura/gui/mainWindow.py b/Cura/gui/mainWindow.py index 3cd22ab2..34d2f965 100644 --- a/Cura/gui/mainWindow.py +++ b/Cura/gui/mainWindow.py @@ -86,6 +86,7 @@ class mainWindow(configBase.configWindowBase): if profile.getPreference('lastFile') != '': self.filelist = profile.getPreference('lastFile').split(';') + self.SetTitle(self.filelist[-1] + ' - Cura - ' + version.getVersion()) else: self.filelist = [] self.progressPanelList = [] @@ -299,6 +300,7 @@ class mainWindow(configBase.configWindowBase): filelist.append(self._showOpenDialog("Open file to print")) if filelist[-1] == False: return + self.SetTitle(filelist[-1] + ' - Cura - ' + version.getVersion()) self.filelist = filelist profile.putPreference('lastFile', ';'.join(self.filelist)) self.preview3d.loadModelFiles(self.filelist) diff --git a/Cura/gui/opengl.py b/Cura/gui/opengl.py index f7331aad..92ffcdea 100644 --- a/Cura/gui/opengl.py +++ b/Cura/gui/opengl.py @@ -17,7 +17,9 @@ def InitGL(window, view3D, zoom): glViewport(0,0, size.GetWidth(), size.GetHeight()) glLightfv(GL_LIGHT0, GL_POSITION, [1.0, 1.0, 1.0, 0.0]) + glLightfv(GL_LIGHT1, GL_POSITION, [1.0, 1.0, 1.0, 0.0]) + glEnable(GL_NORMALIZE) glEnable(GL_LIGHTING) glEnable(GL_LIGHT0) glEnable(GL_DEPTH_TEST) @@ -198,6 +200,7 @@ def DrawBox(vMin, vMax): glEnd() def DrawSTL(mesh): + glEnable(GL_CULL_FACE) for face in mesh.faces: glBegin(GL_TRIANGLES) v1 = face.v[0] diff --git a/Cura/gui/preferencesDialog.py b/Cura/gui/preferencesDialog.py index ec315979..41bded92 100644 --- a/Cura/gui/preferencesDialog.py +++ b/Cura/gui/preferencesDialog.py @@ -7,6 +7,7 @@ import ConfigParser from gui import configBase from gui import validators from gui import machineCom +from util import profile class preferencesDialog(configBase.configWindowBase): def __init__(self, parent): @@ -14,6 +15,8 @@ class preferencesDialog(configBase.configWindowBase): wx.EVT_CLOSE(self, self.OnClose) + self.oldExtruderAmount = int(profile.getPreference('extruder_amount')) + left, right, main = self.CreateConfigPanel(self) configBase.TitleRow(left, 'Machine settings') c = configBase.SettingRow(left, 'Steps per E', 'steps_per_e', '0', 'Amount of steps per mm filament extrusion', type = 'preference') @@ -25,6 +28,13 @@ class preferencesDialog(configBase.configWindowBase): c = configBase.SettingRow(left, 'Machine height (mm)', 'machine_height', '200', 'Size of the machine in mm', type = 'preference') validators.validFloat(c, 10.0) c = configBase.SettingRow(left, 'Extruder count', 'extruder_amount', ['1', '2', '3', '4'], 'Amount of extruders in your machine.', type = 'preference') + + for i in xrange(1, self.oldExtruderAmount): + configBase.TitleRow(left, 'Extruder %d' % (i+1)) + c = configBase.SettingRow(left, 'Offset X', 'extruder_offset_x%d' % (i), '0.0', 'The offset of your secondary extruder compared to the primary.', type = 'preference') + validators.validFloat(c) + c = configBase.SettingRow(left, 'Offset Y', 'extruder_offset_y%d' % (i), '0.0', 'The offset of your secondary extruder compared to the primary.', type = 'preference') + validators.validFloat(c) configBase.TitleRow(left, 'Filament settings') c = configBase.SettingRow(left, 'Filament density (kg/m3)', 'filament_density', '1300', 'Weight of the filament per m3. Around 1300 for PLA. And around 1040 for ABS. This value is used to estimate the weight if the filament used for the print.', type = 'preference') @@ -42,10 +52,16 @@ class preferencesDialog(configBase.configWindowBase): #c = configBase.SettingRow(left, 'Slicer selection', 'slicer', ['Cura (Skeinforge based)', 'Slic3r'], 'Which slicer to use to slice objects. Usually the Cura engine produces the best results. But Slic3r is developing fast and is faster with slicing.', type = 'preference') c = configBase.SettingRow(left, 'Save profile on slice', 'save_profile', False, 'When slicing save the profile as [stl_file]_profile.ini next to the model.', type = 'preference') + self.okButton = wx.Button(left, -1, 'Ok') + left.GetSizer().Add(self.okButton, (left.GetSizer().GetRows(), 1)) + self.okButton.Bind(wx.EVT_BUTTON, self.OnClose) + self.MakeModal(True) main.Fit() self.Fit() def OnClose(self, e): + if self.oldExtruderAmount != int(profile.getPreference('extruder_amount')): + wx.MessageBox('After changing the amount of extruders you need to restart Cura for full effect.', 'Extruder amount warning.', wx.OK | wx.ICON_INFORMATION) self.MakeModal(False) self.Destroy() diff --git a/Cura/gui/preview3d.py b/Cura/gui/preview3d.py index ac8eff97..52665936 100644 --- a/Cura/gui/preview3d.py +++ b/Cura/gui/preview3d.py @@ -1,11 +1,6 @@ from __future__ import division -import sys -import math -import threading -import re -import time -import os +import sys, math, threading, re, time, os from wx import glcanvas import wx @@ -43,11 +38,12 @@ class previewPanel(wx.Panel): self.glCanvas = PreviewGLCanvas(self) self.objectList = [] + self.errorList = [] self.gcode = None self.objectsMinV = None self.objectsMaxV = None self.loadThread = None - self.machineSize = util3d.Vector3(float(profile.getPreference('machine_width')), float(profile.getPreference('machine_depth')), float(profile.getPreference('machine_height'))) + self.machineSize = util3d.Vector3(profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height')) self.machineCenter = util3d.Vector3(float(profile.getProfileSetting('machine_center_x')), float(profile.getProfileSetting('machine_center_y')), 0) self.toolbar = toolbarUtil.Toolbar(self) @@ -63,7 +59,6 @@ class previewPanel(wx.Panel): self.xrayViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-xray-on.png', 'view-xray-off.png', 'X-Ray view', callback=self.OnViewChange) self.gcodeViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-gcode-on.png', 'view-gcode-off.png', 'GCode view', callback=self.OnViewChange) self.mixedViewButton = toolbarUtil.RadioButton(self.toolbar, group, 'view-mixed-on.png', 'view-mixed-off.png', 'Mixed model/GCode view', callback=self.OnViewChange) - self.OnViewChange() self.toolbar.AddSeparator() self.layerSpin = wx.SpinCtrl(self.toolbar, -1, '', size=(21*4,21), style=wx.SP_ARROW_KEYS) @@ -93,22 +88,21 @@ class previewPanel(wx.Panel): self.toolbar2.AddSeparator() # Multiply - self.mulXadd = toolbarUtil.NormalButton(self.toolbar2, self.OnMulXAddClick, 'object-mul-x-add.png', 'Increase number of models on X axis') - self.mulXsub = toolbarUtil.NormalButton(self.toolbar2, self.OnMulXSubClick, 'object-mul-x-sub.png', 'Decrease number of models on X axis') - self.mulYadd = toolbarUtil.NormalButton(self.toolbar2, self.OnMulYAddClick, 'object-mul-y-add.png', 'Increase number of models on Y axis') - self.mulYsub = toolbarUtil.NormalButton(self.toolbar2, self.OnMulYSubClick, 'object-mul-y-sub.png', 'Decrease number of models on Y axis') - - self.toolbar2.AddSeparator() + #self.mulXadd = toolbarUtil.NormalButton(self.toolbar2, self.OnMulXAddClick, 'object-mul-x-add.png', 'Increase number of models on X axis') + #self.mulXsub = toolbarUtil.NormalButton(self.toolbar2, self.OnMulXSubClick, 'object-mul-x-sub.png', 'Decrease number of models on X axis') + #self.mulYadd = toolbarUtil.NormalButton(self.toolbar2, self.OnMulYAddClick, 'object-mul-y-add.png', 'Increase number of models on Y axis') + #self.mulYsub = toolbarUtil.NormalButton(self.toolbar2, self.OnMulYSubClick, 'object-mul-y-sub.png', 'Decrease number of models on Y axis') + #self.toolbar2.AddSeparator() # Rotate self.rotateReset = toolbarUtil.NormalButton(self.toolbar2, self.OnRotateReset, 'object-rotate.png', 'Reset model rotation') self.rotate = wx.SpinCtrl(self.toolbar2, -1, profile.getProfileSetting('model_rotate_base'), size=(21*3,21), style=wx.SP_WRAP|wx.SP_ARROW_KEYS) self.rotate.SetRange(0, 360) - self.Bind(wx.EVT_TEXT, self.OnRotate) + self.rotate.Bind(wx.EVT_TEXT, self.OnRotate) self.toolbar2.AddControl(self.rotate) self.toolbar2.Realize() - self.updateToolbar() + self.OnViewChange() sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(self.toolbar, 0, flag=wx.EXPAND|wx.TOP|wx.LEFT|wx.RIGHT, border=1) @@ -181,7 +175,6 @@ class previewPanel(wx.Panel): self.glCanvas.Refresh() def OnLayerNrChange(self, e): - self.gcodeDirty = True self.glCanvas.Refresh() def updateCenterX(self): @@ -203,8 +196,9 @@ class previewPanel(wx.Panel): def loadModelFiles(self, filelist): while len(filelist) > len(self.objectList): self.objectList.append(previewObject()) - for idx in xrange(len(self.objectList), len(filelist)): + for idx in xrange(len(filelist), len(self.objectList)): self.objectList[idx].mesh = None + self.objectList[idx].filename = None for idx in xrange(0, len(filelist)): obj = self.objectList[idx] if obj.filename != filelist[idx]: @@ -232,12 +226,11 @@ class previewPanel(wx.Panel): def doFileLoadThread(self): for obj in self.objectList: - if os.path.isfile(obj.filename) and obj.fileTime != os.stat(obj.filename).st_mtime: + if obj.filename != None and os.path.isfile(obj.filename) and obj.fileTime != os.stat(obj.filename).st_mtime: obj.ileTime = os.stat(obj.filename).st_mtime mesh = stl.stlModel() mesh.load(obj.filename) obj.dirty = False - obj.errorList = [] obj.mesh = mesh self.updateModelTransform() wx.CallAfter(self.updateToolbar) @@ -272,9 +265,11 @@ class previewPanel(wx.Panel): pass def updateToolbar(self): - self.layerSpin.Show(self.gcode != None) + self.gcodeViewButton.Show(self.gcode != None) + self.mixedViewButton.Show(self.gcode != None) + self.layerSpin.Show(self.glCanvas.viewMode == "GCode" or self.glCanvas.viewMode == "Mixed") if self.gcode != None: - self.layerSpin.SetRange(1, len(self.gcode.layerList)) + self.layerSpin.SetRange(1, len(self.gcode.layerList) - 1) self.toolbar.Realize() def OnViewChange(self): @@ -288,6 +283,7 @@ class previewPanel(wx.Panel): self.glCanvas.viewMode = "GCode" elif self.mixedViewButton.GetValue(): self.glCanvas.viewMode = "Mixed" + self.updateToolbar() self.glCanvas.Refresh() def updateModelTransform(self, f=0): @@ -376,6 +372,7 @@ class PreviewGLCanvas(glcanvas.GLCanvas): self.offsetY = 0 self.view3D = True self.gcodeDisplayList = None + self.gcodeDisplayListCount = 0 self.objColor = [[1.0, 0.8, 0.6, 1.0], [0.2, 1.0, 0.1, 1.0], [1.0, 0.2, 0.1, 1.0], [0.1, 0.2, 1.0, 1.0]] def OnMouseMotion(self,e): @@ -424,8 +421,12 @@ class PreviewGLCanvas(glcanvas.GLCanvas): glTranslate(0,0,-self.zoom) glRotate(-self.pitch, 1,0,0) glRotate(self.yaw, 0,0,1) - if self.parent.objectsMaxV != None: - glTranslate(0,0,-self.parent.objectsMaxV.z * profile.getProfileSettingFloat('model_scale') / 2) + if self.viewMode == "GCode" or self.viewMode == "Mixed": + if self.parent.gcode != None: + glTranslate(0,0,-self.parent.gcode.layerList[self.parent.layerSpin.GetValue()][0].list[-1].z) + else: + if self.parent.objectsMaxV != None: + glTranslate(0,0,-self.parent.objectsMaxV.z * profile.getProfileSettingFloat('model_scale') / 2) else: glScale(1.0/self.zoom, 1.0/self.zoom, 1.0) glTranslate(self.offsetX, self.offsetY, 0.0) @@ -438,99 +439,113 @@ class PreviewGLCanvas(glcanvas.GLCanvas): machineSize = self.parent.machineSize opengl.DrawMachine(machineSize) - if self.parent.gcode != None: - if self.gcodeDisplayList == None: - self.gcodeDisplayList = glGenLists(1); - if self.parent.gcodeDirty: - self.parent.gcodeDirty = False - glNewList(self.gcodeDisplayList, GL_COMPILE) - prevLayerZ = 0.0 - curLayerZ = 0.0 - - layerThickness = 0.0 - filamentRadius = float(profile.getProfileSetting('filament_diameter')) / 2 - filamentArea = math.pi * filamentRadius * filamentRadius - lineWidth = float(profile.getProfileSetting('nozzle_size')) / 2 / 10 - - curLayerNum = 0 - for layer in self.parent.gcode.layerList: - curLayerZ = layer[0].list[1].z - layerThickness = curLayerZ - prevLayerZ - prevLayerZ = layer[-1].list[-1].z - for path in layer: - c = 1.0 - if curLayerNum != self.parent.layerSpin.GetValue(): - if curLayerNum < self.parent.layerSpin.GetValue(): - c = 0.9 - (self.parent.layerSpin.GetValue() - curLayerNum) * 0.1 - if c < 0.4: - c = 0.4 - else: - break - if path.type == 'move': - glColor3f(0,0,c) - if path.type == 'extrude': - if path.pathType == 'FILL': - glColor3f(c/2,c/2,0) - elif path.pathType == 'WALL-INNER': - glColor3f(0,c,0) - elif path.pathType == 'SUPPORT': - glColor3f(0,c,c) - elif path.pathType == 'SKIRT': - glColor3f(0,c/2,c/2) - else: - glColor3f(c,0,0) - if path.type == 'retract': - glColor3f(0,c,c) - if c > 0.4 and path.type == 'extrude': - for i in xrange(0, len(path.list)-1): - v0 = path.list[i] - v1 = path.list[i+1] - - # Calculate line width from ePerDistance (needs layer thickness and filament diameter) - dist = (v0 - v1).vsize() - if dist > 0 and layerThickness > 0: - extrusionMMperDist = (v1.e - v0.e) / dist - lineWidth = extrusionMMperDist * filamentArea / layerThickness / 2 - - normal = (v0 - v1).cross(util3d.Vector3(0,0,1)) - normal.normalize() - v2 = v0 + normal * lineWidth - v3 = v1 + normal * lineWidth - v0 = v0 - normal * lineWidth - v1 = v1 - normal * lineWidth - - glBegin(GL_QUADS) - if path.pathType == 'FILL': #Remove depth buffer fighting on infill/wall overlap - glVertex3f(v0.x, v0.y, v0.z - 0.02) - glVertex3f(v1.x, v1.y, v1.z - 0.02) - glVertex3f(v3.x, v3.y, v3.z - 0.02) - glVertex3f(v2.x, v2.y, v2.z - 0.02) - else: - glVertex3f(v0.x, v0.y, v0.z - 0.01) - glVertex3f(v1.x, v1.y, v1.z - 0.01) - glVertex3f(v3.x, v3.y, v3.z - 0.01) - glVertex3f(v2.x, v2.y, v2.z - 0.01) - glEnd() - - #for v in path['list']: - # glBegin(GL_TRIANGLE_FAN) - # glVertex3f(v.x, v.y, v.z - 0.001) - # for i in xrange(0, 16+1): - # if path['pathType'] == 'FILL': #Remove depth buffer fighting on infill/wall overlap - # glVertex3f(v.x + math.cos(math.pi*2/16*i) * lineWidth, v.y + math.sin(math.pi*2/16*i) * lineWidth, v.z - 0.02) - # else: - # glVertex3f(v.x + math.cos(math.pi*2/16*i) * lineWidth, v.y + math.sin(math.pi*2/16*i) * lineWidth, v.z - 0.01) - # glEnd() + if self.parent.gcode != None and self.parent.gcodeDirty: + if self.gcodeDisplayListCount < len(self.parent.gcode.layerList) or self.gcodeDisplayList == None: + if self.gcodeDisplayList != None: + glDeleteLists(self.gcodeDisplayList, self.gcodeDisplayListCount) + self.gcodeDisplayList = glGenLists(len(self.parent.gcode.layerList)); + self.gcodeDisplayListCount = len(self.parent.gcode.layerList) + self.parent.gcodeDirty = False + prevLayerZ = 0.0 + curLayerZ = 0.0 + + layerThickness = 0.0 + filamentRadius = profile.getProfileSettingFloat('filament_diameter') / 2 + filamentArea = math.pi * filamentRadius * filamentRadius + lineWidth = profile.getProfileSettingFloat('nozzle_size') / 2 / 10 + + curLayerNum = 0 + for layer in self.parent.gcode.layerList: + glNewList(self.gcodeDisplayList + curLayerNum, GL_COMPILE) + glDisable(GL_CULL_FACE) + curLayerZ = layer[0].list[1].z + layerThickness = curLayerZ - prevLayerZ + prevLayerZ = layer[-1].list[-1].z + for path in layer: + if path.type == 'move': + glColor3f(0,0,1) + if path.type == 'extrude': + if path.pathType == 'FILL': + glColor3f(0.5,0.5,0) + elif path.pathType == 'WALL-INNER': + glColor3f(0,1,0) + elif path.pathType == 'SUPPORT': + glColor3f(0,1,1) + elif path.pathType == 'SKIRT': + glColor3f(0,0.5,0.5) else: - glBegin(GL_LINE_STRIP) - for v in path.list: - glVertex3f(v.x, v.y, v.z) + glColor3f(1,0,0) + if path.type == 'retract': + glColor3f(0,1,1) + if path.type == 'extrude': + for i in xrange(0, len(path.list)-1): + v0 = path.list[i] + v1 = path.list[i+1] + + # Calculate line width from ePerDistance (needs layer thickness and filament diameter) + dist = (v0 - v1).vsize() + if dist > 0 and layerThickness > 0: + extrusionMMperDist = (v1.e - v0.e) / dist + lineWidth = extrusionMMperDist * filamentArea / layerThickness / 2 + + normal = (v0 - v1).cross(util3d.Vector3(0,0,1)) + normal.normalize() + v2 = v0 + normal * lineWidth + v3 = v1 + normal * lineWidth + v0 = v0 - normal * lineWidth + v1 = v1 - normal * lineWidth + + glBegin(GL_QUADS) + if path.pathType == 'FILL': #Remove depth buffer fighting on infill/wall overlap + glVertex3f(v0.x, v0.y, v0.z - 0.02) + glVertex3f(v1.x, v1.y, v1.z - 0.02) + glVertex3f(v3.x, v3.y, v3.z - 0.02) + glVertex3f(v2.x, v2.y, v2.z - 0.02) + else: + glVertex3f(v0.x, v0.y, v0.z - 0.01) + glVertex3f(v1.x, v1.y, v1.z - 0.01) + glVertex3f(v3.x, v3.y, v3.z - 0.01) + glVertex3f(v2.x, v2.y, v2.z - 0.01) glEnd() - curLayerNum += 1 + + #for v in path['list']: + # glBegin(GL_TRIANGLE_FAN) + # glVertex3f(v.x, v.y, v.z - 0.001) + # for i in xrange(0, 16+1): + # if path['pathType'] == 'FILL': #Remove depth buffer fighting on infill/wall overlap + # glVertex3f(v.x + math.cos(math.pi*2/16*i) * lineWidth, v.y + math.sin(math.pi*2/16*i) * lineWidth, v.z - 0.02) + # else: + # glVertex3f(v.x + math.cos(math.pi*2/16*i) * lineWidth, v.y + math.sin(math.pi*2/16*i) * lineWidth, v.z - 0.01) + # glEnd() + else: + glBegin(GL_LINE_STRIP) + for v in path.list: + glVertex3f(v.x, v.y, v.z) + glEnd() + curLayerNum += 1 + glEnable(GL_CULL_FACE) glEndList() - if self.viewMode == "GCode" or self.viewMode == "Mixed": - glCallList(self.gcodeDisplayList) + if self.parent.gcode != None and (self.viewMode == "GCode" or self.viewMode == "Mixed"): + glEnable(GL_COLOR_MATERIAL) + glEnable(GL_LIGHTING) + for i in xrange(0, self.parent.layerSpin.GetValue() + 1): + c = 1.0 + if i < self.parent.layerSpin.GetValue(): + c = 0.9 - (self.parent.layerSpin.GetValue() - i) * 0.1 + if c < 0.4: + c = (0.4 + c) / 2 + if c < 0.1: + c = 0.1 + glLightfv(GL_LIGHT0, GL_DIFFUSE, [0,0,0,0]) + glLightfv(GL_LIGHT0, GL_AMBIENT, [c,c,c,c]) + glCallList(self.gcodeDisplayList + i) + glDisable(GL_LIGHTING) + glDisable(GL_COLOR_MATERIAL) + glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, [0.2, 0.2, 0.2, 1.0]); + glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, [0.8, 0.8, 0.8, 1.0]); + + glColor3f(1.0,1.0,1.0) glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0) for obj in self.parent.objectList: if obj.mesh == None: @@ -543,17 +558,15 @@ class PreviewGLCanvas(glcanvas.GLCanvas): opengl.DrawSTL(obj.mesh) glEndList() - glEnable(GL_NORMALIZE) if self.viewMode == "Transparent" or self.viewMode == "Mixed": glLightfv(GL_LIGHT0, GL_DIFFUSE, map(lambda x: x / 2, self.objColor[self.parent.objectList.index(obj)])) glLightfv(GL_LIGHT0, GL_AMBIENT, map(lambda x: x / 10, self.objColor[self.parent.objectList.index(obj)])) #If we want transparent, then first render a solid black model to remove the printer size lines. if self.viewMode != "Mixed": glDisable(GL_BLEND) - glDisable(GL_LIGHTING) - glColor3f(0,0,0) + glColor3f(0.0,0.0,0.0) self.drawModel(obj) - glColor3f(1,1,1) + glColor3f(1.0,1.0,1.0) #After the black model is rendered, render the model again but now with lighting and no depth testing. glDisable(GL_DEPTH_TEST) glEnable(GL_LIGHTING) @@ -608,22 +621,22 @@ class PreviewGLCanvas(glcanvas.GLCanvas): glEnable(GL_LIGHTING) self.drawModel(obj) - if self.viewMode == "Normal" or self.viewMode == "Transparent" or self.viewMode == "X-Ray": - glDisable(GL_LIGHTING) - glDisable(GL_DEPTH_TEST) - glDisable(GL_BLEND) - glColor3f(1,0,0) - #glBegin(GL_LINES) - #for err in self.parent.errorList: - # glVertex3f(err[0].x, err[0].y, err[0].z) - # glVertex3f(err[1].x, err[1].y, err[1].z) - #glEnd() - glEnable(GL_DEPTH_TEST) + if self.viewMode == "Normal" or self.viewMode == "Transparent" or self.viewMode == "X-Ray": + glDisable(GL_LIGHTING) + glDisable(GL_DEPTH_TEST) + glDisable(GL_BLEND) + glColor3f(1,0,0) + glBegin(GL_LINES) + for err in self.parent.errorList: + glVertex3f(err[0].x, err[0].y, err[0].z) + glVertex3f(err[1].x, err[1].y, err[1].z) + glEnd() + glEnable(GL_DEPTH_TEST) glFlush() def drawModel(self, obj): - multiX = int(profile.getProfileSetting('model_multiply_x')) - multiY = int(profile.getProfileSetting('model_multiply_y')) + multiX = 1 #int(profile.getProfileSetting('model_multiply_x')) + multiY = 1 #int(profile.getProfileSetting('model_multiply_y')) modelScale = profile.getProfileSettingFloat('model_scale') modelSize = (obj.mesh.getMaximum() - obj.mesh.getMinimum()) * modelScale glPushMatrix() diff --git a/Cura/gui/printWindow.py b/Cura/gui/printWindow.py index 195b5942..5f68556d 100644 --- a/Cura/gui/printWindow.py +++ b/Cura/gui/printWindow.py @@ -176,14 +176,9 @@ class printWindow(wx.Frame): status = "" if self.gcode != None: status += "Filament: %.2fm %.2fg\n" % (self.gcode.extrusionAmount / 1000, self.gcode.calculateWeight() * 1000) - cost_kg = float(profile.getPreference('filament_cost_kg')) - cost_meter = float(profile.getPreference('filament_cost_meter')) - if cost_kg > 0.0 and cost_meter > 0.0: - status += "Filament cost: %.2f / %.2f\n" % (self.gcode.calculateWeight() * cost_kg, self.gcode.extrusionAmount / 1000 * cost_meter) - elif cost_kg > 0.0: - status += "Filament cost: %.2f\n" % (self.gcode.calculateWeight() * cost_kg) - elif cost_meter > 0.0: - status += "Filament cost: %.2f\n" % (self.gcode.extrusionAmount / 1000 * cost_meter) + cost = self.gcode.calculateCost() + if cost != False: + status += "Filament cost: %s\n" % (cost) status += "Print time: %02d:%02d\n" % (int(self.gcode.totalMoveTimeMinute / 60), int(self.gcode.totalMoveTimeMinute % 60)) if self.printIdx == None: self.progress.SetValue(0) diff --git a/Cura/gui/projectPlanner.py b/Cura/gui/projectPlanner.py index 31162255..b24721e5 100644 --- a/Cura/gui/projectPlanner.py +++ b/Cura/gui/projectPlanner.py @@ -23,10 +23,110 @@ from util import profile from util import util3d 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): @@ -39,9 +139,15 @@ class projectPlanner(wx.Frame): self.list = [] self.selection = None - self.machineSize = util3d.Vector3(float(profile.getPreference('machine_width')), float(profile.getPreference('machine_depth')), float(profile.getPreference('machine_height'))) - self.headSizeMin = util3d.Vector3(70,16,0) - self.headSizeMax = util3d.Vector3(16,35,0) + self.machineSize = util3d.Vector3(profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth'), profile.getPreferenceFloat('machine_height')) + self.headSizeMin = util3d.Vector3(profile.getPreferenceFloat('extruder_head_size_min_x'), profile.getPreferenceFloat('extruder_head_size_min_y'),0) + self.headSizeMax = util3d.Vector3(profile.getPreferenceFloat('extruder_head_size_max_x'), profile.getPreferenceFloat('extruder_head_size_max_y'),0) + + self.extruderOffset = [ + util3d.Vector3(0,0,0), + util3d.Vector3(profile.getPreferenceFloat('extruder_offset_x1'), profile.getPreferenceFloat('extruder_offset_y1'), 0), + util3d.Vector3(profile.getPreferenceFloat('extruder_offset_x2'), profile.getPreferenceFloat('extruder_offset_y2'), 0), + util3d.Vector3(profile.getPreferenceFloat('extruder_offset_x3'), profile.getPreferenceFloat('extruder_offset_y3'), 0)] self.toolbar = toolbarUtil.Toolbar(self) @@ -55,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) @@ -65,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)) @@ -95,6 +210,12 @@ class projectPlanner(wx.Frame): 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) + + if int(profile.getPreference('extruder_amount')) > 1: + self.extruderCtrl = wx.ComboBox(panel, -1, '1', choices=map(str, range(1, int(profile.getPreference('extruder_amount'))+1)), style=wx.CB_DROPDOWN|wx.CB_READONLY) + sizer.Add(wx.StaticText(panel, -1, 'Extruder'), (2,0), flag=wx.ALIGN_CENTER_VERTICAL) + sizer.Add(self.extruderCtrl, (2,1), flag=wx.ALIGN_BOTTOM|wx.EXPAND) + self.extruderCtrl.Bind(wx.EVT_COMBOBOX, self.OnExtruderChange) self.scaleCtrl.Bind(wx.EVT_TEXT, self.OnScaleChange) self.rotateCtrl.Bind(wx.EVT_SPINCTRL, self.OnRotateChange) @@ -126,6 +247,7 @@ class projectPlanner(wx.Frame): cp.set(section, 'flipZ', str(item.flipZ)) cp.set(section, 'swapXZ', str(item.swapXZ)) cp.set(section, 'swapYZ', str(item.swapYZ)) + cp.set(section, 'extruder', str(item.extruder+1)) i += 1 cp.write(open(dlg.GetPath(), "w")) dlg.Destroy() @@ -142,18 +264,19 @@ 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')) item.rotate = float(cp.get(section, 'rotate')) - cp.get(section, 'flipX') - cp.get(section, 'flipY') - cp.get(section, 'flipZ') - cp.get(section, 'swapXZ') - cp.get(section, 'swapYZ') + item.flipX = cp.get(section, 'flipX') == 'True' + item.flipY = cp.get(section, 'flipY') == 'True' + item.flipZ = cp.get(section, 'flipZ') == 'True' + item.swapXZ = cp.get(section, 'swapXZ') == 'True' + 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) @@ -161,6 +284,7 @@ class projectPlanner(wx.Frame): self.listbox.SetSelection(len(self.list)-1) self.OnListSelect(None) + self.preview.Refresh() dlg.Destroy() @@ -184,39 +308,76 @@ class projectPlanner(wx.Frame): self.selection = self.list[self.listbox.GetSelection()] self.scaleCtrl.SetValue(str(self.selection.scale)) self.rotateCtrl.SetValue(int(self.selection.rotate)) + if int(profile.getPreference('extruder_amount')) > 1: + self.extruderCtrl.SetValue(str(self.selection.extruder+1)) self.preview.Refresh() - + def OnAddModel(self, e): dlg=wx.FileDialog(self, "Open file to print", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST|wx.FD_MULTIPLE) 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) @@ -233,7 +394,7 @@ class projectPlanner(wx.Frame): extraSizeMax = self.headSizeMax if profile.getProfileSettingFloat('skirt_line_count') > 0: skirtSize = profile.getProfileSettingFloat('skirt_line_count') * profile.calculateEdgeWidth() + profile.getProfileSettingFloat('skirt_gap') - extraSizeMin = extraSizeMin - util3d.Vector3(skirtSize, skirtSize, 0) + extraSizeMin = extraSizeMin + util3d.Vector3(skirtSize, skirtSize, 0) extraSizeMax = extraSizeMax + util3d.Vector3(skirtSize, skirtSize, 0) posX = self.machineSize.x @@ -268,9 +429,7 @@ class projectPlanner(wx.Frame): return (maxX - minX) + (maxY - minY) def OnSlice(self, e): - oldProfile = profile.getGlobalProfileString() - - put = profile.putProfileSetting + put = profile.setTempOverride put('model_multiply_x', '1') put('model_multiply_y', '1') @@ -281,8 +440,8 @@ class projectPlanner(wx.Frame): clearZ = 0 actionList = [] for item in self.list: - put('machine_center_x', item.centerX) - put('machine_center_y', item.centerY) + put('machine_center_x', item.centerX - self.extruderOffset[item.extruder].x) + put('machine_center_y', item.centerY - self.extruderOffset[item.extruder].y) put('model_scale', item.scale) put('flip_x', item.flipX) put('flip_y', item.flipY) @@ -295,13 +454,14 @@ class projectPlanner(wx.Frame): action.sliceCmd = sliceRun.getSliceCommand(item.filename) action.centerX = item.centerX action.centerY = item.centerY + action.extruder = item.extruder 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) + profile.resetTempOverride() dlg=wx.FileDialog(self, "Save project gcode file", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE) dlg.SetWildcard("GCode file (*.gcode)|*.gcode") @@ -312,34 +472,10 @@ class projectPlanner(wx.Frame): dlg.Destroy() pspw = ProjectSliceProgressWindow(actionList, resultFilename) + pspw.extruderOffset = self.extruderOffset 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.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 @@ -353,57 +489,13 @@ 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 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 + def OnExtruderChange(self, e): + if self.selection == None: + return + self.selection.extruder = int(self.extruderCtrl.GetValue()) - 1 self.preview.Refresh() class PreviewGLCanvas(glcanvas.GLCanvas): @@ -445,14 +537,14 @@ class PreviewGLCanvas(glcanvas.GLCanvas): if item != None: item.centerX += float(e.GetX() - self.oldX) * self.zoom / self.GetSize().GetHeight() * 2 item.centerY -= float(e.GetY() - self.oldY) * self.zoom / self.GetSize().GetHeight() * 2 - if item.centerX < -item.getMinimum().x * item.scale: - item.centerX = -item.getMinimum().x * item.scale - if item.centerY < -item.getMinimum().y * item.scale: - item.centerY = -item.getMinimum().y * item.scale - if item.centerX > self.parent.machineSize.x - item.getMaximum().x * item.scale: - item.centerX = self.parent.machineSize.x - item.getMaximum().x * item.scale - if item.centerY > self.parent.machineSize.y - item.getMaximum().y * item.scale: - item.centerY = self.parent.machineSize.y - item.getMaximum().y * item.scale + if item.centerX < -item.getMinimum().x * item.scale + self.parent.extruderOffset[item.extruder].x: + item.centerX = -item.getMinimum().x * item.scale + self.parent.extruderOffset[item.extruder].x + if item.centerY < -item.getMinimum().y * item.scale + self.parent.extruderOffset[item.extruder].y: + item.centerY = -item.getMinimum().y * item.scale + self.parent.extruderOffset[item.extruder].y + if item.centerX > self.parent.machineSize.x + self.parent.extruderOffset[item.extruder].x - item.getMaximum().x * item.scale: + item.centerX = self.parent.machineSize.x + self.parent.extruderOffset[item.extruder].x - item.getMaximum().x * item.scale + if item.centerY > self.parent.machineSize.y + self.parent.extruderOffset[item.extruder].y - item.getMaximum().y * item.scale: + item.centerY = self.parent.machineSize.y + self.parent.extruderOffset[item.extruder].y - item.getMaximum().y * item.scale self.Refresh() else: self.allowDrag = False @@ -508,7 +600,7 @@ class PreviewGLCanvas(glcanvas.GLCanvas): extraSizeMax = self.parent.headSizeMax if profile.getProfileSettingFloat('skirt_line_count') > 0: skirtSize = profile.getProfileSettingFloat('skirt_line_count') * profile.calculateEdgeWidth() + profile.getProfileSettingFloat('skirt_gap') - extraSizeMin = extraSizeMin - util3d.Vector3(skirtSize, skirtSize, 0) + extraSizeMin = extraSizeMin + util3d.Vector3(skirtSize, skirtSize, 0) extraSizeMax = extraSizeMax + util3d.Vector3(skirtSize, skirtSize, 0) for item in self.parent.list: @@ -517,8 +609,8 @@ class PreviewGLCanvas(glcanvas.GLCanvas): for idx1 in xrange(0, len(self.parent.list)): item = self.parent.list[idx1] - iMin1 = item.getMinimum() * item.scale + util3d.Vector3(item.centerX, item.centerY, 0) - extraSizeMin - iMax1 = item.getMaximum() * item.scale + util3d.Vector3(item.centerX, item.centerY, 0) + extraSizeMax + iMin1 = item.getMinimum() * item.scale + util3d.Vector3(item.centerX, item.centerY, 0) - extraSizeMin - self.parent.extruderOffset[item.extruder] + iMax1 = item.getMaximum() * item.scale + util3d.Vector3(item.centerX, item.centerY, 0) + extraSizeMax - self.parent.extruderOffset[item.extruder] for idx2 in xrange(0, idx1): item2 = self.parent.list[idx2] iMin2 = item2.getMinimum() * item2.scale + util3d.Vector3(item2.centerX, item2.centerY, 0) @@ -566,8 +658,8 @@ class PreviewGLCanvas(glcanvas.GLCanvas): vMin = item.getMinimum() * item.scale vMax = item.getMaximum() * item.scale - vMinHead = vMin - extraSizeMin - vMaxHead = vMax + extraSizeMax + vMinHead = vMin - extraSizeMin - self.parent.extruderOffset[item.extruder] + vMaxHead = vMax + extraSizeMax - self.parent.extruderOffset[item.extruder] glDisable(GL_LIGHTING) @@ -612,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) @@ -644,6 +714,7 @@ class ProjectSliceProgressWindow(wx.Frame): 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.sizer.AddGrowableCol(0) self.sizer.AddGrowableRow(0) @@ -664,19 +735,19 @@ 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) + "]") def OnRun(self): resultFile = open(self.resultFilename, "w") - put = profile.putProfileSetting + put = profile.setTempOverride for action in self.actionList: p = subprocess.Popen(action.sliceCmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) line = p.stdout.readline() @@ -697,24 +768,27 @@ class ProjectSliceProgressWindow(wx.Frame): if self.abort: p.terminate() wx.CallAfter(self.statusText.SetLabel, "Aborted by user.") + resultFile.close() 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('machine_center_x', action.centerX - self.extruderOffset[action.extruder].x) + put('machine_center_y', action.centerY - self.extruderOffset[action.extruder].y) put('clear_z', action.clearZ) + put('extruder', action.extruder) if action == self.actionList[0]: resultFile.write(';TYPE:CUSTOM\n') + resultFile.write('T%d\n' % (action.extruder)) + currentExtruder = action.extruder 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) + profile.resetTempOverride() f = open(action.filename[: action.filename.rfind('.')] + "_export.project_tmp", "r") data = f.read(4096) @@ -731,9 +805,20 @@ class ProjectSliceProgressWindow(wx.Frame): resultFile.write(';TYPE:CUSTOM\n') resultFile.write(profile.getAlterationFileContents('end.gcode')) resultFile.close() + + gcode = gcodeInterpreter.gcode() + gcode.load(self.resultFilename) + self.abort = True sliceTime = time.time() - self.sliceStartTime - wx.CallAfter(self.statusText.SetLabel, 'Slicing took: %d:%d' % (sliceTime / 60, sliceTime % 60)) + status = "Slicing took: %02d:%02d\n" % (sliceTime / 60, sliceTime % 60) + status = "Filament: %.2fm %.2fg\n" % (gcode.extrusionAmount / 1000, gcode.calculateWeight() * 1000) + status += "Print time: %02d:%02d\n" % (int(gcode.totalMoveTimeMinute / 60), int(gcode.totalMoveTimeMinute % 60)) + cost = gcode.calculateCost() + if cost != False: + status += "Cost: %s\n" % (cost) + wx.CallAfter(self.statusText.SetLabel, status) + wx.CallAfter(self.abortButton.SetLabel, 'Close') def main(): diff --git a/Cura/gui/simpleMode.py b/Cura/gui/simpleMode.py index f49f1e0a..4f38ca95 100644 --- a/Cura/gui/simpleMode.py +++ b/Cura/gui/simpleMode.py @@ -117,7 +117,7 @@ class simpleModeWindow(configBase.configWindowBase): self.sizer = sizer if self.filename != "None": - self.preview3d.loadModelFile(self.filename) + self.preview3d.loadModelFiles([self.filename]) self.lastPath = os.path.split(self.filename)[0] self.updateProfileToControls() @@ -158,7 +158,7 @@ class simpleModeWindow(configBase.configWindowBase): if not(os.path.exists(self.filename)): return self.lastPath = os.path.split(self.filename)[0] - self.preview3d.loadModelFile(self.filename) + self.preview3d.loadModelFiles([self.filename]) self.preview3d.setViewMode("Normal") dlg.Destroy() diff --git a/Cura/gui/sliceProgessPanel.py b/Cura/gui/sliceProgessPanel.py index 0dba1fbc..9e49e5b3 100644 --- a/Cura/gui/sliceProgessPanel.py +++ b/Cura/gui/sliceProgessPanel.py @@ -1,11 +1,12 @@ from __future__ import absolute_import import __init__ -import wx, sys, os, math, threading, subprocess, time +import wx, sys, os, math, threading, subprocess, time, re from util import profile from util import sliceRun from util import exporer +from util import gcodeInterpreter class sliceProgessPanel(wx.Panel): def __init__(self, mainWindow, parent, filelist): @@ -14,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) @@ -58,18 +38,20 @@ class sliceProgessPanel(wx.Panel): if profile.getPreference('save_profile') == 'True': profile.saveGlobalProfile(self.filelist[0][: self.filelist[0].rfind('.')] + "_profile.ini") cmdList = [] - oldProfile = profile.getGlobalProfileString() for filename in self.filelist: - print filename, self.filelist.index(filename) - if self.filelist.index(filename) > 0: - profile.putProfileSetting('fan_enabled', 'False') - profile.putProfileSetting('skirt_line_count', '0') - profile.putProfileSetting('machine_center_x', profile.getProfileSettingFloat('machine_center_x') + 22) + idx = self.filelist.index(filename) + print filename, idx + if idx > 0: + profile.setTempOverride('fan_enabled', 'False') + profile.setTempOverride('skirt_line_count', '0') + profile.setTempOverride('machine_center_x', profile.getProfileSettingFloat('machine_center_x') - profile.getPreferenceFloat('extruder_offset_x%d' % (idx))) + profile.setTempOverride('machine_center_y', profile.getProfileSettingFloat('machine_center_y') - profile.getPreferenceFloat('extruder_offset_y%d' % (idx))) + profile.setTempOverride('alternative_center', self.filelist[0]) if len(self.filelist) > 1: - profile.putProfileSetting('add_start_end_gcode', 'False') - profile.putProfileSetting('gcode_extension', 'multi_extrude_tmp') + profile.setTempOverride('add_start_end_gcode', 'False') + profile.setTempOverride('gcode_extension', 'multi_extrude_tmp') cmdList.append(sliceRun.getSliceCommand(filename)) - profile.loadGlobalProfileFromString(oldProfile) + profile.resetTempOverride() self.thread = WorkerThread(self, filelist, cmdList) def OnAbort(self, e): @@ -98,7 +80,12 @@ class sliceProgessPanel(wx.Panel): self.Bind(wx.EVT_BUTTON, self.OnAbort, self.abortButton) self.sizer.Add(self.logButton, 0) if result.returnCode == 0: - self.statusText.SetLabel("Ready.") + status = "Ready: Filament: %.2fm %.2fg" % (result.gcode.extrusionAmount / 1000, result.gcode.calculateWeight() * 1000) + status += " Print time: %02d:%02d" % (int(result.gcode.totalMoveTimeMinute / 60), int(result.gcode.totalMoveTimeMinute % 60)) + cost = result.gcode.calculateCost() + if cost != False: + status += " Cost: %s" % (cost) + self.statusText.SetLabel(status) if exporer.hasExporer(): self.openFileLocationButton = wx.Button(self, -1, "Open file location") self.Bind(wx.EVT_BUTTON, self.OnOpenFileLocation, self.openFileLocationButton) @@ -117,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) + "]") @@ -167,6 +154,8 @@ class WorkerThread(threading.Thread): if self.fileIdx == len(self.cmdList): if len(self.filelist) > 1: self._stitchMultiExtruder() + self.gcode = gcodeInterpreter.gcode() + self.gcode.load(self.filelist[0][:self.filelist[0].rfind('.')]+'_export.gcode') wx.CallAfter(self.notifyWindow.OnSliceDone, self) else: self.run() @@ -177,10 +166,13 @@ class WorkerThread(threading.Thread): resultFile.write(';TYPE:CUSTOM\n') resultFile.write(profile.getAlterationFileContents('start.gcode')) for filename in self.filelist: - files.append(open(filename[:filename.rfind('.')]+'_export.multi_extrude_tmp', "r")) + if os.path.isfile(filename[:filename.rfind('.')]+'_export.multi_extrude_tmp'): + files.append(open(filename[:filename.rfind('.')]+'_export.multi_extrude_tmp', "r")) + else: + return currentExtruder = 0 - resultFile.write("T%d\n" % (currentExtruder)) + resultFile.write('T%d\n' % (currentExtruder)) layerNr = -1 hasLine = True while hasLine: @@ -191,14 +183,17 @@ class WorkerThread(threading.Thread): hasLine = True if line.startswith(';LAYER:'): break + if 'Z' in line: + lastZ = float(re.search('Z([^\s]+)', line).group(1)) if not layerHasLine: nextExtruder = files.index(f) resultFile.write(';LAYER:%d\n' % (layerNr)) resultFile.write(';EXTRUDER:%d\n' % (nextExtruder)) if nextExtruder != currentExtruder: - resultFile.write("G1 E-2 F3000\n") + resultFile.write("G1 E-5 F5000\n") + resultFile.write("G92 E0\n") resultFile.write("T%d\n" % (nextExtruder)) - resultFile.write("G1 E2 F3000\n") + resultFile.write("G1 E5 F5000\n") resultFile.write("G92 E0\n") currentExtruder = nextExtruder layerHasLine = True 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 0518be27..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 = [] @@ -36,7 +36,18 @@ class gcode(): #Calculates the weight of the filament in kg radius = float(profile.getProfileSetting('filament_diameter')) / 2 volumeM3 = (self.extrusionAmount * (math.pi * radius * radius)) / (1000*1000*1000) - return volumeM3 * float(profile.getPreference('filament_density')) + return volumeM3 * profile.getPreferenceFloat('filament_density') + + def calculateCost(self): + cost_kg = profile.getPreferenceFloat('filament_cost_kg') + cost_meter = profile.getPreferenceFloat('filament_cost_meter') + if cost_kg > 0.0 and cost_meter > 0.0: + return "%.2f / %.2f" % (self.calculateWeight() * cost_kg, self.extrusionAmount / 1000 * cost_meter) + elif cost_kg > 0.0: + return "%.2f" % (self.calculateWeight() * cost_kg) + elif cost_meter > 0.0: + return "%.2f" % (self.extrusionAmount / 1000 * cost_meter) + return False def _load(self, gcodeFile): filePos = 0 @@ -45,6 +56,7 @@ class gcode(): currentE = 0.0 totalExtrusion = 0.0 maxExtrusion = 0.0 + currentExtruder = 0 totalMoveTimeMinute = 0.0 scale = 1.0 posAbs = True @@ -79,6 +91,15 @@ class gcode(): if pathType != "CUSTOM": startCodeDone = True line = line[0:line.find(';')] + T = self.getCodeInt(line, 'T') + if T is not None: + if currentExtruder > 0: + posOffset.x -= profile.getPreferenceFloat('extruder_offset_x%d' % (currentExtruder)) + posOffset.y -= profile.getPreferenceFloat('extruder_offset_y%d' % (currentExtruder)) + currentExtruder = T + if currentExtruder > 0: + posOffset.x += profile.getPreferenceFloat('extruder_offset_x%d' % (currentExtruder)) + posOffset.y += profile.getPreferenceFloat('extruder_offset_y%d' % (currentExtruder)) G = self.getCodeInt(line, 'G') if G is not None: @@ -91,17 +112,17 @@ class gcode(): oldPos = pos.copy() if x is not None: if posAbs: - pos.x = x * scale + pos.x = x * scale + posOffset.x else: pos.x += x * scale if y is not None: if posAbs: - pos.y = y * scale + pos.y = y * scale + posOffset.y else: pos.y += y * scale if z is not None: if posAbs: - pos.z = z * scale + pos.z = z * scale + posOffset.z else: pos.z += z * scale #Check if we have a new layer. @@ -200,8 +221,8 @@ class gcode(): self.layerList.append(currentLayer) self.extrusionAmount = maxExtrusion self.totalMoveTimeMinute = totalMoveTimeMinute - print "Extruded a total of: %d mm of filament" % (self.extrusionAmount) - print "Estimated print duration: %.2f minutes" % (self.totalMoveTimeMinute) + #print "Extruded a total of: %d mm of filament" % (self.extrusionAmount) + #print "Estimated print duration: %.2f minutes" % (self.totalMoveTimeMinute) def getCodeInt(self, line, code): if code not in self.regMatch: diff --git a/Cura/util/profile.py b/Cura/util/profile.py index b6fb3ac7..5e6f67ef 100644 --- a/Cura/util/profile.py +++ b/Cura/util/profile.py @@ -34,7 +34,7 @@ profileDefaultSettings = { 'bottom_layer_speed': '20', 'cool_min_layer_time': '10', 'fan_enabled': 'True', - 'fan_layer': '0', + 'fan_layer': '1', 'fan_speed': '100', 'model_scale': '1.0', 'flip_x': 'False', @@ -67,6 +67,9 @@ profileDefaultSettings = { 'add_start_end_gcode': 'True', 'gcode_extension': 'gcode', + 'alternative_center': '', + 'clear_z': '0.0', + 'extruder': '0', } alterationDefault = { ####################################################################################### @@ -128,6 +131,12 @@ preferencesDefaultSettings = { 'machine_depth': '205', 'machine_height': '200', 'extruder_amount': '1', + 'extruder_offset_x1': '-22.0', + 'extruder_offset_y1': '0.0', + 'extruder_offset_x2': '0.0', + 'extruder_offset_y2': '0.0', + 'extruder_offset_x3': '0.0', + 'extruder_offset_y3': '0.0', 'filament_density': '1300', 'steps_per_e': '0', 'serial_port': 'AUTO', @@ -136,12 +145,18 @@ preferencesDefaultSettings = { 'save_profile': 'False', 'filament_cost_kg': '0', 'filament_cost_meter': '0', + + 'extruder_head_size_min_x': '70.0', + 'extruder_head_size_min_y': '18.0', + 'extruder_head_size_max_x': '18.0', + 'extruder_head_size_max_y': '35.0', } ######################################################### ## Profile and preferences functions ######################################################### +## Profile functions def getDefaultProfilePath(): return os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "../current_profile.ini")) @@ -177,15 +192,31 @@ def getGlobalProfileString(): p = [] alt = [] - for key in globalProfileParser.options('profile'): - p.append(key + "=" + globalProfileParser.get('profile', key)) - for key in globalProfileParser.options('alterations'): - alt.append(key + "=" + globalProfileParser.get('alterations', key)) + tempDone = [] + if globalProfileParser.has_section('profile'): + for key in globalProfileParser.options('profile'): + if key in tempOverride: + p.append(key + "=" + unicode(tempOverride[key])) + tempDone.append(key) + else: + p.append(key + "=" + globalProfileParser.get('profile', key)) + if globalProfileParser.has_section('alterations'): + for key in globalProfileParser.options('alterations'): + if key in tempOverride: + p.append(key + "=" + tempOverride[key]) + tempDone.append(key) + else: + alt.append(key + "=" + globalProfileParser.get('alterations', key)) + for key in tempOverride: + if key not in tempDone: + p.append(key + "=" + unicode(tempOverride[key])) ret = '\b'.join(p) + '\f' + '\b'.join(alt) ret = base64.b64encode(zlib.compress(ret, 9)) return ret def getProfileSetting(name): + if name in tempOverride: + return unicode(tempOverride[name]) #Check if we have a configuration file loaded, else load the default. if not globals().has_key('globalProfileParser'): loadGlobalProfile(getDefaultProfilePath()) @@ -217,13 +248,27 @@ def putProfileSetting(name, value): globalProfileParser.add_section('profile') globalProfileParser.set('profile', name, str(value)) +def isProfileSetting(name): + if name in profileDefaultSettings: + return True + return False + +## Preferences functions global globalPreferenceParser globalPreferenceParser = None def getPreferencePath(): return os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "../preferences.ini")) +def getPreferenceFloat(name): + try: + return float(eval(getPreference(name), {}, {})) + except (ValueError, SyntaxError): + return 0.0 + def getPreference(name): + if name in tempOverride: + return unicode(tempOverride[name]) global globalPreferenceParser if globalPreferenceParser == None: globalPreferenceParser = ConfigParser.ConfigParser() @@ -253,6 +298,18 @@ def putPreference(name, value): globalPreferenceParser.set('preference', name, unicode(value).encode("utf-8")) globalPreferenceParser.write(open(getPreferencePath(), 'w')) +def isPreference(name): + if name in preferencesDefaultSettings: + return True + return False + +## Temp overrides for multi-extruder slicing and the project planner. +tempOverride = {} +def setTempOverride(name, value): + tempOverride[name] = value +def resetTempOverride(): + tempOverride.clear() + ######################################################### ## Utility functions to calculate common profile values ######################################################### @@ -296,7 +353,11 @@ def replaceTagMatch(m): tag = m.group(0)[1:-1] if tag in ['print_speed', 'retraction_speed', 'travel_speed', 'max_z_speed', 'bottom_layer_speed', 'cool_min_feedrate']: return str(getProfileSettingFloat(tag) * 60) - return str(getProfileSettingFloat(tag)) + if isProfileSetting(tag): + return str(getProfileSettingFloat(tag)) + if isPreference(tag): + return str(getProfileSettingFloat(tag)) + return tag ### Get aleration raw contents. (Used internally in Cura) def getAlterationFile(filename): @@ -333,7 +394,7 @@ def getAlterationFileContents(filename): if filename == 'start.gcode': #For the start code, hack the temperature and the steps per E value into it. So the temperature is reached before the start code extrusion. #We also set our steps per E here, if configured. - eSteps = float(getPreference('steps_per_e')) + eSteps = getPreferenceFloat('steps_per_e') if eSteps > 0: prefix += 'M92 E%f\n' % (eSteps) temp = getProfileSettingFloat('print_temperature') 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