diff --git a/src/libslic3r/CutUtils.cpp b/src/libslic3r/CutUtils.cpp index 8611ec302de..ba1643724f4 100644 --- a/src/libslic3r/CutUtils.cpp +++ b/src/libslic3r/CutUtils.cpp @@ -3,6 +3,7 @@ #include "Geometry.hpp" #include "libslic3r.h" #include "Model.hpp" +#include "ModelTextureDataRemap.hpp" #include "TriangleMeshSlicer.hpp" #include "TriangleSelector.hpp" #include "ObjectID.hpp" @@ -45,10 +46,55 @@ static void apply_tolerance(ModelVolume* vol) vol->set_offset(vol->get_offset() + rot_norm * z_offset); } -static void add_cut_volume(TriangleMesh& mesh, ModelObject* object, const ModelVolume* src_volume, const Transform3d& cut_matrix, const std::string& suffix = {}, ModelVolumeType type = ModelVolumeType::MODEL_PART) +static void apply_cut_texture_data(ModelVolume *volume, const SimplifyTextureDataSnapshot *source_snapshot, const Transform3d &cut_matrix) +{ + if (volume == nullptr || source_snapshot == nullptr || source_snapshot->source == SimplifyColorSource::None || + volume->mesh().empty()) + return; + + SimplifyTextureDataSnapshot snapshot = *source_snapshot; + transform_simplify_texture_data_snapshot(snapshot, cut_matrix); + transform_simplify_texture_data_snapshot(snapshot, translation_transform(-volume->get_offset())); + + SimplifyTextureDataResult result = remap_simplify_texture_data(snapshot, volume->mesh().its); + apply_simplify_texture_data_result(*volume, std::move(result)); +} + +static bool volume_has_remappable_color_data(const ModelVolume &volume) +{ + const indexed_triangle_set &its = volume.mesh().its; + if (its.vertices.empty() || its.indices.empty()) + return false; + + if (!volume.texture_mapping_color_facets.empty()) + return true; + + const bool has_valid_uvs = volume.imported_texture_uv_valid.size() == its.indices.size() && + volume.imported_texture_uvs_per_face.size() >= its.indices.size() * 6; + const bool has_rgba_texture = volume.imported_texture_width > 0 && volume.imported_texture_height > 0 && + volume.imported_texture_rgba.size() >= + size_t(volume.imported_texture_width) * size_t(volume.imported_texture_height) * 4; + const bool has_raw_texture = volume.imported_texture_width > 0 && volume.imported_texture_height > 0 && + volume.imported_texture_raw_channels > 0 && + volume.imported_texture_raw_filament_offsets.size() >= + size_t(volume.imported_texture_width) * size_t(volume.imported_texture_height) * + size_t(volume.imported_texture_raw_channels); + if (has_valid_uvs && (has_rgba_texture || has_raw_texture)) + return true; + + return volume.imported_vertex_colors_rgba.size() == its.vertices.size(); +} + +static ModelVolume* add_cut_volume(TriangleMesh &mesh, + ModelObject *object, + const ModelVolume *src_volume, + const Transform3d &cut_matrix, + const std::string &suffix = {}, + ModelVolumeType type = ModelVolumeType::MODEL_PART, + const SimplifyTextureDataSnapshot *source_snapshot = nullptr) { if (mesh.empty()) - return; + return nullptr; mesh.transform(cut_matrix); ModelVolume* vol = object->add_volume(mesh); @@ -61,20 +107,31 @@ static void add_cut_volume(TriangleMesh& mesh, ModelObject* object, const ModelV assert(vol->config.id() != src_volume->config.id()); vol->set_material(src_volume->material_id(), *src_volume->material()); vol->cut_info = src_volume->cut_info; + apply_cut_texture_data(vol, source_snapshot, cut_matrix); + return vol; } static void process_volume_cut( ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix, - ModelObjectCutAttributes attributes, TriangleMesh& upper_mesh, TriangleMesh& lower_mesh) + ModelObjectCutAttributes attributes, TriangleMesh& upper_mesh, TriangleMesh& lower_mesh, + SimplifyTextureDataSnapshot *source_snapshot = nullptr, + const SimplifyTextureDataSnapshot *volume_snapshot = nullptr) { const auto volume_matrix = volume->get_matrix(); const Transformation cut_transformation = Transformation(cut_matrix); - const Transform3d invert_cut_matrix = cut_transformation.get_rotation_matrix().inverse() * translation_transform(-1 * cut_transformation.get_offset()); + const Transform3d invert_cut_matrix = + cut_transformation.get_rotation_matrix().inverse() * translation_transform(-1 * cut_transformation.get_offset()); + const Transform3d source_to_cut_matrix = invert_cut_matrix * instance_matrix * volume_matrix; + + if (source_snapshot != nullptr) { + *source_snapshot = volume_snapshot != nullptr ? *volume_snapshot : snapshot_simplify_texture_data(*volume); + transform_simplify_texture_data_snapshot(*source_snapshot, source_to_cut_matrix); + } // Transform the mesh by the combined transformation matrix. // Flip the triangles in case the composite transformation is left handed. TriangleMesh mesh(volume->mesh()); - mesh.transform(invert_cut_matrix * instance_matrix * volume_matrix, true); + mesh.transform(source_to_cut_matrix, true); indexed_triangle_set upper_its, lower_its; cut_mesh(mesh.its, 0.0f, &upper_its, &lower_its); @@ -86,7 +143,8 @@ static void process_volume_cut( ModelVolume* volume, const Transform3d& instance static void process_connector_cut( ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix, ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower, - std::vector& dowels) + std::vector& dowels, + const SimplifyTextureDataSnapshot *volume_snapshot = nullptr) { assert(volume->cut_info.is_connector); volume->cut_info.set_processed(); @@ -143,15 +201,16 @@ static void process_connector_cut( ModelVolume* volume, const Transform3d& inst // Perform cut TriangleMesh upper_mesh, lower_mesh; - process_volume_cut(volume, Transform3d::Identity(), cut_matrix, attributes, upper_mesh, lower_mesh); + SimplifyTextureDataSnapshot source_snapshot; + process_volume_cut(volume, Transform3d::Identity(), cut_matrix, attributes, upper_mesh, lower_mesh, &source_snapshot, volume_snapshot); // add small Z offset to better preview upper_mesh.translate((-0.05 * Vec3d::UnitZ()).cast()); lower_mesh.translate((0.05 * Vec3d::UnitZ()).cast()); // Add cut parts to the related objects - add_cut_volume(upper_mesh, upper, volume, cut_matrix, "_A", volume->type()); - add_cut_volume(lower_mesh, lower, volume, cut_matrix, "_B", volume->type()); + add_cut_volume(upper_mesh, upper, volume, cut_matrix, "_A", volume->type(), &source_snapshot); + add_cut_volume(lower_mesh, lower, volume, cut_matrix, "_B", volume->type(), &source_snapshot); } } @@ -179,28 +238,30 @@ static void process_modifier_cut(ModelVolume* volume, const Transform3d& instanc } static void process_solid_part_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix, - ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower) + ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower, + const SimplifyTextureDataSnapshot *volume_snapshot = nullptr) { // Perform cut TriangleMesh upper_mesh, lower_mesh; - process_volume_cut(volume, instance_matrix, cut_matrix, attributes, upper_mesh, lower_mesh); + SimplifyTextureDataSnapshot source_snapshot; + process_volume_cut(volume, instance_matrix, cut_matrix, attributes, upper_mesh, lower_mesh, &source_snapshot, volume_snapshot); // Add required cut parts to the objects if (attributes.has(ModelObjectCutAttribute::KeepAsParts)) { - add_cut_volume(upper_mesh, upper, volume, cut_matrix, "_A"); + add_cut_volume(upper_mesh, upper, volume, cut_matrix, "_A", ModelVolumeType::MODEL_PART, &source_snapshot); if (!lower_mesh.empty()) { - add_cut_volume(lower_mesh, upper, volume, cut_matrix, "_B"); + add_cut_volume(lower_mesh, upper, volume, cut_matrix, "_B", ModelVolumeType::MODEL_PART, &source_snapshot); upper->volumes.back()->cut_info.is_from_upper = false; } return; } if (attributes.has(ModelObjectCutAttribute::KeepUpper)) - add_cut_volume(upper_mesh, upper, volume, cut_matrix); + add_cut_volume(upper_mesh, upper, volume, cut_matrix, {}, ModelVolumeType::MODEL_PART, &source_snapshot); if (attributes.has(ModelObjectCutAttribute::KeepLower) && !lower_mesh.empty()) - add_cut_volume(lower_mesh, lower, volume, cut_matrix); + add_cut_volume(lower_mesh, lower, volume, cut_matrix, {}, ModelVolumeType::MODEL_PART, &source_snapshot); } static void reset_instance_transformation(ModelObject* object, size_t src_instance_idx, @@ -321,16 +382,17 @@ const ModelObjectPtrs& Cut::perform_with_plane() const Transform3d inverse_cut_matrix = cut_transformation.get_rotation_matrix().inverse() * translation_transform(-1. * cut_transformation.get_offset()); for (ModelVolume* volume : mo->volumes) { + SimplifyTextureDataSnapshot volume_snapshot = snapshot_simplify_texture_data(*volume); volume->reset_extra_facets(); if (!volume->is_model_part()) { if (volume->cut_info.is_processed) process_modifier_cut(volume, instance_matrix, inverse_cut_matrix, m_attributes, upper, lower); else - process_connector_cut(volume, instance_matrix, m_cut_matrix, m_attributes, upper, lower, dowels); + process_connector_cut(volume, instance_matrix, m_cut_matrix, m_attributes, upper, lower, dowels, &volume_snapshot); } else if (!volume->mesh().empty()) - process_solid_part_cut(volume, instance_matrix, m_cut_matrix, m_attributes, upper, lower); + process_solid_part_cut(volume, instance_matrix, m_cut_matrix, m_attributes, upper, lower, &volume_snapshot); } // Post-process cut parts @@ -406,6 +468,18 @@ static void distribute_modifiers_from_object(ModelObject* from_obj, const int in static void merge_solid_parts_inside_object(ModelObjectPtrs& objects) { for (ModelObject* mo : objects) { + bool has_remappable_color_data = false; + for (const ModelVolume* mv : mo->volumes) { + if (mv->is_model_part() && !mv->is_cut_connector() && volume_has_remappable_color_data(*mv)) { + has_remappable_color_data = true; + break; + } + } + if (has_remappable_color_data) { + mo->sort_volumes(true); + continue; + } + TriangleMesh mesh; // Merge all SolidPart but not Connectors for (const ModelVolume* mv : mo->volumes) { @@ -664,4 +738,3 @@ const ModelObjectPtrs& Cut::perform_with_groove(const Groove& groove, const Tran } } // namespace Slic3r - diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 14d89e147e9..ff4c80a05ac 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -3354,14 +3354,16 @@ size_t ModelVolume::split(unsigned int max_extruders) else this->object->volumes.insert(this->object->volumes.begin() + (++ivolume), new ModelVolume(object, *this, std::move(mesh))); - this->object->volumes[ivolume]->set_offset(Vec3d::Zero()); - this->object->volumes[ivolume]->center_geometry_after_creation(); - this->object->volumes[ivolume]->translate(offset); - this->object->volumes[ivolume]->name = name + "_" + std::to_string(idx + 1); + ModelVolume *split_volume = this->object->volumes[ivolume]; + const Vec3d local_center = split_volume->mesh().bounding_box().center(); + split_volume->set_offset(Vec3d::Zero()); + split_volume->center_geometry_after_creation(); + split_volume->set_offset(offset + split_volume->get_matrix_no_offset() * local_center); + split_volume->name = name + "_" + std::to_string(idx + 1); //BBS: always set the extruder id the same as original - this->object->volumes[ivolume]->config.set("extruder", this->extruder_id()); + split_volume->config.set("extruder", this->extruder_id()); //this->object->volumes[ivolume]->config.set("extruder", auto_extruder_id(max_extruders, extruder_counter)); - this->object->volumes[ivolume]->m_is_splittable = 0; + split_volume->m_is_splittable = 0; ++ idx; } diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 81763a25319..33d520207e5 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -1042,13 +1042,41 @@ public: // The triangular model. const TriangleMesh& mesh() const { return *m_mesh.get(); } std::shared_ptr mesh_ptr() const { return m_mesh; } - void set_mesh(const TriangleMesh &mesh) { m_mesh = std::make_shared(mesh); } - void set_mesh(TriangleMesh &&mesh) { m_mesh = std::make_shared(std::move(mesh)); } - void set_mesh(const indexed_triangle_set &mesh) { m_mesh = std::make_shared(mesh); } - void set_mesh(indexed_triangle_set &&mesh) { m_mesh = std::make_shared(std::move(mesh)); } - void set_mesh(std::shared_ptr &mesh) { m_mesh = mesh; } - void set_mesh(std::unique_ptr &&mesh) { m_mesh = std::move(mesh); } - void reset_mesh() { m_mesh = std::make_shared(); } + void set_mesh(const TriangleMesh &mesh) + { + m_mesh = std::make_shared(mesh); + m_is_splittable = -1; + } + void set_mesh(TriangleMesh &&mesh) + { + m_mesh = std::make_shared(std::move(mesh)); + m_is_splittable = -1; + } + void set_mesh(const indexed_triangle_set &mesh) + { + m_mesh = std::make_shared(mesh); + m_is_splittable = -1; + } + void set_mesh(indexed_triangle_set &&mesh) + { + m_mesh = std::make_shared(std::move(mesh)); + m_is_splittable = -1; + } + void set_mesh(std::shared_ptr &mesh) + { + m_mesh = mesh; + m_is_splittable = -1; + } + void set_mesh(std::unique_ptr &&mesh) + { + m_mesh = std::move(mesh); + m_is_splittable = -1; + } + void reset_mesh() + { + m_mesh = std::make_shared(); + m_is_splittable = -1; + } const std::shared_ptr& get_mesh_shared_ptr() const { return m_mesh; } // Configuration parameters specific to an object model geometry or a modifier volume, // overriding the global Slic3r settings and the ModelObject settings. diff --git a/src/libslic3r/ModelTextureDataRemap.cpp b/src/libslic3r/ModelTextureDataRemap.cpp index 1d141c46909..c7ca766057a 100644 --- a/src/libslic3r/ModelTextureDataRemap.cpp +++ b/src/libslic3r/ModelTextureDataRemap.cpp @@ -1247,6 +1247,24 @@ SimplifyTextureDataSnapshot snapshot_simplify_texture_data(const ModelVolume &vo return snapshot; } +void transform_simplify_texture_data_snapshot(SimplifyTextureDataSnapshot &snapshot, const Transform3d &transform) +{ + if (snapshot.source == SimplifyColorSource::None) + return; + + if (!snapshot.source_mesh.vertices.empty() && !snapshot.source_mesh.indices.empty()) { + TriangleMesh mesh(std::move(snapshot.source_mesh)); + mesh.transform(transform, false); + snapshot.source_mesh = std::move(mesh.its); + } + + if (!snapshot.rgba_facets.empty()) { + for (ColorFacetTriangle &facet : snapshot.rgba_facets) + for (Vec3f &vertex : facet.vertices) + vertex = (transform * vertex.cast()).cast(); + } +} + SimplifyTextureDataResult remap_simplify_texture_data(const SimplifyTextureDataSnapshot &snapshot, const indexed_triangle_set &simplified_mesh, const SimplifyTextureCancelFn &throw_on_cancel, diff --git a/src/libslic3r/ModelTextureDataRemap.hpp b/src/libslic3r/ModelTextureDataRemap.hpp index e1a9e55bce1..c489af82114 100644 --- a/src/libslic3r/ModelTextureDataRemap.hpp +++ b/src/libslic3r/ModelTextureDataRemap.hpp @@ -63,6 +63,8 @@ using SimplifyTextureProgressFn = std::function; SimplifyTextureDataSnapshot snapshot_simplify_texture_data(const ModelVolume &volume); +void transform_simplify_texture_data_snapshot(SimplifyTextureDataSnapshot &snapshot, const Transform3d &transform); + SimplifyTextureDataResult remap_simplify_texture_data(const SimplifyTextureDataSnapshot &snapshot, const indexed_triangle_set &simplified_mesh, const SimplifyTextureCancelFn &throw_on_cancel = {}, diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 3daa1c6fe76..af55391c427 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -6019,6 +6019,18 @@ void ObjectList::fix_through_cgal() std::vector succes_models; // model_name failing reason std::vector> failed_models; + ModelRepairColorRemapStats color_remap_stats; + + auto merge_color_remap_stats = [&color_remap_stats](const ModelRepairColorRemapStats &stats) { + color_remap_stats.had_color_data |= stats.had_color_data; + color_remap_stats.remap_requested |= stats.remap_requested; + color_remap_stats.remap_skipped |= stats.remap_skipped; + color_remap_stats.remap_canceled |= stats.remap_canceled; + color_remap_stats.remap_failed |= stats.remap_failed; + color_remap_stats.used_fallback_rgba |= stats.used_fallback_rgba; + color_remap_stats.volumes_remapped += stats.volumes_remapped; + color_remap_stats.volumes_cleared += stats.volumes_cleared; + }; std::vector obj_idxs, vol_idxs; get_selection_indexes(obj_idxs, vol_idxs); @@ -6052,7 +6064,7 @@ void ObjectList::fix_through_cgal() auto plater = wxGetApp().plater(); - auto fix_and_update_progress = [this, plater, model_names](const int obj_idx, const int vol_idx, + auto fix_and_update_progress = [this, plater, model_names, &merge_color_remap_stats](const int obj_idx, const int vol_idx, int model_idx, ProgressDialog& progress_dlg, std::vector& succes_models, @@ -6075,7 +6087,10 @@ void ObjectList::fix_through_cgal() plater->clear_before_change_mesh(obj_idx); const size_t volumes_before = object(obj_idx)->volumes.size(); std::string res; - if (!fix_model_with_cgal_gui(*(object(obj_idx)), vol_idx, progress_dlg, msg, res)) + ModelRepairColorRemapStats remap_stats; + const bool repair_finished = fix_model_with_cgal_gui(*(object(obj_idx)), vol_idx, progress_dlg, msg, res, &remap_stats); + merge_color_remap_stats(remap_stats); + if (!repair_finished) return false; //wxGetApp().plater()->changed_mesh(obj_idx); object(obj_idx)->ensure_on_bed(); @@ -6096,7 +6111,7 @@ void ObjectList::fix_through_cgal() update_item_error_icon(obj_idx, vol_idx); update_info_items(obj_idx); - return true; + return !remap_stats.remap_canceled; }; Plater::TakeSnapshot snapshot(plater, "Repairing model object"); @@ -6142,6 +6157,14 @@ void ObjectList::fix_through_cgal() for (auto& model : failed_models) msg += bullet_suf + from_u8(model.first) + ": " + _(model.second); } + if (color_remap_stats.used_fallback_rgba) + msg += "\n\n" + _L("Some image textures could not be regenerated after repair; texture colors were preserved as RGBA data."); + if (color_remap_stats.remap_failed && color_remap_stats.volumes_cleared > 0) + msg += "\n\n" + _L("Some color data could not be remapped after repair and was cleared."); + if (color_remap_stats.remap_skipped && color_remap_stats.volumes_cleared > 0) + msg += "\n\n" + _L("Color remapping was skipped; stale color data was cleared from repaired parts."); + if (color_remap_stats.remap_canceled && color_remap_stats.volumes_cleared > 0) + msg += "\n\n" + _L("Color remapping was canceled; stale color data was cleared from repaired parts."); if (msg.IsEmpty()) msg = _L("Repairing was canceled"); plater->get_notification_manager()->push_notification(NotificationType::CgalFinished, NotificationManager::NotificationLevel::PrintInfoShortNotificationLevel, into_u8(msg)); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.cpp index bd6bf6c67ee..5243dd4fe8a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.cpp @@ -12,9 +12,11 @@ #include #include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/NotificationManager.hpp" #include "slic3r/GUI/Plater.hpp" #include "libslic3r/AppConfig.hpp" #include "../GUI/MsgDialog.hpp" +#include "FixModelByCgal.hpp" #include @@ -662,6 +664,7 @@ void GLGizmoAdvancedCut::perform_cut(const Selection& selection) { bool is_showed_dialog = false; bool user_fix_model = false; + ModelRepairPromptState repair_prompt_state; for (size_t i = 0; i < new_objects.size(); i++) { for (size_t j = 0; j < new_objects[i]->volumes.size(); j++) { if (its_num_open_edges(new_objects[i]->volumes[j]->mesh().its) > 0) { @@ -681,13 +684,38 @@ void GLGizmoAdvancedCut::perform_cut(const Selection& selection) // model_name failing reason std::vector> failed_models; auto plater = wxGetApp().plater(); - auto fix_and_update_progress = [this, plater](ModelObject *model_object, const int vol_idx, const string &model_name, ProgressDialog &progress_dlg, - std::vector &succes_models, std::vector> &failed_models) { + auto fix_and_update_progress = + [this, plater, &repair_prompt_state](ModelObject *model_object, const int vol_idx, const string &model_name, + ProgressDialog &progress_dlg, std::vector &succes_models, + std::vector> &failed_models) { wxString msg = _L("Repairing model object"); msg += ": " + from_u8(model_name) + "\n"; std::string res; - if (!fix_model_with_cgal_gui(*model_object, vol_idx, progress_dlg, msg, res)) return false; - return true; + ModelRepairColorRemapStats remap_stats; + if (!fix_model_with_cgal_gui( + *model_object, vol_idx, progress_dlg, msg, res, &remap_stats, &repair_prompt_state)) + return false; + if (remap_stats.used_fallback_rgba) + plater->get_notification_manager()->push_notification( + NotificationType::CustomNotification, + NotificationManager::NotificationLevel::PrintInfoNotificationLevel, + _u8L("Some image textures could not be regenerated after repair; texture colors were preserved as RGBA data.")); + if (remap_stats.remap_failed && remap_stats.volumes_cleared > 0) + plater->get_notification_manager()->push_notification( + NotificationType::CustomNotification, + NotificationManager::NotificationLevel::PrintInfoNotificationLevel, + _u8L("Some color data could not be remapped after repair and was cleared.")); + if (remap_stats.remap_skipped && remap_stats.volumes_cleared > 0) + plater->get_notification_manager()->push_notification( + NotificationType::CustomNotification, + NotificationManager::NotificationLevel::PrintInfoNotificationLevel, + _u8L("Color remapping was skipped; stale color data was cleared from repaired parts.")); + if (remap_stats.remap_canceled && remap_stats.volumes_cleared > 0) + plater->get_notification_manager()->push_notification( + NotificationType::CustomNotification, + NotificationManager::NotificationLevel::PrintInfoNotificationLevel, + _u8L("Color remapping was canceled; stale color data was cleared from repaired parts.")); + return !remap_stats.remap_canceled; }; ProgressDialog progress_dlg(_L("Repairing model object"), "", 100, find_toplevel_parent(plater), wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT, true); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 2483f7bd1b0..964316b250d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -6,6 +6,7 @@ #include #include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/NotificationManager.hpp" #include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/Gizmos/GizmoObjectManipulation.hpp" #include "slic3r/GUI/format.hpp" @@ -3343,6 +3344,7 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) { bool is_showed_dialog = false; bool user_fix_model = false; + ModelRepairPromptState repair_prompt_state; for (size_t i = 0; i < new_objects.size(); i++) { for (size_t j = 0; j < new_objects[i]->volumes.size(); j++) { if (its_num_open_edges(new_objects[i]->volumes[j]->mesh().its) > 0) { @@ -3362,13 +3364,38 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) // model_name failing reason std::vector> failed_models; auto plater = wxGetApp().plater(); - auto fix_and_update_progress = [this, plater](ModelObject *model_object, const int vol_idx, const string &model_name, ProgressDialog &progress_dlg, - std::vector &succes_models, std::vector> &failed_models) { + auto fix_and_update_progress = + [this, plater, &repair_prompt_state](ModelObject *model_object, const int vol_idx, const string &model_name, + ProgressDialog &progress_dlg, std::vector &succes_models, + std::vector> &failed_models) { wxString msg = _L("Repairing model object"); msg += ": " + from_u8(model_name) + "\n"; std::string res; - if (!fix_model_with_cgal_gui(*model_object, vol_idx, progress_dlg, msg, res)) return false; - return true; + ModelRepairColorRemapStats remap_stats; + if (!fix_model_with_cgal_gui( + *model_object, vol_idx, progress_dlg, msg, res, &remap_stats, &repair_prompt_state)) + return false; + if (remap_stats.used_fallback_rgba) + plater->get_notification_manager()->push_notification( + NotificationType::CustomNotification, + NotificationManager::NotificationLevel::PrintInfoNotificationLevel, + _u8L("Some image textures could not be regenerated after repair; texture colors were preserved as RGBA data.")); + if (remap_stats.remap_failed && remap_stats.volumes_cleared > 0) + plater->get_notification_manager()->push_notification( + NotificationType::CustomNotification, + NotificationManager::NotificationLevel::PrintInfoNotificationLevel, + _u8L("Some color data could not be remapped after repair and was cleared.")); + if (remap_stats.remap_skipped && remap_stats.volumes_cleared > 0) + plater->get_notification_manager()->push_notification( + NotificationType::CustomNotification, + NotificationManager::NotificationLevel::PrintInfoNotificationLevel, + _u8L("Color remapping was skipped; stale color data was cleared from repaired parts.")); + if (remap_stats.remap_canceled && remap_stats.volumes_cleared > 0) + plater->get_notification_manager()->push_notification( + NotificationType::CustomNotification, + NotificationManager::NotificationLevel::PrintInfoNotificationLevel, + _u8L("Color remapping was canceled; stale color data was cleared from repaired parts.")); + return !remap_stats.remap_canceled; }; ProgressDialog progress_dlg(_L("Repairing model object"), "", 100, find_toplevel_parent(plater), wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT, true); diff --git a/src/slic3r/Utils/FixModelByCgal.cpp b/src/slic3r/Utils/FixModelByCgal.cpp index c014ceaf047..effa22bf956 100644 --- a/src/slic3r/Utils/FixModelByCgal.cpp +++ b/src/slic3r/Utils/FixModelByCgal.cpp @@ -1,20 +1,32 @@ #include "FixModelByCgal.hpp" +#include #include #include #include #include +#include +#include +#include #include #include #include #include +#include +#include #include #include "libslic3r/MeshBoolean.hpp" #include "libslic3r/Model.hpp" +#include "libslic3r/ModelTextureDataRemap.hpp" #include "libslic3r/format.hpp" #include "../GUI/I18N.hpp" +#include +#include +#include +#include + // Orca: This file provides utilities for repairing 3D model meshes using the CGAL library, handling mesh splitting, merging, and boolean operations. namespace Slic3r { @@ -56,6 +68,276 @@ bool is_not_3dimensional_part(const TriangleMesh &mesh) return false; } +enum class RepairProgressStage +{ + Repair, + Remap, + Status +}; + +enum class ColorRemapPartState +{ + Unchanged, + Pending, + Preserved, + Cleared +}; + +class ColorRemapCanceledException : public std::exception { +public: + const char* what() const noexcept override { return "Color remap has been canceled"; } +}; + +using ColorSnapshotByVolume = std::unordered_map; + +bool color_snapshot_is_valid(const SimplifyTextureDataSnapshot &snapshot) +{ + return snapshot.source != SimplifyColorSource::None; +} + +bool volume_may_change_during_repair(const ModelVolume &volume, const ModelRepairOptions &options) +{ + return options.weld_same_position_vertices || + (options.split_before_repair && volume.is_splittable()) || + its_num_open_edges(volume.mesh().its) != 0; +} + +bool volume_has_remappable_color_data(const ModelVolume &volume) +{ + const indexed_triangle_set &its = volume.mesh().its; + if (its.vertices.empty() || its.indices.empty()) + return false; + + if (!volume.texture_mapping_color_facets.empty()) + return true; + + const bool has_valid_uvs = volume.imported_texture_uv_valid.size() == its.indices.size() && + volume.imported_texture_uvs_per_face.size() >= its.indices.size() * 6 && + std::any_of(volume.imported_texture_uv_valid.begin(), + volume.imported_texture_uv_valid.end(), + [](uint8_t valid) { return valid != 0; }); + const bool has_rgba_texture = volume.imported_texture_width > 0 && volume.imported_texture_height > 0 && + volume.imported_texture_rgba.size() >= + size_t(volume.imported_texture_width) * size_t(volume.imported_texture_height) * 4; + const bool has_raw_texture = volume.imported_texture_width > 0 && volume.imported_texture_height > 0 && + volume.imported_texture_raw_channels > 0 && + volume.imported_texture_raw_filament_offsets.size() >= + size_t(volume.imported_texture_width) * size_t(volume.imported_texture_height) * + size_t(volume.imported_texture_raw_channels); + if (has_valid_uvs && (has_rgba_texture || has_raw_texture)) + return true; + + return volume.imported_vertex_colors_rgba.size() == its.vertices.size(); +} + +bool target_has_remappable_color_data(const ModelObject &model_object, int volume_idx) +{ + const size_t start_volume = volume_idx == -1 ? 0 : size_t(volume_idx); + const size_t end_volume = + volume_idx == -1 ? model_object.volumes.size() : std::min(model_object.volumes.size(), size_t(volume_idx) + 1); + for (size_t idx = start_volume; idx < end_volume; ++idx) { + const ModelVolume *volume = model_object.volumes[idx]; + if (volume != nullptr && volume_has_remappable_color_data(*volume)) + return true; + } + return false; +} + +ColorSnapshotByVolume collect_color_snapshots(ModelObject &model_object, int volume_idx, const ModelRepairOptions &options) +{ + ColorSnapshotByVolume snapshots; + const size_t start_volume = volume_idx == -1 ? 0 : size_t(volume_idx); + const size_t end_volume = + volume_idx == -1 ? model_object.volumes.size() : std::min(model_object.volumes.size(), size_t(volume_idx) + 1); + for (size_t idx = start_volume; idx < end_volume; ++idx) { + ModelVolume *volume = model_object.volumes[idx]; + if (volume == nullptr || !volume_may_change_during_repair(*volume, options)) + continue; + SimplifyTextureDataSnapshot snapshot = snapshot_simplify_texture_data(*volume); + if (color_snapshot_is_valid(snapshot)) + snapshots.emplace(volume, std::move(snapshot)); + } + return snapshots; +} + +bool ask_repair_options(GUI::ProgressDialog &progress_dialog, + ModelRepairOptions &options, + ModelRepairColorRemapChoice &color_remap_choice, + bool show_color_remap_option) +{ + wxDialog dialog(&progress_dialog, wxID_ANY, _L("Repair options"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER); + auto *top_sizer = new wxBoxSizer(wxVERTICAL); + auto *message = new wxStaticText(&dialog, wxID_ANY, _L("Choose options to use when repairing model:")); + message->Wrap(420); + top_sizer->Add(message, 0, wxEXPAND | wxALL, 12); + + auto *weld_checkbox = new wxCheckBox(&dialog, wxID_ANY, _L("Weld close vertexes")); + weld_checkbox->SetValue(options.weld_same_position_vertices); + top_sizer->Add(weld_checkbox, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 12); + + auto *split_checkbox = new wxCheckBox(&dialog, wxID_ANY, _L("Split parts")); + split_checkbox->SetValue(options.split_before_repair); + top_sizer->Add(split_checkbox, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 12); + + wxCheckBox *remap_colors_checkbox = nullptr; + if (show_color_remap_option) { + remap_colors_checkbox = new wxCheckBox(&dialog, wxID_ANY, _L("Remap colors to repaired mesh")); + remap_colors_checkbox->SetValue(color_remap_choice != ModelRepairColorRemapChoice::Skip); + top_sizer->Add(remap_colors_checkbox, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 12); + } + + wxSizer *buttons = dialog.CreateSeparatedButtonSizer(wxOK | wxCANCEL); + if (buttons != nullptr) + top_sizer->Add(buttons, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 12); + + dialog.SetSizerAndFit(top_sizer); + dialog.Layout(); + + if (dialog.ShowModal() != wxID_OK) + return false; + + options.weld_same_position_vertices = weld_checkbox->GetValue(); + options.split_before_repair = split_checkbox->GetValue(); + if (remap_colors_checkbox != nullptr) + color_remap_choice = remap_colors_checkbox->GetValue() ? ModelRepairColorRemapChoice::Remap : ModelRepairColorRemapChoice::Skip; + return true; +} + +wxString trim_progress_header(wxString header) +{ + while (!header.empty() && (header.Last() == '\n' || header.Last() == '\r')) + header.RemoveLast(); + return header; +} + +wxString compose_progress_message(const wxString &msg_header, RepairProgressStage stage, const std::string &message) +{ + const wxString header = trim_progress_header(msg_header); + const wxString translated_message = _(message); + + if (stage == RepairProgressStage::Repair) { + wxString text = _L("Stage 1 - ") + header; + if (!message.empty() && translated_message != _L("Repairing model object")) + text += "\n" + translated_message; + return text; + } + + if (stage == RepairProgressStage::Remap) + return _L("Stage 2 - Remapping color data"); + + return header + (message.empty() ? wxString() : "\n" + translated_message); +} + +int logarithmic_repair_stage_progress(std::chrono::steady_clock::duration elapsed) +{ + const double seconds = std::chrono::duration(elapsed).count(); + if (seconds <= 0.0) + return 0; + + const double log_elapsed = std::log1p(seconds / 60.0); + const double reference = std::log1p(15.0); + const double ratio = log_elapsed / (reference + log_elapsed * 0.2); + return int(std::floor(std::clamp(50.0 * ratio, 0.0, 49.0))); +} + +void merge_color_remap_stats(ModelRepairColorRemapStats &dst, const ModelRepairColorRemapStats &src) +{ + dst.had_color_data |= src.had_color_data; + dst.remap_requested |= src.remap_requested; + dst.remap_skipped |= src.remap_skipped; + dst.remap_canceled |= src.remap_canceled; + dst.remap_failed |= src.remap_failed; + dst.used_fallback_rgba |= src.used_fallback_rgba; + dst.volumes_remapped += src.volumes_remapped; + dst.volumes_cleared += src.volumes_cleared; +} + +void clear_remappable_color_data(ModelVolume &volume, ModelRepairColorRemapStats &stats) +{ + apply_simplify_texture_data_result(volume, SimplifyTextureDataResult()); + ++stats.volumes_cleared; +} + +bool result_preserved_color(const SimplifyTextureDataSnapshot &snapshot, const SimplifyTextureDataResult &result) +{ + switch (snapshot.source) { + case SimplifyColorSource::RgbaData: + return result.source == SimplifyColorSource::RgbaData && result.rgba_data != nullptr; + case SimplifyColorSource::ImageTexture: + return result.source == SimplifyColorSource::ImageTexture || + (result.source == SimplifyColorSource::RgbaData && result.rgba_data != nullptr); + case SimplifyColorSource::VertexColors: + return result.source == SimplifyColorSource::VertexColors && !result.vertex_colors_rgba.empty(); + case SimplifyColorSource::None: + break; + } + return false; +} + +bool remap_or_clear_color_data(ModelVolume &volume, + const SimplifyTextureDataSnapshot &snapshot, + bool remap_requested, + std::atomic &cancel_requested, + const std::function &status_fn, + ModelRepairColorRemapStats &stats) +{ + if (!color_snapshot_is_valid(snapshot)) + return false; + + if (!remap_requested) { + stats.remap_skipped = true; + clear_remappable_color_data(volume, stats); + return false; + } + + try { + SimplifyTextureDataResult result = remap_simplify_texture_data( + snapshot, + volume.mesh().its, + [&cancel_requested]() { + if (cancel_requested) + throw ColorRemapCanceledException(); + }, + status_fn); + + stats.used_fallback_rgba |= result.used_fallback_rgba; + stats.remap_failed |= result.remap_failed && !result.used_fallback_rgba; + const bool preserved = result_preserved_color(snapshot, result); + apply_simplify_texture_data_result(volume, std::move(result)); + if (preserved) { + ++stats.volumes_remapped; + return true; + } else { + stats.remap_failed = true; + ++stats.volumes_cleared; + } + } catch (ColorRemapCanceledException &) { + stats.remap_canceled = true; + throw; + } catch (std::exception &) { + stats.remap_failed = true; + clear_remappable_color_data(volume, stats); + } + return false; +} + +bool weld_same_position_vertices(TriangleMesh &mesh) +{ + RepairedMeshErrors repaired_errors = mesh.stats().repaired_errors; + const int merged_vertices = its_merge_vertices(mesh.its, false); + if (merged_vertices == 0) + return false; + + const int removed_faces = its_remove_degenerate_faces(mesh.its, false); + its_compactify_vertices(mesh.its, true); + repaired_errors.edges_fixed += merged_vertices; + repaired_errors.degenerate_facets += removed_faces; + + indexed_triangle_set welded_its = std::move(mesh.its); + mesh = TriangleMesh(std::move(welded_its), repaired_errors); + return true; +} + } // namespace // Orca: Exception class for handling user-initiated cancellation of model repair operations. @@ -66,35 +348,105 @@ public: // Orca: Main function to repair model objects using CGAL, with progress dialog and cancellation support. // Returns false if fixing was canceled. fix_result contains error message if failed. -bool fix_model_with_cgal_gui(ModelObject &model_object, int volume_idx, GUI::ProgressDialog &progress_dialog, const wxString &msg_header, std::string &fix_result) +bool fix_model_with_cgal_gui(ModelObject &model_object, + int volume_idx, + GUI::ProgressDialog &progress_dialog, + const wxString &msg_header, + std::string &fix_result, + ModelRepairColorRemapStats *color_remap_stats, + ModelRepairPromptState *prompt_state) { + ModelRepairColorRemapStats local_color_stats; + ModelRepairPromptState local_prompt_state; + ModelRepairPromptState &active_prompt_state = prompt_state != nullptr ? *prompt_state : local_prompt_state; + + if (active_prompt_state.repair_options_canceled) + return false; + + if (!active_prompt_state.repair_options_selected) { + const bool show_color_remap_option = target_has_remappable_color_data(model_object, volume_idx); + if (!ask_repair_options(progress_dialog, + active_prompt_state.repair_options, + active_prompt_state.color_remap_choice, + show_color_remap_option)) { + active_prompt_state.repair_options_canceled = true; + return false; + } + active_prompt_state.repair_options_selected = true; + } + + const ModelRepairOptions repair_options = active_prompt_state.repair_options; + + ColorSnapshotByVolume color_snapshots = collect_color_snapshots(model_object, volume_idx, repair_options); + ModelRepairColorRemapChoice color_remap_choice = active_prompt_state.color_remap_choice; + if (!color_snapshots.empty()) { + local_color_stats.had_color_data = true; + if (color_remap_choice == ModelRepairColorRemapChoice::Cancel) { + if (color_remap_stats) + merge_color_remap_stats(*color_remap_stats, local_color_stats); + return false; + } + local_color_stats.remap_requested = color_remap_choice == ModelRepairColorRemapChoice::Remap; + local_color_stats.remap_skipped = color_remap_choice == ModelRepairColorRemapChoice::Skip; + } + // Orca: Synchronization primitives for progress updates between worker thread and GUI. std::mutex mtx; std::condition_variable condition; struct Progress { - std::string message; - int percent = 0; - bool updated = false; + std::string message; + RepairProgressStage stage = RepairProgressStage::Repair; + int percent = 0; + bool updated = false; } progress; - std::atomic canceled = false; + std::atomic cancel_requested = false; std::atomic finished = false; + std::atomic repair_canceled = false; bool success = false; size_t ivolume = 0; + const bool remap_requested = color_remap_choice == ModelRepairColorRemapChoice::Remap; // Orca: Lambda for updating progress from worker thread. - auto on_progress = [&mtx, &condition, &ivolume, &model_object, &progress](const char *msg, unsigned prcnt) { + auto on_progress = [&mtx, &condition, &ivolume, &model_object, &progress](RepairProgressStage stage, const char *msg, unsigned prcnt) { std::unique_lock lock(mtx); progress.message = msg; + progress.stage = stage; const size_t total = std::max(1, model_object.volumes.size()); - progress.percent = int(std::floor((float(prcnt) + float(ivolume) * 100.f) / float(total))); + const float stage_percent = (float(prcnt) + float(ivolume) * 100.f) / float(total); + switch (stage) { + case RepairProgressStage::Repair: + progress.percent = int(std::ceil(stage_percent * 0.5f)); + break; + case RepairProgressStage::Remap: + progress.percent = 50 + int(std::ceil(stage_percent * 0.5f)); + break; + case RepairProgressStage::Status: + progress.percent = prcnt >= 100 ? 100 : int(std::ceil(stage_percent)); + break; + } + progress.percent = std::clamp(progress.percent, 0, 100); + if (prcnt > 0 && progress.percent == 0) + progress.percent = 1; progress.updated = true; condition.notify_all(); }; // Orca: Worker thread that performs the actual model repair operations. - auto worker_thread = std::thread([&model_object, volume_idx, &ivolume, on_progress, &success, &canceled, &finished, &fix_result]() { + auto worker_thread = std::thread([&model_object, + volume_idx, + &ivolume, + on_progress, + &success, + &cancel_requested, + &finished, + &repair_canceled, + &fix_result, + &local_color_stats, + remap_requested, + repair_options, + color_snapshots = std::move(color_snapshots)]() { try { size_t start_volume = volume_idx == -1 ? 0 : size_t(volume_idx); size_t end_volume = volume_idx == -1 ? std::numeric_limits::max() : size_t(volume_idx); @@ -102,113 +454,218 @@ bool fix_model_with_cgal_gui(ModelObject &model_object, int volume_idx, GUI::Pro for (ivolume = start_volume; ivolume < model_object.volumes.size(); ++ivolume) { if (volume_idx != -1 && ivolume > end_volume) break; - if (canceled) + if (cancel_requested) throw RepairCanceledException(); - on_progress(L("Repairing model object"), 10); + on_progress(RepairProgressStage::Repair, L("Repairing model object"), 15); ModelVolume *volume = model_object.volumes[ivolume]; + const auto color_snapshot_it = color_snapshots.find(volume); + const SimplifyTextureDataSnapshot *color_snapshot = + color_snapshot_it == color_snapshots.end() ? nullptr : &color_snapshot_it->second; // Orca: Split splittable volumes into parts for individual processing. + bool pre_split_weld_changed = false; + if (repair_options.split_before_repair && repair_options.weld_same_position_vertices) { + TriangleMesh welded_mesh = volume->mesh(); + pre_split_weld_changed = weld_same_position_vertices(welded_mesh); + if (pre_split_weld_changed) + volume->set_mesh(std::move(welded_mesh)); + } + size_t parts_count = 1; - if (volume->is_splittable()) { + if (repair_options.split_before_repair && volume->mesh().is_splittable()) { parts_count = volume->split(1); if (parts_count > 1) { const std::string msg = Slic3r::format(L("Split into %1% parts"), parts_count); - on_progress(msg.c_str(), 10); + on_progress(RepairProgressStage::Repair, msg.c_str(), 15); } } + const bool split_changed = parts_count > 1; size_t part_end = std::min(ivolume + parts_count - 1, model_object.volumes.size() - 1); if (volume_idx != -1) end_volume = part_end; size_t removed_parts = 0; - for (size_t idx = part_end + 1; idx > ivolume; --idx) { - const size_t part_idx = idx - 1; - const ModelVolume *part_volume = model_object.volumes[part_idx]; - if (!is_not_3dimensional_part(part_volume->mesh())) - continue; + if (repair_options.split_before_repair) { + for (size_t idx = part_end + 1; idx > ivolume; --idx) { + const size_t part_idx = idx - 1; + const ModelVolume *part_volume = model_object.volumes[part_idx]; + if (!is_not_3dimensional_part(part_volume->mesh())) + continue; - model_object.delete_volume(part_idx); - ++removed_parts; - if (part_end > 0) - --part_end; - else - part_end = 0; - if (volume_idx != -1) - end_volume = part_end; + model_object.delete_volume(part_idx); + ++removed_parts; + if (part_end > 0) + --part_end; + else + part_end = 0; + if (volume_idx != -1) + end_volume = part_end; + } } if (removed_parts >= parts_count) { ivolume = part_end; - on_progress(L("Repair finished"), 100); + on_progress(RepairProgressStage::Repair, L("Repair finished"), 100); continue; } - for (size_t part_idx = ivolume; part_idx <= part_end && part_idx < model_object.volumes.size(); ++part_idx) { - ModelVolume *part_volume = model_object.volumes[part_idx]; - TriangleMesh mesh = part_volume->mesh(); - if (its_num_open_edges(mesh.its) != 0) { - std::string error; - if (!MeshBoolean::cgal::repair(mesh, nullptr, &error)) - throw Slic3r::RuntimeError(error.empty() ? L("Repair failed") : error.c_str()); + std::vector color_part_states(part_end - ivolume + 1, ColorRemapPartState::Unchanged); + if (split_changed && color_snapshot != nullptr) + std::fill(color_part_states.begin(), color_part_states.end(), ColorRemapPartState::Pending); + else if (pre_split_weld_changed && color_snapshot != nullptr && !color_part_states.empty()) + color_part_states.front() = ColorRemapPartState::Pending; - part_volume->set_mesh(std::move(mesh)); - part_volume->calculate_convex_hull(); - part_volume->invalidate_convex_hull_2d(); - part_volume->set_new_unique_id(); + auto clear_pending_color_parts = [&model_object, &local_color_stats, ivolume, part_end, &color_part_states]() { + for (size_t part_idx = ivolume; part_idx <= part_end && part_idx < model_object.volumes.size(); ++part_idx) { + ColorRemapPartState &state = color_part_states[part_idx - ivolume]; + if (state != ColorRemapPartState::Pending) + continue; + clear_remappable_color_data(*model_object.volumes[part_idx], local_color_stats); + state = ColorRemapPartState::Cleared; } + }; + + try { + for (size_t part_idx = ivolume; part_idx <= part_end && part_idx < model_object.volumes.size(); ++part_idx) { + ModelVolume *part_volume = model_object.volumes[part_idx]; + TriangleMesh mesh = part_volume->mesh(); + bool part_changed = split_changed || (part_idx == ivolume && pre_split_weld_changed); + bool mesh_changed = false; + if (repair_options.weld_same_position_vertices && !repair_options.split_before_repair) + mesh_changed = weld_same_position_vertices(mesh); + if (its_num_open_edges(mesh.its) != 0) { + std::string error; + if (!MeshBoolean::cgal::repair(mesh, nullptr, &error)) + throw Slic3r::RuntimeError(error.empty() ? L("Repair failed") : error.c_str()); + + mesh_changed = true; + } + + if (mesh_changed) { + part_volume->set_mesh(std::move(mesh)); + part_changed = true; + } + + if (part_changed && color_snapshot != nullptr) { + ColorRemapPartState &state = color_part_states[part_idx - ivolume]; + if (state == ColorRemapPartState::Unchanged) + state = ColorRemapPartState::Pending; + const bool preserved = remap_or_clear_color_data( + *part_volume, + *color_snapshot, + remap_requested, + cancel_requested, + [on_progress](int percent) { + const int clamped_percent = std::clamp(percent, 0, 100); + on_progress(RepairProgressStage::Remap, L("Remapping color data"), unsigned(clamped_percent)); + }, + local_color_stats); + state = preserved ? ColorRemapPartState::Preserved : ColorRemapPartState::Cleared; + } + + if (part_changed) { + part_volume->calculate_convex_hull(); + part_volume->invalidate_convex_hull_2d(); + part_volume->set_new_unique_id(); + } + } + } catch (ColorRemapCanceledException &) { + clear_pending_color_parts(); + throw; + } catch (std::exception &) { + clear_pending_color_parts(); + throw; } ivolume = part_end; - on_progress(L("Repair finished"), 100); + on_progress(RepairProgressStage::Repair, L("Repair finished"), 100); } model_object.invalidate_bounding_box(); if (ivolume > 0) --ivolume; - on_progress(L("Repair finished"), 100); + on_progress(RepairProgressStage::Status, L("Repair finished"), 100); success = true; finished = true; - } catch (RepairCanceledException &) { - canceled = true; + } catch (ColorRemapCanceledException &) { + success = true; finished = true; - on_progress(L("Repair canceled"), 100); + on_progress(RepairProgressStage::Status, L("Color remap canceled"), 100); + } catch (RepairCanceledException &) { + repair_canceled = true; + finished = true; + on_progress(RepairProgressStage::Status, L("Repair canceled"), 100); } catch (std::exception &ex) { success = false; finished = true; fix_result = ex.what(); - on_progress(ex.what(), 100); + on_progress(RepairProgressStage::Status, ex.what(), 100); } }); + auto repair_stage_started = std::chrono::steady_clock::now(); + RepairProgressStage last_stage = RepairProgressStage::Repair; + int last_shown_percent = 0; + // Orca: Main GUI loop to update progress dialog and handle cancellation. while (!finished) { - std::unique_lock lock(mtx); - condition.wait_for(lock, std::chrono::milliseconds(250), [&progress]{ return progress.updated; }); + std::string progress_message; + RepairProgressStage progress_stage = RepairProgressStage::Repair; + int progress_percent = 0; + { + std::unique_lock lock(mtx); + condition.wait_for(lock, std::chrono::milliseconds(250), [&progress]{ return progress.updated; }); - // Decrease progress percent slightly to avoid auto-closing. - if (!progress_dialog.Update(progress.percent - 1, msg_header + _(progress.message))) - canceled = true; + progress_message = progress.message; + progress_stage = progress.stage; + progress_percent = progress.percent; + progress.updated = false; + } + + if (progress_stage != last_stage) { + last_stage = progress_stage; + if (progress_stage == RepairProgressStage::Repair) + repair_stage_started = std::chrono::steady_clock::now(); + } + + if (progress_stage == RepairProgressStage::Repair) { + const int fallback_progress = logarithmic_repair_stage_progress(std::chrono::steady_clock::now() - repair_stage_started); + progress_percent = std::max(progress_percent, fallback_progress); + progress_percent = std::min(progress_percent, 50); + } else if (progress_stage == RepairProgressStage::Remap) + progress_percent = std::max(progress_percent, 50); + + int shown_percent = std::clamp(progress_percent, 0, 100); + if (progress_stage != RepairProgressStage::Status) + shown_percent = std::max(shown_percent, last_shown_percent); + if (shown_percent >= 100) + shown_percent = 99; + last_shown_percent = shown_percent; + + if (!progress_dialog.Update(shown_percent, compose_progress_message(msg_header, progress_stage, progress_message))) + cancel_requested = true; else progress_dialog.Fit(); - - progress.updated = false; - } - - if (canceled) { - // Nothing to show. - } else if (success) { - fix_result.clear(); } if (worker_thread.joinable()) worker_thread.join(); - return !canceled; + if (local_color_stats.remap_canceled && !repair_canceled) + progress_dialog.Resume(); + + if (!repair_canceled && success) + fix_result.clear(); + + if (color_remap_stats) + merge_color_remap_stats(*color_remap_stats, local_color_stats); + + return !repair_canceled; } } // namespace Slic3r diff --git a/src/slic3r/Utils/FixModelByCgal.hpp b/src/slic3r/Utils/FixModelByCgal.hpp index 3caa6d7fb99..87264f94606 100644 --- a/src/slic3r/Utils/FixModelByCgal.hpp +++ b/src/slic3r/Utils/FixModelByCgal.hpp @@ -1,6 +1,7 @@ #ifndef slic3r_GUI_Utils_FixModelByCgal_hpp_ #define slic3r_GUI_Utils_FixModelByCgal_hpp_ +#include #include #include "../GUI/Widgets/ProgressDialog.hpp" @@ -10,8 +11,47 @@ class Model; class ModelObject; class Print; +struct ModelRepairColorRemapStats +{ + bool had_color_data = false; + bool remap_requested = false; + bool remap_skipped = false; + bool remap_canceled = false; + bool remap_failed = false; + bool used_fallback_rgba = false; + size_t volumes_remapped = 0; + size_t volumes_cleared = 0; +}; + +struct ModelRepairOptions +{ + bool split_before_repair = false; + bool weld_same_position_vertices = true; +}; + +enum class ModelRepairColorRemapChoice +{ + Remap, + Skip, + Cancel +}; + +struct ModelRepairPromptState +{ + bool repair_options_selected = false; + bool repair_options_canceled = false; + ModelRepairOptions repair_options; + ModelRepairColorRemapChoice color_remap_choice = ModelRepairColorRemapChoice::Remap; +}; + // Return false if fixing was canceled. fix_result is empty on success. -extern bool fix_model_with_cgal_gui(ModelObject &model_object, int volume_idx, GUI::ProgressDialog &progress_dlg, const wxString &msg_header, std::string &fix_result); +extern bool fix_model_with_cgal_gui(ModelObject &model_object, + int volume_idx, + GUI::ProgressDialog &progress_dlg, + const wxString &msg_header, + std::string &fix_result, + ModelRepairColorRemapStats *color_remap_stats = nullptr, + ModelRepairPromptState *prompt_state = nullptr); } // namespace Slic3r