示例#1
0
        public void finalize(int maxObjectHeight, int moveSpeed, string endCode)
        {
            writeFanCommand(0);
            writeRetraction();
            setZ(maxObjectHeight + 5000);
            writeMove(getPositionXY(), moveSpeed, 0);
            writeCode(endCode);
            writeComment("filament used = {0:0.0}".FormatWith(getTotalFilamentUsed(0) + getTotalFilamentUsed(1)));
            writeComment("filament used extruder 1 (mm) = {0:0.0}".FormatWith(getTotalFilamentUsed(0)));
            writeComment("filament used extruder 2 (mm) = {0:0.0}".FormatWith(getTotalFilamentUsed(1)));
            writeComment("total print time (s) = {0:0}".FormatWith(getTotalPrintTime()));

            LogOutput.log("Print time: {0}\n".FormatWith((int)(getTotalPrintTime())));
            LogOutput.log("Filament: {0}\n".FormatWith((int)(getTotalFilamentUsed(0))));
            LogOutput.log("Filament2: {0}\n".FormatWith((int)(getTotalFilamentUsed(1))));

            if (GetOutputType() == ConfigConstants.OUTPUT_TYPE.ULTIGCODE)
            {
                string numberString;
                numberString = "{0}".FormatWith((int)(getTotalPrintTime()));
                //replaceTagInStart("<__TIME__>", numberString);
                numberString = "{0}".FormatWith((int)(getTotalFilamentUsed(0)));
                //replaceTagInStart("<FILAMENT>", numberString);
                numberString = "{0}".FormatWith((int)(getTotalFilamentUsed(1)));
                //replaceTagInStart("<FILAMEN2>", numberString);
            }
        }
示例#2
0
 public bool LoadStlFile(string input_filename)
 {
     preSetup(config.extrusionWidth_um);
     timeKeeper.Restart();
     LogOutput.log("Loading {0} from disk...\n".FormatWith(input_filename));
     if (!SimpleModel.loadModelFromFile(simpleModel, input_filename, config.modelRotationMatrix))
     {
         LogOutput.logError("Failed to load model: {0}\n".FormatWith(input_filename));
         return(false);
     }
     LogOutput.log("Loaded from disk in {0:0.0}s\n".FormatWith(timeKeeper.Elapsed.Seconds));
     return(true);
 }
示例#3
0
        void sliceModels(SliceDataStorage storage)
        {
            timeKeeper.Restart();
#if False
            optomizedModel.saveDebugSTL("debug_output.stl");
#endif

            LogOutput.log("Slicing model...\n");
            List <Slicer> slicerList = new List <Slicer>();
            for (int volumeIndex = 0; volumeIndex < optomizedModel.volumes.Count; volumeIndex++)
            {
                Slicer slicer = new Slicer(optomizedModel.volumes[volumeIndex], config);
                slicerList.Add(slicer);
            }

#if false
            slicerList[0].DumpSegmentsToGcode("Volume 0 Segments.gcode");
            slicerList[0].DumpPolygonsToGcode("Volume 0 Polygons.gcode");
            slicerList[0].DumpPolygonsToHTML("Volume 0 Polygons.html");
#endif

            LogOutput.log("Sliced model in {0:0.0}s\n".FormatWith(timeKeeper.Elapsed.Seconds));
            timeKeeper.Restart();

            LogOutput.log("Generating support map...\n");
            storage.support.GenerateSupportGrid(optomizedModel, config);

            storage.modelSize = optomizedModel.size_um;
            storage.modelMin  = optomizedModel.minXYZ_um;
            storage.modelMax  = optomizedModel.maxXYZ_um;

            LogOutput.log("Generating layer parts...\n");
            for (int volumeIndex = 0; volumeIndex < slicerList.Count; volumeIndex++)
            {
                storage.volumes.Add(new SliceVolumeStorage());
                LayerPart.createLayerParts(storage.volumes[volumeIndex], slicerList[volumeIndex], config.repairOverlaps);

                if (config.enableRaft)
                {
                    //Add the raft offset to each layer.
                    for (int layerNr = 0; layerNr < storage.volumes[volumeIndex].layers.Count; layerNr++)
                    {
                        storage.volumes[volumeIndex].layers[layerNr].printZ += config.raftBaseThickness_um + config.raftInterfaceThicknes_um;
                    }
                }
            }
            LogOutput.log("Generated layer parts in {0:0.0}s\n".FormatWith(timeKeeper.Elapsed.Seconds));
            timeKeeper.Restart();
        }
示例#4
0
        public void tellFileSize()
        {
            double fsize = gcodeFileStream.BaseStream.Length;

            if (fsize > 1024 * 1024)
            {
                fsize /= 1024.0 * 1024.0;
                LogOutput.log("Wrote {0:0.0} MB.\n".FormatWith(fsize));
            }
            if (fsize > 1024)
            {
                fsize /= 1024.0;
                LogOutput.log("Wrote {0:0.0} kilobytes.\n".FormatWith(fsize));
            }
        }
示例#5
0
        public void DoProcessing()
        {
            if (!gcode.isOpened())
            {
                return;
            }

            timeKeeper.Restart();
            LogOutput.log("Analyzing and optimizing model...\n");
            optomizedModel = new OptimizedModel(simpleModel);
            if (MatterSlice.Canceled)
            {
                return;
            }
            optomizedModel.SetPositionAndSize(simpleModel, config.positionToPlaceObjectCenter_um.X, config.positionToPlaceObjectCenter_um.Y, -config.bottomClipAmount_um, config.centerObjectInXy);
            for (int volumeIndex = 0; volumeIndex < simpleModel.volumes.Count; volumeIndex++)
            {
                LogOutput.log("  Face counts: {0} . {1} {2:0.0}%\n".FormatWith((int)simpleModel.volumes[volumeIndex].faceTriangles.Count, (int)optomizedModel.volumes[volumeIndex].facesTriangle.Count, (double)(optomizedModel.volumes[volumeIndex].facesTriangle.Count) / (double)(simpleModel.volumes[volumeIndex].faceTriangles.Count) * 100));
                LogOutput.log("  Vertex counts: {0} . {1} {2:0.0}%\n".FormatWith((int)simpleModel.volumes[volumeIndex].faceTriangles.Count * 3, (int)optomizedModel.volumes[volumeIndex].vertices.Count, (double)(optomizedModel.volumes[volumeIndex].vertices.Count) / (double)(simpleModel.volumes[volumeIndex].faceTriangles.Count * 3) * 100));
            }

            LogOutput.log("Optimize model {0:0.0}s \n".FormatWith(timeKeeper.Elapsed.Seconds));
            timeKeeper.Reset();

            Stopwatch timeKeeperTotal = new Stopwatch();

            timeKeeperTotal.Start();
            preSetup(config.extrusionWidth_um);
            sliceModels(storage);

            processSliceData(storage);
            if (MatterSlice.Canceled)
            {
                return;
            }
            writeGCode(storage);
            if (MatterSlice.Canceled)
            {
                return;
            }

            LogOutput.logProgress("process", 1, 1); //Report to the GUI that a file has been fully processed.
            LogOutput.log("Total time elapsed {0:0.00}s.\n".FormatWith(timeKeeperTotal.Elapsed.Seconds));
        }
示例#6
0
        public static int ProcessArgs(string[] args)
        {
#if DEBUG
            Tests.BridgeTests.Run();
#endif
            ConfigSettings config    = new ConfigSettings();
            fffProcessor   processor = new fffProcessor(config);

            LogOutput.log("\nMatterSlice version {0}\n\n".FormatWith(ConfigConstants.VERSION));

            for (int argn = 0; argn < args.Length; argn++)
            {
                string str = args[argn];
                if (str[0] == '-')
                {
                    for (int stringIndex = 1; stringIndex < str.Length; stringIndex++)
                    {
                        switch (str[stringIndex])
                        {
                        case 'h':
                            print_usage();
                            return(0);

                        case 'v':
                            LogOutput.verbose_level++;
                            break;

                        case 'o':
                            argn++;
                            if (!processor.setTargetFile(args[argn]))
                            {
                                LogOutput.logError("Failed to open {0} for output.\n".FormatWith(args[argn]));
                                return(1);
                            }
                            break;

                        case 'c':
                        {
                            // Read a config file from the given path
                            argn++;
                            if (!config.ReadSettings(args[argn]))
                            {
                                LogOutput.logError("Failed to read config '{0}'\n".FormatWith(args[argn]));
                            }
                        }
                        break;

                        case 'd':
                            config.DumpSettings("settings.ini");
                            break;

                        case 's':
                        {
                            argn++;
                            int equalsPos = args[argn].IndexOf('=');
                            if (equalsPos != -1)
                            {
                                string key   = args[argn].Substring(0, equalsPos);
                                string value = args[argn].Substring(equalsPos + 1);
                                if (key.Length > 1)
                                {
                                    if (!config.SetSetting(key, value))
                                    {
                                        LogOutput.logError("Setting not found: {0} {1}\n".FormatWith(key, value));
                                    }
                                }
                            }
                        }
                        break;

                        case 'm':
                            argn++;
                            throw new NotImplementedException("m");
#if false
                            sscanf(argv[argn], "%lf,%lf,%lf,%lf,%lf,%lf,%lf,%lf,%lf",
                                   &config.matrix.m[0][0], &config.matrix.m[0][1], &config.matrix.m[0][2],
                                   &config.matrix.m[1][0], &config.matrix.m[1][1], &config.matrix.m[1][2],
                                   &config.matrix.m[2][0], &config.matrix.m[2][1], &config.matrix.m[2][2]);
#endif
                            break;

                        default:
                            throw new NotImplementedException("Unknown option: {0}\n".FormatWith(str));
                            LogOutput.logError("Unknown option: {0}\n".FormatWith(str));
                            break;
                        }
                    }
                }
                else
                {
                    processor.processFile(args[argn]);
                }
            }

            processor.finalize();
            return(0);
        }
示例#7
0
        public Slicer(OptimizedVolume ov, int initialLayerThickness, int layerThickness, ConfigConstants.REPAIR_OUTLINES outlineRepairTypes)
        {
            modelSize = ov.model.size;
            modelMin  = ov.model.minXYZ;

            int heightWithoutFirstLayer      = modelSize.z - initialLayerThickness;
            int countOfNormalThicknessLayers = heightWithoutFirstLayer / layerThickness;

            int layerCount = countOfNormalThicknessLayers + 1; // we have to add in the first layer (that is a differnt size)

            LogOutput.log(string.Format("Layer count: {0}\n", layerCount));
            layers.Capacity = layerCount;
            for (int i = 0; i < layerCount; i++)
            {
                layers.Add(new SlicerLayer());
            }

            for (int layerIndex = 0; layerIndex < layerCount; layerIndex++)
            {
                if (layerIndex == 0)
                {
                    layers[layerIndex].z = initialLayerThickness / 2;
                }
                else
                {
                    layers[layerIndex].z = initialLayerThickness + layerThickness / 2 + layerThickness * (layerIndex - 1);
                }
            }

            for (int faceIndex = 0; faceIndex < ov.facesTriangle.Count; faceIndex++)
            {
                Point3 p0   = ov.vertices[ov.facesTriangle[faceIndex].vertexIndex[0]].position;
                Point3 p1   = ov.vertices[ov.facesTriangle[faceIndex].vertexIndex[1]].position;
                Point3 p2   = ov.vertices[ov.facesTriangle[faceIndex].vertexIndex[2]].position;
                int    minZ = p0.z;
                int    maxZ = p0.z;
                if (p1.z < minZ)
                {
                    minZ = p1.z;
                }
                if (p2.z < minZ)
                {
                    minZ = p2.z;
                }
                if (p1.z > maxZ)
                {
                    maxZ = p1.z;
                }
                if (p2.z > maxZ)
                {
                    maxZ = p2.z;
                }

                for (int layerIndex = 0; layerIndex < layers.Count; layerIndex++)
                {
                    int z = layers[layerIndex].z;
                    if (z < minZ || layerIndex < 0)
                    {
                        continue;
                    }

                    SlicerSegment polyCrossingAtThisZ;
                    if (p0.z < z && p1.z >= z && p2.z >= z)
                    {
                        // p1   p2
                        // --------
                        //   p0
                        polyCrossingAtThisZ = GetCrossingAtZ(p0, p2, p1, z);
                    }
                    else if (p0.z >= z && p1.z < z && p2.z < z)
                    {
                        //   p0
                        // --------
                        // p1  p2
                        polyCrossingAtThisZ = GetCrossingAtZ(p0, p1, p2, z);
                    }
                    else if (p1.z < z && p0.z >= z && p2.z >= z)
                    {
                        // p0   p2
                        // --------
                        //   p1
                        polyCrossingAtThisZ = GetCrossingAtZ(p1, p0, p2, z);
                    }
                    else if (p1.z >= z && p0.z < z && p2.z < z)
                    {
                        //   p1
                        // --------
                        // p0  p2
                        polyCrossingAtThisZ = GetCrossingAtZ(p1, p2, p0, z);
                    }
                    else if (p2.z < z && p1.z >= z && p0.z >= z)
                    {
                        // p1   p0
                        // --------
                        //   p2
                        polyCrossingAtThisZ = GetCrossingAtZ(p2, p1, p0, z);
                    }
                    else if (p2.z >= z && p1.z < z && p0.z < z)
                    {
                        //   p2
                        // --------
                        // p1  p0
                        polyCrossingAtThisZ = GetCrossingAtZ(p2, p0, p1, z);
                    }
                    else
                    {
                        //Not all cases create a segment, because a point of a face could create just a dot, and two touching faces
                        //  on the slice would create two segments
                        continue;
                    }
                    layers[layerIndex].faceTo2DSegmentIndex[faceIndex] = layers[layerIndex].segmentList.Count;
                    polyCrossingAtThisZ.faceIndex             = faceIndex;
                    polyCrossingAtThisZ.hasBeenAddedToPolygon = false;
                    layers[layerIndex].segmentList.Add(polyCrossingAtThisZ);
                }
            }

            for (int layerIndex = 0; layerIndex < layers.Count; layerIndex++)
            {
                layers[layerIndex].MakePolygons(ov, outlineRepairTypes);
            }
        }
示例#8
0
        void writeGCode(SliceDataStorage storage)
        {
            gcode.writeComment("filamentDiameter = {0}".FormatWith(config.filamentDiameter));
            gcode.writeComment("extrusionWidth = {0}".FormatWith(config.extrusionWidth));
            gcode.writeComment("firstLayerExtrusionWidth = {0}".FormatWith(config.firstLayerExtrusionWidth));
            gcode.writeComment("layerThickness = {0}".FormatWith(config.layerThickness));
            gcode.writeComment("firstLayerThickness = {0}".FormatWith(config.firstLayerThickness));

            if (fileNr == 1)
            {
                if (gcode.GetOutputType() == ConfigConstants.OUTPUT_TYPE.ULTIGCODE)
                {
                    gcode.writeComment("TYPE:UltiGCode");
                    gcode.writeComment("TIME:<__TIME__>");
                    gcode.writeComment("MATERIAL:<FILAMENT>");
                    gcode.writeComment("MATERIAL2:<FILAMEN2>");
                }
                gcode.writeCode(config.startCode);
                if (gcode.GetOutputType() == ConfigConstants.OUTPUT_TYPE.BFB)
                {
                    gcode.writeComment("enable auto-retraction");
                    gcode.writeLine("M227 S{0} P{1}".FormatWith(config.retractionOnTravel * 2560, config.retractionOnTravel * 2560));
                }
            }
            else
            {
                gcode.writeFanCommand(0);
                gcode.resetExtrusionValue();
                gcode.writeRetraction();
                gcode.setZ(maxObjectHeight + 5000);
                gcode.writeMove(gcode.getPositionXY(), config.travelSpeed, 0);
                gcode.writeMove(new IntPoint(storage.modelMin.x, storage.modelMin.y), config.travelSpeed, 0);
            }
            fileNr++;

            int totalLayers = storage.volumes[0].layers.Count;

            // let's remove any of the layers on top that are empty
            {
                for (int layerIndex = totalLayers - 1; layerIndex >= 0; layerIndex--)
                {
                    bool layerHasData = false;
                    foreach (SliceVolumeStorage currentVolume in storage.volumes)
                    {
                        SliceLayer currentLayer = currentVolume.layers[layerIndex];
                        for (int partIndex = 0; partIndex < currentVolume.layers[layerIndex].parts.Count; partIndex++)
                        {
                            SliceLayerPart currentPart = currentLayer.parts[partIndex];
                            if (currentPart.outline.Count > 0)
                            {
                                layerHasData = true;
                                break;
                            }
                        }
                    }

                    if (layerHasData)
                    {
                        break;
                    }
                    totalLayers--;
                }
            }
            gcode.writeComment("Layer count: {0}".FormatWith(totalLayers));

            // keep the raft generation code inside of raft
            Raft.GenerateRaftGCodeIfRequired(storage, config, gcode);

            int volumeIdx = 0;

            for (int layerIndex = 0; layerIndex < totalLayers; layerIndex++)
            {
                if (MatterSlice.Canceled)
                {
                    return;
                }
                LogOutput.log("Writing Layers {0}/{1}\n".FormatWith(layerIndex + 1, totalLayers));

                LogOutput.logProgress("export", layerIndex + 1, totalLayers);

                int extrusionWidth_um = config.extrusionWidth_um;
                if (layerIndex == 0)
                {
                    extrusionWidth_um = config.firstLayerExtrusionWidth_um;
                }

                if (layerIndex == 0)
                {
                    skirtConfig.setData(config.firstLayerSpeed, extrusionWidth_um, "SKIRT");
                    inset0Config.setData(config.firstLayerSpeed, extrusionWidth_um, "WALL-OUTER");
                    insetXConfig.setData(config.firstLayerSpeed, extrusionWidth_um, "WALL-INNER");
                    fillConfig.setData(config.firstLayerSpeed, extrusionWidth_um, "FILL", false);
                    bridgConfig.setData(config.firstLayerSpeed, extrusionWidth_um, "BRIDGE");

                    supportNormalConfig.setData(config.firstLayerSpeed, config.supportExtrusionWidth_um, "SUPPORT");
                    supportInterfaceConfig.setData(config.firstLayerSpeed, config.extrusionWidth_um, "SUPPORT-INTERFACE");
                }
                else
                {
                    skirtConfig.setData(config.insidePerimetersSpeed, extrusionWidth_um, "SKIRT");
                    inset0Config.setData(config.outsidePerimeterSpeed, extrusionWidth_um, "WALL-OUTER");
                    insetXConfig.setData(config.insidePerimetersSpeed, extrusionWidth_um, "WALL-INNER");
                    fillConfig.setData(config.infillSpeed, extrusionWidth_um, "FILL", false);
                    bridgConfig.setData(config.bridgeSpeed, extrusionWidth_um, "BRIDGE");

                    supportNormalConfig.setData(config.supportMaterialSpeed, config.supportExtrusionWidth_um, "SUPPORT");
                    supportInterfaceConfig.setData(config.supportMaterialSpeed, config.extrusionWidth_um, "SUPPORT-INTERFACE");
                }

                gcode.writeComment("LAYER:{0}".FormatWith(layerIndex));
                if (layerIndex == 0)
                {
                    gcode.setExtrusion(config.firstLayerThickness_um, config.filamentDiameter_um, config.extrusionMultiplier);
                }
                else
                {
                    gcode.setExtrusion(config.layerThickness_um, config.filamentDiameter_um, config.extrusionMultiplier);
                }

                GCodePlanner gcodeLayer = new GCodePlanner(gcode, config.travelSpeed, config.minimumTravelToCauseRetraction_um);

                // get the correct height for this layer
                int z = config.firstLayerThickness_um + layerIndex * config.layerThickness_um;
                if (config.enableRaft)
                {
                    z += config.raftBaseThickness_um + config.raftInterfaceThicknes_um + config.raftSurfaceLayers * config.raftSurfaceThickness_um;
                    if (layerIndex == 0)
                    {
                        // We only raise the first layer of the print up by the air gap.
                        // To give it:
                        //   Less press into the raft
                        //   More time to cool
                        //   more surface area to air while extruding
                        z += config.raftAirGap_um;
                    }
                }

                gcode.setZ(z);

                bool printSupportFirst = (storage.support.generated && config.supportExtruder > 0 && config.supportExtruder == gcodeLayer.getExtruder());
                if (printSupportFirst)
                {
                    AddSupportToGCode(storage, gcodeLayer, layerIndex, config);
                }

                int fanSpeedPercent = GetFanSpeed(layerIndex, gcodeLayer);

                for (int volumeCnt = 0; volumeCnt < storage.volumes.Count; volumeCnt++)
                {
                    if (volumeCnt > 0)
                    {
                        volumeIdx = (volumeIdx + 1) % storage.volumes.Count;
                    }

                    AddVolumeLayerToGCode(storage, gcodeLayer, volumeIdx, layerIndex, extrusionWidth_um, fanSpeedPercent);
                }

                if (!printSupportFirst)
                {
                    AddSupportToGCode(storage, gcodeLayer, layerIndex, config);
                }

                //Finish the layer by applying speed corrections for minimum layer times.
                gcodeLayer.forceMinimumLayerTime(config.minimumLayerTimeSeconds, config.minimumPrintingSpeed);

                gcode.writeFanCommand(fanSpeedPercent);

                int currentLayerThickness_um = config.layerThickness_um;
                if (layerIndex <= 0)
                {
                    currentLayerThickness_um = config.firstLayerThickness_um;
                }

                gcodeLayer.writeGCode(config.doCoolHeadLift, currentLayerThickness_um);
            }

            LogOutput.log("Wrote layers in {0:0.00}s.\n".FormatWith(timeKeeper.Elapsed.Seconds));
            timeKeeper.Restart();
            gcode.tellFileSize();
            gcode.writeFanCommand(0);

            //Store the object height for when we are printing multiple objects, as we need to clear every one of them when moving to the next position.
            maxObjectHeight = Math.Max(maxObjectHeight, storage.modelSize.z);
        }
示例#9
0
        void processSliceData(SliceDataStorage storage)
        {
            MultiVolumes.RemoveVolumesIntersections(storage.volumes);
            MultiVolumes.OverlapMultipleVolumesSlightly(storage.volumes, config.multiVolumeOverlapPercent);
#if False
            LayerPart.dumpLayerparts(storage, "output.html");
#endif

            int totalLayers = storage.volumes[0].layers.Count;
#if DEBUG
            for (int volumeIndex = 1; volumeIndex < storage.volumes.Count; volumeIndex++)
            {
                if (totalLayers != storage.volumes[volumeIndex].layers.Count)
                {
                    throw new Exception("All the valumes must have the same number of layers (they just can have empty layers).");
                }
            }
#endif

            for (int layerIndex = 0; layerIndex < totalLayers; layerIndex++)
            {
                for (int volumeIndex = 0; volumeIndex < storage.volumes.Count; volumeIndex++)
                {
                    if (MatterSlice.Canceled)
                    {
                        return;
                    }
                    int insetCount = config.numberOfPerimeters;
                    if (config.continuousSpiralOuterPerimeter && (int)(layerIndex) < config.numberOfBottomLayers && layerIndex % 2 == 1)
                    {
                        //Add extra insets every 2 layers when spiralizing, this makes bottoms of cups watertight.
                        insetCount += 5;
                    }

                    SliceLayer layer          = storage.volumes[volumeIndex].layers[layerIndex];
                    int        extrusionWidth = config.extrusionWidth_um;
                    if (layerIndex == 0)
                    {
                        extrusionWidth = config.firstLayerExtrusionWidth_um;
                    }
                    Inset.generateInsets(layer, extrusionWidth, insetCount);
                }
                LogOutput.log("Creating Insets {0}/{1}\n".FormatWith(layerIndex + 1, totalLayers));
            }

            if (config.wipeShieldDistanceFromShapes_um > 0)
            {
                CreateWipeShields(storage, totalLayers);
            }

            LogOutput.log("Generated inset in {0:0.0}s\n".FormatWith(timeKeeper.Elapsed.Seconds));
            timeKeeper.Restart();

            for (int layerIndex = 0; layerIndex < totalLayers; layerIndex++)
            {
                if (MatterSlice.Canceled)
                {
                    return;
                }
                //Only generate bottom and top layers and infill for the first X layers when spiralize is choosen.
                if (!config.continuousSpiralOuterPerimeter || (int)(layerIndex) < config.numberOfBottomLayers)
                {
                    for (int volumeIndex = 0; volumeIndex < storage.volumes.Count; volumeIndex++)
                    {
                        int extrusionWidth = config.extrusionWidth_um;
                        if (layerIndex == 0)
                        {
                            extrusionWidth = config.firstLayerExtrusionWidth_um;
                        }

                        Skin.generateTopAndBottomLayers(layerIndex, storage.volumes[volumeIndex], extrusionWidth, config.numberOfBottomLayers, config.numberOfTopLayers);
                        Skin.generateSparse(layerIndex, storage.volumes[volumeIndex], extrusionWidth, config.numberOfBottomLayers, config.numberOfTopLayers);
                    }
                }
                LogOutput.log("Creating Top & Bottom Layers {0}/{1}\n".FormatWith(layerIndex + 1, totalLayers));
            }
            LogOutput.log("Generated top bottom layers in {0:0.0}s\n".FormatWith(timeKeeper.Elapsed.Seconds));
            timeKeeper.Restart();

            if (config.wipeTowerSize_um > 0)
            {
                Polygon p = new Polygon();
                storage.wipeTower.Add(p);
                p.Add(new IntPoint(storage.modelMin.x - 3000, storage.modelMax.y + 3000));
                p.Add(new IntPoint(storage.modelMin.x - 3000, storage.modelMax.y + 3000 + config.wipeTowerSize_um));
                p.Add(new IntPoint(storage.modelMin.x - 3000 - config.wipeTowerSize_um, storage.modelMax.y + 3000 + config.wipeTowerSize_um));
                p.Add(new IntPoint(storage.modelMin.x - 3000 - config.wipeTowerSize_um, storage.modelMax.y + 3000));

                storage.wipePoint = new IntPoint(storage.modelMin.x - 3000 - config.wipeTowerSize_um / 2, storage.modelMax.y + 3000 + config.wipeTowerSize_um / 2);
            }

            if (config.enableRaft)
            {
                Raft.GenerateRaftOutlines(storage, config.raftExtraDistanceAroundPart_um);
                Skirt.generateSkirt(storage,
                                    config.skirtDistance_um + config.raftBaseLineSpacing_um,
                                    config.raftBaseLineSpacing_um,
                                    config.numberOfSkirtLoops,
                                    config.skirtMinLength_um,
                                    config.raftBaseThickness_um);
            }
            else
            {
                Skirt.generateSkirt(storage,
                                    config.skirtDistance_um,
                                    config.firstLayerExtrusionWidth_um,
                                    config.numberOfSkirtLoops,
                                    config.skirtMinLength_um,
                                    config.firstLayerThickness_um);
            }
        }