diff --git a/src/libslic3r/PrintBase.cpp b/src/libslic3r/PrintBase.cpp index 821cf8a7f8..4448c12c76 100644 --- a/src/libslic3r/PrintBase.cpp +++ b/src/libslic3r/PrintBase.cpp @@ -21,11 +21,12 @@ void PrintTryCancel::operator()() size_t PrintStateBase::g_last_timestamp = 0; -// Update "scale", "input_filename", "input_filename_base" placeholders from the current m_objects. +// Update "scale", "input_filename", "input_filename_base", "first_object_name" placeholders from the current m_objects. void PrintBase::update_object_placeholders(DynamicConfig &config, const std::string &default_ext) const { // get the first input file name std::string input_file; + std::string first_object_name; std::vector v_scale; int num_objects = 0; int num_instances = 0; @@ -38,6 +39,8 @@ void PrintBase::update_object_placeholders(DynamicConfig &config, const std::str } if (printable) { ++ num_objects; + if (num_objects == 1) + first_object_name = model_object->name; // CHECK_ME -> Is the following correct ? v_scale.push_back("x:" + boost::lexical_cast(printable->get_scaling_factor(X) * 100) + "% y:" + boost::lexical_cast(printable->get_scaling_factor(Y) * 100) + @@ -51,6 +54,7 @@ void PrintBase::update_object_placeholders(DynamicConfig &config, const std::str config.set_key_value("num_instances", new ConfigOptionInt(num_instances)); config.set_key_value("scale", new ConfigOptionStrings(v_scale)); + config.set_key_value("first_object_name", new ConfigOptionString(first_object_name)); if (! input_file.empty()) { // get basename with and without suffix const std::string input_filename = boost::filesystem::path(input_file).filename().string(); diff --git a/tests/fff_print/test_print.cpp b/tests/fff_print/test_print.cpp index c86cfa71dd..0791d4f7f1 100644 --- a/tests/fff_print/test_print.cpp +++ b/tests/fff_print/test_print.cpp @@ -183,6 +183,74 @@ void trigger_precise_wall_warning(DynamicPrintConfig& c) } // namespace +// --------------------------------------------------------------------------- +// {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"); +} + TEST_CASE("Print::validate stacks independent warnings", "[Print][validate]") { // Two unrelated checks (region precise-wall + machine acceleration) must each