Map model colors to new mesh after using 'Fix Model' or Cut tool

This commit is contained in:
sentientstardust
2026-05-13 04:10:21 +01:00
parent 7066faa9d7
commit aea4282633
10 changed files with 798 additions and 100 deletions

View File

@@ -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<ModelObject*>& dowels)
std::vector<ModelObject*>& 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<float>());
lower_mesh.translate((0.05 * Vec3d::UnitZ()).cast<float>());
// 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

View File

@@ -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;
}

View File

@@ -1042,13 +1042,41 @@ public:
// The triangular model.
const TriangleMesh& mesh() const { return *m_mesh.get(); }
std::shared_ptr<const TriangleMesh> mesh_ptr() const { return m_mesh; }
void set_mesh(const TriangleMesh &mesh) { m_mesh = std::make_shared<const TriangleMesh>(mesh); }
void set_mesh(TriangleMesh &&mesh) { m_mesh = std::make_shared<const TriangleMesh>(std::move(mesh)); }
void set_mesh(const indexed_triangle_set &mesh) { m_mesh = std::make_shared<const TriangleMesh>(mesh); }
void set_mesh(indexed_triangle_set &&mesh) { m_mesh = std::make_shared<const TriangleMesh>(std::move(mesh)); }
void set_mesh(std::shared_ptr<const TriangleMesh> &mesh) { m_mesh = mesh; }
void set_mesh(std::unique_ptr<const TriangleMesh> &&mesh) { m_mesh = std::move(mesh); }
void reset_mesh() { m_mesh = std::make_shared<const TriangleMesh>(); }
void set_mesh(const TriangleMesh &mesh)
{
m_mesh = std::make_shared<const TriangleMesh>(mesh);
m_is_splittable = -1;
}
void set_mesh(TriangleMesh &&mesh)
{
m_mesh = std::make_shared<const TriangleMesh>(std::move(mesh));
m_is_splittable = -1;
}
void set_mesh(const indexed_triangle_set &mesh)
{
m_mesh = std::make_shared<const TriangleMesh>(mesh);
m_is_splittable = -1;
}
void set_mesh(indexed_triangle_set &&mesh)
{
m_mesh = std::make_shared<const TriangleMesh>(std::move(mesh));
m_is_splittable = -1;
}
void set_mesh(std::shared_ptr<const TriangleMesh> &mesh)
{
m_mesh = mesh;
m_is_splittable = -1;
}
void set_mesh(std::unique_ptr<const TriangleMesh> &&mesh)
{
m_mesh = std::move(mesh);
m_is_splittable = -1;
}
void reset_mesh()
{
m_mesh = std::make_shared<const TriangleMesh>();
m_is_splittable = -1;
}
const std::shared_ptr<const TriangleMesh>& 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.

View File

@@ -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<double>()).cast<float>();
}
}
SimplifyTextureDataResult remap_simplify_texture_data(const SimplifyTextureDataSnapshot &snapshot,
const indexed_triangle_set &simplified_mesh,
const SimplifyTextureCancelFn &throw_on_cancel,

View File

@@ -63,6 +63,8 @@ using SimplifyTextureProgressFn = std::function<void(int)>;
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 = {},

View File

@@ -6019,6 +6019,18 @@ void ObjectList::fix_through_cgal()
std::vector<std::string> succes_models;
// model_name failing reason
std::vector<std::pair<std::string, std::string>> 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<int> 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<std::string>& 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));

View File

@@ -12,9 +12,11 @@
#include <algorithm>
#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 <imgui/imgui_internal.h>
@@ -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<std::pair<std::string, std::string>> 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<std::string> &succes_models, std::vector<std::pair<std::string, std::string>> &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<std::string> &succes_models,
std::vector<std::pair<std::string, std::string>> &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);

View File

@@ -6,6 +6,7 @@
#include <algorithm>
#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<std::pair<std::string, std::string>> 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<std::string> &succes_models, std::vector<std::pair<std::string, std::string>> &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<std::string> &succes_models,
std::vector<std::pair<std::string, std::string>> &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);

View File

@@ -1,20 +1,32 @@
#include "FixModelByCgal.hpp"
#include <algorithm>
#include <atomic>
#include <chrono>
#include <cmath>
#include <condition_variable>
#include <cstdint>
#include <exception>
#include <functional>
#include <limits>
#include <mutex>
#include <string>
#include <thread>
#include <unordered_map>
#include <utility>
#include <vector>
#include "libslic3r/MeshBoolean.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/ModelTextureDataRemap.hpp"
#include "libslic3r/format.hpp"
#include "../GUI/I18N.hpp"
#include <wx/checkbox.h>
#include <wx/dialog.h>
#include <wx/sizer.h>
#include <wx/stattext.h>
// 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<ModelVolume*, SimplifyTextureDataSnapshot>;
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<double>(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<bool> &cancel_requested,
const std::function<void(int)> &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<bool> canceled = false;
std::atomic<bool> cancel_requested = false;
std::atomic<bool> finished = false;
std::atomic<bool> 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<std::mutex> lock(mtx);
progress.message = msg;
progress.stage = stage;
const size_t total = std::max<size_t>(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<size_t>::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<ColorRemapPartState> 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<std::mutex> 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<std::mutex> 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

View File

@@ -1,6 +1,7 @@
#ifndef slic3r_GUI_Utils_FixModelByCgal_hpp_
#define slic3r_GUI_Utils_FixModelByCgal_hpp_
#include <cstddef>
#include <string>
#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