diff --git a/README b/README index 20f2b20..d371684 100644 --- a/README +++ b/README @@ -17,12 +17,12 @@ SkeinPyPy: BUILDING ======== - ./build.sh + ./package.sh The build script defaults to building for Windows. If you want to build for Mac OS X or Linux, choose one of: - ./build.sh osx64 - ./build.sh linux + ./package.sh osx64 + ./package.sh linux Note that Mac OS X currently requires the manual installation of wxPython, PySerial, and PyOpenGL: diff --git a/SkeinPyPy/avr_isp/stk500v2.py b/SkeinPyPy/avr_isp/stk500v2.py index a420bdc..e076e66 100644 --- a/SkeinPyPy/avr_isp/stk500v2.py +++ b/SkeinPyPy/avr_isp/stk500v2.py @@ -18,6 +18,8 @@ class Stk500v2(ispBase.IspBase): self.serial = Serial(port, speed, timeout=1) except SerialException as e: raise ispBase.IspError("Failed to open serial port") + except: + raise ispBase.IspError("Unexpected error while connecting to serial port:" + port + ":" + str(sys.exc_info()[0])) self.seq = 1 #Reset the controller diff --git a/SkeinPyPy/fabmetheus_utilities/fonts/gentium_basic_regular.svg b/SkeinPyPy/fabmetheus_utilities/fonts/gentium_basic_regular.svg deleted file mode 100644 index 418f023..0000000 --- a/SkeinPyPy/fabmetheus_utilities/fonts/gentium_basic_regular.svg +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/SkeinPyPy/fabmetheus_utilities/images/display_line.ppm b/SkeinPyPy/fabmetheus_utilities/images/display_line.ppm deleted file mode 100644 index 4738d03..0000000 --- a/SkeinPyPy/fabmetheus_utilities/images/display_line.ppm +++ /dev/null @@ -1,5 +0,0 @@ -P6 -# CREATOR: The GIMP's PNM Filter Version 1.0 -27 27 -255 -ZHMPRRcޫCHMRV\k\T@EKPV~~TR@FLS\\\:@GMTkYkQ:@FLS\\Q9?EKPV~~VP⿾6=BHMRV\k\VRKⱯ4:?DIMPSTSPME⢢C7<@DHKLMLKHGƩ287عѪv\Rҗϒ}ʈƃ5y0ʟeģSKMEe^DžZS3v..o)ͻѹȚ`ʠoç>7}2t,̙čOѫZML5y00q+*k&գץڻe?˹XŏӠǓɛaaFɸQ׵}ֵÑV{QɸmHHyR \ No newline at end of file diff --git a/SkeinPyPy/fabmetheus_utilities/images/zoom_out.ppm b/SkeinPyPy/fabmetheus_utilities/images/zoom_out.ppm deleted file mode 100644 index 108f893..0000000 --- a/SkeinPyPy/fabmetheus_utilities/images/zoom_out.ppm +++ /dev/null @@ -1,5 +0,0 @@ -P6 -# CREATOR: The GIMP's PNM Filter Version 1.0 -16 16 -255 -ÑҮmϩeΧcШjٻܿ԰p׷}ѯάҭw̡gӺ԰pϩţɛc‘׶}ͤqҮҭlѮħÒ[Ϩc⿋Pͦb⾇Mͧh̬xtomcbTVEH5:&.عѪvomȿ#ʟeģcbTVEH5:&.#ͻѹȚ`ʠoçܾSִy̙čOѫZMLRLۿգץڻe?˹XŏӠǓɛaaFɸQ׵}ֵÑVrEǶ\3HsK \ No newline at end of file diff --git a/SkeinPyPy/fabmetheus_utilities/settings.py b/SkeinPyPy/fabmetheus_utilities/settings.py index daa99dc..ce6f48b 100644 --- a/SkeinPyPy/fabmetheus_utilities/settings.py +++ b/SkeinPyPy/fabmetheus_utilities/settings.py @@ -18,6 +18,8 @@ def DEFSET(setting): def storedSetting(name): return lambda setting: profile.getProfileSetting(name) +def storedPreference(name): + return lambda setting: profile.getPreference(name) def ifSettingAboveZero(name): return lambda setting: float(profile.getProfileSetting(name)) > 0 @@ -25,12 +27,17 @@ def ifSettingAboveZero(name): def ifSettingIs(name, value): return lambda setting: profile.getProfileSetting(name) == value +def raftLayerCount(setting): + if profile.getProfileSetting('enable_raft') == "True": + return '1' + return '0' + def storedPercentSetting(name): return lambda setting: float(profile.getProfileSetting(name)) / 100 def calculateEdgeWidth(setting): wallThickness = float(profile.getProfileSetting('wall_thickness')) - nozzleSize = float(profile.getProfileSetting('nozzle_size')) + nozzleSize = float(profile.getPreference('nozzle_size')) if wallThickness < nozzleSize: return wallThickness @@ -49,7 +56,7 @@ def calculateShellsBase(setting): return calculateShellsImp(float(profile.getProfileSetting('wall_thickness')) + float(profile.getProfileSetting('extra_base_wall_thickness'))) def calculateShellsImp(wallThickness): - nozzleSize = float(profile.getProfileSetting('nozzle_size')) + nozzleSize = float(profile.getPreference('nozzle_size')) if wallThickness < nozzleSize: return 0 @@ -145,7 +152,7 @@ def getSkeinPyPyProfileInformation(): 'Line': ifSettingIs('infill_type', 'Line'), 'Infill_Perimeter_Overlap_ratio': storedPercentSetting('fill_overlap'), 'Infill_Solidity_ratio': storedPercentSetting('fill_density'), - 'Infill_Width': storedSetting("nozzle_size"), + 'Infill_Width': storedPreference("nozzle_size"), 'Solid_Surface_Thickness_layers': calculateSolidLayerCount, 'Start_From_Choice': DEFSET, 'Surrounding_Angle_degrees': DEFSET, @@ -161,8 +168,8 @@ def getSkeinPyPyProfileInformation(): },'speed': { 'Activate_Speed': "True", 'Add_Flow_Rate': "True", - 'Bridge_Feed_Rate_Multiplier_ratio': DEFSET, - 'Bridge_Flow_Rate_Multiplier_ratio': DEFSET, + 'Bridge_Feed_Rate_Multiplier_ratio': storedPercentSetting('bridge_speed'), + 'Bridge_Flow_Rate_Multiplier_ratio': storedPercentSetting('bridge_material_amount'), 'Duty_Cyle_at_Beginning_portion': DEFSET, 'Duty_Cyle_at_Ending_portion': DEFSET, 'Feed_Rate_mm/s': storedSetting("print_speed"), @@ -193,28 +200,28 @@ def getSkeinPyPyProfileInformation(): 'Activate_Raft': "True", 'Add_Raft,_Elevate_Nozzle,_Orbit': DEFSET, 'Base_Feed_Rate_Multiplier_ratio': DEFSET, - 'Base_Flow_Rate_Multiplier_ratio': DEFSET, + 'Base_Flow_Rate_Multiplier_ratio': storedPercentSetting('raft_base_material_amount'), 'Base_Infill_Density_ratio': DEFSET, 'Base_Layer_Thickness_over_Layer_Thickness': DEFSET, - 'Base_Layers_integer': '0', + 'Base_Layers_integer': raftLayerCount, 'Base_Nozzle_Lift_over_Base_Layer_Thickness_ratio': DEFSET, 'Initial_Circling': DEFSET, 'Infill_Overhang_over_Extrusion_Width_ratio': DEFSET, 'Interface_Feed_Rate_Multiplier_ratio': DEFSET, - 'Interface_Flow_Rate_Multiplier_ratio': DEFSET, + 'Interface_Flow_Rate_Multiplier_ratio': storedPercentSetting('raft_interface_material_amount'), 'Interface_Infill_Density_ratio': DEFSET, 'Interface_Layer_Thickness_over_Layer_Thickness': DEFSET, - 'Interface_Layers_integer': '0', + 'Interface_Layers_integer': raftLayerCount, 'Interface_Nozzle_Lift_over_Interface_Layer_Thickness_ratio': DEFSET, 'Name_of_Support_End_File': DEFSET, 'Name_of_Support_Start_File': DEFSET, 'Operating_Nozzle_Lift_over_Layer_Thickness_ratio': DEFSET, 'Raft_Additional_Margin_over_Length_%': DEFSET, - 'Raft_Margin_mm': DEFSET, + 'Raft_Margin_mm': storedSetting('raft_margin'), 'Support_Cross_Hatch': 'False', 'Support_Flow_Rate_over_Operating_Flow_Rate_ratio': storedPercentSetting('support_rate'), 'Support_Gap_over_Perimeter_Extrusion_Width_ratio': calcSupportDistanceRatio, - 'Support_Material_Choice_': storedSetting("support"), + 'Support_Material_Choice_': storedSetting('support'), 'Support_Minimum_Angle_degrees': DEFSET, },'skirt': { 'Skirt_line_count': storedSetting("skirt_line_count"), @@ -278,6 +285,7 @@ def getSkeinPyPyProfileInformation(): 'Orbital_Outset_millimeters': DEFSET, 'Turn_Fan_On_at_Beginning': DEFSET, 'Turn_Fan_Off_at_Ending': DEFSET, + 'Minimum_feed_rate_mm/s': storedSetting("cool_min_feedrate"), },'hop': { 'Activate_Hop': "False", 'Hop_Over_Layer_Thickness_ratio': DEFSET, diff --git a/SkeinPyPy/newui/advancedConfig.py b/SkeinPyPy/newui/advancedConfig.py index bb2beac..8312e8b 100644 --- a/SkeinPyPy/newui/advancedConfig.py +++ b/SkeinPyPy/newui/advancedConfig.py @@ -26,6 +26,17 @@ class advancedConfigWindow(configBase.configWindowBase): c = configBase.SettingRow(left, "Print order sequence", 'sequence', ['Loops > Perimeter > Infill', 'Loops > Infill > Perimeter', 'Infill > Loops > Perimeter', 'Infill > Perimeter > Loops', 'Perimeter > Infill > Loops', 'Perimeter > Loops > Infill'], 'Sequence of printing. The perimeter is the outer print edge, the loops are the insides of the walls, and the infill is the insides.'); c = configBase.SettingRow(left, "Force first layer sequence", 'force_first_layer_sequence', True, 'This setting forces the order of the first layer to be \'Perimeter > Loops > Infill\'') + configBase.TitleRow(left, "Cool") + c = configBase.SettingRow(left, "Minimum feedrate (mm/s)", 'cool_min_feedrate', '5', 'The minimal layer time can cause the print to slow down so much it starts to ooze. The minimal feedrate protects against this. Even if a print gets slown down it will never be slower then this minimal feedrate.') + + configBase.TitleRow(left, "Joris") + c = configBase.SettingRow(left, "Joris the outer edge", 'joris', False, '[Joris] is a code name for smoothing out the Z move of the outer edge. This will create a steady Z increase over the whole print. It is intended to be used with a single walled wall thickness to make cups/vases.') + + configBase.TitleRow(left, "Raft (if enabled)") + c = configBase.SettingRow(left, "Raft extra margin (mm)", 'raft_margin', '3.0', 'If the raft is enabled, this is the extra raft area around the object which is also rafted. Increasing this margin will create a stronger raft.') + c = configBase.SettingRow(left, "Raft base material amount (%)", 'raft_base_material_amount', '100', 'The base layer is the first layer put down as a raft. This layer has thick strong lines and is put firmly on the bed to prevent warping. This setting adjust the amount of material used for the base layer.') + c = configBase.SettingRow(left, "Raft interface material amount (%)", 'raft_interface_material_amount', '100', 'The interface layer is a weak thin layer between the base layer and the printed object. It is designed to has little material to make it easy to break the base off the printed object. This setting adjusts the amount of material used for the interface layer.') + configBase.TitleRow(right, "Infill") c = configBase.SettingRow(right, "Infill pattern", 'infill_type', ['Line', 'Grid Circular', 'Grid Hexagonal', 'Grid Rectangular'], 'Pattern of the none-solid infill. Line is default, but grids can provide a strong print.') c = configBase.SettingRow(right, "Solid infill top", 'solid_top', True, 'Create a solid top surface, if set to false the top is filled with the fill percentage. Useful for cups/vases.') @@ -35,8 +46,9 @@ class advancedConfigWindow(configBase.configWindowBase): c = configBase.SettingRow(right, "Support material amount (%)", 'support_rate', '100', 'Amount of material used for support, less material gives a weaker support structure which is easier to remove.') c = configBase.SettingRow(right, "Support distance from object (mm)", 'support_distance', '0.5', 'Distance between the support structure and the object.') - configBase.TitleRow(left, "Joris") - c = configBase.SettingRow(left, "Joris the outer edge", 'joris', False, '[Joris] is a code name for smoothing out the Z move of the outer edge. This will create a steady Z increase over the whole print. It is intended to be used with a single walled wall thickness to make cups/vases.') + configBase.TitleRow(right, "Bridge") + c = configBase.SettingRow(right, "Bridge speed (%)", 'bridge_speed', '100', 'Speed at which bridges are printed, compared to normal printing speed.') + c = configBase.SettingRow(right, "Bridge material (%)", 'bridge_material_amount', '100', 'Amount of material used for bridges, increase go extrude more material when printing a bridge.') main.Fit() self.Fit() diff --git a/SkeinPyPy/newui/configWizard.py b/SkeinPyPy/newui/configWizard.py index 1b6f2f6..7070158 100644 --- a/SkeinPyPy/newui/configWizard.py +++ b/SkeinPyPy/newui/configWizard.py @@ -97,17 +97,17 @@ class MachineSelectPage(InfoPage): profile.putPreference('machine_width', '205') profile.putPreference('machine_depth', '205') profile.putPreference('machine_height', '200') - profile.putProfileSetting('nozzle_size', '0.4') + profile.putPreference('nozzle_size', '0.4') profile.putProfileSetting('machine_center_x', '100') profile.putProfileSetting('machine_center_y', '100') else: profile.putPreference('machine_width', '80') profile.putPreference('machine_depth', '80') profile.putPreference('machine_height', '60') - profile.putProfileSetting('nozzle_size', '0.5') + profile.putPreference('nozzle_size', '0.5') profile.putProfileSetting('machine_center_x', '40') profile.putProfileSetting('machine_center_y', '40') - profile.putProfileSetting('wall_thickness', float(profile.getProfileSetting('nozzle_size')) * 2) + profile.putProfileSetting('wall_thickness', float(profile.getPreference('nozzle_size')) * 2) class FirmwareUpgradePage(InfoPage): def __init__(self, parent): diff --git a/SkeinPyPy/newui/machineCom.py b/SkeinPyPy/newui/machineCom.py index 47041ed..5f6974d 100644 --- a/SkeinPyPy/newui/machineCom.py +++ b/SkeinPyPy/newui/machineCom.py @@ -9,6 +9,8 @@ from avr_isp import stk500v2 from avr_isp import ispBase from avr_isp import intelHex +from newui import profile + try: import _winreg except: @@ -28,8 +30,11 @@ def serialList(): return baselist+glob.glob('/dev/ttyUSB*') + glob.glob('/dev/ttyACM*') +glob.glob("/dev/tty.usb*")+glob.glob("/dev/cu.*")+glob.glob("/dev/rfcomm*") class InstallFirmware(wx.Dialog): - def __init__(self, filename, port = 'AUTO'): + def __init__(self, filename, port = None): super(InstallFirmware, self).__init__(parent=None, title="Firmware install", size=(250, 100)) + if port == None: + port = profile.getPreference('serial_port') + sizer = wx.BoxSizer(wx.VERTICAL) self.progressLabel = wx.StaticText(self, -1, 'Reading firmware...') @@ -99,7 +104,11 @@ class InstallFirmware(wx.Dialog): self.Destroy() class MachineCom(): - def __init__(self, port = 'AUTO', baudrate = 250000): + def __init__(self, port = None, baudrate = None): + if port == None: + port = profile.getPreference('serial_port') + if baudrate == None: + baudrate = profile.getPreference('serial_baud') self.serial = None if port == 'AUTO': programmer = stk500v2.Stk500v2() @@ -111,9 +120,14 @@ class MachineCom(): break except ispBase.IspError: pass + except: + print "Unexpected error while connecting to serial port:" + port, sys.exc_info()[0] programmer.close() else: - self.serial = Serial(port, baudrate, timeout=5) + try: + self.serial = Serial(port, baudrate, timeout=5) + except: + print "Unexpected error while connecting to serial port:" + port, sys.exc_info()[0] def readline(self): if self.serial == None: diff --git a/SkeinPyPy/newui/mainWindow.py b/SkeinPyPy/newui/mainWindow.py index 13f0e89..84f025a 100644 --- a/SkeinPyPy/newui/mainWindow.py +++ b/SkeinPyPy/newui/mainWindow.py @@ -84,11 +84,10 @@ class mainWindow(configBase.configWindowBase): configBase.TitleRow(left, "Accuracy") c = configBase.SettingRow(left, "Layer height (mm)", 'layer_height', '0.2', 'Layer height in millimeters.\n0.2 is a good value for quick prints.\n0.1 gives high quality prints.') validators.validFloat(c, 0.0) - validators.warningAbove(c, 0.31, "Thicker layers then 0.3mm usually give bad results and are not recommended.") + validators.warningAbove(c, lambda : (float(profile.getPreference('nozzle_size')) * 80 / 100), "Thicker layers then %.2fmm (80%% nozzle size) usually give bad results and are not recommended.") c = configBase.SettingRow(left, "Wall thickness (mm)", 'wall_thickness', '0.8', 'Thickness of the walls.\nThis is used in combination with the nozzle size to define the number\nof perimeter lines and the thickness of those perimeter lines.') validators.validFloat(c, 0.0) validators.wallThicknessValidator(c) - configBase.settingNotify(c, self.preview3d.updateWallLineWidth) configBase.TitleRow(left, "Fill") c = configBase.SettingRow(left, "Bottom/Top thickness (mm)", 'solid_layer_thickness', '0.6', 'This controls the thickness of the bottom and top layers, the amount of solid layers put down is calculated by the layer thickness and this value.\nHaving this value a multiply of the layer thickness makes sense. And keep it near your wall thickness to make an evenly strong part.') @@ -106,6 +105,7 @@ class mainWindow(configBase.configWindowBase): c = configBase.SettingRow(right, "Print speed (mm/s)", 'print_speed', '50', 'Speed at which printing happens. A well adjusted Ultimaker can reach 150mm/s, but for good quality prints you want to print slower. Printing speed depends on a lot of factors. So you will be experimenting with optimal settings for this.') validators.validFloat(c, 1.0) validators.warningAbove(c, 150.0, "It is highly unlikely that your machine can achieve a printing speed above 150mm/s") + validators.printSpeedValidator(c) configBase.TitleRow(right, "Temperature") c = configBase.SettingRow(right, "Printing temperature", 'print_temperature', '0', 'Temperature used for printing. Set at 0 to pre-heat yourself') @@ -114,6 +114,7 @@ class mainWindow(configBase.configWindowBase): configBase.TitleRow(right, "Support") c = configBase.SettingRow(right, "Support type", 'support', ['None', 'Exterior Only', 'Everywhere', 'Empty Layers Only'], 'Type of support structure build.\nNone does not do any support.\nExterior only only creates support on the outside.\nEverywhere creates support even on the insides of the model.\nOnly on empty layers is for stacked objects.') + c = configBase.SettingRow(right, "Add raft", 'enable_raft', False, 'A raft is a few layers of lines below the bottom of the object. It prevents warping. Full raft settings can be found in the advanced settings.\nFor PLA this is usually not required. But if you print with ABS it is almost required.') configBase.TitleRow(right, "Filament") c = configBase.SettingRow(right, "Diameter (mm)", 'filament_diameter', '2.89', 'Diameter of your filament, as accurately as possible.\nIf you cannot measure this value you will have to callibrate it, a higher number means less extrusion, a smaller number generates more extrusion.') @@ -132,12 +133,6 @@ class mainWindow(configBase.configWindowBase): validators.validInt(c, 10) configBase.settingNotify(c, self.preview3d.updateCenterY) - configBase.TitleRow(left, "Machine nozzle") - c = configBase.SettingRow(left, "Nozzle size (mm)", 'nozzle_size', '0.4', 'The nozzle size is very important, this is used to calculate the line width of the infill, and used to calculate the amount of outside wall lines and thickness for the wall thickness you entered in the print settings.') - validators.validFloat(c, 0.1, 1.0) - configBase.settingNotify(c, self.preview3d.updateWallLineWidth) - configBase.settingNotify(c, self.preview3d.updateInfillLineWidth) - configBase.TitleRow(left, "Retraction") c = configBase.SettingRow(left, "Minimal travel (mm)", 'retraction_min_travel', '5.0', 'Minimal amount of travel needed for a retraction to happen at all. To make sure you do not get a lot of retractions in a small area') validators.validFloat(c, 0.0) @@ -213,6 +208,7 @@ class mainWindow(configBase.configWindowBase): self.updateProfileToControls() self.Fit() + self.SetMinSize(self.GetSize()) self.Centre() self.Show(True) @@ -241,7 +237,7 @@ class mainWindow(configBase.configWindowBase): prefDialog.Show(True) def OnDefaultMarlinFirmware(self, e): - machineCom.InstallFirmware(os.path.join(os.path.dirname(os.path.abspath(__file__)), "../firmware/default.hex"), profile.getPreference('serial_port')) + machineCom.InstallFirmware(os.path.join(os.path.dirname(os.path.abspath(__file__)), "../firmware/default.hex")) def OnCustomFirmware(self, e): dlg=wx.FileDialog(self, "Open firmware to upload", self.lastPath, style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST) @@ -251,7 +247,7 @@ class mainWindow(configBase.configWindowBase): if not(os.path.exists(filename)): return #For some reason my Ubuntu 10.10 crashes here. - machineCom.InstallFirmware(filename, profile.getPreference('serial_port')) + machineCom.InstallFirmware(filename) def OnFirstRunWizard(self, e): configWizard.configWizard() diff --git a/SkeinPyPy/newui/preferencesDialog.py b/SkeinPyPy/newui/preferencesDialog.py index 11369d3..539a69a 100644 --- a/SkeinPyPy/newui/preferencesDialog.py +++ b/SkeinPyPy/newui/preferencesDialog.py @@ -16,13 +16,15 @@ class preferencesDialog(configBase.configWindowBase): left, right, main = self.CreateConfigPanel(self) configBase.TitleRow(left, 'Machine settings') + c = configBase.SettingRow(left, "Nozzle size (mm)", 'nozzle_size', '0.4', 'The nozzle size is very important, this is used to calculate the line width of the infill, and used to calculate the amount of outside wall lines and thickness for the wall thickness you entered in the print settings.', type = 'preference') + validators.validFloat(c, 0.1, 1.0) c = configBase.SettingRow(left, 'Steps per E', 'steps_per_e', '0', 'Amount of steps per mm filament extrusion', type = 'preference') validators.validFloat(c, 0.1) - c = configBase.SettingRow(left, 'Machine width', 'machine_width', '205', 'Size of the machine in mm', type = 'preference') + c = configBase.SettingRow(left, 'Machine width (mm)', 'machine_width', '205', 'Size of the machine in mm', type = 'preference') validators.validFloat(c, 10.0) - c = configBase.SettingRow(left, 'Machine depth', 'machine_depth', '205', 'Size of the machine in mm', type = 'preference') + c = configBase.SettingRow(left, 'Machine depth (mm)', 'machine_depth', '205', 'Size of the machine in mm', type = 'preference') validators.validFloat(c, 10.0) - c = configBase.SettingRow(left, 'Machine height', 'machine_height', '200', 'Size of the machine in mm', type = 'preference') + c = configBase.SettingRow(left, 'Machine height (mm)', 'machine_height', '200', 'Size of the machine in mm', type = 'preference') validators.validFloat(c, 10.0) configBase.TitleRow(left, 'Communication settings') diff --git a/SkeinPyPy/newui/preview3d.py b/SkeinPyPy/newui/preview3d.py index 7611ab2..483af4d 100644 --- a/SkeinPyPy/newui/preview3d.py +++ b/SkeinPyPy/newui/preview3d.py @@ -19,7 +19,6 @@ from newui import profile from newui import gcodeInterpreter from newui import util3d -from fabmetheus_utilities import settings from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret from fabmetheus_utilities.vector3 import Vector3 @@ -34,6 +33,7 @@ class previewPanel(wx.Panel): self.init = 0 self.triangleMesh = None self.gcode = None + self.modelFilename = None self.machineSize = Vector3(float(profile.getPreference('machine_width')), float(profile.getPreference('machine_depth')), float(profile.getPreference('machine_height'))) self.machineCenter = Vector3(0, 0, 0) @@ -92,49 +92,61 @@ class previewPanel(wx.Panel): self.moveModel() self.glCanvas.Refresh() - def updateWallLineWidth(self, setting): - #TODO: this shouldn't be needed, you can calculate the line width from the E values combined with the steps_per_E and the filament diameter (reverse volumatric) - self.glCanvas.lineWidth = settings.calculateEdgeWidth(setting) - - def updateInfillLineWidth(self, setting): - #TODO: this shouldn't be needed, you can calculate the line width from the E values combined with the steps_per_E and the filament diameter (reverse volumatric) - self.glCanvas.infillLineWidth = profile.getProfileSetting('nozzle_size') - def loadModelFile(self, filename): + if self.modelFilename != filename: + self.modelFileTime = None + self.gcodeFileTime = None + self.logFileTime = None + self.modelFilename = filename self.gcodeFilename = filename[: filename.rfind('.')] + "_export.gcode" + self.logFilename = filename[: filename.rfind('.')] + "_export.log" #Do the STL file loading in a background thread so we don't block the UI. - thread = threading.Thread(target=self.DoModelLoad) - thread.start() - - def loadGCodeFile(self, filename): - self.gcodeFilename = filename - #Do the STL file loading in a background thread so we don't block the UI. - thread = threading.Thread(target=self.DoGCodeLoad) - thread.start() + threading.Thread(target=self.doFileLoad).start() - def DoModelLoad(self): - self.modelDirty = False - triangleMesh = fabmetheus_interpret.getCarving(self.modelFilename) - triangleMesh.origonalVertexes = list(triangleMesh.vertexes) - for i in xrange(0, len(triangleMesh.origonalVertexes)): - triangleMesh.origonalVertexes[i] = triangleMesh.origonalVertexes[i].copy() - triangleMesh.getMinimumZ() - self.triangleMesh = triangleMesh - self.updateModelTransform() - wx.CallAfter(self.updateToolbar) - wx.CallAfter(self.glCanvas.Refresh) + def loadReModelFile(self, filename): + #Only load this again if the filename matches the file we have already loaded (for auto loading GCode after slicing) + if self.modelFilename != filename: + return + threading.Thread(target=self.doFileLoad).start() + + def doFileLoad(self): + if os.path.isfile(self.modelFilename) and self.modelFileTime != os.stat(self.modelFilename).st_mtime: + self.modelFileTime = os.stat(self.modelFilename).st_mtime + triangleMesh = fabmetheus_interpret.getCarving(self.modelFilename) + triangleMesh.origonalVertexes = list(triangleMesh.vertexes) + for i in xrange(0, len(triangleMesh.origonalVertexes)): + triangleMesh.origonalVertexes[i] = triangleMesh.origonalVertexes[i].copy() + triangleMesh.getMinimumZ() + self.modelDirty = False + self.errorList = [] + self.triangleMesh = triangleMesh + self.updateModelTransform() + wx.CallAfter(self.updateToolbar) + wx.CallAfter(self.glCanvas.Refresh) - if os.path.isfile(self.gcodeFilename): - self.DoGCodeLoad() - - def DoGCodeLoad(self): - gcode = gcodeInterpreter.gcode(self.gcodeFilename) - self.gcodeDirty = False - self.gcode = gcode - self.gcodeDirty = True - wx.CallAfter(self.updateToolbar) - wx.CallAfter(self.glCanvas.Refresh) + if os.path.isfile(self.gcodeFilename) and self.gcodeFileTime != os.stat(self.gcodeFilename).st_mtime: + self.gcodeFileTime = os.stat(self.gcodeFilename).st_mtime + gcode = gcodeInterpreter.gcode(self.gcodeFilename) + self.gcodeDirty = False + self.errorList = [] + self.gcode = gcode + self.gcodeDirty = True + wx.CallAfter(self.updateToolbar) + wx.CallAfter(self.glCanvas.Refresh) + elif not os.path.isfile(self.gcodeFilename): + self.gcode = None + + if os.path.isfile(self.logFilename): + errorList = [] + for line in open(self.logFilename, "rt"): + res = re.search('Model error\(([a-z ]*)\): \(([0-9\.\-e]*), ([0-9\.\-e]*), ([0-9\.\-e]*)\) \(([0-9\.\-e]*), ([0-9\.\-e]*), ([0-9\.\-e]*)\)', line) + if res != None: + v1 = util3d.Vector3(float(res.group(2)), float(res.group(3)), float(res.group(4))) + v2 = util3d.Vector3(float(res.group(5)), float(res.group(6)), float(res.group(7))) + errorList.append([v1, v2]) + self.errorList = errorList + wx.CallAfter(self.glCanvas.Refresh) def updateToolbar(self): self.layerSpin.Show(self.gcode != None) @@ -216,8 +228,6 @@ class PreviewGLCanvas(glcanvas.GLCanvas): self.zoom = 150 self.offsetX = 0 self.offsetY = 0 - self.lineWidth = 0.4 - self.fillLineWidth = 0.4 self.view3D = True self.modelDisplayList = None self.gcodeDisplayList = None @@ -315,7 +325,22 @@ class PreviewGLCanvas(glcanvas.GLCanvas): 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.getPreference('nozzle_size')) / 2 + + curLayerNum = 0 for path in self.parent.gcode.pathList: + if path['layerNr'] != curLayerNum: + prevLayerZ = curLayerZ + curLayerZ = path['list'][1].z + curLayerNum = path['layerNr'] + layerThickness = curLayerZ - prevLayerZ + c = 1.0 if path['layerNr'] != self.parent.layerSpin.GetValue(): if path['layerNr'] < self.parent.layerSpin.GetValue(): @@ -336,17 +361,16 @@ class PreviewGLCanvas(glcanvas.GLCanvas): if path['type'] == 'retract': glColor3f(0,c,c) if c > 0.4 and path['type'] == 'extrude': - if path['pathType'] == 'FILL': - lineWidth = self.fillLineWidth / 2 - else: - lineWidth = self.lineWidth / 2 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: - extrusionMMperDist = (v1.e - v0.e) / (v0 - v1).vsize() / self.parent.gcode.stepsPerE - #TODO: Calculate line width from ePerDistance (needs layer thickness, steps_per_E and filament diameter) + if dist > 0 and layerThickness > 0: + extrusionMMperDist = (v1.e - v0.e) / (v0 - v1).vsize() + lineWidth = extrusionMMperDist * filamentArea / layerThickness / 2 + normal = (v0 - v1).cross(util3d.Vector3(0,0,1)) normal.normalize() v2 = v0 + normal * lineWidth @@ -394,12 +418,12 @@ class PreviewGLCanvas(glcanvas.GLCanvas): modelSize = self.parent.triangleMesh.getCarveCornerMaximum() - self.parent.triangleMesh.getCarveCornerMinimum() glNewList(self.modelDisplayList, GL_COMPILE) glPushMatrix() - glTranslate(-(modelSize.x+self.lineWidth*15)*(multiX-1)/2,-(modelSize.y+self.lineWidth*15)*(multiY-1)/2, 0) + glTranslate(-(modelSize.x+10)*(multiX-1)/2,-(modelSize.y+10)*(multiY-1)/2, 0) for mx in xrange(0, multiX): for my in xrange(0, multiY): for face in self.parent.triangleMesh.faces: glPushMatrix() - glTranslate((modelSize.x+self.lineWidth*15)*mx,(modelSize.y+self.lineWidth*15)*my, 0) + glTranslate((modelSize.x+10)*mx,(modelSize.y+10)*my, 0) glBegin(GL_TRIANGLES) v1 = self.parent.triangleMesh.vertexes[face.vertexIndexes[0]] v2 = self.parent.triangleMesh.vertexes[face.vertexIndexes[1]] @@ -475,6 +499,16 @@ class PreviewGLCanvas(glcanvas.GLCanvas): elif self.viewMode == "Model - Normal": glEnable(GL_LIGHTING) glCallList(self.modelDisplayList) + + glDisable(GL_LIGHTING) + glDisable(GL_DEPTH_TEST) + glColor3f(1,0,0) + glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 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() glFlush() def InitGL(self): diff --git a/SkeinPyPy/newui/profile.py b/SkeinPyPy/newui/profile.py index f672bc8..3c1ec2a 100644 --- a/SkeinPyPy/newui/profile.py +++ b/SkeinPyPy/newui/profile.py @@ -21,7 +21,6 @@ profileDefaultSettings = { 'filament_density': '1.00', 'machine_center_x': '100', 'machine_center_y': '100', - 'nozzle_size': '0.4', 'retraction_min_travel': '5.0', 'retraction_speed': '13.5', 'retraction_amount': '0.0', @@ -46,6 +45,13 @@ profileDefaultSettings = { 'support_rate': '100', 'support_distance': '0.5', 'joris': 'False', + 'enable_raft': 'False', + 'cool_min_feedrate': '5', + 'bridge_speed': '100', + 'bridge_material_amount': '100', + 'raft_margin': '5', + 'raft_base_material_amount': '100', + 'raft_interface_material_amount': '100', } preferencesDefaultSettings = { 'wizardDone': 'False', @@ -53,6 +59,7 @@ preferencesDefaultSettings = { 'machine_width': '205', 'machine_depth': '205', 'machine_height': '200', + 'nozzle_size': '0.4', 'steps_per_e': '0', 'serial_port': 'AUTO', 'serial_baud': '250000', diff --git a/SkeinPyPy/newui/sliceProgessPanel.py b/SkeinPyPy/newui/sliceProgessPanel.py index 59b095a..8d2a83e 100644 --- a/SkeinPyPy/newui/sliceProgessPanel.py +++ b/SkeinPyPy/newui/sliceProgessPanel.py @@ -75,14 +75,16 @@ class sliceProgessPanel(wx.Panel): def OnSliceDone(self, result): self.progressGauge.Destroy() + self.abortButton.Destroy() self.progressLog = result.progressLog - self.sizer.Remove(self.abortButton) self.logButton = wx.Button(self, -1, "Show Log") + self.abortButton = wx.Button(self, -1, "X", style=wx.BU_EXACTFIT) self.Bind(wx.EVT_BUTTON, self.OnShowLog, self.logButton) + self.Bind(wx.EVT_BUTTON, self.OnAbort, self.abortButton) self.sizer.Add(self.logButton, 0) if result.returnCode == 0: self.statusText.SetLabel("Ready.") - self.showButton = wx.Button(self, -1, "Show GCode") + self.showButton = wx.Button(self, -1, "Show result") self.Bind(wx.EVT_BUTTON, self.OnShowGCode, self.showButton) self.sizer.Add(self.showButton, 0) else: @@ -91,6 +93,7 @@ class sliceProgessPanel(wx.Panel): self.sizer.Layout() self.Layout() self.abort = True + self.mainWindow.preview3d.loadReModelFile(self.filename) def SetProgress(self, stepName, layer, maxLayer): if self.prevStep != stepName: @@ -133,7 +136,6 @@ class WorkerThread(threading.Thread): return line = p.stdout.readline() self.returnCode = p.wait() - self.gcodeFilename = self.filename[: self.filename.rfind('.')] + "_export.gcode" logfile = open(self.filename[: self.filename.rfind('.')] + "_export.log", "w") for logLine in self.progressLog: logfile.write(logLine) diff --git a/SkeinPyPy/newui/validators.py b/SkeinPyPy/newui/validators.py index d9d141a..79a3d31 100644 --- a/SkeinPyPy/newui/validators.py +++ b/SkeinPyPy/newui/validators.py @@ -1,6 +1,9 @@ from __future__ import absolute_import import __init__ +import types +import math + from newui import profile SUCCESS = 0 @@ -53,8 +56,12 @@ class warningAbove(): def validate(self): try: f = float(self.setting.GetValue()) - if f >= self.minValueForWarning: - return WARNING, self.warningMessage + if isinstance(self.minValueForWarning, types.FunctionType): + if f >= self.minValueForWarning(): + return WARNING, self.warningMessage % (self.minValueForWarning()) + else: + if f >= self.minValueForWarning: + return WARNING, self.warningMessage return SUCCESS, '' except ValueError: #We already have an error by the int/float validator in this case. @@ -68,7 +75,7 @@ class wallThicknessValidator(): def validate(self): try: wallThickness = float(self.setting.GetValue()) - nozzleSize = float(profile.getProfileSetting('nozzle_size')) + nozzleSize = float(profile.getPreference('nozzle_size')) if wallThickness <= nozzleSize * 0.5: return ERROR, 'Trying to print walls thinner then the half of your nozzle size, this will not produce anything usable' if wallThickness <= nozzleSize * 0.85: @@ -86,3 +93,27 @@ class wallThicknessValidator(): #We already have an error by the int/float validator in this case. return SUCCESS, '' +class printSpeedValidator(): + def __init__(self, setting): + self.setting = setting + self.setting.validators.append(self) + + def validate(self): + try: + nozzleSize = float(profile.getPreference('nozzle_size')) + layerHeight = float(profile.getProfileSetting('layer_height')) + printSpeed = float(profile.getProfileSetting('print_speed')) + + printVolumePerMM = layerHeight * nozzleSize + printVolumePerSecond = printVolumePerMM * printSpeed + #Using 10mm3 per second with a 0.4mm nozzle (normal max according to Joergen Geerds) + maxPrintVolumePerSecond = 10 / (math.pi*(0.2*0.2)) * (math.pi*(nozzleSize/2*nozzleSize/2)) + + if printVolumePerSecond > maxPrintVolumePerSecond: + return WARNING, 'You are trying to print more then %.1fmm^3 of filament per second. This might cause filament slipping. (You are printing at %0.1fmm^3 per second)' % (maxPrintVolumePerSecond, printVolumePerSecond) + + return SUCCESS, '' + except ValueError: + #We already have an error by the int/float validator in this case. + return SUCCESS, '' + diff --git a/SkeinPyPy/skeinforge_application/alterations/end.gcode b/SkeinPyPy/skeinforge_application/alterations/end.gcode index 7d83d9b..7131e56 100644 --- a/SkeinPyPy/skeinforge_application/alterations/end.gcode +++ b/SkeinPyPy/skeinforge_application/alterations/end.gcode @@ -1,7 +1,8 @@ M104 S0 ;extruder heat off -M106 ;fan on G91 ;relative positioning G1 Z+10 E-5 F400 ;move Z up a bit and retract filament by 5mm G28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way M84 ;steppers off G90 ;absolute positioning +M107 ;fan off + diff --git a/SkeinPyPy/skeinforge_application/skeinforge_plugins/craft_plugins/carve.py b/SkeinPyPy/skeinforge_application/skeinforge_plugins/craft_plugins/carve.py index 5a01ede..af138a9 100644 --- a/SkeinPyPy/skeinforge_application/skeinforge_plugins/craft_plugins/carve.py +++ b/SkeinPyPy/skeinforge_application/skeinforge_plugins/craft_plugins/carve.py @@ -214,6 +214,16 @@ class CarveSkein: mat01 =-math.sin(rotate) * scaleY 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)): carving.vertexes[i] = Vector3( diff --git a/SkeinPyPy/skeinforge_application/skeinforge_plugins/craft_plugins/cool.py b/SkeinPyPy/skeinforge_application/skeinforge_plugins/craft_plugins/cool.py index d8572d3..419eab9 100644 --- a/SkeinPyPy/skeinforge_application/skeinforge_plugins/craft_plugins/cool.py +++ b/SkeinPyPy/skeinforge_application/skeinforge_plugins/craft_plugins/cool.py @@ -161,6 +161,8 @@ class CoolRepository: self.turnFanOnAtBeginning = settings.BooleanSetting().getFromValue('Turn Fan On at Beginning', self, True) self.turnFanOffAtEnding = settings.BooleanSetting().getFromValue('Turn Fan Off at Ending', self, True) self.executeTitle = 'Cool' + + self.minimumFeedRate = settings.FloatSpin().getFromValue(0.0, 'Minimum feed rate (mm/s):', self, 10.0, 5.0) def execute(self): 'Cool button has been clicked.' @@ -188,6 +190,7 @@ class CoolSkein: self.oldFlowRateString = None self.oldLocation = None self.oldTemperature = None + self.minFeedrate = 0 def addCoolOrbits(self, remainingOrbitTime): 'Add the minimum radius cool orbits.' @@ -245,7 +248,7 @@ class CoolSkein: def getCoolMove(self, line, location, splitLine): 'Get cool line according to time spent on layer.' self.feedRateMinute = gcodec.getFeedRateMinute(self.feedRateMinute, splitLine) - return self.distanceFeedRate.getLineWithFeedRate(self.multiplier * self.feedRateMinute, line, splitLine) + return self.distanceFeedRate.getLineWithFeedRate(max(self.minFeedrate, self.multiplier * self.feedRateMinute), line, splitLine) def getCraftedGcode(self, gcodeText, repository): 'Parse gcode text and store the cool gcode.' @@ -255,6 +258,7 @@ class CoolSkein: self.halfCorner = complex(repository.minimumOrbitalRadius.value, repository.minimumOrbitalRadius.value) self.lines = archive.getTextLines(gcodeText) self.minimumArea = 4.0 * repository.minimumOrbitalRadius.value * repository.minimumOrbitalRadius.value + self.minFeedrate = repository.minimumFeedRate.value * 60 self.parseInitialization() self.boundingRectangle = gcodec.BoundingRectangle().getFromGcodeLines(self.lines[self.lineIndex :], 0.5 * self.edgeWidth) margin = 0.2 * self.edgeWidth diff --git a/SkeinPyPy/skeinforge_application/skeinforge_plugins/craft_plugins/export_plugins/static_plugins/gcode_small.py b/SkeinPyPy/skeinforge_application/skeinforge_plugins/craft_plugins/export_plugins/static_plugins/gcode_small.py index a09e0a0..8837e37 100644 --- a/SkeinPyPy/skeinforge_application/skeinforge_plugins/craft_plugins/export_plugins/static_plugins/gcode_small.py +++ b/SkeinPyPy/skeinforge_application/skeinforge_plugins/craft_plugins/export_plugins/static_plugins/gcode_small.py @@ -68,6 +68,7 @@ class GcodeSmallSkein: self.lastFeedRateString = None self.lastZString = None self.output = cStringIO.StringIO() + self.layerNr = 0 def getCraftedGcode( self, gcodeText ): "Parse gcode text and store the gcode." @@ -125,4 +126,7 @@ class GcodeSmallSkein: self.output.write(';TYPE:FILL\n'); elif line.startswith('('): self.output.write(';TYPE:CUSTOM\n'); + elif line.startswith('('): + self.output.write(';LAYER:%d\n' % (self.layerNr)); + self.layerNr += 1 diff --git a/scripts/osx64/pronterface.sh b/scripts/osx64/pronterface.sh index 83896aa..86e7c29 100755 --- a/scripts/osx64/pronterface.sh +++ b/scripts/osx64/pronterface.sh @@ -1,19 +1,19 @@ #!/bin/bash -python2.7 -c 'import wx' +python -c 'import wx' if [ $? != 0 ]; then echo "Requires wx. Download and install (the Cocoa/64-bit variant) from:" echo " http://www.wxpython.org/download.php" exit 1 fi -python2.7 -c 'import serial' +python -c 'import serial' if [ $? != 0 ]; then echo "Requires pyserial." - echo " sudo easy_install-2.7 pyserial" + echo " sudo easy_install pyserial" exit 1 fi SCRIPT_DIR=`dirname $0` -python2.7 ${SCRIPT_DIR}/Printrun/pronterface.py +python ${SCRIPT_DIR}/Printrun/pronterface.py diff --git a/scripts/osx64/skeinpypy.sh b/scripts/osx64/skeinpypy.sh index 736b301..14e10fb 100755 --- a/scripts/osx64/skeinpypy.sh +++ b/scripts/osx64/skeinpypy.sh @@ -1,26 +1,26 @@ #!/bin/bash -python2.7 -c 'import OpenGL' +python -c 'import OpenGL' if [ $? != 0 ]; then echo "Requires PyOpenGL" - echo " sudo easy_install-2.7 PyOpenGL" + echo " sudo easy_install PyOpenGL" exit 1 fi -python2.7 -c 'import wx' +python -c 'import wx' if [ $? != 0 ]; then echo "Requires wx. Download and install (the Cocoa/64-bit variant) from:" echo " http://www.wxpython.org/download.php" exit 1 fi -python2.7 -c 'import serial' +python -c 'import serial' if [ $? != 0 ]; then echo "Requires pyserial." - echo " sudo easy_install-2.7 pyserial" + echo " sudo easy_install pyserial" exit 1 fi SCRIPT_DIR=`dirname $0` -python2.7 ${SCRIPT_DIR}/SkeinPyPy/skeinpypy.py +python ${SCRIPT_DIR}/SkeinPyPy/skeinpypy.py