mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-21 07:52:32 +00:00
* fix: prevent heap corruption in model repair with auto-backup The CGAL model repair (fix_model_with_cgal_gui) runs on a worker thread that mutates the live ModelObject (split / delete_volume / set_mesh). Those mutators transitively call save_object_mesh(), which hands the object to the auto-backup manager. The manager clones and serializes the object on its own thread via an internal Model documented as "visit only in main thread". Running that path from the repair worker races the backup thread on the shared model, causing use-after-free / heap corruption -- EXC_BAD_ACCESS and libmalloc "corruption of free block" aborts, always with the "cgal_fix_model" worker on the stack inside add_object_mesh -> Model::add_object / delete_object. Wrap the repair in a SaveObjectGaurd so the backup manager ignores the object for the duration of the repair; a single backup is taken when the guard is released on the main thread after the worker joins. This mirrors existing batch-edit usage of SaveObjectGaurd (Model.hpp, GUI_ObjectList). Repro: repair a multi-part / splittable object with auto-backup enabled (Preferences > Backup); crashed within a few repairs on macOS arm64. * Update FixModelByCgal.cpp --------- Co-authored-by: Ian Bassi <ian.bassi@outlook.com>
231 lines
8.6 KiB
C++
231 lines
8.6 KiB
C++
#include "FixModelByCgal.hpp"
|
|
|
|
#include <atomic>
|
|
#include <chrono>
|
|
#include <cmath>
|
|
#include <condition_variable>
|
|
#include <limits>
|
|
#include <mutex>
|
|
#include <string>
|
|
#include <thread>
|
|
#include <vector>
|
|
|
|
#include "libslic3r/MeshBoolean.hpp"
|
|
#include "libslic3r/Model.hpp"
|
|
#include "libslic3r/Format/bbs_3mf.hpp"
|
|
#include "libslic3r/format.hpp"
|
|
#include "libslic3r/Thread.hpp"
|
|
#include "../GUI/I18N.hpp"
|
|
|
|
// Orca: This file provides utilities for repairing 3D model meshes using the CGAL library, handling mesh splitting, merging, and boolean operations.
|
|
|
|
namespace Slic3r {
|
|
|
|
namespace {
|
|
|
|
// Orca: Helper functions for analyzing mesh properties and transformations.
|
|
|
|
bool is_not_3dimensional_part(const TriangleMesh &mesh)
|
|
{
|
|
// Orca: Determines if a mesh is degenerate or represents a non-3dimensional part by checking volume and bounding box dimensions.
|
|
if (mesh.its.indices.empty())
|
|
return true;
|
|
|
|
indexed_triangle_set tmp = mesh.its;
|
|
its_remove_degenerate_faces(tmp, true);
|
|
if (tmp.indices.empty())
|
|
return true;
|
|
|
|
const BoundingBoxf3 bbox = mesh.bounding_box();
|
|
const Vec3d size = bbox.size();
|
|
const double min_dim = std::min(size.x(), std::min(size.y(), size.z()));
|
|
const double max_dim = std::max(size.x(), std::max(size.y(), size.z()));
|
|
if (min_dim <= EPSILON)
|
|
return true;
|
|
|
|
const double volume = std::abs(its_volume(mesh.its));
|
|
const double bbox_volume = size.x() * size.y() * size.z();
|
|
if (volume <= EPSILON)
|
|
return true;
|
|
|
|
const double min_relative_thickness = 1e-6;
|
|
const double min_volume_ratio = 1e-6;
|
|
if (min_dim / max_dim <= min_relative_thickness)
|
|
return true;
|
|
if (bbox_volume > 0.0 && volume / bbox_volume <= min_volume_ratio)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
// Orca: Exception class for handling user-initiated cancellation of model repair operations.
|
|
class RepairCanceledException : public std::exception {
|
|
public:
|
|
const char* what() const noexcept override { return "Model repair has been canceled"; }
|
|
};
|
|
|
|
// 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 keep_painting)
|
|
{
|
|
// Hold SaveObjectGaurd to prevent backup manager from racing concurrent mesh mutations (use-after-free).
|
|
SaveObjectGaurd backup_gaurd(model_object);
|
|
|
|
// 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;
|
|
} progress;
|
|
|
|
std::atomic<bool> canceled = false;
|
|
std::atomic<bool> finished = false;
|
|
|
|
bool success = false;
|
|
size_t ivolume = 0;
|
|
|
|
// Orca: Lambda for updating progress from worker thread.
|
|
auto on_progress = [&mtx, &condition, &ivolume, &model_object, &progress](const std::string &msg, unsigned prcnt) {
|
|
std::unique_lock<std::mutex> lock(mtx);
|
|
progress.message = msg;
|
|
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)));
|
|
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, keep_painting]() {
|
|
try {
|
|
set_current_thread_name("cgal_fix_model");
|
|
|
|
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);
|
|
|
|
for (ivolume = start_volume; ivolume < model_object.volumes.size(); ++ivolume) {
|
|
if (volume_idx != -1 && ivolume > end_volume)
|
|
break;
|
|
if (canceled)
|
|
throw RepairCanceledException();
|
|
|
|
on_progress(_u8L("Repairing model object"), 10);
|
|
|
|
ModelVolume *volume = model_object.volumes[ivolume];
|
|
|
|
// Orca: Split splittable volumes into parts for individual processing.
|
|
size_t parts_count = 1;
|
|
if (volume->is_splittable()) {
|
|
parts_count = volume->split(1, keep_painting);
|
|
if (parts_count > 1) {
|
|
const std::string msg = Slic3r::format(L("Split into %1% parts"), parts_count);
|
|
on_progress(msg, 10);
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
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(_u8L("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) {
|
|
|
|
// Save painting for later remap
|
|
const std::optional<TriangleSelector::SavedPainting> saved_painting = keep_painting ?
|
|
part_volume->save_painting() :
|
|
std::optional<TriangleSelector::SavedPainting>{};
|
|
|
|
std::string error;
|
|
if (!MeshBoolean::cgal::repair(mesh, nullptr, &error))
|
|
throw Slic3r::RuntimeError(error.empty() ? _u8L("Repair failed") : error);
|
|
|
|
part_volume->set_mesh(std::move(mesh));
|
|
part_volume->calculate_convex_hull();
|
|
part_volume->invalidate_convex_hull_2d();
|
|
part_volume->set_new_unique_id();
|
|
|
|
// Remap paint back
|
|
part_volume->restore_painting(saved_painting);
|
|
}
|
|
}
|
|
|
|
ivolume = part_end;
|
|
|
|
on_progress(_u8L("Repair finished"), 100);
|
|
}
|
|
|
|
model_object.invalidate_bounding_box();
|
|
|
|
if (ivolume > 0)
|
|
--ivolume;
|
|
on_progress(_u8L("Repair finished"), 100);
|
|
success = true;
|
|
finished = true;
|
|
} catch (RepairCanceledException &) {
|
|
canceled = true;
|
|
finished = true;
|
|
on_progress(_u8L("Repair canceled"), 100);
|
|
} catch (std::exception &ex) {
|
|
success = false;
|
|
finished = true;
|
|
fix_result = ex.what();
|
|
on_progress(ex.what(), 100);
|
|
}
|
|
});
|
|
|
|
// 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; });
|
|
|
|
// Decrease progress percent slightly to avoid auto-closing.
|
|
if (!progress_dialog.Update(progress.percent - 1, msg_header + _(progress.message)))
|
|
canceled = 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;
|
|
}
|
|
|
|
} // namespace Slic3r
|