Fix: collision warnings (#12122)

* Fix: Enable instance collision detection in GCode and Print clearance

Squashed commit containing:
- Fix gcode path conflict detection in ConflictChecker.cpp by iterating all instances.
- Improve clearance validation in Print.cpp by calculating convex hulls per instance (fixes scaling/mirroring issues).
- Added // Orca: comments to mark changes.

* Fix Wipe Tower G-code conflict detection for WipeTower2

* Fix: Improve object/instance selection for collision and validation warnings

- Updated validation logic in Print.cpp to report specific ModelInstance instead of ModelObject for collision/clearance warnings.

- Updated NotificationManager and Plater to handle ModelInstance selection in 'Jump to' links.

- Added fallback to object selection if specific instance cannot be selected.

- Included fixes for G-code conflict detection (ConflictChecker, GLCanvas3D) to also report instances.

- Improved GUI_ObjectList to update canvas selection when items are selected via API.

* Fix: Prevent crash when loading .3mf projects

Moved update_selections_on_canvas() out of ObjectList::select_items() to avoid premature UI updates during loading. Canvas updates are now explicitly called in NotificationManager and Plater callbacks where needed.

* Fix: Address code review comments

- Fix memory allocation for extrusion layers deep copy

- Remove unused variable in GLCanvas3D

- Fix string formatting crash risk in NotificationManager

- Remove dead code in Plater

---------

Co-authored-by: SoftFever <softfeverever@gmail.com>
This commit is contained in:
tome9111991
2026-04-26 12:01:41 +02:00
committed by GitHub
parent a9cfcb9d5d
commit fdff62c796
6 changed files with 343 additions and 67 deletions

View File

@@ -9496,6 +9496,7 @@ void GLCanvas3D::_set_warning_notification(EWarning warning, bool state)
std::string text;
ErrorType error = ErrorType::PLATER_WARNING;
const ModelObject* conflictObj=nullptr;
const ModelInstance* conflictInst=nullptr;
switch (warning) {
case EWarning::GCodeConflict: {
static std::string prevConflictText;
@@ -9506,12 +9507,17 @@ void GLCanvas3D::_set_warning_notification(EWarning warning, bool state)
std::string objName2 = m_gcode_viewer.m_conflict_result.value()._objName2;
double height = m_gcode_viewer.m_conflict_result.value()._height;
int layer = m_gcode_viewer.m_conflict_result.value().layer;
const PrintInstance *inst2 = reinterpret_cast<const PrintInstance *>(m_gcode_viewer.m_conflict_result.value()._obj2);
text = (boost::format(_u8L("Conflicts of G-code paths have been found at layer %d, Z = %.2lfmm. Please separate the conflicted objects farther (%s <-> %s).")) % layer %
height % objName1 % objName2)
.str();
prevConflictText = text;
const PrintObject *obj2 = reinterpret_cast<const PrintObject *>(m_gcode_viewer.m_conflict_result.value()._obj2);
conflictObj = obj2->model_object();
if (inst2) {
if (inst2->model_instance) conflictInst = inst2->model_instance;
else if (inst2->print_object) conflictObj = inst2->print_object->model_object();
}
break;
}
case EWarning::ObjectOutside: text = _u8L("An object is laid over the plate boundaries."); break;
@@ -9759,8 +9765,13 @@ void GLCanvas3D::_set_warning_notification(EWarning warning, bool state)
}
break;
case SLICING_SERIOUS_WARNING:
if (state)
notification_manager.push_slicing_serious_warning_notification(text, conflictObj ? std::vector<ModelObject const*>{conflictObj} : std::vector<ModelObject const*>{});
if (state) {
if (conflictInst) {
notification_manager.push_slicing_serious_warning_notification(text, std::vector<ModelInstance const*>{conflictInst});
} else {
notification_manager.push_slicing_serious_warning_notification(text, conflictObj ? std::vector<ModelObject const*>{conflictObj} : std::vector<ModelObject const*>{});
}
}
else
notification_manager.close_slicing_serious_warning_notification(text);
break;

View File

@@ -1840,14 +1840,18 @@ void NotificationManager::push_validate_error_notification(StringObjectException
{
auto po = dynamic_cast<PrintObjectBase const *>(error.object);
auto mo = po ? po->model_object() : dynamic_cast<ModelObject const *>(error.object);
//ORCA: Update to handle ModelInstance selection for validation errors with fallback
/*
std::function<bool(wxEvtHandler*)> callback;
if (mo || !error.opt_key.empty()) {
callback =
[id = mo ? mo->id() : 0, opt = error.opt_key](wxEvtHandler*) {
auto& objects = wxGetApp().model().objects;
auto iter = id.id ? std::find_if(objects.begin(), objects.end(), [id](auto o) { return o->id() == id; }) : objects.end();
if (iter != objects.end())
if (iter != objects.end()) {
wxGetApp().obj_list()->select_items({ {*iter, nullptr} });
wxGetApp().obj_list()->update_selections_on_canvas();
}
if (!opt.empty()) {
if (iter != objects.end())
wxGetApp().params_panel()->switch_to_object();
@@ -1861,6 +1865,71 @@ void NotificationManager::push_validate_error_notification(StringObjectException
}
auto link = (mo || !error.opt_key.empty()) ? _u8L("Jump to") : "";
if (mo) link += std::string(" [") + mo->name + "]";
*/
auto mi = dynamic_cast<ModelInstance const *>(error.object);
std::function<bool(wxEvtHandler*)> callback;
if (mo || mi || !error.opt_key.empty()) {
callback =
[id = mo ? mo->id() : (mi ? mi->id() : 0),
parent_id = mi ? mi->get_object()->id() : 0,
is_inst = (mi != nullptr),
opt = error.opt_key](wxEvtHandler*) {
auto& objects = wxGetApp().model().objects;
if (is_inst) {
bool selected = false;
auto iter = std::find_if(objects.begin(), objects.end(), [parent_id](auto o) { return o->id() == parent_id; });
if (iter != objects.end()) {
ModelObject* obj = *iter;
int inst_idx = -1;
for(size_t i=0; i<obj->instances.size(); ++i) {
if (obj->instances[i]->id() == id) {
inst_idx = i;
break;
}
}
if (inst_idx != -1) {
auto* model = wxGetApp().obj_list()->GetModel();
wxDataViewItem item;
wxDataViewItem objItem = model->GetObjectItem(obj);
if (objItem.IsOk()) {
int vm_obj_idx = model->GetIdByItem(objItem);
if (vm_obj_idx != -1) {
item = model->GetItemByInstanceId(vm_obj_idx, inst_idx);
}
}
if (item.IsOk()) {
wxDataViewItemArray sel_items;
sel_items.Add(item);
wxGetApp().obj_list()->select_items(sel_items);
selected = true;
}
}
if (!selected) {
wxGetApp().obj_list()->select_items({ {obj, nullptr} });
}
}
} else {
auto iter = id.id ? std::find_if(objects.begin(), objects.end(), [id](auto o) { return o->id() == id; }) : objects.end();
if (iter != objects.end())
wxGetApp().obj_list()->select_items({ {*iter, nullptr} });
}
if (!opt.empty()) {
if ((!is_inst && id.id) || (is_inst && parent_id.id)) // if object found
wxGetApp().params_panel()->switch_to_object();
wxGetApp().sidebar().jump_to_option(opt, Preset::TYPE_PRINT, L"");
}
else {
wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
}
return false;
};
}
auto link = (mo || mi || !error.opt_key.empty()) ? _u8L("Jump to") : "";
if (mo) link += std::string(" [") + mo->name + "]";
if (mi) link += std::string(" [") + mi->get_object()->name + "]";
if (!error.opt_key.empty()) link += std::string(" (") + error.opt_key + ")";
push_notification_data({NotificationType::ValidateError, NotificationLevel::ErrorNotificationLevel, 0, _u8L("Error:") + "\n" + error.string, link, callback}, 0);
set_slicing_progress_hidden();
@@ -2290,7 +2359,7 @@ void NotificationManager::upload_job_notification_show_error(int id, const std::
}
}
void NotificationManager::push_slicing_serious_warning_notification(const std::string &text, std::vector<ModelObject const *> objs)
void NotificationManager::push_slicing_serious_warning_notification(const std::string &text, std::vector<ModelObject const *> objs)
{
std::vector<ObjectID> ids;
for (auto optr : objs) {
@@ -2308,19 +2377,113 @@ void NotificationManager::push_slicing_serious_warning_notification(const std::s
if (!ovs.empty()) {
wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
wxGetApp().obj_list()->select_items(ovs);
wxGetApp().obj_list()->update_selections_on_canvas();
}
return false;
};
}
auto link = callback ? _u8L("Jump to") : "";
if (!objs.empty()) {
link += " [";
for (auto obj : objs) {
if (obj) link += obj->name + ", ";
std::vector<std::string> names;
for (auto optr : objs) {
if (optr) {
names.push_back(optr->name);
}
if (!objs.empty()) {
link.pop_back();
link.pop_back();
}
if (!names.empty()) {
link += " [";
for (size_t i = 0; i < names.size(); ++i) {
if (i > 0) link += ", ";
link += names[i];
}
link += "] ";
}
set_all_slicing_warnings_gray(false);
push_notification_data({NotificationType::SlicingSeriousWarning, NotificationLevel::SeriousWarningNotificationLevel, 0, _u8L("Serious warning:") + "\n" + text, link,
callback},
0);
set_slicing_progress_hidden();
}
void NotificationManager::push_slicing_serious_warning_notification(const std::string &text, std::vector<ModelInstance const *> insts)
{
std::vector<std::pair<ObjectID, ObjectID>> ids;
for (auto iptr : insts) {
if (iptr && iptr->get_object()) {
ids.push_back({iptr->get_object()->id(), iptr->id()});
}
}
std::function<bool(wxEvtHandler*)> callback;
if (!insts.empty()) {
callback = [ids](wxEvtHandler*) {
auto& objects = wxGetApp().model().objects;
wxDataViewItemArray sel_items;
std::vector<ObjectVolumeID> fallback_ovs;
auto* obj_list = wxGetApp().obj_list();
if (!obj_list) return false;
auto* model = obj_list->GetModel();
if (!model) return false;
for (const auto& pair : ids) {
ObjectID obj_id = pair.first;
ObjectID inst_id = pair.second;
auto iter = std::find_if(objects.begin(), objects.end(), [obj_id](auto o) { return o->id() == obj_id; });
if (iter != objects.end()) {
ModelObject* obj = *iter;
int inst_idx = -1;
for (int i=0; i<obj->instances.size(); ++i) {
if (obj->instances[i]->id() == inst_id) {
inst_idx = i;
break;
}
}
if (inst_idx != -1) {
wxDataViewItem item;
wxDataViewItem objItem = model->GetObjectItem(obj);
if (objItem.IsOk()) {
int vm_obj_idx = model->GetIdByItem(objItem);
if (vm_obj_idx != -1) {
item = model->GetItemByInstanceId(vm_obj_idx, inst_idx);
}
}
if (item.IsOk()) {
sel_items.Add(item);
} else {
fallback_ovs.push_back({obj, nullptr});
}
} else {
fallback_ovs.push_back({obj, nullptr});
}
}
}
wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
if (!sel_items.empty()) {
obj_list->select_items(sel_items);
obj_list->update_selections_on_canvas();
} else if (!fallback_ovs.empty()) {
obj_list->select_items(fallback_ovs);
obj_list->update_selections_on_canvas();
}
return false;
};
}
auto link = callback ? _u8L("Jump to") : "";
std::vector<std::string> names;
for (auto iptr : insts) {
if (iptr && iptr->get_object()) {
names.push_back(iptr->get_object()->name);
}
}
if (!names.empty()) {
link += " [";
for (size_t i = 0; i < names.size(); ++i) {
if (i > 0) link += ", ";
link += names[i];
}
link += "] ";
}

View File

@@ -218,6 +218,7 @@ public:
void upload_job_notification_show_canceled(int id, const std::string& filename, const std::string& host);
void upload_job_notification_show_error(int id, const std::string& filename, const std::string& host);
void push_slicing_serious_warning_notification(const std::string &text, std::vector<ModelObject const *> objs);
void push_slicing_serious_warning_notification(const std::string &text, std::vector<ModelInstance const *> insts);
void close_slicing_serious_warning_notification(const std::string &text);
// Creates Slicing Error notification with a custom text and no fade out.
void push_slicing_error_notification(const std::string &text, std::vector<ModelObject const *> objs);

View File

@@ -7773,22 +7773,72 @@ void Plater::priv::process_validation_warning(StringObjectException const &warni
std::string text = warning.string;
auto po = dynamic_cast<PrintObjectBase const *>(warning.object);
auto mo = po ? po->model_object() : dynamic_cast<ModelObject const *>(warning.object);
auto action_fn = (mo || !warning.opt_key.empty()) ? [id = mo ? mo->id() : 0, opt = warning.opt_key](wxEvtHandler *) {
//ORCA: Update process_validation_warning to handle ModelInstance selection and include fallback
auto mi = dynamic_cast<ModelInstance const *>(warning.object);
auto action_fn = (mo || mi || !warning.opt_key.empty()) ? [id = mo ? mo->id() : (mi ? mi->id() : 0),
parent_id = mi ? mi->get_object()->id() : 0,
is_inst = (mi != nullptr),
opt = warning.opt_key](wxEvtHandler *) {
auto & objects = wxGetApp().model().objects;
auto iter = id.id ? std::find_if(objects.begin(), objects.end(), [id](auto o) { return o->id() == id; }) : objects.end();
if (iter != objects.end()) {
wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
wxGetApp().obj_list()->select_items({{*iter, nullptr}});
if (is_inst) {
bool selected = false;
auto iter = std::find_if(objects.begin(), objects.end(), [parent_id](auto o) { return o->id() == parent_id; });
if (iter != objects.end()) {
ModelObject* obj = *iter;
int inst_idx = -1;
for(size_t i=0; i<obj->instances.size(); ++i) {
if (obj->instances[i]->id() == id) {
inst_idx = i;
break;
}
}
wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
if (inst_idx != -1) {
auto* model = wxGetApp().obj_list()->GetModel();
wxDataViewItem item;
wxDataViewItem objItem = model->GetObjectItem(obj);
if (objItem.IsOk()) {
int vm_obj_idx = model->GetIdByItem(objItem);
if (vm_obj_idx != -1) {
item = model->GetItemByInstanceId(vm_obj_idx, inst_idx);
}
}
if (item.IsOk()) {
wxDataViewItemArray sel_items;
sel_items.Add(item);
wxGetApp().obj_list()->select_items(sel_items);
wxGetApp().obj_list()->update_selections_on_canvas();
selected = true;
}
}
if (!selected) {
wxGetApp().obj_list()->select_items({ {obj, nullptr} });
wxGetApp().obj_list()->update_selections_on_canvas();
}
}
} else {
auto iter = id.id ? std::find_if(objects.begin(), objects.end(), [id](auto o) { return o->id() == id; }) : objects.end();
if (iter != objects.end()) {
wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
wxGetApp().obj_list()->select_items({{*iter, nullptr}});
wxGetApp().obj_list()->update_selections_on_canvas();
}
}
if (!opt.empty()) {
if (iter != objects.end())
if ((!is_inst && id.id) || (is_inst && parent_id.id))
wxGetApp().params_panel()->switch_to_object();
wxGetApp().sidebar().jump_to_option(opt, Preset::TYPE_PRINT, L"");
}
return false;
} : std::function<bool(wxEvtHandler *)>();
auto hypertext = (mo || !warning.opt_key.empty()) ? _u8L("Jump to") : "";
auto hypertext = (mo || mi || !warning.opt_key.empty()) ? _u8L("Jump to") : "";
if (mo) hypertext += std::string(" [") + mo->name + "]";
if (mi) hypertext += std::string(" [") + mi->get_object()->name + "]";
if (!warning.opt_key.empty()) hypertext += std::string(" (") + warning.opt_key + ")";
// BBS disable support enforcer