diff --git a/src/OrcaSlicer.cpp b/src/OrcaSlicer.cpp index 1b764fe468..c42ae676cc 100644 --- a/src/OrcaSlicer.cpp +++ b/src/OrcaSlicer.cpp @@ -1463,7 +1463,7 @@ int CLI::run(int argc, char **argv) //int arrange_option; int plate_to_slice = 0, filament_count = 0, duplicate_count = 0, real_duplicate_count = 0, current_extruder_count = 1, new_extruder_count = 1, current_printer_variant_count = 1, current_print_variant_count = 1, new_printer_variant_count = 1; bool first_file = true, is_bbl_3mf = false, need_arrange = true, has_thumbnails = false, up_config_to_date = false, normative_check = true, duplicate_single_object = false, use_first_fila_as_default = false, minimum_save = false, enable_timelapse = false; - bool allow_rotations = true, skip_modified_gcodes = false, avoid_extrusion_cali_region = false, skip_useless_pick = false, allow_newer_file = false, current_is_multi_extruder = false, new_is_multi_extruder = false, allow_mix_temp = false, enable_wrapping_detect = false; + bool allow_rotations = false, skip_modified_gcodes = false, avoid_extrusion_cali_region = false, skip_useless_pick = false, allow_newer_file = false, current_is_multi_extruder = false, new_is_multi_extruder = false, allow_mix_temp = false, enable_wrapping_detect = false; Semver file_version; std::map orients_requirement; std::vector project_presets; @@ -5142,6 +5142,7 @@ int CLI::run(int argc, char **argv) BOOST_LOG_TRIVIAL(info) << "finished model pre-process commands\n"; bool oriented_or_arranged = false; + bool arranged_without_rotations = false; //BBS: add orient and arrange logic here for (auto& model : m_models) { @@ -5409,6 +5410,7 @@ int CLI::run(int argc, char **argv) BOOST_LOG_TRIVIAL(info) << boost::format("start plate %1%'s arranging...") % (i + 1); arrangement::arrange(selected, unselected, beds, arrange_cfg); //arrangement::arrange(unprintable, {}, beds, arrange_cfg); + arranged_without_rotations |= !arrange_cfg.allow_rotations; BOOST_LOG_TRIVIAL(info) << boost::format("finished plate %1%'s arranging") % (i + 1); //step-4: postprocess the bed index and result @@ -5881,6 +5883,7 @@ int CLI::run(int argc, char **argv) BOOST_LOG_TRIVIAL(info) << boost::format("start %1% th arranging...")%arrange_count; arrangement::arrange(selected, unselected, beds, arrange_cfg); arrangement::arrange(unprintable, {}, beds, arrange_cfg); + arranged_without_rotations |= !arrange_cfg.allow_rotations; BOOST_LOG_TRIVIAL(info) << boost::format("finished %1% th arranging...")%arrange_count; //Step-4:postprocess by partplate list&&apply the result @@ -6425,7 +6428,15 @@ int CLI::run(int argc, char **argv) if (count == 0) { BOOST_LOG_TRIVIAL(error) << "plate "<< index+1<< ": Nothing to be sliced, Either the print is empty or no object is fully inside the print volume before apply." << std::endl; - record_exit_reson(outfile_dir, CLI_NO_SUITABLE_OBJECTS, index+1, cli_errors[CLI_NO_SUITABLE_OBJECTS], sliced_info); + std::string no_objects_reason = cli_errors[CLI_NO_SUITABLE_OBJECTS]; + if (arranged_without_rotations) { + //ORCA: result.json is only written on Linux, so also print the reason to stderr. + no_objects_reason = "No object is fully inside the print volume. Arrange ran without rotations." + " If an object only fits the bed when rotated, pass --allow-rotations." + " Otherwise the plate is empty or an object is larger than the printable area or height."; + boost::nowide::cerr << "plate " << index+1 << ": " << no_objects_reason << std::endl; + } + record_exit_reson(outfile_dir, CLI_NO_SUITABLE_OBJECTS, index+1, no_objects_reason, sliced_info); flush_and_exit(CLI_NO_SUITABLE_OBJECTS); } else if ((plate_to_slice != 0) || pre_check) { diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 9e3958bc25..b081f14248 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -13085,7 +13085,7 @@ CLIMiscConfigDef::CLIMiscConfigDef() def = this->add("allow_rotations", coBool); def->label = L("Allow rotation when arranging"); def->tooltip = L("If enabled, Arrange will allow rotation when placing objects."); - def->set_default_value(new ConfigOptionBool(true)); + def->set_default_value(new ConfigOptionBool(false)); def = this->add("align_to_y_axis", coBool); def->label = L("Align to Y axis when arranging"); diff --git a/tests/cli/CMakeLists.txt b/tests/cli/CMakeLists.txt index dddebee566..d48fe479cf 100644 --- a/tests/cli/CMakeLists.txt +++ b/tests/cli/CMakeLists.txt @@ -31,6 +31,14 @@ set_tests_properties(cli_project_missing_keys PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 900) +add_test(NAME cli_arrange_rotations + COMMAND bash ${CMAKE_CURRENT_SOURCE_DIR}/test_cli_arrange_rotations.sh $ ${ORCA_CLI_TEST_PYTHON} + ${CMAKE_SOURCE_DIR}/resources/profiles/BBL) +set_tests_properties(cli_arrange_rotations PROPERTIES + LABELS "CLI;RequiresApp" + SKIP_RETURN_CODE 77 + TIMEOUT 900) + add_test(NAME cli_malformed_input COMMAND bash ${CMAKE_CURRENT_SOURCE_DIR}/test_cli_malformed_input.sh $ ${ORCA_CLI_TEST_PYTHON}) set_tests_properties(cli_malformed_input PROPERTIES diff --git a/tests/cli/test_cli_arrange_rotations.sh b/tests/cli/test_cli_arrange_rotations.sh new file mode 100755 index 0000000000..e0f9d33adc --- /dev/null +++ b/tests/cli/test_cli_arrange_rotations.sh @@ -0,0 +1,89 @@ +#!/usr/bin/env bash +# End-to-end check of the CLI's arrange rotation policy. +# +# Arrange is translate-only unless --allow-rotations is passed, so a bar that fits the bed only +# diagonally is not placed and the plate ends up with nothing fully inside the print volume. The +# run must then say that rotations were off, on stderr as well as in result.json, because +# result.json is written on Linux only. Passing the flag must place the same bar and slice it, and +# an object that fits axis-aligned must be unaffected by the default. +# +# usage: test_cli_arrange_rotations.sh +set -u + +BIN="${1:-}" +PY="${2:-python3}" +PROFILES="${3:-}" +# 77 is the test's SKIP_RETURN_CODE. +[ -x "$BIN" ] || { echo "SKIP: orca-slicer binary not found: $BIN"; exit 77; } +[ -d "$PROFILES" ] || { echo "FAIL: profiles directory not found: $PROFILES"; exit 1; } + +WORK="$(mktemp -d "${TMPDIR:-/tmp}/orca-cli-arrange-rotations.XXXXXX")" +trap 'rm -rf "$WORK"' EXIT + +# box : an ASCII STL box, so the test needs no fixture. +box() { + "$PY" - "$@" <<'EOF' +import sys + +out, sx, sy, sz = sys.argv[1], float(sys.argv[2]), float(sys.argv[3]), float(sys.argv[4]) +v = [(x, y, z) for z in (0, sz) for y in (0, sy) for x in (0, sx)] +with open(out, "w") as f: + f.write("solid box\n") + for a, b, c, d in ((0, 2, 3, 1), (4, 5, 7, 6), (0, 1, 5, 4), (2, 6, 7, 3), (0, 4, 6, 2), (1, 3, 7, 5)): + for tri in ((v[a], v[b], v[c]), (v[a], v[c], v[d])): + f.write("facet normal 0 0 0\nouter loop\n") + for p in tri: + f.write("vertex %g %g %g\n" % p) + f.write("endloop\nendfacet\n") + f.write("endsolid box\n") +EOF +} + +# Longer than the 256 mm bed edge, shorter than its 362 mm diagonal: it fits only turned. +box "$WORK/bar.stl" 300 10 5 +# Fits axis-aligned, so the default must not change anything for it. +box "$WORK/cube.stl" 10 10 10 + +# slice [option...] -> $WORK//{rc,stdout,stderr,result.json} +slice() { + local out="$WORK/$1" input="$2"; shift 2 + mkdir -p "$out" + timeout 300 "$BIN" --datadir "$out/datadir" \ + --load-settings "$PROFILES/machine/Bambu Lab X1 Carbon 0.4 nozzle.json;$PROFILES/process/0.20mm Standard @BBL X1C.json" \ + --load-defaultfila "$@" --slice 0 --outputdir "$out" "$input" \ + > "$out/stdout" 2> "$out/stderr" + echo "$?" > "$out/rc" +} + +# exit_code : the CLI's negative code as the shell saw it, e.g. -50 arrives as 206. +exit_code() { "$PY" -c "import sys; rc = int(open(sys.argv[1]).read()); print(rc - 256 if rc > 127 else rc)" "$WORK/$1/rc"; } + +fail() { echo "FAIL: $*"; exit 1; } + +# 1. Default: no flag, so no rotations, so the bar does not fit and the reason names rotation. +slice default "$WORK/bar.stl" +[ "$(exit_code default)" = "-50" ] || fail "default: expected -50, got $(exit_code default)" +grep -q "Arrange ran without rotations" "$WORK/default/stderr" \ + || fail "default: stderr does not mention rotations: $(cat "$WORK/default/stderr")" +"$PY" - "$WORK/default/result.json" <<'EOF' || exit 1 +import json, sys + +result = json.load(open(sys.argv[1])) +if result["return_code"] != -50: + sys.exit("FAIL: result.json return_code %s, expected -50" % result["return_code"]) +if "Arrange ran without rotations" not in result["error_string"]: + sys.exit("FAIL: result.json error_string does not mention rotations: %s" % result["error_string"]) +EOF + +# 2. The flag is coBool, so it takes no value: it must place the same bar and slice it. +slice rotations "$WORK/bar.stl" --allow-rotations +[ "$(exit_code rotations)" = "0" ] || fail "--allow-rotations: expected 0, got $(exit_code rotations); $(tail -n 5 "$WORK/rotations/stdout")" +[ -s "$WORK/rotations/plate_1.gcode" ] || fail "--allow-rotations: no g-code written" + +# 3. An object that fits axis-aligned is unaffected by the default, and says nothing about rotation. +slice cube "$WORK/cube.stl" +[ "$(exit_code cube)" = "0" ] || fail "cube: expected 0, got $(exit_code cube); $(tail -n 5 "$WORK/cube/stdout")" +[ -s "$WORK/cube/plate_1.gcode" ] || fail "cube: no g-code written" +grep -q "Arrange ran without rotations" "$WORK/cube/stderr" && fail "cube: unexpected rotation message on a successful run" + +echo "PASS: arrange rotation default, its failure message, and the opt-in flag"