diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ed396304a..85abe164b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -75,6 +75,7 @@ set(GUI_TESTDIR ${CMAKE_CURRENT_SOURCE_DIR}/test/GUI/) # directory that contains the dependent non-source files, like models and configurations set(TESTFILE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/test/inputs/) + include_directories(${LIBDIR}) include_directories(${LIBDIR}/libslic3r) include_directories(${LIBDIR}/slic3r/GUI/) @@ -112,6 +113,7 @@ add_library(libslic3r STATIC ${LIBDIR}/libslic3r/Fill/FillGyroid.cpp ${LIBDIR}/libslic3r/Flow.cpp ${LIBDIR}/libslic3r/GCode.cpp + ${LIBDIR}/libslic3r/PrintGCode.cpp ${LIBDIR}/libslic3r/GCode/CoolingBuffer.cpp ${LIBDIR}/libslic3r/GCode/SpiralVase.cpp ${LIBDIR}/libslic3r/GCodeReader.cpp @@ -166,6 +168,7 @@ add_library(admesh STATIC set_property(TARGET admesh PROPERTY C_STANDARD 99) add_library(clipper STATIC ${LIBDIR}/clipper.cpp) + add_library(expat STATIC ${LIBDIR}/expat/xmlparse.c ${LIBDIR}/expat/xmlrole.c @@ -182,8 +185,6 @@ add_library(poly2tri STATIC ${LIBDIR}/poly2tri/sweep/sweep.cc ) - - set(UI_TEST_SOURCES ${GUI_TESTDIR}/testableframe.cpp ${GUI_TESTDIR}/test_harness_gui.cpp @@ -196,13 +197,17 @@ set(UI_TEST_SOURCES ${GUI_TESTDIR}/test_field_point3.cpp ${GUI_TESTDIR}/test_misc_ui.cpp ) + set(SLIC3R_TEST_SOURCES ${TESTDIR}/test_harness.cpp ${TESTDIR}/test_data.cpp ${TESTDIR}/libslic3r/test_trianglemesh.cpp ${TESTDIR}/libslic3r/test_config.cpp ${TESTDIR}/libslic3r/test_support_material.cpp + ${TESTDIR}/libslic3r/test_flow.cpp + ${TESTDIR}/libslic3r/test_printgcode.cpp ) + add_executable(slic3r slic3r.cpp) #set_target_properties(slic3r PROPERTIES LINK_SEARCH_START_STATIC 1) #set_target_properties(slic3r PROPERTIES LINK_SEARCH_END_STATIC 1) diff --git a/src/test/libslic3r/test_flow.cpp b/src/test/libslic3r/test_flow.cpp new file mode 100644 index 000000000..d74f2e84c --- /dev/null +++ b/src/test/libslic3r/test_flow.cpp @@ -0,0 +1,127 @@ +#include + +#include +#include + +#include "test_data.hpp" // get access to init_print, etc + +#include "Config.hpp" +#include "Model.hpp" +#include "ConfigBase.hpp" +#include "GCodeReader.hpp" +#include "Flow.hpp" + +using namespace Slic3r::Test; +using namespace Slic3r; + +SCENARIO("Extrusion width specifics") { + GIVEN("A config with a skirt, brim, some fill density, 3 perimeters, and 1 bottom solid layer and a 20mm cube mesh") { + // this is a sharedptr + auto config {Slic3r::Config::new_from_defaults()}; + config->set("skirts", 1); + config->set("brim_width", 2); + config->set("perimeters", 3); + config->set("fill_density", 0.4); + config->set("first_layer_height", "100%"); + + WHEN("first layer width set to 2mm") { + config->set("first_layer_extrusion_width", 2); + auto print {Slic3r::Test::init_print(std::make_tuple(20,20,20), config)}; + std::vector E_per_mm_bottom; + auto gcode {std::stringstream("")}; + Slic3r::Test::gcode(gcode, print); + auto parser {Slic3r::GCodeReader()}; + const auto layer_height { config->getFloat("layer_height") }; + parser.parse_stream(gcode, [&E_per_mm_bottom, layer_height] (Slic3r::GCodeReader& self, const Slic3r::GCodeReader::GCodeLine& line) + { + if (self.Z == layer_height) { // only consider first layer + if (line.extruding() && line.dist_XY() > 0) { + E_per_mm_bottom.emplace_back(line.dist_E() / line.dist_XY()); + } + } + }); + THEN(" First layer width applies to everything on first layer.") { + bool pass = false; + auto avg_E {std::accumulate(E_per_mm_bottom.cbegin(), E_per_mm_bottom.cend(), 0.0) / static_cast(E_per_mm_bottom.size())}; + + pass = std::count_if(E_per_mm_bottom.cbegin(), E_per_mm_bottom.cend(), [avg_E] (double v) { return _equiv(v, avg_E, 0.015); }) == 0; + REQUIRE(pass == true); + REQUIRE(E_per_mm_bottom.size() > 0); + } + THEN(" First layer width does not apply to upper layer.") { + } + } + } +} +SCENARIO(" Bridge flow specifics.", "[!mayfail]") { + GIVEN("A default config with no cooling and a fixed bridge speed, flow ratio and an overhang mesh.") { + WHEN("bridge_flow_ratio is set to 1.0") { + THEN("Output flow is as expected.") { + } + } + WHEN("bridge_flow_ratio is set to 0.5") { + THEN("Output flow is as expected.") { + } + } + WHEN("bridge_flow_ratio is set to 2.0") { + THEN("Output flow is as expected.") { + } + } + } + GIVEN("A default config with no cooling and a fixed bridge speed, flow ratio, fixed extrusion width of 0.4mm and an overhang mesh.") { + WHEN("bridge_flow_ratio is set to 1.0") { + THEN("Output flow is as expected.") { + } + } + WHEN("bridge_flow_ratio is set to 0.5") { + THEN("Output flow is as expected.") { + } + } + WHEN("bridge_flow_ratio is set to 2.0") { + THEN("Output flow is as expected.") { + } + } + } +} + +/// Test the expected behavior for auto-width, +/// spacing, etc +SCENARIO("Flow: Flow math for non-bridges", "[!mayfail]") { + GIVEN("Nozzle Diameter of 0.4, a desired width of 1mm and layer height of 0.5") { + auto width {ConfigOptionFloat(1.0)}; + auto spacing {0.4}; + auto nozzle_diameter {0.4}; + auto bridge_flow {1.0}; + + // Spacing for non-bridges is has some overlap + THEN("External perimeter flow has spacing of <>") { + } + + THEN("Internal perimeter flow has spacing of <>") { + } + } + /// Check the min/max + GIVEN("Nozzle Diameter of 0.4") { + WHEN("AA") { + } + } + + GIVEN("Nozzle Diameter of 0.4, a desired spacing of 1mm and layer height of 0.5") { + } +} + +/// Spacing, width calculation for bridge extrusions +SCENARIO("Flow: Flow math for bridges", "[!mayfail]") { + GIVEN("Nozzle Diameter of 0.4, a desired width of 1mm and layer height of 0.5") { +// auto width {ConfigOptionFloat(1.0)}; + auto spacing {0.4}; + auto nozzle_diameter {0.4}; + auto bridge_flow {1.0}; + } + + GIVEN("Nozzle Diameter of 0.4, a desired spacing of 1mm and layer height of 0.5") { + } +} + +SCENARIO("Flow: Auto spacing", "[!mayfail]") { +} diff --git a/src/test/libslic3r/test_printgcode.cpp b/src/test/libslic3r/test_printgcode.cpp new file mode 100644 index 000000000..bab916280 --- /dev/null +++ b/src/test/libslic3r/test_printgcode.cpp @@ -0,0 +1,91 @@ +#include + +#include +#include "test_data.hpp" +#include "libslic3r.h" + +SCENARIO( "PrintGCode basic functionality", "[!mayfail]") { + GIVEN("A default configuration and a print test object") { + auto config {Slic3r::Config::new_from_defaults()}; + auto gcode {std::stringstream("")}; + + WHEN("the output is executed with no support material") { + config->set("first_layer_extrusion_width", 0); + auto print {Slic3r::Test::init_print(std::make_tuple(20,20,20), config)}; + Slic3r::Test::gcode(gcode, print); + auto exported {gcode.str()}; + THEN("Some text output is generated.") { + REQUIRE(exported.size() > 0); + } + THEN("Exported text contains slic3r version") { + REQUIRE(exported.find(SLIC3R_VERSION) != std::string::npos); + } + THEN("Exported text contains git commit id") { + REQUIRE(exported.find("; Git Commit") != std::string::npos); + REQUIRE(exported.find(BUILD_COMMIT) != std::string::npos); + } + THEN("Exported text contains extrusion statistics.") { + REQUIRE(exported.find("; external perimeters extrusion width") != std::string::npos); + REQUIRE(exported.find("; perimeters extrusion width") != std::string::npos); + REQUIRE(exported.find("; infill extrusion width") != std::string::npos); + REQUIRE(exported.find("; solid infill extrusion width") != std::string::npos); + REQUIRE(exported.find("; top solid infill extrusion width") != std::string::npos); + REQUIRE(exported.find("; support material extrusion width") == std::string::npos); + REQUIRE(exported.find("; first layer extrusion width") == std::string::npos); + } + + THEN("GCode preamble is emitted.") { + REQUIRE(exported.find("G21 ; set units to millimeters") != std::string::npos); + } + } + WHEN("the output is executed with support material") { + config->set("first_layer_extrusion_width", 0); + config->set("support_material", true); + config->set("raft_layers", 3); + auto print {Slic3r::Test::init_print(std::make_tuple(20,20,20), config)}; + Slic3r::Test::gcode(gcode, print); + auto exported {gcode.str()}; + THEN("Some text output is generated.") { + REQUIRE(exported.size() > 0); + } + THEN("Exported text contains extrusion statistics.") { + REQUIRE(exported.find("; external perimeters extrusion width") != std::string::npos); + REQUIRE(exported.find("; perimeters extrusion width") != std::string::npos); + REQUIRE(exported.find("; infill extrusion width") != std::string::npos); + REQUIRE(exported.find("; solid infill extrusion width") != std::string::npos); + REQUIRE(exported.find("; top solid infill extrusion width") != std::string::npos); + REQUIRE(exported.find("; support material extrusion width") != std::string::npos); + REQUIRE(exported.find("; first layer extrusion width") == std::string::npos); + } + } + WHEN("the output is executed with a separate first layer extrusion width") { + config->set("first_layer_extrusion_width", 0.5); + auto print {Slic3r::Test::init_print(std::make_tuple(20,20,20), config)}; + Slic3r::Test::gcode(gcode, print); + auto exported {gcode.str()}; + THEN("Some text output is generated.") { + REQUIRE(exported.size() > 0); + } + THEN("Exported text contains extrusion statistics.") { + REQUIRE(exported.find("; external perimeters extrusion width") != std::string::npos); + REQUIRE(exported.find("; perimeters extrusion width") != std::string::npos); + REQUIRE(exported.find("; infill extrusion width") != std::string::npos); + REQUIRE(exported.find("; solid infill extrusion width") != std::string::npos); + REQUIRE(exported.find("; top solid infill extrusion width") != std::string::npos); + REQUIRE(exported.find("; support material extrusion width") == std::string::npos); + REQUIRE(exported.find("; first layer extrusion width") != std::string::npos); + } + } + WHEN("Cooling is enabled and the fan is disabled.") { + config->set("cooling", true); + config->set("disable_fan_first_layers", 5); + auto print {Slic3r::Test::init_print(std::make_tuple(20,20,20), config)}; + Slic3r::Test::gcode(gcode, print); + auto exported {gcode.str()}; + THEN("GCode to disable fan is emitted."){ + REQUIRE(exported.find("M107") != std::string::npos); + } + } + gcode.clear(); + } +} diff --git a/src/test/test_data.cpp b/src/test/test_data.cpp index c2943db62..1cc52b79b 100644 --- a/src/test/test_data.cpp +++ b/src/test/test_data.cpp @@ -1,7 +1,39 @@ #include "test_data.hpp" +#include "TriangleMesh.hpp" +#include "GCodeReader.hpp" +#include "Config.hpp" +#include "Print.hpp" +#include +#include + +using namespace std::string_literals; +using namespace std; namespace Slic3r { namespace Test { +// Mesh enumeration to name mapping +const std::unordered_map mesh_names { + std::make_pair(TestMesh::A,"A"), + std::make_pair(TestMesh::L,"L"), + std::make_pair(TestMesh::V,"V"), + std::make_pair(TestMesh::_40x10,"40x10"), + std::make_pair(TestMesh::bridge,"bridge"), + std::make_pair(TestMesh::bridge_with_hole,"bridge_with_hole"), + std::make_pair(TestMesh::cube_with_concave_hole,"cube_with_concave_hole"), + std::make_pair(TestMesh::cube_with_hole,"cube_with_hole"), + std::make_pair(TestMesh::gt2_teeth,"gt2_teeth"), + std::make_pair(TestMesh::ipadstand,"ipadstand"), + std::make_pair(TestMesh::overhang,"overhang"), + std::make_pair(TestMesh::pyramid,"pyramid"), + std::make_pair(TestMesh::sloping_hole,"sloping_hole"), + std::make_pair(TestMesh::slopy_cube,"slopy_cube"), + std::make_pair(TestMesh::small_dorito,"small_dorito"), + std::make_pair(TestMesh::step,"step"), + std::make_pair(TestMesh::two_hollow_squares,"two_hollow_squares") +}; + + + TriangleMesh mesh(TestMesh m) { Point3s facets; Pointf3s vertices; @@ -150,11 +182,71 @@ TriangleMesh mesh(TestMesh m) { return _mesh; } +TriangleMesh mesh(TestMesh m, Pointf3 translate, double scale) { + return mesh(m, translate, Pointf3(scale, scale, scale)); +} TriangleMesh mesh(TestMesh m, Pointf3 translate, Pointf3 scale) { TriangleMesh _mesh {mesh(m)}; _mesh.scale(scale); _mesh.translate(translate); return _mesh; } +/* +Slic3r::Model model(const std::string& model_name, TestMesh m, Pointf3 translate = Pointf3(0,0,0), double scale = 1.0) { + return model(model_name, m, translate, Pointf3(scale, scale, scale)); +} +*/ + +Slic3r::Test::Print init_print(std::tuple cube, config_ptr _config) { + std::stringstream s; + s << "cube_" << get<0>(cube) << "x" << get<1>(cube) << "x" << get<2>(cube); + + auto config {Slic3r::Config::new_from_defaults()}; + + config->apply(_config); + const char* v {std::getenv("SLIC3R_TESTS_GCODE")}; + auto tests_gcode {(v == nullptr ? ""s : std::string(v))}; + + if (tests_gcode != ""s) + config->set("gcode_comments", 1); + + std::shared_ptr print = std::make_shared(); + print->apply_config(config); + + auto model {Slic3r::Test::model(s.str(), Slic3r::TriangleMesh::make_cube(get<0>(cube), get<1>(cube), get<1>(cube))) }; + + model.arrange_objects(print->config.min_object_distance()); + model.center_instances_around_point(Slic3r::Pointf(100,100)); + for (auto* mo : model.objects) { + print->auto_assign_extruders(mo); + print->add_model_object(mo); + } + + print->validate(); + + std::vector models {}; + models.emplace_back(model); + return Slic3r::Test::Print(print, models); +} + +void gcode(std::stringstream& gcode, Slic3r::Test::Print& _print) { + Slic3r::Print& print {_print.print()}; + print.process(); + print.export_gcode(gcode, true); +} + +Slic3r::Model model(const std::string& model_name, TriangleMesh&& _mesh) { + Slic3r::Model result; + + auto* object {result.add_object()}; + object->name += model_name + ".stl"s; + object->add_volume(_mesh); + + auto* inst {object->add_instance()}; + inst->rotation = 0; + inst->scaling_factor = 1.0; + + return result; +} } } // namespace Slic3r::Test diff --git a/src/test/test_data.hpp b/src/test/test_data.hpp index 9edd37629..4ad4eb93b 100644 --- a/src/test/test_data.hpp +++ b/src/test/test_data.hpp @@ -2,9 +2,15 @@ #include "Point.hpp" #include "TriangleMesh.hpp" #include "Geometry.hpp" +#include "Model.hpp" +#include "Print.hpp" +#include "Config.hpp" + +#include namespace Slic3r { namespace Test { +/// Enumeration of test meshes enum class TestMesh { A, L, @@ -25,16 +31,46 @@ enum class TestMesh { two_hollow_squares }; +class Print { +public: + Print(std::shared_ptr _print, std::vector _models) : models(_models), _print(_print) {} + void process() { _print->process(); } + void apply_config(config_ptr _config) { _print->apply_config(_config); } + Slic3r::Print& print() { return *(_print); } + + const std::vector models {}; +private: + std::shared_ptr _print {nullptr}; +}; + + +/// Mesh enumeration to name mapping +extern const std::unordered_map mesh_names; + /// Port of Slic3r::Test::mesh /// Basic cubes/boxes should call TriangleMesh::make_cube() directly and rescale/translate it TriangleMesh mesh(TestMesh m); TriangleMesh mesh(TestMesh m, Pointf3 translate, Pointf3 scale = Pointf3(1.0, 1.0, 1.0)); +TriangleMesh mesh(TestMesh m, Pointf3 translate, double scale = 1.0); /// Templated function to see if two values are equivalent (+/- epsilon) template -bool _equiv(T a, U b) { return abs(a - b) < Slic3r::Geometry::epsilon; } +bool _equiv(const T& a, const U& b) { return abs(a - b) < Slic3r::Geometry::epsilon; } + +template +bool _equiv(const T& a, const U& b, double epsilon) { return abs(a - b) < epsilon; } + +//Slic3r::Model model(const std::string& model_name, TestMesh m, Pointf3 translate = Pointf3(0,0,0), Pointf3 scale = Pointf3(1.0,1.0,1.0)); +//Slic3r::Model model(const std::string& model_name, TestMesh m, Pointf3 translate = Pointf3(0,0,0), double scale = 1.0); + +Slic3r::Model model(const std::string& model_name, TriangleMesh&& _mesh); + +Slic3r::Test::Print init_print(std::tuple cube, config_ptr _config = Slic3r::Config::new_from_defaults()); + +void gcode(std::stringstream& gcode, Slic3r::Test::Print& print); } } // namespace Slic3r::Test + #endif // SLIC3R_TEST_DATA_HPP diff --git a/xs/src/libslic3r/Print.cpp b/xs/src/libslic3r/Print.cpp index e6dae3435..f020935e0 100644 --- a/xs/src/libslic3r/Print.cpp +++ b/xs/src/libslic3r/Print.cpp @@ -1,4 +1,5 @@ #include "Print.hpp" +#include "PrintGCode.hpp" #include "BoundingBox.hpp" #include "ClipperUtils.hpp" #include "Fill/Fill.hpp" @@ -91,6 +92,25 @@ Print::delete_object(size_t idx) // TODO: purge unused regions } +#ifndef SLIC3RXS + +void +Print::process() +{ +} + +void +Print::make_brim() +{ +} + +void +Print::make_skirt() +{ +} + +#endif // SLIC3RXS + void Print::reload_object(size_t idx) { @@ -500,6 +520,27 @@ Print::add_model_object(ModelObject* model_object, int idx) } } +#ifndef SLIC3RXS +void +Print::export_gcode(std::ostream& output, bool quiet) +{ + this->process(); + if (this->status_cb != nullptr) + this->status_cb(90, "Exporting G-Code..."); + + auto export_handler {Slic3r::PrintGCode(*this, output)}; + export_handler.output(); + +} + +bool +Print::apply_config(config_ptr config) { + // dereference the stored pointer and pass the resulting data to apply_config() + return this->apply_config(config->config()); +} + +#endif + bool Print::apply_config(DynamicPrintConfig config) { diff --git a/xs/src/libslic3r/Print.hpp b/xs/src/libslic3r/Print.hpp index 1dda0974a..78dd0b963 100644 --- a/xs/src/libslic3r/Print.hpp +++ b/xs/src/libslic3r/Print.hpp @@ -9,6 +9,7 @@ #include "BoundingBox.hpp" #include "Flow.hpp" #include "PrintConfig.hpp" +#include "Config.hpp" #include "Point.hpp" #include "Layer.hpp" #include "Model.hpp" @@ -184,6 +185,13 @@ class Print PrintRegionPtrs regions; PlaceholderParser placeholder_parser; // TODO: status_cb + #ifndef SLIC3RXS + std::function status_cb {nullptr}; + + /// Function pointer for the UI side to call post-processing scripts. + /// Vector is assumed to be the executable script and all arguments. + std::function)> post_process_cb {nullptr}; + #endif double total_used_filament, total_extruded_volume, total_cost, total_weight; std::map filament_stats; PrintState state; @@ -206,6 +214,21 @@ class Print PrintRegion* get_region(size_t idx) { return this->regions.at(idx); }; const PrintRegion* get_region(size_t idx) const { return this->regions.at(idx); }; PrintRegion* add_region(); + + #ifndef SLIC3RXS + /// Triggers the rest of the print process + void process(); + + /// Performs a gcode export. + void export_gcode(std::ostream& output, bool quiet = false); + + /// Performs a gcode export and then runs post-processing scripts (if any) + void export_gcode(const std::string& filename, bool quiet = false); + + /// commands a gcode export to a temporary file and return its name + std::string export_gcode(bool quiet = false); + + #endif // SLIC3RXS // methods for handling state bool invalidate_state_by_config(const PrintConfigBase &config); @@ -214,6 +237,10 @@ class Print bool step_done(PrintObjectStep step) const; void add_model_object(ModelObject* model_object, int idx = -1); + #ifndef SLIC3RXS + /// Apply a provided configuration to the internal copy + bool apply_config(config_ptr config); + #endif // SLIC3RXS bool apply_config(DynamicPrintConfig config); bool has_infinite_skirt() const; bool has_skirt() const; @@ -225,6 +252,16 @@ class Print Flow brim_flow() const; Flow skirt_flow() const; void _make_brim(); + + #ifndef SLIC3RXS + /// Generates a skirt around the union of all of + /// the objects in the print. + void make_skirt(); + + /// Generates a brim around all of the objects in the print. + void make_brim(); + #endif // SLIC3RXS + std::set object_extruders() const; std::set support_material_extruders() const; diff --git a/xs/src/libslic3r/PrintGCode.cpp b/xs/src/libslic3r/PrintGCode.cpp new file mode 100644 index 000000000..0d72cb0b3 --- /dev/null +++ b/xs/src/libslic3r/PrintGCode.cpp @@ -0,0 +1,211 @@ +#ifndef SLIC3RXS +#include "PrintGCode.hpp" + +#include +#include + +namespace Slic3r { +void +PrintGCode::output() +{ + auto& gcodegen {this->_gcodegen}; + auto& fh {this->fh}; + auto& print {this->_print}; + const auto& config {this->config}; + + // Write information about the generator. + time_t rawtime; tm * timeinfo; + time(&rawtime); + timeinfo = localtime(&rawtime); + + fh << "; generated by Slic3r " << SLIC3R_VERSION << " on "; + fh << asctime(timeinfo) << "\n"; + fh << "; Git Commit: " << BUILD_COMMIT << "\n\n"; + + // Writes notes (content of all Settings tabs -> Notes) + fh << gcodegen.notes(); + + // Write some terse information on the slicing parameters. + auto& first_object {*(this->objects.at(0))}; + auto layer_height {first_object.config.layer_height.getFloat()}; + + for (auto* region : print.regions) { + { + auto flow {region->flow(frExternalPerimeter, layer_height, false, false, -1, first_object)}; + auto vol_speed {flow.mm3_per_mm() * region->config.get_abs_value("external_perimeter_speed")}; + if (config.max_volumetric_speed.getInt() > 0) + vol_speed = std::min(vol_speed, config.max_volumetric_speed.getFloat()); + fh << "; external perimeters extrusion width = "; + fh << std::fixed << std::setprecision(2) << flow.width << "mm "; + fh << "(" << vol_speed << "mm^3/s)\n"; + } + { + auto flow {region->flow(frPerimeter, layer_height, false, false, -1, first_object)}; + auto vol_speed {flow.mm3_per_mm() * region->config.get_abs_value("perimeter_speed")}; + if (config.max_volumetric_speed.getInt() > 0) + vol_speed = std::min(vol_speed, config.max_volumetric_speed.getFloat()); + fh << "; perimeters extrusion width = "; + fh << std::fixed << std::setprecision(2) << flow.width << "mm "; + fh << "(" << vol_speed << "mm^3/s)\n"; + } + { + auto flow {region->flow(frInfill, layer_height, false, false, -1, first_object)}; + auto vol_speed {flow.mm3_per_mm() * region->config.get_abs_value("infill_speed")}; + if (config.max_volumetric_speed.getInt() > 0) + vol_speed = std::min(vol_speed, config.max_volumetric_speed.getFloat()); + fh << "; infill extrusion width = "; + fh << std::fixed << std::setprecision(2) << flow.width << "mm "; + fh << "(" << vol_speed << "mm^3/s)\n"; + } + { + auto flow {region->flow(frSolidInfill, layer_height, false, false, -1, first_object)}; + auto vol_speed {flow.mm3_per_mm() * region->config.get_abs_value("solid_infill_speed")}; + if (config.max_volumetric_speed.getInt() > 0) + vol_speed = std::min(vol_speed, config.max_volumetric_speed.getFloat()); + fh << "; solid infill extrusion width = "; + fh << std::fixed << std::setprecision(2) << flow.width << "mm "; + fh << "(" << vol_speed << "mm^3/s)\n"; + } + { + auto flow {region->flow(frTopSolidInfill, layer_height, false, false, -1, first_object)}; + auto vol_speed {flow.mm3_per_mm() * region->config.get_abs_value("top_solid_infill_speed")}; + if (config.max_volumetric_speed.getInt() > 0) + vol_speed = std::min(vol_speed, config.max_volumetric_speed.getFloat()); + fh << "; top solid infill extrusion width = "; + fh << std::fixed << std::setprecision(2) << flow.width << "mm "; + fh << "(" << vol_speed << "mm^3/s)\n"; + } + if (print.has_support_material()) { + auto flow {first_object._support_material_flow()}; + auto vol_speed {flow.mm3_per_mm() * first_object.config.get_abs_value("support_material_speed")}; + if (config.max_volumetric_speed.getInt() > 0) + vol_speed = std::min(vol_speed, config.max_volumetric_speed.getFloat()); + fh << "; support material extrusion width = "; + fh << std::fixed << std::setprecision(2) << flow.width << "mm "; + fh << "(" << vol_speed << "mm^3/s)\n"; + } + if (print.config.first_layer_extrusion_width.getFloat() > 0) { + auto flow {region->flow(frPerimeter, layer_height, false, false, -1, first_object)}; +// auto vol_speed {flow.mm3_per_mm() * print.config.get_abs_value("first_layer_speed")}; +// if (config.max_volumetric_speed.getInt() > 0) +// vol_speed = std::min(vol_speed, config.max_volumetric_speed.getFloat()); + fh << "; first layer extrusion width = "; + fh << std::fixed << std::setprecision(2) << flow.width << "mm "; +// fh << "(" << vol_speed << "mm^3/s)\n"; + } + + fh << std::endl; + } + // Prepare the helper object for replacing placeholders in custom G-Code and output filename + print.placeholder_parser.update_timestamp(); + + // GCode sets this automatically when change_layer() is called, but needed for skirt/brim as well + gcodegen.first_layer = true; + + // disable fan + if (config.cooling.getBool() && config.disable_fan_first_layers.getInt() > 0) { + fh << gcodegen.writer.set_fan(0,1) << "\n"; + } + + // set bed temperature + auto bed_temp_regex { std::regex("M(?:190|140)", std::regex_constants::icase)}; + auto ex_temp_regex { std::regex("M(?:109|104)", std::regex_constants::icase)}; + auto temp{config.first_layer_bed_temperature.getFloat()}; + if (config.has_heatbed.getBool() && temp > 0 && std::regex_search(config.start_gcode.getString(), bed_temp_regex)) { + fh << gcodegen.writer.set_bed_temperature(temp, 1); + } + + // Set extruder(s) temperature before and after start gcode. + auto include_start_extruder_temp {!std::regex_search(config.start_gcode.getString(), ex_temp_regex)}; + for(const auto& start_gcode : config.start_filament_gcode.values) { + include_start_extruder_temp = include_start_extruder_temp && !std::regex_search(start_gcode, ex_temp_regex); + } + + auto include_end_extruder_temp {!std::regex_search(config.end_gcode.getString(), ex_temp_regex)}; + for(const auto& end_gcode : config.end_filament_gcode.values) { + include_end_extruder_temp = include_end_extruder_temp && !std::regex_search(end_gcode, ex_temp_regex); + } + + if (include_start_extruder_temp) this->_print_first_layer_temperature(0); + + // Apply gcode math to start and end gcode +// fh << apply_math(gcodegen.placeholder_parser->process(config.start_gcode.value)); + + for(const auto& start_gcode : config.start_filament_gcode.values) { +// fh << apply_math(gcodegen.placeholder_parser->process(start_gcode)); + } + + if (include_start_extruder_temp) this->_print_first_layer_temperature(1); + + + // Set other general things (preamble) + fh << gcodegen.preamble(); + + // initialize motion planner for object-to-object travel moves + if (config.avoid_crossing_perimeters.getBool()) { + auto distance_from_objects {scale_(1)}; + + // compute the offsetted convex hull for each object and repeat it for each copy + Polygons islands_p {}; + for (auto object : this->objects) { + Polygons polygons {}; + for (auto layer : object->layers) { + const auto& slice {ExPolygons(layer->slices)}; + std::for_each(slice.cbegin(), slice.cend(), [&polygons] (const ExPolygon& a) { polygons.emplace_back(a.contour); }); + } + + if (polygons.size() == 0) continue; + + for (auto copy : object->_shifted_copies) { + Polygons copy_islands_p {polygons}; + std::for_each(copy_islands_p.begin(), copy_islands_p.end(), [copy] (Polygon& obj) { obj.translate(copy); }); + islands_p.insert(islands_p.cend(), copy_islands_p.begin(), copy_islands_p.end()); + } + } + + gcodegen.avoid_crossing_perimeters.init_external_mp(union_ex(islands_p)); + } + + // Calculate wiping points if needed. + // + + // Set initial extruder only after custom start gcode + + // Do all objects for each layer. + + if (config.complete_objects.getBool()) { + } else { + } + + // Write end commands to file. + + // set bed temperature + + // Get filament stats + + // Append full config +} + +std::string +PrintGCode::filter(const std::string& in, bool wait) +{ + return in; +} + +void +PrintGCode::_print_first_layer_temperature(bool wait) +{ + auto& gcodegen {this->_gcodegen}; + auto& fh {this->fh}; + const auto& print {this->_print}; + const auto& config {this->config}; + + for (auto& t : print.extruders()) { + auto temp { config.first_layer_temperature.get_at(t) }; + if (config.ooze_prevention.value) temp += config.standby_temperature_delta.value; + if (temp > 0) fh << gcodegen.writer.set_temperature(temp, wait, t); + } +} + +} // namespace Slic3r +#endif //SLIC3RXS diff --git a/xs/src/libslic3r/PrintGCode.hpp b/xs/src/libslic3r/PrintGCode.hpp new file mode 100644 index 000000000..4204a8e21 --- /dev/null +++ b/xs/src/libslic3r/PrintGCode.hpp @@ -0,0 +1,69 @@ +#ifndef SLIC3RXS +#ifndef slic3r_PrintGCode_hpp +#define slic3r_PrintGCode_hpp + +#include "GCode.hpp" +#include "GCode/CoolingBuffer.hpp" +#include "GCode/SpiralVase.hpp" +#include "Geometry.hpp" +#include "Flow.hpp" +#include "ExtrusionEntity.hpp" +#include "libslic3r.h" + +#include +#include + +namespace Slic3r { + +class PrintGCode { +public: + PrintGCode(Slic3r::Print& print, std::ostream& _fh) : + _print(print), + config(print.config), + _gcodegen(Slic3r::GCode()), + objects(print.objects), + fh(_fh), + _cooling_buffer(Slic3r::CoolingBuffer(this->_gcodegen)), + _spiral_vase(Slic3r::SpiralVase(this->config)) + { }; + + /// Perform the export. export is a reserved name in C++, so changed to output + void output(); + + void flush_filters() { fh << this->filter(this->_cooling_buffer.flush(), 1); } + + /// Applies various filters, if enabled. + std::string filter(const std::string& in, bool wait); + +private: + + Slic3r::Print& _print; + const Slic3r::PrintConfig& config; + + Slic3r::GCode _gcodegen; + + const PrintObjectPtrs& objects; + + std::ostream& fh; + + Slic3r::CoolingBuffer _cooling_buffer; + Slic3r::SpiralVase _spiral_vase; +// Slic3r::VibrationLimit _vibration_limit; +// Slic3r::ArcFitting _arc_fitting; +// Slic3r::PressureRegulator _pressure_regulator; + + size_t _skirt_done {0}; + bool _brim_done {false}; + bool _second_layer_things_done {false}; + std::string _last_obj_copy {""}; + bool _autospeed {false}; + + void _print_first_layer_temperature(bool wait); + void _print_off_temperature(bool wait); + +}; + +} // namespace Slic3r + +#endif // slic3r_PrintGCode_hpp +#endif // SLIC3RXS