mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-10 17:21:10 +00:00
fix(imex): keep the placement error across every validation path
Plater::validate_current_plate() runs the same background_process.validate() as update_background_process(), and on success clears update_apply_result_invalid(false) and closes the ValidateError notification -- but it never consulted imex_placement_violation(). Any event reaching it wiped a live IMEX placement error and re-enabled the Slice button on a plate the slicer still refused; pressing Slice then hit the check in reslice() and returned early, so the job simply never started. Reproduce by clicking the bed with the prime tower overlapping a reserved area. Plater::select_plate_by_hover_id() -> select_plate() calls validate_current_plate() unconditionally, even when the clicked plate is already current, and deselects as a side effect -- which makes it look as though deselecting the tower cleared the error. Escape and clicks off the bed go through selection_changed(), which only renders and clears nothing. Extract the violation-to-message mapping into one helper and call it from both paths. model_fits is set alongside err.string in validate_current_plate, mirroring the missing-plugin block below it: the slice is already gated by m_apply_invalid, but leaving m_ready_for_slice true would trap a future consumer that reads it alone. These are the only two sites that matter. NotificationType::ValidateError has exactly three references in the tree and update_apply_result_invalid exactly four; the other slice-ready writers can only touch m_ready_for_slice, never m_apply_invalid, so they cannot re-enable Slice on their own. Three adjacent gaps are left alone, all pre-existing: "Slice all" is hard-coded always-enabled regardless of plate state; a slice-all batch halts silently at a violating plate because reslice() returns above the line that queues the advance; and object_list_changed() computes its own can_slice from geometry, harmless only because the result is ANDed with PartPlate::can_slice(). This is a hole in the shipped tower-zone check rather than a regression from rotating the tower hull -- it was simply invisible until a tower could be placed in violation. No automated gate: Plater is GUI-only, the helper is file-local and unlinkable, and imex_placement_violation() needs a live wxApp and preset bundle. "Both call sites consult it" is a call-graph property no unit test can express. The grep for a single imex_placement_violation reference is a future regression tripwire, not evidence this refactor happened -- it already returned 1 beforehand. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
baef398ee3
commit
008d13a59e
+47
-23
@@ -8897,6 +8897,40 @@ void Plater::priv::process_validation_warnings(const std::vector<StringObjectExc
|
||||
|
||||
// Update background processing thread from the current config and Model.
|
||||
// Returns a bitmask of UpdateBackgroundProcessReturnState.
|
||||
namespace {
|
||||
|
||||
// IDEX/IQEX placement check, shared by every path that decides whether the plate can be
|
||||
// sliced. It must not live in only one of them: update_background_process() and
|
||||
// Plater::validate_current_plate() both clear update_apply_result_invalid(false) and close
|
||||
// the ValidateError notification on success, so a path that skips this check silently
|
||||
// re-enables the Slice button on a plate the slicer will still refuse.
|
||||
//
|
||||
// The message names both kinds of reserved area because the check covers secondary zones
|
||||
// AND the carriage clearance strips, and the strips sit INSIDE the primary zone -- saying
|
||||
// "secondary zone" alone sends the user looking in the wrong half of the bed.
|
||||
//
|
||||
// The null guard mirrors the one it replaced. PartPlateList::get_curr_plate() indexes a
|
||||
// vector and cannot actually return null, and the guard is fail-open, so do not read it as
|
||||
// making the surrounding code null-safe.
|
||||
std::string imex_placement_error(PartPlate* plate)
|
||||
{
|
||||
if (!plate)
|
||||
return {};
|
||||
switch (plate->imex_placement_violation()) {
|
||||
case PartPlate::ImexPlacementViolation::Object:
|
||||
return _u8L("Cannot slice: an object overlaps an area reserved for IMEX parallel printing "
|
||||
"(a secondary zone, or a carriage clearance strip inside the primary zone).");
|
||||
case PartPlate::ImexPlacementViolation::PrimeTower:
|
||||
return _u8L("Cannot slice: the prime tower overlaps an area reserved for IMEX parallel printing "
|
||||
"(a secondary zone, or a carriage clearance strip inside the primary zone).");
|
||||
case PartPlate::ImexPlacementViolation::None:
|
||||
break;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
unsigned int Plater::priv::update_background_process(bool force_validation, bool postpone_error_messages, bool switch_print)
|
||||
{
|
||||
// bitmap of enum UpdateBackgroundProcessReturnState
|
||||
@@ -8994,29 +9028,8 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool
|
||||
q->post_process_string_object_exception(err);
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": validate err=%1%, warnings=%2%")%err.string%warnings.size();
|
||||
|
||||
// IDEX/IQEX placement check: if anything overlaps a reserved area, treat it as a
|
||||
// validation error so the standard pathway handles button state, notifications,
|
||||
// and auto-slice blocking consistently. The message names both kinds of reserved
|
||||
// area because the check covers secondary zones AND the carriage clearance strips,
|
||||
// and the strips sit INSIDE the primary zone — saying "secondary zone" alone sends
|
||||
// the user looking in the wrong half of the bed.
|
||||
if (err.string.empty()) {
|
||||
PartPlate* imex_plate = partplate_list.get_curr_plate();
|
||||
if (imex_plate) {
|
||||
switch (imex_plate->imex_placement_violation()) {
|
||||
case PartPlate::ImexPlacementViolation::Object:
|
||||
err.string = _u8L("Cannot slice: an object overlaps an area reserved for IMEX parallel printing "
|
||||
"(a secondary zone, or a carriage clearance strip inside the primary zone).");
|
||||
break;
|
||||
case PartPlate::ImexPlacementViolation::PrimeTower:
|
||||
err.string = _u8L("Cannot slice: the prime tower overlaps an area reserved for IMEX parallel printing "
|
||||
"(a secondary zone, or a carriage clearance strip inside the primary zone).");
|
||||
break;
|
||||
case PartPlate::ImexPlacementViolation::None:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (err.string.empty())
|
||||
err.string = imex_placement_error(partplate_list.get_curr_plate());
|
||||
|
||||
if (err.string.empty()) {
|
||||
this->partplate_list.get_curr_plate()->update_apply_result_invalid(false);
|
||||
@@ -19074,6 +19087,17 @@ void Plater::validate_current_plate(bool& model_fits, bool& validate_error)
|
||||
post_process_string_object_exception(err);
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": validate err=%1%, warnings=%2%, model_fits %3%")%err.string%warnings.size() %model_fits;
|
||||
|
||||
// The same check update_background_process() applies. Without it this path clears
|
||||
// the invalid flag and closes the notification on a plate that is still unslicable.
|
||||
// model_fits is set too, mirroring the missing-plugin block further down: the slice
|
||||
// is already gated by m_apply_invalid, but leaving m_ready_for_slice true would be a
|
||||
// trap for any future consumer that reads it on its own.
|
||||
if (err.string.empty()) {
|
||||
err.string = imex_placement_error(p->partplate_list.get_curr_plate());
|
||||
if (!err.string.empty())
|
||||
model_fits = false;
|
||||
}
|
||||
|
||||
if (err.string.empty()) {
|
||||
p->partplate_list.get_curr_plate()->update_apply_result_invalid(false);
|
||||
p->notification_manager->set_all_slicing_errors_gray(true);
|
||||
|
||||
Reference in New Issue
Block a user