public void TopBottomWalkingTest() { { var planes = new List <(double z, bool bottom)>() { (0, false), (5, true), (10, false), }; int bottom = SupportGenerator.GetNextBottom(0, planes); Assert.AreEqual(1, bottom); int bottom1 = SupportGenerator.GetNextBottom(1, planes); Assert.AreEqual(0, bottom); } { var planes = new List <(double z, bool bottom)>() { (10, false), (10, true), (20, false) }; int bottom = SupportGenerator.GetNextBottom(0, planes); Assert.AreEqual(0, bottom); } }
public void TopBottomWalkingTest() { // a box in the air { var planes = new List <(double z, bool bottom)>() { (0, false), // top at 0 (the bed) (5, true), // bottom at 5 (the bottom of a box) (10, false), // top at 10 (the top of the box) }; int bottom = SupportGenerator.GetNextBottom(0, planes, 0); Assert.AreEqual(1, bottom); // we get the bottom int bottom1 = SupportGenerator.GetNextBottom(1, planes, 0); Assert.AreEqual(-1, bottom1, "There are no more bottoms so we get back a -1."); } // two boxes, the bottom touching the bed, the top touching the bottom { var planes = new List <(double z, bool bottom)>() { (0, false), // top at 0 (the bed) (0, true), // bottom at 0 (box a on bed) (10, false), // top at 10 (box a top) (10, true), // bottom at 10 (box b bottom) (20, false) // top at 20 (box b top) }; int bottom = SupportGenerator.GetNextBottom(0, planes, 0); Assert.AreEqual(-1, bottom, "The boxes are sitting on the bed and no support is required"); } // two boxes, the bottom touching the bed, the top inside the bottom { var planes = new List <(double z, bool bottom)>() { (0, false), // top at 0 (the bed) (0, true), // bottom at 0 (box a on bed) (5, true), // bottom at 5 (box b bottom) (10, false), // top at 10 (box a top) (20, false) // top at 20 (box b top) }; int bottom = SupportGenerator.GetNextBottom(0, planes, 0); Assert.AreEqual(-1, bottom, "The boxes are sitting on the bed and no support is required"); } // get next top skips any tops before checking for bottom { var planes = new List <(double z, bool bottom)>() { (0, false), (5, true), (10, false), (20, false), (25, true) }; int top = SupportGenerator.GetNextTop(0, planes, 0); Assert.AreEqual(3, top); } }
public async Task SupportsFromBedTests() { var minimumSupportHeight = .05; // Set the static data to point to the directory of MatterControl AggContext.StaticData = new FileSystemStaticData(TestContext.CurrentContext.ResolveProjectPath(4, "StaticData")); MatterControlUtilities.OverrideAppDataLocation(TestContext.CurrentContext.ResolveProjectPath(4)); // make a single cube in the air and ensure that support is generated // _________ // | | // | | // |_______| // //______________ { InteractiveScene scene = new InteractiveScene(); var cube = await CubeObject3D.Create(20, 20, 20); var aabb = cube.GetAxisAlignedBoundingBox(); // move it so the bottom is 15 above the bed cube.Matrix = Matrix4X4.CreateTranslation(0, 0, -aabb.MinXYZ.Z + 15); scene.Children.Add(cube); var supportGenerator = new SupportGenerator(scene, minimumSupportHeight); supportGenerator.SupportType = SupportGenerator.SupportGenerationType.From_Bed; await supportGenerator.Create(null, CancellationToken.None); Assert.Greater(scene.Children.Count, 1, "We should have added some support"); foreach (var support in scene.Children.Where(i => i.OutputType == PrintOutputTypes.Support)) { Assert.AreEqual(0, support.GetAxisAlignedBoundingBox().MinXYZ.Z, .001, "Support columns are all on the bed"); Assert.AreEqual(15, support.GetAxisAlignedBoundingBox().ZSize, .02, "Support columns should be the right height from the bed"); } } // make a single cube in the bed and ensure that no support is generated // _________ // | | // __| |__ // |_______| // { InteractiveScene scene = new InteractiveScene(); var cube = await CubeObject3D.Create(20, 20, 20); var aabb = cube.GetAxisAlignedBoundingBox(); // move it so the bottom is 15 above the bed cube.Matrix = Matrix4X4.CreateTranslation(0, 0, -aabb.MinXYZ.Z - 5); scene.Children.Add(cube); var supportGenerator = new SupportGenerator(scene, minimumSupportHeight); supportGenerator.SupportType = SupportGenerator.SupportGenerationType.From_Bed; await supportGenerator.Create(null, CancellationToken.None); Assert.AreEqual(1, scene.Children.Count, "We should not have added any support"); } // make a cube on the bed and single cube in the air and ensure that support is not generated // _________ // | | // | | // |_______| // _________ // | | // | | //___|_______|___ { InteractiveScene scene = new InteractiveScene(); var cubeOnBed = await CubeObject3D.Create(20, 20, 20); var aabbBed = cubeOnBed.GetAxisAlignedBoundingBox(); // move it so the bottom is 15 above the bed cubeOnBed.Matrix = Matrix4X4.CreateTranslation(0, 0, -aabbBed.MinXYZ.Z - 5); scene.Children.Add(cubeOnBed); var cubeInAir = await CubeObject3D.Create(20, 20, 20); var aabbAir = cubeInAir.GetAxisAlignedBoundingBox(); // move it so the bottom is 15 above the bed cubeInAir.Matrix = Matrix4X4.CreateTranslation(0, 0, -aabbAir.MinXYZ.Z + 25); scene.Children.Add(cubeInAir); var supportGenerator = new SupportGenerator(scene, minimumSupportHeight); supportGenerator.SupportType = SupportGenerator.SupportGenerationType.From_Bed; await supportGenerator.Create(null, CancellationToken.None); Assert.AreEqual(2, scene.Children.Count, "We should not have added support"); } // make a single cube in the bed and another cube on top, ensure that no support is generated // _________ // | | // | | // |_______| // _________ // | | // __| |__ // |_______| // { InteractiveScene scene = new InteractiveScene(); var cubeOnBed = await CubeObject3D.Create(20, 20, 20); var aabbBed = cubeOnBed.GetAxisAlignedBoundingBox(); // move it so the bottom is 15 above the bed cubeOnBed.Matrix = Matrix4X4.CreateTranslation(0, 0, -aabbBed.MinXYZ.Z); scene.Children.Add(cubeOnBed); var cubeInAir = await CubeObject3D.Create(20, 20, 20); var aabbAir = cubeInAir.GetAxisAlignedBoundingBox(); // move it so the bottom is 15 above the bed cubeInAir.Matrix = Matrix4X4.CreateTranslation(0, 0, -aabbAir.MinXYZ.Z + 25); scene.Children.Add(cubeInAir); var supportGenerator = new SupportGenerator(scene, minimumSupportHeight); supportGenerator.SupportType = SupportGenerator.SupportGenerationType.From_Bed; await supportGenerator.Create(null, CancellationToken.None); Assert.AreEqual(2, scene.Children.Count, "We should not have added support"); } // make a cube on the bed and another cube exactly on top of it and ensure that support is not generated // _________ // | | // | | // |_______| // | | // | | //___|_______|___ { InteractiveScene scene = new InteractiveScene(); var cubeOnBed = await CubeObject3D.Create(20, 20, 20); var aabbBed = cubeOnBed.GetAxisAlignedBoundingBox(); // move it so the bottom is 15 above the bed cubeOnBed.Matrix = Matrix4X4.CreateTranslation(0, 0, -aabbBed.MinXYZ.Z); scene.Children.Add(cubeOnBed); var cubeInAir = await CubeObject3D.Create(20, 20, 20); var aabbAir = cubeInAir.GetAxisAlignedBoundingBox(); // move it so the bottom is 15 above the bed cubeInAir.Matrix = Matrix4X4.CreateTranslation(0, 0, -aabbAir.MinXYZ.Z + 20); scene.Children.Add(cubeInAir); var supportGenerator = new SupportGenerator(scene, minimumSupportHeight); supportGenerator.SupportType = SupportGenerator.SupportGenerationType.From_Bed; await supportGenerator.Create(null, CancellationToken.None); Assert.AreEqual(2, scene.Children.Count, "We should not have added support"); } // make a cube on the bed and single cube in the air that intersects it and ensure that support is not generated // _________ // | | // |______ | // top cube actually exactly on top of bottom cube // ||______|| // | | //___|_______|___ { InteractiveScene scene = new InteractiveScene(); var cubeOnBed = await CubeObject3D.Create(20, 20, 20); var aabbBed = cubeOnBed.GetAxisAlignedBoundingBox(); // move it so the bottom is 15 above the bed cubeOnBed.Matrix = Matrix4X4.CreateTranslation(0, 0, -aabbBed.MinXYZ.Z); scene.Children.Add(cubeOnBed); var cubeInAir = await CubeObject3D.Create(20, 20, 20); var aabbAir = cubeInAir.GetAxisAlignedBoundingBox(); // move it so the bottom is 15 above the bed cubeInAir.Matrix = Matrix4X4.CreateTranslation(0, 0, -aabbAir.MinXYZ.Z + 15); scene.Children.Add(cubeInAir); var supportGenerator = new SupportGenerator(scene, minimumSupportHeight); supportGenerator.SupportType = SupportGenerator.SupportGenerationType.From_Bed; await supportGenerator.Create(null, CancellationToken.None); Assert.AreEqual(2, scene.Children.Count, "We should not have added support"); } // Make a cube on the bed and single cube in the air that intersects it. // SELECT the cube on top // Ensure that support is not generated. // _________ // | | // |______ | // top cube actually exactly on top of bottom cube // ||______|| // | | //___|_______|___ { InteractiveScene scene = new InteractiveScene(); var cubeOnBed = await CubeObject3D.Create(20, 20, 20); var aabbBed = cubeOnBed.GetAxisAlignedBoundingBox(); // move it so the bottom is 15 above the bed cubeOnBed.Matrix = Matrix4X4.CreateTranslation(0, 0, -aabbBed.MinXYZ.Z); scene.Children.Add(cubeOnBed); var cubeInAir = await CubeObject3D.Create(20, 20, 20); var aabbAir = cubeInAir.GetAxisAlignedBoundingBox(); // move it so the bottom is 15 above the bed cubeInAir.Matrix = Matrix4X4.CreateTranslation(0, 0, -aabbAir.MinXYZ.Z + 15); scene.Children.Add(cubeInAir); scene.SelectedItem = cubeInAir; var supportGenerator = new SupportGenerator(scene, minimumSupportHeight); supportGenerator.SupportType = SupportGenerator.SupportGenerationType.From_Bed; await supportGenerator.Create(null, CancellationToken.None); Assert.AreEqual(2, scene.Children.Count, "We should not have added support"); } // Make a cube above the bed and a second above that. Ensure only one set of support material // _________ // | | // | | // |_______| // _________ // | | // | | // |_______| //_______________ { InteractiveScene scene = new InteractiveScene(); var cube5AboveBed = await CubeObject3D.Create(20, 20, 20); var aabb5Above = cube5AboveBed.GetAxisAlignedBoundingBox(); // move it so the bottom is 15 above the bed cube5AboveBed.Matrix = Matrix4X4.CreateTranslation(0, 0, -aabb5Above.MinXYZ.Z + 5); scene.Children.Add(cube5AboveBed); var cube30AboveBed = await CubeObject3D.Create(20, 20, 20); var aabb30Above = cube30AboveBed.GetAxisAlignedBoundingBox(); // move it so the bottom is 15 above the bed cube30AboveBed.Matrix = Matrix4X4.CreateTranslation(0, 0, -aabb30Above.MinXYZ.Z + 30); scene.Children.Add(cube30AboveBed); var supportGenerator = new SupportGenerator(scene, minimumSupportHeight); supportGenerator.SupportType = SupportGenerator.SupportGenerationType.From_Bed; await supportGenerator.Create(null, CancellationToken.None); Assert.Greater(scene.Children.Count, 1, "We should have added some support"); foreach (var support in scene.Children.Where(i => i.OutputType == PrintOutputTypes.Support)) { Assert.AreEqual(0, support.GetAxisAlignedBoundingBox().MinXYZ.Z, .001, "Support columns are all on the bed"); Assert.AreEqual(5, support.GetAxisAlignedBoundingBox().ZSize, .02, "Support columns should be the right height from the bed"); } } }
/// <summary> /// Validates the printer settings satisfy all requirements /// </summary> /// <param name="printer">The printer to validate</param> /// <returns>A list of all warnings and errors</returns> public static List <ValidationError> ValidateSettings(this PrinterConfig printer, SettingsContext settings = null) { if (settings == null) { settings = new SettingsContext(printer, null, NamedSettingsLayers.All); } var errors = new List <ValidationError>(); var extruderCount = settings.GetValue <int>(SettingsKey.extruder_count); // last let's check if there is any support in the scene and if it looks like it is needed var supportGenerator = new SupportGenerator(printer.Bed.Scene); if (supportGenerator.RequiresSupport()) { errors.Add(new ValidationError() { Error = "Unsupported Parts Detected".Localize(), Details = "Some parts are unsupported and require support structures to print correctly".Localize(), ErrorLevel = ValidationErrorLevel.Warning, FixAction = new NamedAction() { Title = "Generate Supports".Localize(), Action = () => { // Find and InvokeClick on the Generate Supports toolbar button var sharedParent = ApplicationController.Instance.DragDropData.View3DWidget.Parents <GuiWidget>().FirstOrDefault(w => w.Name == "View3DContainerParent"); if (sharedParent != null) { var supportsPopup = sharedParent.FindDescendant("Support SplitButton"); supportsPopup.InvokeClick(); } } } }); } try { if (settings.GetValue <bool>(SettingsKey.validate_layer_height)) { if (settings.GetValue <double>(SettingsKey.layer_height) > settings.GetValue <double>(SettingsKey.nozzle_diameter)) { errors.Add( new SettingsValidationError(SettingsKey.layer_height) { Error = "{0} must be less than or equal to the {1}.".Localize().FormatWith( GetSettingsName(SettingsKey.layer_height), GetSettingsName(SettingsKey.nozzle_diameter)), ValueDetails = "{0} = {1}\n{2} = {3}".FormatWith( GetSettingsName(SettingsKey.layer_height), settings.GetValue <double>(SettingsKey.layer_height), GetSettingsName(SettingsKey.nozzle_diameter), settings.GetValue <double>(SettingsKey.nozzle_diameter)), }); } else if (settings.GetValue <double>(SettingsKey.layer_height) <= 0) { errors.Add( new SettingsValidationError(SettingsKey.layer_height) { Error = "{0} must be greater than 0.".Localize().FormatWith(GetSettingsName(SettingsKey.layer_height)), }); } if (settings.GetValue <double>(SettingsKey.first_layer_height) > settings.GetValue <double>(SettingsKey.nozzle_diameter)) { errors.Add( new SettingsValidationError(SettingsKey.first_layer_height) { Error = "{0} must be less than or equal to the {1}.".Localize().FormatWith( GetSettingsName(SettingsKey.first_layer_height), GetSettingsName(SettingsKey.nozzle_diameter)), ValueDetails = "{0} = {1}\n{2} = {3}".FormatWith( GetSettingsName(SettingsKey.first_layer_height), settings.GetValue <double>(SettingsKey.first_layer_height), GetSettingsName(SettingsKey.nozzle_diameter), settings.GetValue <double>(SettingsKey.nozzle_diameter)), }); } } string[] startGCode = settings.GetValue(SettingsKey.start_gcode).Replace("\\n", "\n").Split('\n'); // Print recovery can only work with a manually leveled or software leveled bed. Hardware leveling does not work. if (settings.GetValue <bool>(SettingsKey.recover_is_enabled)) { foreach (string startGCodeLine in startGCode) { if (startGCodeLine.StartsWith("G29")) { errors.Add( new SettingsValidationError(SettingsKey.start_gcode) { Error = "Start G-Code cannot contain G29 if Print Recovery is enabled.".Localize(), Details = "Your Start G-Code should not contain a G29 if you are planning on using Print Recovery. Change your start G-Code or turn off Print Recovery.".Localize(), }); } if (startGCodeLine.StartsWith("G30")) { errors.Add( new SettingsValidationError(SettingsKey.start_gcode) { Error = "Start G-Code cannot contain G30 if Print Leveling is enabled.".Localize(), Details = "Your Start G-Code should not contain a G30 if you are planning on using Print Recovery. Change your start G-Code or turn off Print Recovery.".Localize(), }); } } } // If we have print leveling turned on then make sure we don't have any leveling commands in the start gcode. if (settings.GetValue <bool>(SettingsKey.print_leveling_enabled)) { foreach (string startGCodeLine in startGCode) { if (startGCodeLine.StartsWith("G29")) { errors.Add( new SettingsValidationError(SettingsKey.start_gcode) { Error = "Start G-Code cannot contain G29 if Print Leveling is enabled.".Localize(), Details = "Your Start G-Code should not contain a G29 if you are planning on using print leveling. Change your start G-Code or turn off print leveling.".Localize(), }); } if (startGCodeLine.StartsWith("G30")) { errors.Add( new SettingsValidationError(SettingsKey.start_gcode) { Error = "Start G-Code cannot contain G30 if Print Leveling is enabled.".Localize(), Details = "Your Start G-Code should not contain a G30 if you are planning on using print leveling. Change your start G-Code or turn off print leveling.".Localize(), }); } } } // Make sure the z offsets are not too big if (Math.Abs(settings.GetValue <double>(SettingsKey.baby_step_z_offset)) > 2) { // Static path generation for non-SliceSettings value var location = "Location".Localize() + ":" + "\n" + "Controls".Localize() + "\n • " + "Movement".Localize() + "\n • " + "Z Offset".Localize(); errors.Add( new ValidationError() { Error = "Z Offset is too large.".Localize(), Details = string.Format( "{0}\n\n{1}", "The Z Offset for your printer, sometimes called Baby Stepping, is greater than 2mm and invalid. Clear the value and re-level the bed.".Localize(), location) }); } if (extruderCount > 1 && Math.Abs(settings.GetValue <double>(SettingsKey.baby_step_z_offset_1)) > 2) { // Static path generation for non-SliceSettings value var location = "Location".Localize() + ":" + "\n" + "Controls".Localize() + "\n • " + "Movement".Localize() + "\n • " + "Z Offset 2".Localize(); errors.Add( new ValidationError() { Error = "Z Offset 2 is too large.".Localize(), Details = string.Format( "{0}\n\n{1}", "The Z Offset for your second extruder, is greater than 2mm and invalid. Clear the value and re-level the bed.".Localize(), location) }); } if (settings.GetValue <double>(SettingsKey.first_layer_extrusion_width) > settings.GetValue <double>(SettingsKey.nozzle_diameter) * 4) { errors.Add( new SettingsValidationError(SettingsKey.first_layer_extrusion_width) { Error = "{0} must be less than or equal to the {1} * 4.".Localize().FormatWith( GetSettingsName(SettingsKey.first_layer_extrusion_width), GetSettingsName(SettingsKey.nozzle_diameter)), ValueDetails = "{0} = {1}\n{2} = {3}".FormatWith( GetSettingsName(SettingsKey.first_layer_extrusion_width), settings.GetValue <double>(SettingsKey.first_layer_extrusion_width), GetSettingsName(SettingsKey.nozzle_diameter), settings.GetValue <double>(SettingsKey.nozzle_diameter)) }); } if (settings.GetValue <double>(SettingsKey.first_layer_extrusion_width) <= 0) { errors.Add( new SettingsValidationError(SettingsKey.first_layer_extrusion_width) { Error = "{0} must be greater than 0.".Localize().FormatWith( GetSettingsName(SettingsKey.first_layer_extrusion_width)), ValueDetails = "{0} = {1}".FormatWith( GetSettingsName(SettingsKey.first_layer_extrusion_width), settings.GetValue <double>(SettingsKey.first_layer_extrusion_width)), }); } if (settings.GetValue <double>(SettingsKey.external_perimeter_extrusion_width) > settings.GetValue <double>(SettingsKey.nozzle_diameter) * 4) { errors.Add( new SettingsValidationError(SettingsKey.external_perimeter_extrusion_width) { Error = "{0} must be less than or equal to the {1} * 4.".Localize().FormatWith( GetSettingsName(SettingsKey.external_perimeter_extrusion_width), GetSettingsName(SettingsKey.nozzle_diameter)), ValueDetails = "{0} = {1}\n{2} = {3}".FormatWith( GetSettingsName(SettingsKey.external_perimeter_extrusion_width), settings.GetValue <double>(SettingsKey.external_perimeter_extrusion_width), GetSettingsName(SettingsKey.nozzle_diameter), settings.GetValue <double>(SettingsKey.nozzle_diameter)), }); } if (settings.GetValue <double>(SettingsKey.external_perimeter_extrusion_width) <= 0) { errors.Add( new SettingsValidationError(SettingsKey.external_perimeter_extrusion_width) { Error = "{0} must be greater than 0.".Localize().FormatWith( GetSettingsName(SettingsKey.external_perimeter_extrusion_width)), ValueDetails = "{0} = {1}".FormatWith( GetSettingsName(SettingsKey.external_perimeter_extrusion_width), settings.GetValue <double>(SettingsKey.external_perimeter_extrusion_width)), }); } if (settings.GetValue <double>(SettingsKey.min_fan_speed) > 100) { errors.Add( new SettingsValidationError(SettingsKey.min_fan_speed) { Error = "The {0} can only go as high as 100%.".Localize().FormatWith( GetSettingsName(SettingsKey.min_fan_speed)), ValueDetails = "It is currently set to {0}.".Localize().FormatWith( settings.GetValue <double>(SettingsKey.min_fan_speed)), }); } if (settings.GetValue <double>(SettingsKey.max_fan_speed) > 100) { errors.Add( new SettingsValidationError(SettingsKey.max_fan_speed) { Error = "The {0} can only go as high as 100%.".Localize().FormatWith( GetSettingsName(SettingsKey.max_fan_speed)), ValueDetails = "It is currently set to {0}.".Localize().FormatWith( settings.GetValue <double>(SettingsKey.max_fan_speed)), }); } if (extruderCount < 1) { errors.Add( new SettingsValidationError(SettingsKey.extruder_count) { Error = "The {0} must be at least 1.".Localize().FormatWith( GetSettingsName(SettingsKey.extruder_count)), ValueDetails = "It is currently set to {0}.".Localize().FormatWith(extruderCount), }); } if (settings.GetValue <double>(SettingsKey.fill_density) < 0 || settings.GetValue <double>(SettingsKey.fill_density) > 1) { errors.Add( new SettingsValidationError(SettingsKey.fill_density) { Error = "The {0} must be between 0 and 1.".Localize().FormatWith( GetSettingsName(SettingsKey.fill_density)), ValueDetails = "It is currently set to {0}.".Localize().FormatWith( settings.GetValue <double>(SettingsKey.fill_density)), }); } // marlin firmware can only take a max of 128 bytes in a single instruction, make sure no lines are longer than that ValidateGCodeLinesShortEnough(SettingsKey.cancel_gcode, printer, errors); ValidateGCodeLinesShortEnough(SettingsKey.connect_gcode, printer, errors); ValidateGCodeLinesShortEnough(SettingsKey.end_gcode, printer, errors); ValidateGCodeLinesShortEnough(SettingsKey.layer_gcode, printer, errors); ValidateGCodeLinesShortEnough(SettingsKey.pause_gcode, printer, errors); ValidateGCodeLinesShortEnough(SettingsKey.resume_gcode, printer, errors); ValidateGCodeLinesShortEnough(SettingsKey.start_gcode, printer, errors); // If the given speed is part of the current slice engine then check that it is greater than 0. ValidateGoodSpeedSettingGreaterThan0(SettingsKey.bridge_speed, printer, errors); ValidateGoodSpeedSettingGreaterThan0(SettingsKey.air_gap_speed, printer, errors); ValidateGoodSpeedSettingGreaterThan0(SettingsKey.external_perimeter_speed, printer, errors); ValidateGoodSpeedSettingGreaterThan0(SettingsKey.first_layer_speed, printer, errors); ValidateGoodSpeedSettingGreaterThan0(SettingsKey.infill_speed, printer, errors); ValidateGoodSpeedSettingGreaterThan0(SettingsKey.perimeter_speed, printer, errors); ValidateGoodSpeedSettingGreaterThan0(SettingsKey.small_perimeter_speed, printer, errors); ValidateGoodSpeedSettingGreaterThan0(SettingsKey.solid_infill_speed, printer, errors); ValidateGoodSpeedSettingGreaterThan0(SettingsKey.support_material_speed, printer, errors); ValidateGoodSpeedSettingGreaterThan0(SettingsKey.top_solid_infill_speed, printer, errors); ValidateGoodSpeedSettingGreaterThan0(SettingsKey.travel_speed, printer, errors); ValidateGoodSpeedSettingGreaterThan0(SettingsKey.retract_speed, printer, errors); } catch (Exception e) { errors.Add( new ValidationError() { Error = "Unexpected error validating settings".Localize(), Details = e.Message }); } return(errors); }
public GenerateSupportPanel(ThemeConfig theme, InteractiveScene scene, double minimumSupportHeight) : base(FlowDirection.TopToBottom) { supportGenerator = new SupportGenerator(scene, minimumSupportHeight); this.VAnchor = VAnchor.Fit; this.HAnchor = HAnchor.Absolute; this.Width = 300 * GuiWidget.DeviceScale; this.BackgroundColor = theme.BackgroundColor; this.Padding = theme.DefaultContainerPadding; // Add an editor field for the SupportGenerator.SupportType PropertyInfo propertyInfo = typeof(SupportGenerator).GetProperty(nameof(SupportGenerator.SupportType)); var editor = PublicPropertyEditor.CreatePropertyEditor( new EditableProperty(propertyInfo, supportGenerator), null, new PPEContext(), theme); if (editor != null) { this.AddChild(editor); } // put in support pillar size var pillarSizeField = new DoubleField(theme); pillarSizeField.Initialize(0); pillarSizeField.DoubleValue = supportGenerator.PillarSize; pillarSizeField.ValueChanged += (s, e) => { supportGenerator.PillarSize = pillarSizeField.DoubleValue; // in case it was corrected set it back again if (pillarSizeField.DoubleValue != supportGenerator.PillarSize) { pillarSizeField.DoubleValue = supportGenerator.PillarSize; } }; // pillar rows this.AddChild( new SettingsRow( "Pillar Size".Localize(), "The width and depth of the support pillars".Localize(), pillarSizeField.Content, theme)); // put in the angle setting var overHangField = new DoubleField(theme); overHangField.Initialize(0); overHangField.DoubleValue = supportGenerator.MaxOverHangAngle; overHangField.ValueChanged += (s, e) => { supportGenerator.MaxOverHangAngle = overHangField.DoubleValue; // in case it was corrected set it back again if (overHangField.DoubleValue != supportGenerator.MaxOverHangAngle) { overHangField.DoubleValue = supportGenerator.MaxOverHangAngle; } }; // overhang row this.AddChild( new SettingsRow( "Overhang Angle".Localize(), "The angle to generate support for".Localize(), overHangField.Content, theme)); // Button Row var buttonRow = new FlowLayoutWidget() { HAnchor = HAnchor.Stretch, VAnchor = VAnchor.Fit, Margin = new BorderDouble(top: 5) }; this.AddChild(buttonRow); buttonRow.AddChild(new HorizontalSpacer()); // add 'Remove Auto Supports' button var removeButton = theme.CreateDialogButton("Remove".Localize()); removeButton.ToolTipText = "Remove all auto generated supports".Localize(); removeButton.Click += (s, e) => supportGenerator.RemoveExisting(); buttonRow.AddChild(removeButton); // add 'Generate Supports' button var generateButton = theme.CreateDialogButton("Generate".Localize()); generateButton.Name = "Generate Support Button"; generateButton.ToolTipText = "Find and create supports where needed".Localize(); generateButton.Click += (s, e) => Rebuild(); buttonRow.AddChild(generateButton); theme.ApplyPrimaryActionStyle(generateButton); }
public void SupportExtentsTests() { // make a cube in the air and ensure that no mater where it is placed, support is always under the entire extents InteractiveScene scene = new InteractiveScene(); var supportGenerator = new SupportGenerator(scene); }
/// <summary> /// Validates the printer settings satisfy all requirements. /// </summary> /// <param name="printer">The printer to validate.</param> /// <returns>A list of all warnings and errors.</returns> public static List <ValidationError> ValidateSettings(this PrinterConfig printer, SettingsContext settings = null, bool validatePrintBed = true) { var fffPrinter = printer.Settings.Slicer.PrinterType == PrinterType.FFF; if (settings == null) { settings = new SettingsContext(printer, null, NamedSettingsLayers.All); } var errors = new List <ValidationError>(); var extruderCount = settings.GetValue <int>(SettingsKey.extruder_count); // Check to see if supports are required if (!settings.GetValue <bool>(SettingsKey.create_per_layer_support)) { var supportGenerator = new SupportGenerator(printer.Bed.Scene, .05); if (supportGenerator.RequiresSupport()) { errors.Add(new ValidationError(ValidationErrors.UnsupportedParts) { Error = "Possible Unsupported Parts Detected".Localize(), Details = "Some parts may require support structures to print correctly".Localize(), ErrorLevel = ValidationErrorLevel.Warning, FixAction = new NamedAction() { Title = "Generate Supports".Localize(), Action = () => { // Find and InvokeClick on the Generate Supports toolbar button var sharedParent = ApplicationController.Instance.DragDropData.View3DWidget.Parents <GuiWidget>().FirstOrDefault(w => w.Name == "View3DContainerParent"); if (sharedParent != null) { var supportsPopup = sharedParent.FindDescendant("Support SplitButton"); supportsPopup.InvokeClick(); } } } }); } } if (!settings.GetValue <bool>(SettingsKey.extruder_offset)) { var t0Offset = printer.Settings.Helpers.ExtruderOffset(0); if (t0Offset != Vector3.Zero) { errors.Add( new SettingsValidationError(SettingsKey.extruder_offset) { Error = "Nozzle 1 should have offsets set to 0.".Localize(), ValueDetails = "{0} = {1}\n{2} = {3}".FormatWith( GetSettingsName(SettingsKey.extruder_offset), settings.GetValue <double>(SettingsKey.extruder_offset), GetSettingsName(SettingsKey.extruder_offset), settings.GetValue <double>(SettingsKey.extruder_offset)), ErrorLevel = ValidationErrorLevel.Warning, }); } } // Check to see if current OEM layer matches downloaded OEM layer { if (printer.Settings.GetValue(SettingsKey.make) != "Other" && ProfileManager.GetOemSettingsNeedingUpdate(printer).Any()) { errors.Add(new ValidationError(ValidationErrors.SettingsUpdateAvailable) { Error = "Settings Update Available".Localize(), Details = "The default settings for this printer have changed and can be updated".Localize(), ErrorLevel = ValidationErrorLevel.Warning, FixAction = new NamedAction() { Title = "Update Settings...".Localize(), Action = () => { DialogWindow.Show(new UpdateSettingsPage(printer)); } } }); } } if (printer.Connection.IsConnected && !PrinterSetupRequired(printer) && validatePrintBed && errors.Count(e => e.ErrorLevel == ValidationErrorLevel.Error) == 0 && !printer.PrintableItems(printer.Bed.Scene).Any()) { errors.Add(new ValidationError(ValidationErrors.NoPrintableParts) { Error = "Empty Bed".Localize(), Details = "No printable parts exists within the bounds of the printer bed. Add content to continue".Localize(), ErrorLevel = ValidationErrorLevel.Error, }); } try { if (settings.GetValue <bool>(SettingsKey.validate_layer_height)) { if (settings.GetValue <double>(SettingsKey.layer_height) > settings.GetValue <double>(SettingsKey.nozzle_diameter)) { errors.Add( new SettingsValidationError(SettingsKey.layer_height) { Error = "{0} must be less than or equal to the {1}.".Localize().FormatWith( GetSettingsName(SettingsKey.layer_height), GetSettingsName(SettingsKey.nozzle_diameter)), ValueDetails = "{0} = {1}\n{2} = {3}".FormatWith( GetSettingsName(SettingsKey.layer_height), settings.GetValue <double>(SettingsKey.layer_height), GetSettingsName(SettingsKey.nozzle_diameter), settings.GetValue <double>(SettingsKey.nozzle_diameter)), }); } else if (settings.GetValue <double>(SettingsKey.layer_height) <= 0) { errors.Add( new SettingsValidationError(SettingsKey.layer_height) { Error = "{0} must be greater than 0.".Localize().FormatWith(GetSettingsName(SettingsKey.layer_height)), }); } // make sure the first layer height is not too big if (settings.GetValue <double>(SettingsKey.first_layer_height) > settings.GetValue <double>(SettingsKey.nozzle_diameter)) { errors.Add( new SettingsValidationError(SettingsKey.first_layer_height) { Error = "{0} must be less than or equal to the {1}.".Localize().FormatWith( GetSettingsName(SettingsKey.first_layer_height), GetSettingsName(SettingsKey.nozzle_diameter)), ValueDetails = "{0} = {1}\n{2} = {3}".FormatWith( GetSettingsName(SettingsKey.first_layer_height), settings.GetValue <double>(SettingsKey.first_layer_height), GetSettingsName(SettingsKey.nozzle_diameter), settings.GetValue <double>(SettingsKey.nozzle_diameter)), }); } // make sure the first layer height is not too small else if (settings.GetValue <double>(SettingsKey.first_layer_height) < settings.GetValue <double>(SettingsKey.nozzle_diameter) / 2) { errors.Add( new SettingsValidationError(SettingsKey.first_layer_height) { Error = "{0} should be greater than or equal to 1/2 the {1}.".Localize().FormatWith( GetSettingsName(SettingsKey.first_layer_height), GetSettingsName(SettingsKey.nozzle_diameter)), ValueDetails = "{0} = {1}\n1/2 {2} = {3}".FormatWith( GetSettingsName(SettingsKey.first_layer_height), settings.GetValue <double>(SettingsKey.first_layer_height), GetSettingsName(SettingsKey.nozzle_diameter), settings.GetValue <double>(SettingsKey.nozzle_diameter) / 2), ErrorLevel = ValidationErrorLevel.Warning, }); } } string[] startGCode = settings.GetValue(SettingsKey.start_gcode).Replace("\\n", "\n").Split('\n'); // Print recovery is incompatible with firmware leveling - ensure not enabled in startGCode if (settings.GetValue <bool>(SettingsKey.recover_is_enabled) && !settings.GetValue <bool>(SettingsKey.has_hardware_leveling)) { // Ensure we don't have hardware leveling commands in the start gcode. foreach (string startGCodeLine in startGCode) { if (startGCodeLine.StartsWith("G29")) { errors.Add( new SettingsValidationError(SettingsKey.start_gcode) { Error = "Start G-Code cannot contain G29 if Print Recovery is enabled.".Localize(), Details = "Your Start G-Code should not contain a G29 if you are planning on using Print Recovery. Change your start G-Code or turn off Print Recovery.".Localize(), }); } if (startGCodeLine.StartsWith("G30")) { errors.Add( new SettingsValidationError(SettingsKey.start_gcode) { Error = "Start G-Code cannot contain G30 if Print Leveling is enabled.".Localize(), Details = "Your Start G-Code should not contain a G30 if you are planning on using Print Recovery. Change your start G-Code or turn off Print Recovery.".Localize(), }); } } } var levelingEnabled = printer.Settings.GetValue <bool>(SettingsKey.print_leveling_enabled) & !settings.GetValue <bool>(SettingsKey.has_hardware_leveling); var levelingRequired = printer.Settings.GetValue <bool>(SettingsKey.print_leveling_required_to_print); if (levelingEnabled || levelingRequired) { // Ensure we don't have hardware leveling commands in the start gcode. foreach (string startGCodeLine in startGCode) { if (startGCodeLine.StartsWith("G29")) { errors.Add( new SettingsValidationError(SettingsKey.start_gcode) { Error = "Start G-Code cannot contain G29 if Print Leveling is enabled.".Localize(), Details = "Your Start G-Code should not contain a G29 if you are planning on using print leveling. Change your start G-Code or turn off print leveling.".Localize(), }); } if (startGCodeLine.StartsWith("G30")) { errors.Add( new SettingsValidationError(SettingsKey.start_gcode) { Error = "Start G-Code cannot contain G30 if Print Leveling is enabled.".Localize(), Details = "Your Start G-Code should not contain a G30 if you are planning on using print leveling. Change your start G-Code or turn off print leveling.".Localize(), }); } } bool heatedBed = printer.Settings.GetValue <bool>(SettingsKey.has_heated_bed); double bedTemperature = printer.Settings.GetValue <double>(SettingsKey.bed_temperature); if (heatedBed && printer.Connection.IsConnected && !PrinterSetupRequired(printer) && printer.Settings.Helpers.PrintLevelingData is PrintLevelingData levelingData && !levelingData.IssuedLevelingTempWarning && Math.Abs(bedTemperature - levelingData.BedTemperature) > 10 && !printer.Settings.Helpers.ValidateLevelingWithProbe) { errors.Add( new ValidationError(ValidationErrors.BedLevelingTemperature) { Error = "Bed Leveling Temperature".Localize(), Details = string.Format( "Bed Leveling data created at {0}°C versus current {1}°C".Localize(), levelingData.BedTemperature, bedTemperature), ErrorLevel = ValidationErrorLevel.Warning, FixAction = new NamedAction() { Title = "Recalibrate", Action = () => { UiThread.RunOnIdle(() => { DialogWindow.Show(new PrintLevelingWizard(printer)); }); }, IsEnabled = () => printer.Connection.IsConnected } }); } if (levelingEnabled && !settings.GetValue <bool>(SettingsKey.has_hardware_leveling) && settings.GetValue <bool>(SettingsKey.has_z_probe) && settings.GetValue <bool>(SettingsKey.use_z_probe) && settings.GetValue <bool>(SettingsKey.validate_leveling) && (settings.GetValue <double>(SettingsKey.validation_threshold) < .001 || settings.GetValue <double>(SettingsKey.validation_threshold) > .5)) { var threshold = settings.GetValue <double>(SettingsKey.validation_threshold); errors.Add( new SettingsValidationError(SettingsKey.validation_threshold) { Error = "The Validation Threshold mush be greater than 0 and less than .5mm.".Localize().FormatWith(threshold), ValueDetails = "{0} = {1}".FormatWith(GetSettingsName(SettingsKey.validation_threshold), threshold), }); } // check if the leveling data has too large a range if (printer.Settings.Helpers.PrintLevelingData.SampledPositions.Count > 3) { var minLevelZ = double.MaxValue; var maxLevelZ = double.MinValue; foreach (var levelPosition in printer.Settings.Helpers.PrintLevelingData.SampledPositions) { minLevelZ = Math.Min(minLevelZ, levelPosition.Z); maxLevelZ = Math.Max(maxLevelZ, levelPosition.Z); } var delta = maxLevelZ - minLevelZ; var maxDelta = printer.Settings.GetValue <double>(SettingsKey.nozzle_diameter) * 10; if (delta > maxDelta) { errors.Add( new ValidationError(ValidationErrors.BedLevelingMesh) { Error = "Leveling Data Warning".Localize(), Details = "The leveling data might be invalid. It changes by as much as {0:0.##}mm. Leveling calibration should be re-run".Localize().FormatWith(delta), ErrorLevel = ValidationErrorLevel.Warning, FixAction = new NamedAction() { Title = "Recalibrate", Action = () => { UiThread.RunOnIdle(() => { DialogWindow.Show(new PrintLevelingWizard(printer)); }); }, IsEnabled = () => printer.Connection.IsConnected } }); } } } printer.Settings.ForTools <double>(SettingsKey.baby_step_z_offset, (key, value, i) => { // Make sure the z offsets are not too big if (Math.Abs(value) > 2) { // Static path generation for non-SliceSettings value var location = "Location".Localize() + ":" + "\n" + "Controls".Localize() + "\n • " + "Movement".Localize() + "\n • " + "Z Offset".Localize(); errors.Add( new ValidationError(ValidationErrors.ZOffset) { Error = "Z Offset is too large.".Localize(), Details = string.Format( "{0}\n\n{1}", "The Z Offset for your printer, sometimes called Baby Stepping, is greater than 2mm and invalid. Clear the value and re-level the bed.".Localize(), location) }); } }); if (settings.GetValue <double>(SettingsKey.first_layer_extrusion_width) > settings.GetValue <double>(SettingsKey.nozzle_diameter) * 4) { errors.Add( new SettingsValidationError(SettingsKey.first_layer_extrusion_width) { Error = "{0} must be less than or equal to the {1} * 4.".Localize().FormatWith( GetSettingsName(SettingsKey.first_layer_extrusion_width), GetSettingsName(SettingsKey.nozzle_diameter)), ValueDetails = "{0} = {1}\n{2} * 4 = {3}".FormatWith( GetSettingsName(SettingsKey.first_layer_extrusion_width), settings.GetValue <double>(SettingsKey.first_layer_extrusion_width), GetSettingsName(SettingsKey.nozzle_diameter), settings.GetValue <double>(SettingsKey.nozzle_diameter) * 4) }); } if (settings.GetValue <double>(SettingsKey.first_layer_extrusion_width) <= 0) { errors.Add( new SettingsValidationError(SettingsKey.first_layer_extrusion_width) { Error = "{0} must be greater than 0.".Localize().FormatWith( GetSettingsName(SettingsKey.first_layer_extrusion_width)), ValueDetails = "{0} = {1}".FormatWith( GetSettingsName(SettingsKey.first_layer_extrusion_width), settings.GetValue <double>(SettingsKey.first_layer_extrusion_width)), }); } if (settings.GetValue <double>(SettingsKey.fill_density) <= 0) { errors.Add( new SettingsValidationError(SettingsKey.fill_density) { Error = "{0} should be greater than 0.".Localize().FormatWith( GetSettingsName(SettingsKey.fill_density)), ErrorLevel = ValidationErrorLevel.Warning, ValueDetails = "{0} = {1}".FormatWith( GetSettingsName(SettingsKey.fill_density), settings.GetValue <double>(SettingsKey.fill_density)), }); } if (settings.GetValue <double>(SettingsKey.perimeters) <= 0) { errors.Add( new SettingsValidationError(SettingsKey.perimeters) { Error = "{0} should be greater than 0.".Localize().FormatWith( GetSettingsName(SettingsKey.perimeters)), ErrorLevel = ValidationErrorLevel.Warning, ValueDetails = "{0} = {1}".FormatWith( GetSettingsName(SettingsKey.perimeters), settings.GetValue <double>(SettingsKey.perimeters)), }); } if (settings.GetValue <int>(SettingsKey.extruder_count) > 1 && settings.GetValue <double>(SettingsKey.wipe_tower_perimeters_per_extruder) < 3) { errors.Add( new SettingsValidationError(SettingsKey.wipe_tower_perimeters_per_extruder) { Error = "{0} should be greater than 2.".Localize().FormatWith( GetSettingsName(SettingsKey.wipe_tower_perimeters_per_extruder)), ErrorLevel = ValidationErrorLevel.Warning, ValueDetails = "{0} = {1}".FormatWith( GetSettingsName(SettingsKey.wipe_tower_perimeters_per_extruder), settings.GetValue <double>(SettingsKey.wipe_tower_perimeters_per_extruder)), }); } if (settings.GetValue <double>(SettingsKey.infill_overlap_perimeter) < -settings.GetValue <double>(SettingsKey.nozzle_diameter) || settings.GetValue <double>(SettingsKey.infill_overlap_perimeter) > settings.GetValue <double>(SettingsKey.nozzle_diameter)) { errors.Add( new SettingsValidationError(SettingsKey.infill_overlap_perimeter) { Error = "{0} must be greater than 0 and less than your nozzle diameter. You may be missing a '%'.".Localize().FormatWith( GetSettingsName(SettingsKey.infill_overlap_perimeter)), ValueDetails = "{0} = {1}, {2} = {3}".FormatWith( GetSettingsName(SettingsKey.infill_overlap_perimeter), settings.GetValue <double>(SettingsKey.infill_overlap_perimeter), GetSettingsName(SettingsKey.nozzle_diameter), settings.GetValue <double>(SettingsKey.nozzle_diameter)), }); } if (printer.Connection.IsConnected && printer.Settings?.Helpers.ComPort() == "Emulator" && fffPrinter) { errors.Add( new SettingsValidationError(SettingsKey.com_port, "Connected to Emulator".Localize()) { Error = "You are connected to the Emulator not an actual printer.".Localize(), ErrorLevel = ValidationErrorLevel.Warning, FixAction = new NamedAction() { Title = "Switch".Localize(), IsEnabled = () => !printer.Connection.Printing && !printer.Connection.Paused, Action = () => UiThread.RunOnIdle(() => { // make sure we are not connected or we can't change the port printer.Connection.Disable(); // User initiated connect attempt failed, show port selection dialog DialogWindow.Show(new SetupStepComPortOne(printer)); }) } }); } if (settings.GetValue <double>(SettingsKey.external_perimeter_extrusion_width) <= 0) { errors.Add( new SettingsValidationError(SettingsKey.external_perimeter_extrusion_width) { Error = "{0} must be greater than 0.".Localize().FormatWith( GetSettingsName(SettingsKey.external_perimeter_extrusion_width)), ValueDetails = "{0} = {1}".FormatWith( GetSettingsName(SettingsKey.external_perimeter_extrusion_width), settings.GetValue <double>(SettingsKey.external_perimeter_extrusion_width)), }); } if (settings.GetValue <double>(SettingsKey.min_fan_speed) > 100) { errors.Add( new SettingsValidationError(SettingsKey.min_fan_speed) { Error = "The {0} can only go as high as 100%.".Localize().FormatWith( GetSettingsName(SettingsKey.min_fan_speed)), ValueDetails = "It is currently set to {0}.".Localize().FormatWith( settings.GetValue <double>(SettingsKey.min_fan_speed)), }); } if (settings.GetValue <double>(SettingsKey.max_fan_speed) > 100) { errors.Add( new SettingsValidationError(SettingsKey.max_fan_speed) { Error = "The {0} can only go as high as 100%.".Localize().FormatWith( GetSettingsName(SettingsKey.max_fan_speed)), ValueDetails = "It is currently set to {0}.".Localize().FormatWith( settings.GetValue <double>(SettingsKey.max_fan_speed)), }); } if (extruderCount < 1) { errors.Add( new SettingsValidationError(SettingsKey.extruder_count) { Error = "The {0} must be at least 1.".Localize().FormatWith( GetSettingsName(SettingsKey.extruder_count)), ValueDetails = "It is currently set to {0}.".Localize().FormatWith(extruderCount), }); } if (settings.GetValue <double>(SettingsKey.fill_density) < 0 || settings.GetValue <double>(SettingsKey.fill_density) > 1) { errors.Add( new SettingsValidationError(SettingsKey.fill_density) { Error = "The {0} must be between 0 and 1.".Localize().FormatWith( GetSettingsName(SettingsKey.fill_density)), ValueDetails = "It is currently set to {0}.".Localize().FormatWith( settings.GetValue <double>(SettingsKey.fill_density)), }); } // marlin firmware can only take a max of 128 bytes in a single instruction, make sure no lines are longer than that ValidateGCodeLinesShortEnough(SettingsKey.cancel_gcode, printer, errors); ValidateGCodeLinesShortEnough(SettingsKey.connect_gcode, printer, errors); ValidateGCodeLinesShortEnough(SettingsKey.end_gcode, printer, errors); ValidateGCodeLinesShortEnough(SettingsKey.layer_gcode, printer, errors); ValidateGCodeLinesShortEnough(SettingsKey.pause_gcode, printer, errors); ValidateGCodeLinesShortEnough(SettingsKey.resume_gcode, printer, errors); ValidateGCodeLinesShortEnough(SettingsKey.start_gcode, printer, errors); // If the given speed is part of the current slice engine then check that it is greater than 0. ValidateGoodSpeedSettingGreaterThan0(SettingsKey.bridge_speed, printer, errors); ValidateGoodSpeedSettingGreaterThan0(SettingsKey.air_gap_speed, printer, errors); ValidateGoodSpeedSettingGreaterThan0(SettingsKey.external_perimeter_speed, printer, errors); ValidateGoodSpeedSettingGreaterThan0(SettingsKey.first_layer_speed, printer, errors); ValidateGoodSpeedSettingGreaterThan0(SettingsKey.infill_speed, printer, errors); ValidateGoodSpeedSettingGreaterThan0(SettingsKey.perimeter_speed, printer, errors); ValidateGoodSpeedSettingGreaterThan0(SettingsKey.small_perimeter_speed, printer, errors); ValidateGoodSpeedSettingGreaterThan0(SettingsKey.solid_infill_speed, printer, errors); ValidateGoodSpeedSettingGreaterThan0(SettingsKey.support_material_speed, printer, errors); ValidateGoodSpeedSettingGreaterThan0(SettingsKey.top_solid_infill_speed, printer, errors); ValidateGoodSpeedSettingGreaterThan0(SettingsKey.travel_speed, printer, errors); ValidateGoodSpeedSettingGreaterThan0(SettingsKey.retract_speed, printer, errors); } catch (Exception e) { errors.Add( new ValidationError(ValidationErrors.ExceptionDuringSliceSettingsValidation) { Error = "Unexpected error validating settings".Localize(), Details = e.Message }); } return(errors); }
/// <summary> /// Validates the printer settings satisfy all requirements. /// </summary> /// <param name="printer">The printer to validate.</param> /// <returns>A list of all warnings and errors.</returns> public static List <ValidationError> ValidateSettings(this PrinterConfig printer, SettingsContext settings = null, bool validatePrintBed = true) { if (settings == null) { settings = new SettingsContext(printer, null, NamedSettingsLayers.All); } var errors = new List <ValidationError>(); var extruderCount = settings.GetValue <int>(SettingsKey.extruder_count); // Check to see if supports are required var supportGenerator = new SupportGenerator(printer.Bed.Scene, .05); if (supportGenerator.RequiresSupport()) { errors.Add(new ValidationError("UnsupportedParts") { Error = "Possible Unsupported Parts Detected".Localize(), Details = "Some parts may require support structures to print correctly".Localize(), ErrorLevel = ValidationErrorLevel.Warning, FixAction = new NamedAction() { Title = "Generate Supports".Localize(), Action = () => { // Find and InvokeClick on the Generate Supports toolbar button var sharedParent = ApplicationController.Instance.DragDropData.View3DWidget.Parents <GuiWidget>().FirstOrDefault(w => w.Name == "View3DContainerParent"); if (sharedParent != null) { var supportsPopup = sharedParent.FindDescendant("Support SplitButton"); supportsPopup.InvokeClick(); } } } }); } if (printer.Connection.IsConnected && !PrinterSetupRequired(printer) && validatePrintBed && errors.Count(e => e.ErrorLevel == ValidationErrorLevel.Error) == 0 && !printer.PrintableItems(printer.Bed.Scene).Any()) { errors.Add(new ValidationError("NoPrintableParts") { Error = "Empty Bed".Localize(), Details = "No printable parts exists within the bounds of the printer bed. Add content to continue".Localize(), ErrorLevel = ValidationErrorLevel.Error, }); } try { if (settings.GetValue <bool>(SettingsKey.validate_layer_height)) { if (settings.GetValue <double>(SettingsKey.layer_height) > settings.GetValue <double>(SettingsKey.nozzle_diameter)) { errors.Add( new SettingsValidationError(SettingsKey.layer_height) { Error = "{0} must be less than or equal to the {1}.".Localize().FormatWith( GetSettingsName(SettingsKey.layer_height), GetSettingsName(SettingsKey.nozzle_diameter)), ValueDetails = "{0} = {1}\n{2} = {3}".FormatWith( GetSettingsName(SettingsKey.layer_height), settings.GetValue <double>(SettingsKey.layer_height), GetSettingsName(SettingsKey.nozzle_diameter), settings.GetValue <double>(SettingsKey.nozzle_diameter)), }); } else if (settings.GetValue <double>(SettingsKey.layer_height) <= 0) { errors.Add( new SettingsValidationError(SettingsKey.layer_height) { Error = "{0} must be greater than 0.".Localize().FormatWith(GetSettingsName(SettingsKey.layer_height)), }); } if (settings.GetValue <double>(SettingsKey.first_layer_height) > settings.GetValue <double>(SettingsKey.nozzle_diameter)) { errors.Add( new SettingsValidationError(SettingsKey.first_layer_height) { Error = "{0} must be less than or equal to the {1}.".Localize().FormatWith( GetSettingsName(SettingsKey.first_layer_height), GetSettingsName(SettingsKey.nozzle_diameter)), ValueDetails = "{0} = {1}\n{2} = {3}".FormatWith( GetSettingsName(SettingsKey.first_layer_height), settings.GetValue <double>(SettingsKey.first_layer_height), GetSettingsName(SettingsKey.nozzle_diameter), settings.GetValue <double>(SettingsKey.nozzle_diameter)), }); } } string[] startGCode = settings.GetValue(SettingsKey.start_gcode).Replace("\\n", "\n").Split('\n'); // Print recovery is incompatible with firmware leveling - ensure not enabled in startGCode if (settings.GetValue <bool>(SettingsKey.recover_is_enabled)) { // Ensure we don't have hardware leveling commands in the start gcode. foreach (string startGCodeLine in startGCode) { if (startGCodeLine.StartsWith("G29")) { errors.Add( new SettingsValidationError(SettingsKey.start_gcode) { Error = "Start G-Code cannot contain G29 if Print Recovery is enabled.".Localize(), Details = "Your Start G-Code should not contain a G29 if you are planning on using Print Recovery. Change your start G-Code or turn off Print Recovery.".Localize(), }); } if (startGCodeLine.StartsWith("G30")) { errors.Add( new SettingsValidationError(SettingsKey.start_gcode) { Error = "Start G-Code cannot contain G30 if Print Leveling is enabled.".Localize(), Details = "Your Start G-Code should not contain a G30 if you are planning on using Print Recovery. Change your start G-Code or turn off Print Recovery.".Localize(), }); } } } var levelingEnabled = printer.Settings.GetValue <bool>(SettingsKey.print_leveling_enabled); var levelingRequired = printer.Settings.GetValue <bool>(SettingsKey.print_leveling_required_to_print); if (levelingEnabled || levelingRequired) { // Ensure we don't have hardware leveling commands in the start gcode. foreach (string startGCodeLine in startGCode) { if (startGCodeLine.StartsWith("G29")) { errors.Add( new SettingsValidationError(SettingsKey.start_gcode) { Error = "Start G-Code cannot contain G29 if Print Leveling is enabled.".Localize(), Details = "Your Start G-Code should not contain a G29 if you are planning on using print leveling. Change your start G-Code or turn off print leveling.".Localize(), }); } if (startGCodeLine.StartsWith("G30")) { errors.Add( new SettingsValidationError(SettingsKey.start_gcode) { Error = "Start G-Code cannot contain G30 if Print Leveling is enabled.".Localize(), Details = "Your Start G-Code should not contain a G30 if you are planning on using print leveling. Change your start G-Code or turn off print leveling.".Localize(), }); } } bool heatedBed = printer.Settings.GetValue <bool>(SettingsKey.has_heated_bed); double bedTemperature = printer.Settings.GetValue <double>(SettingsKey.bed_temperature); if (heatedBed && printer.Connection.IsConnected && !PrinterSetupRequired(printer) && printer.Settings.Helpers.PrintLevelingData is PrintLevelingData levelingData && !levelingData.IssuedLevelingTempWarning && Math.Abs(bedTemperature - levelingData.BedTemperature) > 10) { errors.Add( new ValidationError("BedLevelingTemperature") { Error = "Bed Leveling Temperature".Localize(), Details = string.Format( "Bed Leveling data created at {0}°C versus current {1}°C".Localize(), levelingData.BedTemperature, bedTemperature), ErrorLevel = ValidationErrorLevel.Warning, FixAction = new NamedAction() { Title = "Recalibrate", Action = () => { UiThread.RunOnIdle(() => { DialogWindow.Show(new PrintLevelingWizard(printer)); }); }, IsEnabled = () => printer.Connection.IsConnected } }); } } // Make sure the z offsets are not too big if (Math.Abs(settings.GetValue <double>(SettingsKey.baby_step_z_offset)) > 2) { // Static path generation for non-SliceSettings value var location = "Location".Localize() + ":" + "\n" + "Controls".Localize() + "\n • " + "Movement".Localize() + "\n • " + "Z Offset".Localize(); errors.Add( new ValidationError("ZOffset0TooLarge") { Error = "Z Offset is too large.".Localize(), Details = string.Format( "{0}\n\n{1}", "The Z Offset for your printer, sometimes called Baby Stepping, is greater than 2mm and invalid. Clear the value and re-level the bed.".Localize(), location) }); } if (settings.GetValue <double>(SettingsKey.first_layer_extrusion_width) > settings.GetValue <double>(SettingsKey.nozzle_diameter) * 4) { errors.Add( new SettingsValidationError(SettingsKey.first_layer_extrusion_width) { Error = "{0} must be less than or equal to the {1} * 4.".Localize().FormatWith( GetSettingsName(SettingsKey.first_layer_extrusion_width), GetSettingsName(SettingsKey.nozzle_diameter)), ValueDetails = "{0} = {1}\n{2} = {3}".FormatWith( GetSettingsName(SettingsKey.first_layer_extrusion_width), settings.GetValue <double>(SettingsKey.first_layer_extrusion_width), GetSettingsName(SettingsKey.nozzle_diameter), settings.GetValue <double>(SettingsKey.nozzle_diameter)) }); } if (settings.GetValue <double>(SettingsKey.first_layer_extrusion_width) <= 0) { errors.Add( new SettingsValidationError(SettingsKey.first_layer_extrusion_width) { Error = "{0} must be greater than 0.".Localize().FormatWith( GetSettingsName(SettingsKey.first_layer_extrusion_width)), ValueDetails = "{0} = {1}".FormatWith( GetSettingsName(SettingsKey.first_layer_extrusion_width), settings.GetValue <double>(SettingsKey.first_layer_extrusion_width)), }); } if (settings.GetValue <double>(SettingsKey.external_perimeter_extrusion_width) > settings.GetValue <double>(SettingsKey.nozzle_diameter) * 4) { errors.Add( new SettingsValidationError(SettingsKey.external_perimeter_extrusion_width) { Error = "{0} must be less than or equal to the {1} * 4.".Localize().FormatWith( GetSettingsName(SettingsKey.external_perimeter_extrusion_width), GetSettingsName(SettingsKey.nozzle_diameter)), ValueDetails = "{0} = {1}\n{2} = {3}".FormatWith( GetSettingsName(SettingsKey.external_perimeter_extrusion_width), settings.GetValue <double>(SettingsKey.external_perimeter_extrusion_width), GetSettingsName(SettingsKey.nozzle_diameter), settings.GetValue <double>(SettingsKey.nozzle_diameter)), }); } if (settings.GetValue <double>(SettingsKey.external_perimeter_extrusion_width) <= 0) { errors.Add( new SettingsValidationError(SettingsKey.external_perimeter_extrusion_width) { Error = "{0} must be greater than 0.".Localize().FormatWith( GetSettingsName(SettingsKey.external_perimeter_extrusion_width)), ValueDetails = "{0} = {1}".FormatWith( GetSettingsName(SettingsKey.external_perimeter_extrusion_width), settings.GetValue <double>(SettingsKey.external_perimeter_extrusion_width)), }); } if (settings.GetValue <double>(SettingsKey.min_fan_speed) > 100) { errors.Add( new SettingsValidationError(SettingsKey.min_fan_speed) { Error = "The {0} can only go as high as 100%.".Localize().FormatWith( GetSettingsName(SettingsKey.min_fan_speed)), ValueDetails = "It is currently set to {0}.".Localize().FormatWith( settings.GetValue <double>(SettingsKey.min_fan_speed)), }); } if (settings.GetValue <double>(SettingsKey.max_fan_speed) > 100) { errors.Add( new SettingsValidationError(SettingsKey.max_fan_speed) { Error = "The {0} can only go as high as 100%.".Localize().FormatWith( GetSettingsName(SettingsKey.max_fan_speed)), ValueDetails = "It is currently set to {0}.".Localize().FormatWith( settings.GetValue <double>(SettingsKey.max_fan_speed)), }); } if (extruderCount < 1) { errors.Add( new SettingsValidationError(SettingsKey.extruder_count) { Error = "The {0} must be at least 1.".Localize().FormatWith( GetSettingsName(SettingsKey.extruder_count)), ValueDetails = "It is currently set to {0}.".Localize().FormatWith(extruderCount), }); } if (settings.GetValue <double>(SettingsKey.fill_density) < 0 || settings.GetValue <double>(SettingsKey.fill_density) > 1) { errors.Add( new SettingsValidationError(SettingsKey.fill_density) { Error = "The {0} must be between 0 and 1.".Localize().FormatWith( GetSettingsName(SettingsKey.fill_density)), ValueDetails = "It is currently set to {0}.".Localize().FormatWith( settings.GetValue <double>(SettingsKey.fill_density)), }); } // marlin firmware can only take a max of 128 bytes in a single instruction, make sure no lines are longer than that ValidateGCodeLinesShortEnough(SettingsKey.cancel_gcode, printer, errors); ValidateGCodeLinesShortEnough(SettingsKey.connect_gcode, printer, errors); ValidateGCodeLinesShortEnough(SettingsKey.end_gcode, printer, errors); ValidateGCodeLinesShortEnough(SettingsKey.layer_gcode, printer, errors); ValidateGCodeLinesShortEnough(SettingsKey.pause_gcode, printer, errors); ValidateGCodeLinesShortEnough(SettingsKey.resume_gcode, printer, errors); ValidateGCodeLinesShortEnough(SettingsKey.start_gcode, printer, errors); // If the given speed is part of the current slice engine then check that it is greater than 0. ValidateGoodSpeedSettingGreaterThan0(SettingsKey.bridge_speed, printer, errors); ValidateGoodSpeedSettingGreaterThan0(SettingsKey.air_gap_speed, printer, errors); ValidateGoodSpeedSettingGreaterThan0(SettingsKey.external_perimeter_speed, printer, errors); ValidateGoodSpeedSettingGreaterThan0(SettingsKey.first_layer_speed, printer, errors); ValidateGoodSpeedSettingGreaterThan0(SettingsKey.infill_speed, printer, errors); ValidateGoodSpeedSettingGreaterThan0(SettingsKey.perimeter_speed, printer, errors); ValidateGoodSpeedSettingGreaterThan0(SettingsKey.small_perimeter_speed, printer, errors); ValidateGoodSpeedSettingGreaterThan0(SettingsKey.solid_infill_speed, printer, errors); ValidateGoodSpeedSettingGreaterThan0(SettingsKey.support_material_speed, printer, errors); ValidateGoodSpeedSettingGreaterThan0(SettingsKey.top_solid_infill_speed, printer, errors); ValidateGoodSpeedSettingGreaterThan0(SettingsKey.travel_speed, printer, errors); ValidateGoodSpeedSettingGreaterThan0(SettingsKey.retract_speed, printer, errors); } catch (Exception e) { errors.Add( new ValidationError("ExceptionDuringSliceSettingsValidation") { Error = "Unexpected error validating settings".Localize(), Details = e.Message }); } return(errors); }