Repository navigation
Fix model on Linux and macOS: CGAL repair instead of the Netfabb service (OrcaSlicer#12155) - #991
Draft
alex-mextner wants to merge 10 commits into
Draft
alex-mextner wants to merge 10 commits into
alex-mextner wants to merge 10 commits into
Conversation
Replace Windows 10 SDK model fixer with CGAL-based fixing for cross-platform support (Windows/Linux/macOS). Faster, preserves multi-part objects, and fixes only broken parts. Cherry-picked (squashed) from OrcaSlicer#12155.
…ic3r Snapmaker bundles CGAL 5.4, which has no triangulate_and_refine_hole() overload without output iterators and needs border.h / triangulate_hole.h included explicitly. Port OrcaSlicer#15395: hold a SaveObjectGaurd while the worker mutates the object, so the auto-backup manager does not clone it concurrently (heap corruption). Move the repair loop unchanged from FixModelByCgal.cpp into libslic3r/ModelObjectRepair so it can be unit tested; the GUI keeps the progress dialog, cancellation and error reporting. Refs: OrcaSlicer#12155 Co-authored-by: weng haishi <74546450+wenghaishi@users.noreply.github.com> Co-authored-by: Ian Bassi <ian.bassi@outlook.com>
repair(): weld near-coincident points (seams of STEP conversions), orient the soup before building the mesh, fill holes before resolving self-intersections and fill holes pinched at a vertex loop by loop, replace the self-union (CGAL requires distinct, non-self-intersecting inputs) by the union of the solid shells minus the cavities, always orient the result outwards, and fail on a result without volume. repair_model_object(): do not skip the volume after a dropped flat first volume, reject invalid volume indices and objects without a 3D part before changing them, honour cancellation during that check, and translate the formatted messages. Refs: OrcaSlicer#12155
…a copy off the GUI thread repair_model_object() leaves a volume without open edges and self-intersections as it is, even when it is made of several shells: splitting it dropped its painted facets. Parts that only intersect themselves are repaired too, and the returned ModelObjectRepairReport tells whether a repaired part still intersects itself; the object list then warns instead of reporting a clean repair. MeshBoolean::cgal::repair() resolves overlapping shells by nesting level, the longest chain of shells containing each other: level 0 minus (level 1 minus (level 2 ...)), so a solid inside a cavity survives and overlapping solids around a cavity count as one level. It re-checks self-intersections after resolving them (new optional out-parameter) and runs inside try_catch_signal like the mesh booleans (traps signals where try_catch_signal can, i.e. Windows). fix_model_with_cgal_gui() repairs a copy of the object (Model::add_object() into a scratch model) on the worker thread and applies it on the GUI thread after join with apply_repaired_volumes(), so the progress dialog never draws a half-repaired object and a cancelled or failed repair leaves it unchanged.
The CGAL fix model replaced the Windows 10 SDK (Netfabb) fixer, so the WIN10SDK detection in CMakeLists.txt, the Windows SDK env of the CI build step and the -DWIN10SDK_PATH arguments of the Windows build scripts no longer configure anything.
Adds the strings the CGAL fix model introduced to Snapmaker_Orca.pot, so a catalog merge keeps them, and to the zh_CN catalog: "Repair failed", "Split into %1% parts", "The object has no part that encloses a volume", "Repaired, but self-intersections remain in" and "Use \"Fix Model\" to repair the mesh.". The Chinese texts are machine translations, marked "# AI Translated" for a native speaker to review.
…ort on a crash weld_close_points() keys its grid by int64 cells of coord / tolerance, which overflows for coordinates beyond about 9e15 tolerances; the weld is now skipped there, and a test repairs a part 2^54 mm from the origin. repair() clears self_intersections_remain before the guarded call, so a trapped CGAL crash does not leave it unset.
apply_repaired_volumes() deletes the dropped volumes itself instead of through ModelObject::delete_volume(), so a repair that only drops volumes never called save_object_mesh() and the project backup missed the change.
…deleted volume ModelVolume::split() also deletes the other volumes of the object whose convex hull is degenerate, so the repair loop lost track of the parts of the volume it split: it now finds them as the volumes that were neither before nor after that volume. Pointers, not ObjectIDs: delete_volume() gives the last volume left a new id, which made an unrelated volume look like a part when every part was flat. split() deletes the volume it is called on when its first shell is flat, and then kept reading this->object; the discard loop now keeps the owning object in a local. Tests: a selected volume whose split drops a flat volume before it; a clean volume whose tiny mesh is scaled up; a volume whose first shell is flat; a volume whose shells are all flat next to one other volume.
A volume deleted by split() leaves an invalid pointer value, and comparing such values is undefined. The addresses are now kept as uintptr_t, as in the OrcaSlicer port.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
What. "Fix model" now works on Linux and macOS, and Windows uses the same code. The Windows 10 SDK (Netfabb) fixer is replaced by a local CGAL repair. This ports OrcaSlicer#12155 and fixes the repair problems found while testing it.
Why.
Commits
Multi OS fix model with CGAL(author @ealap): CGAL Fix Model: New for Linux and Mac OrcaSlicer/OrcaSlicer#12155 squashed, taken from ealap's Snapmaker port. RemovesFixModelByWin10.*, addsFixModelByCgal.*andMeshBoolean::cgal::repair(), and enables the menu item on every OS.Build the CGAL fix model with CGAL 5.4 and move its worker into libslic3r:triangulate_and_refine_hole()overload without output iterators and needsborder.h/triangulate_hole.hincluded.SaveObjectGaurd, so auto-backup does not clone the object while it changes (heap corruption).src/libslic3r/ModelObjectRepair.*so it can be unit tested.localization/i18n/list.txtswapsFixModelByWin10.cppforFixModelByCgal.cppand addsModelObjectRepair.cpp.Fix CGAL fix model repair and the volume loop, with tests.repair()now:corefine_and_compute_union(m, m)with a union of the solid shells minus the cavities (CGAL needs distinct inputs without self-intersections);The volume loop:
Fix model: leave clean volumes untouched, keep nested solids, repair a copy off the GUI thread:Delete the commented-out Netfabb build settings. CGAL Fix Model: New for Linux and Mac OrcaSlicer/OrcaSlicer#12155 commented out the WIN10SDK CMake block and the CI env; Orca reviewers asked to delete such code instead. The three Windows.batscripts no longer pass the unused-DWIN10SDK_PATH.Add the new Fix model messages to the catalog, with zh_CN translations. Five new strings go intoSnapmaker_Orca.potandzh_CN. The Chinese texts are machine translations, marked# AI Translated, and need a native-speaker review. Other languages show English for these strings until the next translation update.Fix model: skip the weld where its grid would overflow, reset the report on a crash. The weld grid uses int64 cells of coordinate / tolerance, so it is now skipped once that ratio reaches 1e15 (a test repairs a part 2^54 mm from the origin). Theself_intersections_remainout-parameter is cleared before the guarded call.Fix model: record the repaired mesh for the project backup.apply_repaired_volumes()deletes the dropped volumes itself, so it now callssave_object_mesh()likeModelObject::delete_volume()does. No unit test covers this: the tests run without the project backup.Fix model: find the split parts by pointer, and stop split() using a deleted volume, plus a follow-up commit that keeps the volume addresses asuintptr_t, so deleted volumes leave no invalid pointer values to compare.ModelVolume::split()also deletes other volumes of the object whose convex hull is degenerate (flat sheets), wherever they are. The repair loop then counted the wrong volumes as the parts of the split volume. It now finds them as the volumes that were neither before nor after it, compared by address:delete_volume()gives the last remaining volume a new ObjectID, so ids would mistake an unrelated volume for a part.ModelVolume::split()deletes the volume it was called on when its first shell is flat, and then kept readingthis->object(use-after-free). This existed before this PR; Fix model reaches it whenever the first shell of a broken volume is flat. The discard loop now keeps the owning object in a local.[ModelObjectRepair]cases: a selected volume whose split drops a flat volume before it; a clean volume whose tiny mesh is scaled up; a volume whose first shell is flat; a volume whose shells are all flat next to one other volume.The commits cherry-picked onto
main(441f73c) with no conflicts.Behaviour notes
Remaining risks (known, not addressed here)
try_catch_signal({SIGSEGV, SIGFPE}), but that only traps on Windows. On Linux and macOS a crash inside CGAL still ends the app.resources/data/hints.inistill says Fix model works "on the Windows system". It is left as upstream has it, because changing it would invalidate that hint's translation in every language.Not tested: Windows and macOS builds and runs (no machines; Snapmaker CI does not build PRs). The code paths are OS-independent apart from the build-script cleanup.
Related:
Credits:
Screenshots/Recordings/Graphs
These shots come from this branch's AppImage (head 87dc3e2, built on
mainas described under Tests). A scripted GUI test drives the app on Xvfb. The model is a real user project: 11 copies of a part imported from a Creo surface STEP with the old importer, so every copy has open edges. The model itself is not attached because its licence is unknown.Before: every copy shows the warning icon and non-manifold edges.

After Fix model on all objects: no warnings, and the notification lists the repaired objects.

After auto-arrange:

Sliced:

Tests
Current head 87dc3e2 (commits 1–9), Snapmaker-main build: Ubuntu 26.04 on a Runpod cpu5c pod with 16 vCPU, running
build_linux.sh -sitr(dependencies from a cache built from the samedeps/tree) and thenctest -C Release -j1 --output-on-failure -E 'render_realtime_body|rate limit throttles beyond cap'.[ModelObjectRepair]and 27[MeshBoolean]cases. The three excluded SnapLogClient tests are the tworender_realtime_bodySIGSEGVs thatmainitself has and the flaky timing test (see below and the earlier head).7e8f7b87…7d688).gstreamer1.0-x, part of a normal desktop install),maincrashes at startup in thewxMediaCtrl2constructor (StatusBasePanel::create_monitoring_page), becausewxMediaCtrlthen has no backend. The test machine has that package installed, as a desktop does.Everything below is evidence for earlier heads, labelled as such.
Snapmaker-main build of the earlier head 21d7aa4 (commits 1–8, without commit 9): Ubuntu 26.04 on a Runpod cpu3c pod with 32 vCPU, running
build_linux.sh -dsitrand thenctest -C Release -j1 --output-on-failure.Full ctest: 581 of 583 passed. The only failures were the two
render_realtime_bodySIGSEGVs thatmainitself has (see below). All 47[MeshBoolean]and[ModelObjectRepair]cases passed.A second run with those two tests excluded hit a flaky timing test in SnapLogClient (
log(): rate limit throttles beyond cap), which passed in the first run.The final run excluded that test as well: 575 of 575 passed, and the AppImage was built (sha256
8bfe0f49…d573c).Red run, same pod.
src/libslic3r/MeshBoolean.cppandModelObjectRepair.cppwere reverted to the ported upstream repair, with only the new API stubbed and the tests kept. 21 Fix model tests fail, among them:The other 26 cases pass on red: they are guards. Besides those 21, three SnapLogClient tests failed on red, all unrelated: the two known SIGSEGVs and one flaky test.
A GUI run of this earlier head did not get past startup (see the current head above).
Evidence from the fork (Ubuntu 26.04, Runpod CPU pods, uncached dependencies,
build_linux.sh -dsitr, thenctest -C Release -j1 --output-on-failure):fixmodel/ubuntuf773e60ubuntu-26.04a75a248 (release v2.4.0-linux.2)New tests (Catch2, in
tests/libslic3r):test_meshboolean.cpp,[MeshBoolean]: 26 new cases. They cover:test_model_object_repair.cpp(new),[ModelObjectRepair]: 24 cases. They cover:Red → green, on the fork. Each red run used the new tests on top of the ported upstream repair. Logs are kept locally.
-1000 > 0); self-union lost volume (1833.33vs2000); overlapping shells still self-intersecting; seams split by noise not welded (-0.00003 > 0); after a dropped flat first volume, the next volume was skipped (open_edges 3 == 0); an object was left with no volumes1800vs1675); object of flat parts changed before failing; invalid volume index accepted; stacked flat sheets; cancel during the pre-check2 == 1); island inside a cavity erased (13400vs13464); repaired copy not applied; self-intersections not reported; cavity lost inside overlapping solids (11999.999vs11792)The tests that already passed on red are guards: the conversion test, overlapping closed shells being split, cancelling a copy, and "no self-intersections" reports.
Known failures on
main, not related to this PR. Buildingmain(441f73c) itself fails 2 of 537 tests:slic3rutils:render_realtime_body: huge ext value truncated, eventName survivesand... per-value truncation keeps eventName in ext, both SIGSEGV in SnapLogClient.GUI test. A scripted run on Xvfb:
The analysis arguments were
--count 11 --volume 3143.3 --size 25.98,22.50,10.50.Not tested: Windows, macOS.