mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-07-02 16:41:11 +00:00
feat: add {first_object_name} filename placeholder (#14497)
{input_filename_base} is meant to be the saved project's file name. Before
#13753 a bug made it fall back to the first object's name when a project was
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
This commit is contained in:
@@ -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<std::string> 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<std::string>(printable->get_scaling_factor(X) * 100) +
|
||||
"% y:" + boost::lexical_cast<std::string>(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();
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user