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);
        }
Esempio n. 5
0
        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);
        }
Esempio n. 8
0
        /// <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);
        }