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:
@@ -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<const FakeWipeTower *> 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<LineWithIDs> 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<const PrintObject *>(ptr2);
|
||||
return std::make_optional<ConflictResult>("WipeTower", obj2->model_object()->name, conflictPrintZ, nullptr, ptr2);
|
||||
// ptr1 is now wipe tower, ptr2 is PrintInstance*
|
||||
const PrintInstance *inst2 = reinterpret_cast<const PrintInstance *>(ptr2);
|
||||
const PrintObject *obj2 = inst2->print_object;
|
||||
return std::make_optional<ConflictResult>("WipeTower", obj2->model_object()->name, conflictPrintZ, nullptr, inst2);
|
||||
}
|
||||
}
|
||||
const PrintObject *obj1 = reinterpret_cast<const PrintObject *>(ptr1);
|
||||
const PrintObject *obj2 = reinterpret_cast<const PrintObject *>(ptr2);
|
||||
return std::make_optional<ConflictResult>(obj1->model_object()->name, obj2->model_object()->name, conflictPrintZ, ptr1, ptr2);
|
||||
|
||||
const PrintInstance *inst1 = reinterpret_cast<const PrintInstance *>(ptr1);
|
||||
const PrintInstance *inst2 = reinterpret_cast<const PrintInstance *>(ptr2);
|
||||
const PrintObject *obj1 = inst1->print_object;
|
||||
const PrintObject *obj2 = inst2->print_object;
|
||||
return std::make_optional<ConflictResult>(obj1->model_object()->name, obj2->model_object()->name, conflictPrintZ, inst1, inst2);
|
||||
} else
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -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<ObjectID, Polygon> 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<const ModelVolume*, Polygon> 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<float> layer_heights;
|
||||
layer_heights.reserve(outer_wall.size());
|
||||
auto pre = outer_wall.begin();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 += "] ";
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user