From a691fb0f0d51e5968ca39f7abfd95aee73496a64 Mon Sep 17 00:00:00 2001 From: tome9111991 <57866234+tome9111991@users.noreply.github.com> Date: Sun, 26 Apr 2026 12:01:41 +0200 Subject: [PATCH] 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 --- src/libslic3r/GCode/ConflictChecker.cpp | 38 +++-- src/libslic3r/Print.cpp | 107 +++++++++----- src/slic3r/GUI/GLCanvas3D.cpp | 19 ++- src/slic3r/GUI/NotificationManager.cpp | 181 ++++++++++++++++++++++-- src/slic3r/GUI/NotificationManager.hpp | 1 + src/slic3r/GUI/Plater.cpp | 64 ++++++++- 6 files changed, 343 insertions(+), 67 deletions(-) diff --git a/src/libslic3r/GCode/ConflictChecker.cpp b/src/libslic3r/GCode/ConflictChecker.cpp index e8780a818c..e8f533be7c 100644 --- a/src/libslic3r/GCode/ConflictChecker.cpp +++ b/src/libslic3r/GCode/ConflictChecker.cpp @@ -220,7 +220,13 @@ ConflictComputeOpt ConflictChecker::find_inter_of_lines(const LineWithIDs &lines ConflictResultOpt ConflictChecker::find_inter_of_lines_in_diff_objs(PrintObjectPtrs objs, std::optional wtdptr) // find the first intersection point of lines in different objects { - if (objs.size() <= 1 && !wtdptr) { return {}; } + if (objs.empty() && !wtdptr) { return {}; } + + // Orca: check if we have enough items to potentially conflict (instances count) + size_t total_instances = 0; + for (auto obj : objs) total_instances += obj->instances().size(); + if (total_instances <= 1 && !wtdptr) return {}; + LinesBucketQueue conflictQueue; if (wtdptr.has_value()) { // wipe tower at 0 by default @@ -238,8 +244,19 @@ ConflictResultOpt ConflictChecker::find_inter_of_lines_in_diff_objs(PrintObjectP } for (PrintObject *obj : objs) { auto layers = getAllLayersExtrusionPathsFromObject(obj); - conflictQueue.emplace_back_bucket(std::move(layers.perimeters), obj, obj->instances().front().shift); - conflictQueue.emplace_back_bucket(std::move(layers.support), obj, obj->instances().front().shift); + // Orca: check for collisions between all instances + const auto& instances = obj->instances(); + for (size_t inst_idx = 0; inst_idx < instances.size(); ++inst_idx) { + const PrintInstance& inst = instances[inst_idx]; + const bool is_last_instance = inst_idx + 1 == instances.size(); + if (is_last_instance) { + conflictQueue.emplace_back_bucket(std::move(layers.perimeters), &inst, inst.shift); + conflictQueue.emplace_back_bucket(std::move(layers.support), &inst, inst.shift); + } else { + conflictQueue.emplace_back_bucket(ExtrusionLayers(layers.perimeters), &inst, inst.shift); + conflictQueue.emplace_back_bucket(ExtrusionLayers(layers.support), &inst, inst.shift); + } + } } std::vector layersLines; @@ -272,13 +289,18 @@ ConflictResultOpt ConflictChecker::find_inter_of_lines_in_diff_objs(PrintObjectP const FakeWipeTower *wtdp = wtdptr.value(); if (ptr1 == wtdp || ptr2 == wtdp) { if (ptr2 == wtdp) { std::swap(ptr1, ptr2); } - const PrintObject *obj2 = reinterpret_cast(ptr2); - return std::make_optional("WipeTower", obj2->model_object()->name, conflictPrintZ, nullptr, ptr2); + // ptr1 is now wipe tower, ptr2 is PrintInstance* + const PrintInstance *inst2 = reinterpret_cast(ptr2); + const PrintObject *obj2 = inst2->print_object; + return std::make_optional("WipeTower", obj2->model_object()->name, conflictPrintZ, nullptr, inst2); } } - const PrintObject *obj1 = reinterpret_cast(ptr1); - const PrintObject *obj2 = reinterpret_cast(ptr2); - return std::make_optional(obj1->model_object()->name, obj2->model_object()->name, conflictPrintZ, ptr1, ptr2); + + const PrintInstance *inst1 = reinterpret_cast(ptr1); + const PrintInstance *inst2 = reinterpret_cast(ptr2); + const PrintObject *obj1 = inst1->print_object; + const PrintObject *obj2 = inst2->print_object; + return std::make_optional(obj1->model_object()->name, obj2->model_object()->name, conflictPrintZ, inst1, inst2); } else return {}; } diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index ad6e279cd4..b5fe490c9f 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -614,7 +614,6 @@ StringObjectException Print::sequential_print_clearance_valid(const Print &print std::for_each(exclude_polys.begin(), exclude_polys.end(), [&print_origin](Polygon& p) { p.translate(scale_(print_origin.x()), scale_(print_origin.y())); }); - std::map map_model_object_to_convex_hull; struct print_instance_info { const PrintInstance *print_instance; @@ -649,27 +648,13 @@ StringObjectException Print::sequential_print_clearance_valid(const Print &print for (const PrintObject *print_object : print.objects()) { assert(! print_object->model_object()->instances.empty()); assert(! print_object->instances().empty()); - ObjectID model_object_id = print_object->model_object()->id(); - auto it_convex_hull = map_model_object_to_convex_hull.find(model_object_id); - // Get convex hull of all printable volumes assigned to this print object. - ModelInstance *model_instance0 = print_object->model_object()->instances.front(); - if (it_convex_hull == map_model_object_to_convex_hull.end()) { - // Calculate the convex hull of a printable object. - // Grow convex hull with the clearance margin. - // FIXME: Arrangement has different parameters for offsetting (jtMiter, limit 2) - // which causes that the warning will be showed after arrangement with the - // appropriate object distance. Even if I set this to jtMiter the warning still shows up. - it_convex_hull = map_model_object_to_convex_hull.emplace_hint(it_convex_hull, model_object_id, - print_object->model_object()->convex_hull_2d(Geometry::assemble_transform( - { 0.0, 0.0, model_instance0->get_offset().z() }, model_instance0->get_rotation(), model_instance0->get_scaling_factor(), model_instance0->get_mirror()))); - } - // Make a copy, so it may be rotated for instances. - Polygon convex_hull0 = it_convex_hull->second; - const double z_diff = Geometry::rotation_diff_z(model_instance0->get_rotation(), print_object->instances().front().model_instance->get_rotation()); - if (std::abs(z_diff) > EPSILON) - convex_hull0.rotate(z_diff); + + // Orca: check convex hull intersection for each instance individually to handle rotation/offset differences correctly // Now we check that no instance of convex_hull intersects any of the previously checked object instances. for (const PrintInstance &instance : print_object->instances()) { + Polygon convex_hull0 = print_object->model_object()->convex_hull_2d(Geometry::assemble_transform( + { 0.0, 0.0, instance.model_instance->get_offset().z() }, instance.model_instance->get_rotation(), instance.model_instance->get_scaling_factor(), instance.model_instance->get_mirror())); + Polygon convex_hull_no_offset = convex_hull0, convex_hull; auto tmp = offset(convex_hull_no_offset, obj_distance, jtRound, scale_(0.1)); if (!tmp.empty()) { // tmp may be empty due to clipper's bug, see STUDIO-2452 @@ -683,7 +668,9 @@ StringObjectException Print::sequential_print_clearance_valid(const Print &print if (!intersection(exclude_polys, convex_hull_no_offset).empty()) { if (single_object_exception.string.empty()) { single_object_exception.string = (boost::format(L("%1% is too close to exclusion area, there may be collisions when printing.")) %instance.model_instance->get_object()->name).str(); - single_object_exception.object = instance.model_instance->get_object(); + // single_object_exception.object = instance.model_instance->get_object(); + //ORCA: Pass ModelInstance instead of ModelObject + single_object_exception.object = instance.model_instance; } else { single_object_exception.string += "\n"+(boost::format(L("%1% is too close to exclusion area, there may be collisions when printing.")) %instance.model_instance->get_object()->name).str(); @@ -700,12 +687,16 @@ StringObjectException Print::sequential_print_clearance_valid(const Print &print bool has_exception = false; if (single_object_exception.string.empty()) { single_object_exception.string = (boost::format(L("%1% is too close to others, and collisions may be caused.")) %instance.model_instance->get_object()->name).str(); - single_object_exception.object = instance.model_instance->get_object(); + // single_object_exception.object = instance.model_instance->get_object(); + //ORCA: Pass ModelInstance instead of ModelObject for better selection + single_object_exception.object = instance.model_instance; has_exception = true; } else { single_object_exception.string += "\n"+(boost::format(L("%1% is too close to others, and collisions may be caused.")) %instance.model_instance->get_object()->name).str(); - single_object_exception.object = nullptr; + // single_object_exception.object = nullptr; + // ORCA: Keep the first object so jump works + // has_exception = true; has_exception = true; } @@ -952,33 +943,52 @@ static StringObjectException layered_print_cleareance_valid(const Print &print, wrapping_poly.points.emplace_back(scale_(pt.x() + print_origin.x()), scale_(pt.y() + print_origin.y())); } - std::map map_model_volume_to_convex_hull; Polygons convex_hulls_other; + // Orca: check convex hull intersection for each instance individually for (auto& inst : print_instances_ordered) { + Polygons current_instance_hulls; for (const ModelVolume *v : inst->print_object->model_object()->volumes) { if (!v->is_model_part()) continue; - auto it_convex_hull = map_model_volume_to_convex_hull.find(v); - if (it_convex_hull == map_model_volume_to_convex_hull.end()) { - auto volume_hull = v->get_convex_hull_2d(Geometry::assemble_transform(Vec3d::Zero(), inst->model_instance->get_rotation(), - inst->model_instance->get_scaling_factor(), inst->model_instance->get_mirror())); - volume_hull.translate(inst->shift - inst->print_object->center_offset()); + + auto volume_hull = v->get_convex_hull_2d(Geometry::assemble_transform(Vec3d::Zero(), inst->model_instance->get_rotation(), + inst->model_instance->get_scaling_factor(), inst->model_instance->get_mirror())); + volume_hull.translate(inst->shift - inst->print_object->center_offset()); - it_convex_hull = map_model_volume_to_convex_hull.emplace_hint(it_convex_hull, v, volume_hull); - } - Polygon &convex_hull = it_convex_hull->second; - Polygons convex_hulls_temp; - convex_hulls_temp.push_back(convex_hull); - if (!intersection(exclude_polys, convex_hull).empty()) { + if (!intersection(exclude_polys, volume_hull).empty()) { + // return {inst->model_instance->get_object()->name + L(" is too close to exclusion area, there may be collisions when printing.") + "\n", + // inst->model_instance->get_object()}; + //ORCA: Pass ModelInstance instead of ModelObject return {inst->model_instance->get_object()->name + L(" is too close to exclusion area, there may be collisions when printing.") + "\n", - inst->model_instance->get_object()}; + inst->model_instance}; } - if (print_config.enable_wrapping_detection.value && !intersection(wrapping_poly, convex_hull).empty()) { + if (print_config.enable_wrapping_detection.value && !intersection(wrapping_poly, volume_hull).empty()) { + // return {inst->model_instance->get_object()->name + L(" is too close to clumping detection area, there may be collisions when printing.") + "\n", + // inst->model_instance->get_object()}; + //ORCA: Pass ModelInstance instead of ModelObject return {inst->model_instance->get_object()->name + L(" is too close to clumping detection area, there may be collisions when printing.") + "\n", - inst->model_instance->get_object()}; + inst->model_instance}; } - convex_hulls_other.emplace_back(convex_hull); + current_instance_hulls.emplace_back(volume_hull); } + + if (!intersection(convex_hulls_other, current_instance_hulls).empty()) { + if (warning) { + if (warning->string.empty()) { + warning->string = (boost::format(L("%1% is too close to others, and collisions may be caused.")) % inst->model_instance->get_object()->name).str(); + // warning->object = inst->model_instance->get_object(); + //ORCA: Pass ModelInstance instead of ModelObject for better selection + warning->object = inst->model_instance; + } else { + warning->string += "\n" + (boost::format(L("%1% is too close to others, and collisions may be caused.")) % inst->model_instance->get_object()->name).str(); + // ORCA: Keep the first object so jump works + if (!warning->object) warning->object = inst->model_instance; + } + warning->is_warning = true; + warning->type = STRING_EXCEPT_OBJECT_COLLISION_IN_LAYER_PRINT; + } + } + append(convex_hulls_other, current_instance_hulls); } //BBS: add the wipe tower check logic @@ -4898,6 +4908,25 @@ ExtrusionLayers FakeWipeTower::getTrueExtrusionLayersFromWipeTower() const { ExtrusionLayers wtels; wtels.type = ExtrusionLayersType::WIPE_TOWER; + + //ORCA: Fallback for WipeTower2 if outer_wall is empty + if (outer_wall.empty()) { + auto fake_paths = getFakeExtrusionPathsFromWipeTower2(); + float current_z = 0.f; + for (auto& layer_paths : fake_paths) { + if (layer_paths.empty()) continue; + ExtrusionLayer el; + float lh = layer_paths.front().height; + el.height = lh; + el.bottom_z = current_z; + el.layer = nullptr; + el.paths = std::move(layer_paths); + wtels.push_back(std::move(el)); + current_z += lh; + } + return wtels; + } + std::vector layer_heights; layer_heights.reserve(outer_wall.size()); auto pre = outer_wall.begin(); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 642ec7f36f..eae70f788a 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -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(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(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{conflictObj} : std::vector{}); + if (state) { + if (conflictInst) { + notification_manager.push_slicing_serious_warning_notification(text, std::vector{conflictInst}); + } else { + notification_manager.push_slicing_serious_warning_notification(text, conflictObj ? std::vector{conflictObj} : std::vector{}); + } + } else notification_manager.close_slicing_serious_warning_notification(text); break; diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index 620e5fa8ef..f3dcd37d55 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -1840,14 +1840,18 @@ void NotificationManager::push_validate_error_notification(StringObjectException { auto po = dynamic_cast(error.object); auto mo = po ? po->model_object() : dynamic_cast(error.object); + //ORCA: Update to handle ModelInstance selection for validation errors with fallback + /* std::function 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(error.object); + std::function 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; iinstances.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 objs) +void NotificationManager::push_slicing_serious_warning_notification(const std::string &text, std::vector objs) { std::vector 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 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 insts) +{ + std::vector> ids; + for (auto iptr : insts) { + if (iptr && iptr->get_object()) { + ids.push_back({iptr->get_object()->id(), iptr->id()}); + } + } + std::function callback; + if (!insts.empty()) { + callback = [ids](wxEvtHandler*) { + auto& objects = wxGetApp().model().objects; + wxDataViewItemArray sel_items; + std::vector 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; iinstances.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 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 += "] "; } diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index 4d31861390..44263451ba 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -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 objs); + void push_slicing_serious_warning_notification(const std::string &text, std::vector 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 objs); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 4f6406d9b4..449aff77e9 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -7773,22 +7773,72 @@ void Plater::priv::process_validation_warning(StringObjectException const &warni std::string text = warning.string; auto po = dynamic_cast(warning.object); auto mo = po ? po->model_object() : dynamic_cast(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(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; iinstances.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(); - 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