mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-07-13 05:51:10 +00:00
{input_filename_base} is meant to be the saved project's file name. Before
saved; #13753 fixed it to use the project name. Some users relied on the old
behavior to get the part name into their output file name and had no
placeholder to recover it ({model_name} is the 3mf designer metadata, blank
for plain STL imports).
Add {first_object_name} as a dedicated placeholder for the first printable
object on the current plate, populated in update_object_placeholders()
independently of {input_filename_base}.
Closes #14493
175 lines
6.9 KiB
C++
175 lines
6.9 KiB
C++
#include <catch2/catch_all.hpp>
|
|
|
|
#include "libslic3r/libslic3r.h"
|
|
#include "libslic3r/Print.hpp"
|
|
#include "libslic3r/Layer.hpp"
|
|
|
|
#include "test_data.hpp"
|
|
|
|
using namespace Slic3r;
|
|
using namespace Slic3r::Test;
|
|
|
|
SCENARIO("Print: Skirt generation", "[Print]") {
|
|
GIVEN("20mm cube and default config") {
|
|
WHEN("skirt_loops is set to 2") {
|
|
Slic3r::Print print;
|
|
Slic3r::Test::init_and_process_print({TestMesh::cube_20x20x20}, print, {
|
|
{ "skirt_height", 1 },
|
|
{ "skirt_distance", 1 },
|
|
{ "skirt_loops", 2 }
|
|
});
|
|
THEN("Skirt Extrusion collection has 2 loops in it") {
|
|
REQUIRE(print.skirt().items_count() == 2);
|
|
REQUIRE(print.skirt().flatten().entities.size() == 2);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
SCENARIO("Print: Changing number of solid shell layers does not cause all surfaces to become internal.", "[Print]") {
|
|
GIVEN("sliced 20mm cube and config with top_shell_layers = 2 and bottom_shell_layers = 1") {
|
|
Slic3r::DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
|
|
config.set_deserialize_strict({
|
|
{ "top_shell_layers", 2 },
|
|
{ "bottom_shell_layers", 1 },
|
|
{ "layer_height", 0.25 }, // get a known number of layers
|
|
{ "initial_layer_print_height", 0.25 }
|
|
});
|
|
Slic3r::Print print;
|
|
Slic3r::Model model;
|
|
Slic3r::Test::init_print({TestMesh::cube_20x20x20}, print, model, config);
|
|
// Precondition: Ensure that the model has 2 solid top layers (79, 78)
|
|
// and one solid bottom layer (0).
|
|
auto test_is_solid_infill = [&print](size_t obj_id, size_t layer_id) {
|
|
const Layer &layer = *(print.objects().at(obj_id)->get_layer((int)layer_id));
|
|
// iterate over all of the regions in the layer
|
|
for (const LayerRegion *region : layer.regions()) {
|
|
// for each region, iterate over the fill surfaces
|
|
for (const Surface &surface : region->fill_surfaces.surfaces)
|
|
CHECK(surface.is_solid());
|
|
}
|
|
};
|
|
print.process();
|
|
test_is_solid_infill(0, 0); // should be solid
|
|
test_is_solid_infill(0, 79); // should be solid
|
|
test_is_solid_infill(0, 78); // should be solid
|
|
WHEN("Model is re-sliced with top_shell_layers == 3") {
|
|
config.set("top_shell_layers", 3);
|
|
print.apply(model, config);
|
|
print.process();
|
|
THEN("Print object does not have 0 solid bottom layers.") {
|
|
test_is_solid_infill(0, 0);
|
|
}
|
|
AND_THEN("Print object has 3 top solid layers") {
|
|
test_is_solid_infill(0, 79);
|
|
test_is_solid_infill(0, 78);
|
|
test_is_solid_infill(0, 77);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
SCENARIO("Print: Brim generation", "[Print]") {
|
|
GIVEN("20mm cube and default config, 1mm first layer width") {
|
|
WHEN("Brim is set to 6mm") {
|
|
Slic3r::Print print;
|
|
Slic3r::Test::init_and_process_print({TestMesh::cube_20x20x20}, print, {
|
|
{ "brim_type", "outer_only" },
|
|
{ "initial_layer_line_width", 1 },
|
|
{ "brim_width", 6 }
|
|
});
|
|
THEN("Brim Extrusion collection has 6 loops in it") {
|
|
size_t total_items = 0;
|
|
for (const auto& pair : print.get_brimMap()) {
|
|
total_items += pair.second.items_count();
|
|
}
|
|
REQUIRE(total_items == 6);
|
|
}
|
|
}
|
|
WHEN("Brim is set to 6mm, extrusion width 0.5mm") {
|
|
Slic3r::Print print;
|
|
Slic3r::Test::init_and_process_print({TestMesh::cube_20x20x20}, print, {
|
|
{ "brim_type", "outer_only" },
|
|
{ "brim_width", 6 },
|
|
{ "initial_layer_line_width", 0.5 }
|
|
});
|
|
THEN("Brim Extrusion collection has 12 loops in it") {
|
|
size_t total_items = 0;
|
|
for (const auto& pair : print.get_brimMap()) {
|
|
total_items += pair.second.items_count();
|
|
}
|
|
REQUIRE(total_items == 12);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// {first_object_name} filename placeholder
|
|
// ---------------------------------------------------------------------------
|
|
namespace {
|
|
|
|
// Add a printable 20mm cube named `name` to `model`; returns it so the caller can tweak it.
|
|
ModelObject* add_named_cube(Model& model, const std::string& name)
|
|
{
|
|
ModelObject* obj = model.add_object();
|
|
obj->name = name;
|
|
obj->add_volume(make_cube(20.0, 20.0, 20.0));
|
|
obj->add_instance();
|
|
obj->ensure_on_bed();
|
|
return obj;
|
|
}
|
|
|
|
// Resolve `format` to an output file name for a print of `model`. `filename_base`, when set,
|
|
// is the saved-project name passed to output_filename().
|
|
std::string resolved_output_name(Model& model, const std::string& format, const std::string& filename_base = {})
|
|
{
|
|
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
|
|
config.set_key_value("filename_format", new ConfigOptionString(format));
|
|
|
|
Print print;
|
|
for (ModelObject* obj : model.objects)
|
|
print.auto_assign_extruders(obj);
|
|
print.apply(model, config);
|
|
return print.output_filename(filename_base);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST_CASE("Print: {first_object_name} names the first printable object on the plate", "[Print]")
|
|
{
|
|
Model model;
|
|
|
|
SECTION("uses the object's name") {
|
|
add_named_cube(model, "WidgetPart");
|
|
CHECK(resolved_output_name(model, "{first_object_name}") == "WidgetPart.gcode");
|
|
}
|
|
|
|
SECTION("picks the first when several objects are printable") {
|
|
add_named_cube(model, "FirstPart");
|
|
add_named_cube(model, "SecondPart");
|
|
CHECK(resolved_output_name(model, "{first_object_name}") == "FirstPart.gcode");
|
|
}
|
|
|
|
SECTION("skips objects outside the print volume (e.g. on another plate)") {
|
|
// First in model order, but not on the current plate, so is_printable() is false.
|
|
add_named_cube(model, "OtherPlatePart")->instances.front()->print_volume_state = ModelInstancePVS_Fully_Outside;
|
|
add_named_cube(model, "OnPlatePart");
|
|
CHECK(resolved_output_name(model, "{first_object_name}") == "OnPlatePart.gcode");
|
|
}
|
|
|
|
SECTION("is empty when the object has no name") {
|
|
add_named_cube(model, "");
|
|
CHECK(resolved_output_name(model, "part_{first_object_name}") == "part_.gcode");
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Print: {first_object_name} is not replaced by the saved-project file name", "[Print]")
|
|
{
|
|
// Passing a saved-project file name as the filename_base must not change {first_object_name}.
|
|
Model model;
|
|
add_named_cube(model, "WidgetPart");
|
|
CHECK(resolved_output_name(model, "{first_object_name}", "SavedProject") == "WidgetPart.gcode");
|
|
}
|
|
|