Map model colors to new mesh after using 'Fix Model' or Cut tool
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 = {},
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user