mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-07-23 02:42:08 +00:00
Merge branch 'main' into feat/plugin-feature
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -11,8 +11,8 @@ SCENARIO("Placeholder parser scripting", "[PlaceholderParser]") {
|
||||
|
||||
config.set_deserialize_strict( {
|
||||
{ "printer_notes", " PRINTER_VENDOR_PRUSA3D PRINTER_MODEL_MK2 " },
|
||||
{ "nozzle_diameter", "0.6;0.6;0.6;0.6" },
|
||||
{ "nozzle_temperature", "357;359;363;378" }
|
||||
{ "nozzle_diameter", "0.6,0.6,0.6,0.6" },
|
||||
{ "nozzle_temperature", "357,359,363,378" }
|
||||
});
|
||||
// To test the "min_width_top_surface" over "inner_wall_line_width".
|
||||
config.option<ConfigOptionFloatOrPercent>("inner_wall_line_width")->value = 150.;
|
||||
@@ -121,8 +121,8 @@ SCENARIO("Placeholder parser variables", "[PlaceholderParser]") {
|
||||
config.set_deserialize_strict({
|
||||
{ "filament_notes", "testnotes" },
|
||||
{ "enable_pressure_advance", "1" },
|
||||
{ "nozzle_diameter", "0.6;0.6;0.6;0.6" },
|
||||
{ "nozzle_temperature", "357;359;363;378" }
|
||||
{ "nozzle_diameter", "0.6,0.6,0.6,0.6" },
|
||||
{ "nozzle_temperature", "357,359,363,378" }
|
||||
});
|
||||
|
||||
PlaceholderParser::ContextData context_with_global_dict;
|
||||
@@ -241,3 +241,97 @@ SCENARIO("Placeholder parser variables", "[PlaceholderParser]") {
|
||||
}
|
||||
SECTION("if else completely empty") { REQUIRE(parser.process("{if false then elsif false then else endif}", 0, nullptr, nullptr, nullptr) == ""); }
|
||||
}
|
||||
|
||||
SCENARIO("Placeholder parser coFloatsOrPercents vector access", "[PlaceholderParser]") {
|
||||
PlaceholderParser parser;
|
||||
auto config = DynamicPrintConfig::full_print_config();
|
||||
|
||||
// outer_wall_speed is the ratio_over target for small_perimeter_speed.
|
||||
// Different values per extruder to verify parent resolves at the same element index.
|
||||
config.set_deserialize_strict({
|
||||
{ "outer_wall_speed", "60,70,80,90" },
|
||||
{ "nozzle_diameter", "0.4,0.4,0.4,0.4" },
|
||||
{ "pressure_advance", "1.5,2.0,3.0,4.0" } // coFloats non-nullable
|
||||
});
|
||||
// small_perimeter_speed:
|
||||
// [0] = 50% of outer_wall_speed[0] (= 60) → 30
|
||||
// [1] = 80% of outer_wall_speed[1] (= 70) → 56
|
||||
// [2] = 0 absolute
|
||||
// [3] = 50% of outer_wall_speed[3] (= 90) → 45
|
||||
config.option<ConfigOptionFloatsOrPercentsNullable>("small_perimeter_speed")->values = {
|
||||
FloatOrPercent{50.0, true}, // 50% of outer_wall_speed[0] (60) = 30
|
||||
FloatOrPercent{80.0, true}, // 80% of outer_wall_speed[1] (70) = 56
|
||||
FloatOrPercent{0.0, false}, // absolute: 0
|
||||
FloatOrPercent{50.0, true}, // 50% of outer_wall_speed[3] (90) = 45
|
||||
};
|
||||
|
||||
parser.apply_config(config);
|
||||
parser.set("foo", 0);
|
||||
parser.set("bar", 1);
|
||||
parser.set("baz", 3);
|
||||
parser.set("num_extruders", 4);
|
||||
|
||||
SECTION("Indexed access - percent resolved against parent at same index [0]") {
|
||||
// 50% of outer_wall_speed[0] (60) = 30
|
||||
REQUIRE(std::stod(parser.process("{small_perimeter_speed[0]}")) == Catch::Approx(30.0));
|
||||
}
|
||||
|
||||
SECTION("Indexed access - percent resolved against parent at same index [1]") {
|
||||
// 80% of outer_wall_speed[1] (70) = 56
|
||||
REQUIRE(std::stod(parser.process("{small_perimeter_speed[1]}")) == Catch::Approx(56.0));
|
||||
}
|
||||
|
||||
SECTION("Indexed access - percent resolved against parent at same index [3]") {
|
||||
// 50% of outer_wall_speed[3] (90) = 45
|
||||
REQUIRE(std::stod(parser.process("{small_perimeter_speed[3]}")) == Catch::Approx(45.0));
|
||||
}
|
||||
|
||||
SECTION("Variable-indexed access via foo (=0) - percent value") {
|
||||
// 50% of outer_wall_speed[0] (60) = 30
|
||||
REQUIRE(std::stod(parser.process("{small_perimeter_speed[foo]}")) == Catch::Approx(30.0));
|
||||
}
|
||||
|
||||
SECTION("Variable-indexed access via bar (=1) - percent value") {
|
||||
// 80% of outer_wall_speed[1] (70) = 56
|
||||
REQUIRE(std::stod(parser.process("{small_perimeter_speed[bar]}")) == Catch::Approx(56.0));
|
||||
}
|
||||
|
||||
SECTION("Variable-indexed access via baz (=3) - percent value") {
|
||||
// 50% of outer_wall_speed[3] (90) = 45
|
||||
REQUIRE(std::stod(parser.process("{small_perimeter_speed[baz]}")) == Catch::Approx(45.0));
|
||||
}
|
||||
|
||||
SECTION("Literal-indexed access - absolute value") {
|
||||
REQUIRE(std::stod(parser.process("{small_perimeter_speed[2]}")) == Catch::Approx(0.0));
|
||||
}
|
||||
|
||||
SECTION("No-index (extruder-based) access - percent resolved via current extruder") {
|
||||
// Extruder 0 = 50% of outer_wall_speed[0] (60) = 30
|
||||
REQUIRE(std::stod(parser.process("{small_perimeter_speed}")) == Catch::Approx(30.0));
|
||||
}
|
||||
|
||||
SECTION("Out-of-range index clamps to index 0") {
|
||||
// Index 99 is out of range, clamps to 0: 50% of outer_wall_speed[0] (60) = 30
|
||||
REQUIRE(std::stod(parser.process("{small_perimeter_speed[99]}")) == Catch::Approx(30.0));
|
||||
}
|
||||
|
||||
SECTION("coFloats no-index access - nullable (outer_wall_speed)") {
|
||||
// outer_wall_speed is ConfigOptionFloatsNullable, exercises the 'if' branch
|
||||
REQUIRE(std::stod(parser.process("{outer_wall_speed}")) == Catch::Approx(60.0));
|
||||
}
|
||||
|
||||
SECTION("coFloats indexed access - nullable") {
|
||||
// outer_wall_speed[2] = 80
|
||||
REQUIRE(std::stod(parser.process("{outer_wall_speed[2]}")) == Catch::Approx(80.0));
|
||||
}
|
||||
|
||||
SECTION("coFloats no-index access - non-nullable (pressure_advance)") {
|
||||
// pressure_advance is ConfigOptionFloats (non-nullable), exercises the 'else' branch
|
||||
REQUIRE(std::stod(parser.process("{pressure_advance}")) == Catch::Approx(1.5));
|
||||
}
|
||||
|
||||
SECTION("coFloats indexed access - non-nullable") {
|
||||
// pressure_advance[2] = 3.0
|
||||
REQUIRE(std::stod(parser.process("{pressure_advance[2]}")) == Catch::Approx(3.0));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user