void SetUp() { output << std::fixed; gcode.output_stream = &output; //Since GCodeExport doesn't support copying, we have to reset everything in-place. gcode.currentPosition = Point3(0, 0, MM2INT(20)); gcode.layer_nr = 0; gcode.current_e_value = 0; gcode.current_e_offset = 0; gcode.current_extruder = 0; gcode.current_fan_speed = -1; gcode.total_print_times = std::vector<Duration>(static_cast<unsigned char>(PrintFeatureType::NumPrintFeatureTypes), 0.0); gcode.currentSpeed = 1; gcode.current_print_acceleration = -1; gcode.current_travel_acceleration = -1; gcode.current_jerk = -1; gcode.current_max_z_feedrate = -1; gcode.is_z_hopped = 0; gcode.setFlavor(EGCodeFlavor::MARLIN); gcode.initial_bed_temp = 0; gcode.fan_number = 0; gcode.total_bounding_box = AABB3D(); gcode.current_layer_z = 0; gcode.relative_extrusion = false; gcode.new_line = "\n"; //Not BFB flavour by default. gcode.machine_name = "Your favourite 3D printer"; gcode.machine_buildplate_type = "Your favourite build plate"; //Set up a scene so that we may request settings. Application::getInstance().current_slice = new Slice(1); mock_communication = new MockCommunication(); Application::getInstance().communication = mock_communication; }
void processFile(const char* input_filename, ConfigSettings& config, GCodeExport& gcode, bool firstFile) { for(unsigned int n=1; n<16; n++) gcode.setExtruderOffset(n, config.extruderOffset[n].p()); gcode.setFlavor(config.gcodeFlavor); double t = getTime(); log("Loading %s from disk...\n", input_filename); SimpleModel* m = loadModel(input_filename, config.matrix); if (!m) { log("Failed to load model: %s\n", input_filename); return; } log("Loaded from disk in %5.3fs\n", timeElapsed(t)); log("Analyzing and optimizing model...\n"); OptimizedModel* om = new OptimizedModel(m, Point3(config.objectPosition.X, config.objectPosition.Y, -config.objectSink)); for(unsigned int v = 0; v < m->volumes.size(); v++) { log(" Face counts: %i -> %i %0.1f%%\n", (int)m->volumes[v].faces.size(), (int)om->volumes[v].faces.size(), float(om->volumes[v].faces.size()) / float(m->volumes[v].faces.size()) * 100); log(" Vertex counts: %i -> %i %0.1f%%\n", (int)m->volumes[v].faces.size() * 3, (int)om->volumes[v].points.size(), float(om->volumes[v].points.size()) / float(m->volumes[v].faces.size() * 3) * 100); } delete m; log("Optimize model %5.3fs \n", timeElapsed(t)); //om->saveDebugSTL("c:\\models\\output.stl"); log("Slicing model...\n"); vector<Slicer*> slicerList; for(unsigned int volumeIdx=0; volumeIdx < om->volumes.size(); volumeIdx++) { slicerList.push_back(new Slicer(&om->volumes[volumeIdx], config.initialLayerThickness / 2, config.layerThickness, config.fixHorrible & FIX_HORRIBLE_KEEP_NONE_CLOSED, config.fixHorrible & FIX_HORRIBLE_EXTENSIVE_STITCHING)); //slicerList[volumeIdx]->dumpSegmentsToHTML("C:\\models\\output.html"); } log("Sliced model in %5.3fs\n", timeElapsed(t)); SliceDataStorage storage; fprintf(stdout,"Generating support map...\n"); generateSupportGrid(storage.support, om, config.supportAngle, config.supportEverywhere > 0, config.supportXYDistance, config.supportZDistance); storage.modelSize = om->modelSize; storage.modelMin = om->vMin; storage.modelMax = om->vMax; delete om; log("Generating layer parts...\n"); for(unsigned int volumeIdx=0; volumeIdx < slicerList.size(); volumeIdx++) { storage.volumes.push_back(SliceVolumeStorage()); createLayerParts(storage.volumes[volumeIdx], slicerList[volumeIdx], config.fixHorrible & (FIX_HORRIBLE_UNION_ALL_TYPE_A | FIX_HORRIBLE_UNION_ALL_TYPE_B | FIX_HORRIBLE_UNION_ALL_TYPE_C)); delete slicerList[volumeIdx]; } //carveMultipleVolumes(storage.volumes); generateMultipleVolumesOverlap(storage.volumes, config.multiVolumeOverlap); log("Generated layer parts in %5.3fs\n", timeElapsed(t)); //dumpLayerparts(storage, "c:/models/output.html"); const unsigned int totalLayers = storage.volumes[0].layers.size(); for(unsigned int layerNr=0; layerNr<totalLayers; layerNr++) { for(unsigned int volumeIdx=0; volumeIdx<storage.volumes.size(); volumeIdx++) { int insetCount = config.insetCount; if (config.spiralizeMode && int(layerNr) < config.downSkinCount && layerNr % 2 == 1)//Add extra insets every 2 layers when spiralizing, this makes bottoms of cups watertight. insetCount += 5; generateInsets(&storage.volumes[volumeIdx].layers[layerNr], config.extrusionWidth, insetCount); } logProgress("inset",layerNr+1,totalLayers); } log("Generated inset in %5.3fs\n", timeElapsed(t)); for(unsigned int layerNr=0; layerNr<totalLayers; layerNr++) { if (!config.spiralizeMode || int(layerNr) < config.downSkinCount) //Only generate up/downskin and infill for the first X layers when spiralize is choosen. { for(unsigned int volumeIdx=0; volumeIdx<storage.volumes.size(); volumeIdx++) { generateSkins(layerNr, storage.volumes[volumeIdx], config.extrusionWidth, config.downSkinCount, config.upSkinCount, config.infillOverlap); generateSparse(layerNr, storage.volumes[volumeIdx], config.extrusionWidth, config.downSkinCount, config.upSkinCount); } } logProgress("skin",layerNr+1,totalLayers); } log("Generated up/down skin in %5.3fs\n", timeElapsed(t)); generateSkirt(storage, config.skirtDistance, config.extrusionWidth, config.skirtLineCount, config.skirtMinLength); generateRaft(storage, config.raftMargin); for(unsigned int volumeIdx=0; volumeIdx<storage.volumes.size(); volumeIdx++) { for(unsigned int layerNr=0; layerNr<totalLayers; layerNr++) { for(unsigned int partNr=0; partNr<storage.volumes[volumeIdx].layers[layerNr].parts.size(); partNr++) { if (layerNr > 0) storage.volumes[volumeIdx].layers[layerNr].parts[partNr].bridgeAngle = bridgeAngle(&storage.volumes[volumeIdx].layers[layerNr].parts[partNr], &storage.volumes[volumeIdx].layers[layerNr-1]); else storage.volumes[volumeIdx].layers[layerNr].parts[partNr].bridgeAngle = -1; } } } gcode.setRetractionSettings(config.retractionAmount, config.retractionSpeed, config.retractionAmountExtruderSwitch, config.minimalExtrusionBeforeRetraction); if (firstFile) { if (gcode.getFlavor() == GCODE_FLAVOR_ULTIGCODE) { gcode.addCode(";FLAVOR:UltiGCode"); gcode.addCode(";TIME:<__TIME__>"); gcode.addCode(";MATERIAL:<FILAMENT>"); } gcode.addCode(config.startCode); } else { gcode.addFanCommand(0); gcode.resetExtrusionValue(); gcode.addRetraction(); gcode.setZ(maxObjectHeight + 5000); gcode.addMove(Point(storage.modelMin.x, storage.modelMin.y), config.moveSpeed, 0); } gcode.addComment("total_layers=%d",totalLayers); GCodePathConfig skirtConfig(config.printSpeed, config.extrusionWidth, "SKIRT"); GCodePathConfig inset0Config(config.printSpeed, config.extrusionWidth, "WALL-OUTER"); GCodePathConfig inset1Config(config.printSpeed, config.extrusionWidth, "WALL-INNER"); GCodePathConfig fillConfig(config.infillSpeed, config.extrusionWidth, "FILL"); GCodePathConfig supportConfig(config.printSpeed, config.extrusionWidth, "SUPPORT"); if (config.raftBaseThickness > 0 && config.raftInterfaceThickness > 0) { GCodePathConfig raftBaseConfig(config.initialLayerSpeed, config.raftBaseLinewidth, "SUPPORT"); GCodePathConfig raftInterfaceConfig(config.initialLayerSpeed, config.raftInterfaceLinewidth, "SUPPORT"); { gcode.addComment("LAYER:-2"); gcode.addComment("RAFT"); GCodePlanner gcodeLayer(gcode, config.moveSpeed, config.retractionMinimalDistance); gcode.setZ(config.raftBaseThickness); gcode.setExtrusion(config.raftBaseThickness, config.filamentDiameter, config.filamentFlow); gcodeLayer.addPolygonsByOptimizer(storage.raftOutline, &raftBaseConfig); Polygons raftLines; generateLineInfill(storage.raftOutline, raftLines, config.raftBaseLinewidth, config.raftLineSpacing, config.infillOverlap, 0); gcodeLayer.addPolygonsByOptimizer(raftLines, &raftBaseConfig); gcodeLayer.writeGCode(false, config.raftBaseThickness); } { gcode.addComment("LAYER:-1"); gcode.addComment("RAFT"); GCodePlanner gcodeLayer(gcode, config.moveSpeed, config.retractionMinimalDistance); gcode.setZ(config.raftBaseThickness + config.raftInterfaceThickness); gcode.setExtrusion(config.raftInterfaceThickness, config.filamentDiameter, config.filamentFlow); Polygons raftLines; generateLineInfill(storage.raftOutline, raftLines, config.raftInterfaceLinewidth, config.raftLineSpacing, config.infillOverlap, 90); gcodeLayer.addPolygonsByOptimizer(raftLines, &raftInterfaceConfig); gcodeLayer.writeGCode(false, config.raftInterfaceThickness); } } int volumeIdx = 0; for(unsigned int layerNr=0; layerNr<totalLayers; layerNr++) { logProgress("export", layerNr+1, totalLayers); GCodePlanner gcodeLayer(gcode, config.moveSpeed, config.retractionMinimalDistance); gcode.addComment("LAYER:%d", layerNr); int32_t z = config.initialLayerThickness + layerNr * config.layerThickness; z += config.raftBaseThickness + config.raftInterfaceThickness; gcode.setZ(z); if (layerNr == 0) gcodeLayer.addPolygonsByOptimizer(storage.skirt, &skirtConfig); for(unsigned int volumeCnt = 0; volumeCnt < storage.volumes.size(); volumeCnt++) { if (volumeCnt > 0) volumeIdx = (volumeIdx + 1) % storage.volumes.size(); SliceLayer* layer = &storage.volumes[volumeIdx].layers[layerNr]; gcodeLayer.setExtruder(volumeIdx); PathOrderOptimizer partOrderOptimizer(gcode.getPositionXY()); for(unsigned int partNr=0; partNr<layer->parts.size(); partNr++) { partOrderOptimizer.addPolygon(layer->parts[partNr].insets[0][0]); } partOrderOptimizer.optimize(); for(unsigned int partCounter=0; partCounter<partOrderOptimizer.polyOrder.size(); partCounter++) { SliceLayerPart* part = &layer->parts[partOrderOptimizer.polyOrder[partCounter]]; if (config.enableCombing) gcodeLayer.setCombBoundary(&part->combBoundery); else gcodeLayer.setAlwaysRetract(true); gcodeLayer.forceRetract(); if (config.insetCount > 0) { if (config.spiralizeMode) { if (int(layerNr) >= config.downSkinCount) inset0Config.spiralize = true; if (int(layerNr) == config.downSkinCount && part->insets.size() > 0) gcodeLayer.addPolygonsByOptimizer(part->insets[0], &inset1Config); } for(int insetNr=part->insets.size()-1; insetNr>-1; insetNr--) { if (insetNr == 0) gcodeLayer.addPolygonsByOptimizer(part->insets[insetNr], &inset0Config); else gcodeLayer.addPolygonsByOptimizer(part->insets[insetNr], &inset1Config); } } Polygons fillPolygons; int fillAngle = 45; if (layerNr & 1) fillAngle += 90; //int sparseSteps[1] = {config.extrusionWidth}; //generateConcentricInfill(part->skinOutline, fillPolygons, sparseSteps, 1); generateLineInfill(part->skinOutline, fillPolygons, config.extrusionWidth, config.extrusionWidth, config.infillOverlap, (part->bridgeAngle > -1) ? part->bridgeAngle : fillAngle); //int sparseSteps[2] = {config.extrusionWidth*5, config.extrusionWidth * 0.8}; //generateConcentricInfill(part->sparseOutline, fillPolygons, sparseSteps, 2); if (config.sparseInfillLineDistance > 0) { if (config.sparseInfillLineDistance > config.extrusionWidth * 4) { generateLineInfill(part->sparseOutline, fillPolygons, config.extrusionWidth, config.sparseInfillLineDistance * 2, config.infillOverlap, 45); generateLineInfill(part->sparseOutline, fillPolygons, config.extrusionWidth, config.sparseInfillLineDistance * 2, config.infillOverlap, 45 + 90); } else { generateLineInfill(part->sparseOutline, fillPolygons, config.extrusionWidth, config.sparseInfillLineDistance, config.infillOverlap, fillAngle); } } gcodeLayer.addPolygonsByOptimizer(fillPolygons, &fillConfig); //After a layer part, make sure the nozzle is inside the comb boundary, so we do not retract on the perimeter. if (!config.spiralizeMode || int(layerNr) < config.downSkinCount) gcodeLayer.moveInsideCombBoundary(config.extrusionWidth * 2); } gcodeLayer.setCombBoundary(NULL); } if (storage.support.generated) { if (config.supportExtruder > -1) gcodeLayer.setExtruder(config.supportExtruder); SupportPolyGenerator supportGenerator(storage.support, z); for(unsigned int volumeCnt = 0; volumeCnt < storage.volumes.size(); volumeCnt++) { SliceLayer* layer = &storage.volumes[volumeIdx].layers[layerNr]; Polygons polys; for(unsigned int n=0; n<layer->parts.size(); n++) supportGenerator.polygons = supportGenerator.polygons.difference(layer->parts[n].outline.offset(config.supportXYDistance)); } //Contract and expand the suppory polygons so small sections are removed and the final polygon is smoothed a bit. supportGenerator.polygons = supportGenerator.polygons.offset(-1000); supportGenerator.polygons = supportGenerator.polygons.offset(1000); Polygons supportLines; if (config.supportLineDistance > 0) { if (config.supportLineDistance > config.extrusionWidth * 4) { generateLineInfill(supportGenerator.polygons, supportLines, config.extrusionWidth, config.supportLineDistance*2, config.infillOverlap, 0); generateLineInfill(supportGenerator.polygons, supportLines, config.extrusionWidth, config.supportLineDistance*2, config.infillOverlap, 90); } else { generateLineInfill(supportGenerator.polygons, supportLines, config.extrusionWidth, config.supportLineDistance, config.infillOverlap, (layerNr & 1) ? 0 : 90); } } gcodeLayer.addPolygonsByOptimizer(supportGenerator.polygons, &supportConfig); gcodeLayer.addPolygonsByOptimizer(supportLines, &supportConfig); } //Finish the layer by applying speed corrections for minimal layer times and slowdown for the initial layer. if (int(layerNr) < config.initialSpeedupLayers) { int n = config.initialSpeedupLayers; int layer0Factor = config.initialLayerSpeed * 100 / config.printSpeed; gcodeLayer.setExtrudeSpeedFactor((layer0Factor * (n - layerNr) + 100 * (layerNr)) / n); if (layerNr == 0)//On the first layer, also slow down the travel gcodeLayer.setTravelSpeedFactor(layer0Factor); } gcodeLayer.forceMinimalLayerTime(config.minimalLayerTime, config.minimalFeedrate); if (layerNr == 0) gcode.setExtrusion(config.initialLayerThickness, config.filamentDiameter, config.filamentFlow); else gcode.setExtrusion(config.layerThickness, config.filamentDiameter, config.filamentFlow); int fanSpeed = config.fanSpeedMin; if (gcodeLayer.getExtrudeSpeedFactor() <= 50) { fanSpeed = config.fanSpeedMax; } else { int n = gcodeLayer.getExtrudeSpeedFactor() - 50; fanSpeed = config.fanSpeedMin * n / 50 + config.fanSpeedMax * (50 - n) / 50; } if (int(layerNr) < config.fanFullOnLayerNr) { //Slow down the fan on the layers below the [fanFullOnLayerNr], where layer 0 is speed 0. fanSpeed = fanSpeed * layerNr / config.fanFullOnLayerNr; } gcode.addFanCommand(fanSpeed); gcodeLayer.writeGCode(config.coolHeadLift > 0, int(layerNr) > 0 ? config.layerThickness : config.initialLayerThickness); } /* support debug for(int32_t y=0; y<storage.support.gridHeight; y++) { for(int32_t x=0; x<storage.support.gridWidth; x++) { unsigned int n = x+y*storage.support.gridWidth; if (storage.support.grid[n].size() < 1) continue; int32_t z = storage.support.grid[n][0].z; gcode.addMove(Point3(x * storage.support.gridScale + storage.support.gridOffset.X, y * storage.support.gridScale + storage.support.gridOffset.Y, 0), 0); gcode.addMove(Point3(x * storage.support.gridScale + storage.support.gridOffset.X, y * storage.support.gridScale + storage.support.gridOffset.Y, z), z); gcode.addMove(Point3(x * storage.support.gridScale + storage.support.gridOffset.X, y * storage.support.gridScale + storage.support.gridOffset.Y, 0), 0); } } //*/ log("Wrote layers in %5.2fs.\n", timeElapsed(t)); gcode.tellFileSize(); gcode.addFanCommand(0); logProgress("process", 1, 1); log("Total time elapsed %5.2fs.\n", timeElapsed(t,true)); //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 = std::max(maxObjectHeight, storage.modelSize.z); }