Compare commits

..
Author SHA1 Message Date
ExPikaPaka ea60911bd3 Block undo and redo while a background job runs
A job is queued against the model as it stands and hands its result back
when it finishes, so undoing underneath it leaves that result landing on
geometry it was never computed for. Undo and redo now wait for the job
and say so, and can_undo()/can_redo() report the same, so the toolbar
and the menu items stay in step.
2026-09-30 09:00:29 +02:00
4 changed files with 27 additions and 69 deletions
+3 -8
View File
@@ -1913,10 +1913,7 @@ int CLI::run(int argc, char **argv)
old_printable_width = static_cast<int>(old_printable_bbox.size().x());
old_printable_depth = static_cast<int>(old_printable_bbox.size().y());
}
// A 3mf can carry an empty project_settings.config - the models in
// resources/handy_models do - and opt_float() dereferences without checking.
if (config.option<ConfigOptionFloat>("printable_height"))
old_printable_height = (int)(config.opt_float("printable_height"));
old_printable_height = (int)(config.opt_float("printable_height"));
if (config.option<ConfigOptionFloat>("extruder_clearance_height_to_rod"))
old_height_to_rod = config.opt_float("extruder_clearance_height_to_rod");
@@ -2592,8 +2589,7 @@ int CLI::run(int argc, char **argv)
orig_printable_width = static_cast<int>(orig_printable_bbox.size().x());
orig_printable_depth = static_cast<int>(orig_printable_bbox.size().y());
}
if (config.option<ConfigOptionFloat>("printable_height"))
orig_printable_height = (int)(config.opt_float("printable_height"));
orig_printable_height = (int)(config.opt_float("printable_height"));
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(":%1%, check printable size: old_printable_width=%2%, orig_printable_width=%3%, old_printable_depth=%4%, orig_printable_depth=%5%, old_printable_height=%6%, orig_printable_height=%7%")
%__LINE__ %old_printable_width %orig_printable_width %old_printable_depth %orig_printable_depth %old_printable_height %orig_printable_height;
if ((orig_printable_width > 0) && (orig_printable_depth > 0) && (orig_printable_height > 0))
@@ -4621,8 +4617,7 @@ int CLI::run(int argc, char **argv)
BoundingBoxf temp_printable_bbox(temp_printable_area);
printer_plate.printable_width = static_cast<int>(temp_printable_bbox.size().x());
printer_plate.printable_depth = static_cast<int>(temp_printable_bbox.size().y());
if (config.option<ConfigOptionFloat>("printable_height"))
printer_plate.printable_height = (int)(config.opt_float("printable_height"));
printer_plate.printable_height = (int)(config.opt_float("printable_height"));
}
if (temp_exclude_area.size() >= 4) {
printer_plate.exclude_width = (int)(temp_exclude_area[2].x() - temp_exclude_area[0].x());
+24 -2
View File
@@ -7297,6 +7297,8 @@ struct Plater::priv
void undo();
void redo();
// True, and tells the user, while a background job is working on the model - see the definition.
bool undo_redo_blocked_by_job();
void undo_redo_to(size_t time_to_load);
// BBS: backup
@@ -14933,8 +14935,25 @@ void Plater::priv::take_snapshot(const std::string& snapshot_name, const UndoRed
BOOST_LOG_TRIVIAL(info) << "Undo / Redo snapshot taken: " << snapshot_name << ", Undo / Redo stack memory: " << Slic3r::format_memsize_MB(this->undo_redo_stack().memsize()) << log_memory_info();
}
// A background job holds the model it is working on: it was queued against the geometry as it stood at
// the time and hands its result back when it finishes. Undoing while it runs restores an older state
// underneath it, a different transform or a different mesh, and the result then lands on geometry it
// was never computed for. Undo and redo therefore wait for the job, and say so rather than doing
// nothing.
bool Plater::priv::undo_redo_blocked_by_job()
{
if (m_worker.is_idle())
return false;
notification_manager->push_notification(NotificationType::CustomNotification,
NotificationManager::NotificationLevel::RegularNotificationLevel,
_u8L("Cannot undo or redo while an operation is running. Stop it first."));
return true;
}
void Plater::priv::undo()
{
if (this->undo_redo_blocked_by_job())
return;
const std::vector<UndoRedo::Snapshot> &snapshots = this->undo_redo_stack().snapshots();
auto it_current = std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(this->undo_redo_stack().active_snapshot_time()));
// BBS: undo-redo until modify record
@@ -14952,6 +14971,8 @@ void Plater::priv::undo()
void Plater::priv::redo()
{
if (this->undo_redo_blocked_by_job())
return;
const std::vector<UndoRedo::Snapshot> &snapshots = this->undo_redo_stack().snapshots();
auto it_current = std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(this->undo_redo_stack().active_snapshot_time()));
// BBS: undo-redo until modify record
@@ -22808,8 +22829,9 @@ bool Plater::can_copy_to_clipboard() const
return true;
}
bool Plater::can_undo() const { return IsShown() && p->is_view3D_shown() && p->undo_redo_stack().has_undo_snapshot(); }
bool Plater::can_redo() const { return IsShown() && p->is_view3D_shown() && p->undo_redo_stack().has_redo_snapshot(); }
// The job check keeps the buttons in step with priv::undo()/redo(), which refuse while one runs.
bool Plater::can_undo() const { return IsShown() && p->is_view3D_shown() && p->m_worker.is_idle() && p->undo_redo_stack().has_undo_snapshot(); }
bool Plater::can_redo() const { return IsShown() && p->is_view3D_shown() && p->m_worker.is_idle() && p->undo_redo_stack().has_redo_snapshot(); }
bool Plater::can_reload_from_disk() const { return p->can_reload_from_disk(); }
//BBS
bool Plater::can_fillcolor() const { return p->can_fillcolor(); }
-7
View File
@@ -11,13 +11,6 @@ endif ()
add_test(NAME cli_strict_mode
COMMAND bash ${CMAKE_CURRENT_SOURCE_DIR}/test_cli_strict.sh $<TARGET_FILE:OrcaSlicer> ${ORCA_CLI_TEST_PYTHON})
add_test(NAME cli_empty_project_config
COMMAND bash ${CMAKE_CURRENT_SOURCE_DIR}/test_cli_empty_project_config.sh $<TARGET_FILE:OrcaSlicer> ${ORCA_CLI_TEST_PYTHON} ${CMAKE_SOURCE_DIR})
set_tests_properties(cli_empty_project_config PROPERTIES
LABELS "CLI;RequiresApp"
SKIP_RETURN_CODE 77
TIMEOUT 300)
set_tests_properties(cli_strict_mode PROPERTIES
LABELS "CLI;RequiresApp"
SKIP_RETURN_CODE 77
@@ -1,52 +0,0 @@
#!/usr/bin/env bash
# Regression check: a 3mf whose Metadata/project_settings.config carries no settings must load.
#
# The CLI reads printable_height out of the project config with opt_float(), which dereferences
# what option<>() returns. With create = false that is null when the key is absent, so a project
# saved without settings used to take the CLI down with a segfault. Both models in
# resources/handy_models are such files.
#
# usage: test_cli_empty_project_config.sh <orca-slicer binary> <python3> <source dir>
set -u
BIN="${1:-}"
PY="${2:-python3}"
SRC="${3:-}"
# 77 is the test's SKIP_RETURN_CODE.
[ -x "$BIN" ] || { echo "SKIP: orca-slicer binary not found: $BIN"; exit 77; }
[ -f "$SRC/resources/handy_models/OrcaBadge.3mf" ] || { echo "SKIP: handy model not found"; exit 77; }
WORK="$(mktemp -d "${TMPDIR:-/tmp}/orca-cli-emptycfg.XXXXXX")"
trap 'rm -rf "$WORK"' EXIT
mkdir -p "$WORK/datadir"
# Rewrite the project settings to an empty object, so the test holds no matter what the shipped
# models carry later on.
cp "$SRC/resources/handy_models/OrcaBadge.3mf" "$WORK/empty_config.3mf"
"$PY" - "$WORK/empty_config.3mf" <<'PYEOF'
import shutil, sys, zipfile
path = sys.argv[1]
entry = "Metadata/project_settings.config"
with zipfile.ZipFile(path) as src:
items = [(i, src.read(i.filename)) for i in src.infolist()]
with zipfile.ZipFile(path + ".new", "w", zipfile.ZIP_DEFLATED) as dst:
seen = False
for info, data in items:
if info.filename == entry:
data, seen = b"{\n}\n", True
dst.writestr(info, data)
if not seen:
dst.writestr(entry, b"{\n}\n")
shutil.move(path + ".new", path)
PYEOF
"$BIN" --datadir "$WORK/datadir" --info "$WORK/empty_config.3mf" > "$WORK/info.txt" 2>&1
rc=$?
if [ $rc -ne 0 ]; then
echo "FAIL: --info on a project with empty settings exited $rc"
tail -20 "$WORK/info.txt"
exit 1
fi
grep -q "size_x" "$WORK/info.txt" || { echo "FAIL: --info printed no geometry"; cat "$WORK/info.txt"; exit 1; }
echo "PASS: a project with empty settings loads"