Print::apply rebuilds m_config.filament_map_2 to the real per-filament slot
map on every apply, while the incoming full config only ever carries the
ConfigDef default. The resulting phantom one-key print_diff hit the
invalidator's catch-all branch and killed every print-level step on each
apply, so on multi-extruder printers a fresh slice result was invalidated
the moment the GUI re-applied after slicing completed.
Dropping the key from print_diff loses no information: it is never a user
input, and the rebuild derives it from filament_map, filament_volume_map
and the variant slots, each of which is diffed and invalidation-listed on
its own.
Regression test: re-applying an unchanged config after process() must not
invalidate psSlicingFinished (fails with APPLY_STATUS_INVALIDATED without
the fix). Suites green (libslic3r 48891/154, fff_print 631/59); 19-fixture
byte gate identical incl. the Hybrid repro project, determinism x2.
- the g-code writer tracks the current layer id and resolves
FILAMENT_CONFIG/NOZZLE_CONFIG (plus every non-macro variant lookup,
toolchange placeholder scalars, and the change-filament flush
overrides) through Print's per-filament, per-layer config-index
resolvers instead of the filament->extruder collapse
- update_layer_related_config refreshes the per-layer
extruder/volume/nozzle maps in the writer config;
update_placeholder_parser_with_variant_params remaps the
filament-variant arrays into filament-id space for custom g-code
(Orca's flush placeholder computation moves inside it)
- the engine's concrete per-filament volume assignment now merges into
the config write-back (the temporary hold from the producer commit
is lifted together with these consumers), and the background process
reads the computed volume map back to the plate
- append_full_config dumps the resolved filament_map_2 slots
- update_used_filament_values gains a bounds guard
- tests: per-filament Hybrid slot resolution + null-result fallback
Result: on a Hybrid extruder, each filament's features slice with its
assigned sub-nozzle's variant values (speeds, volumetric limits,
retraction). Verified on a 4-filament H2C Hybrid project: outer walls
split into three feedrate populations (30/50/200 mm/s), toolpath
geometry byte-identical, deterministic across repeated slices. All 18
non-Hybrid reference fixtures stay byte-identical except the
filament_map_2 header value now showing the real slot. Auto grouping
ties (multiple zero-flush perfect matchings) may pick a different
filament-to-nozzle isolation than other slicers; verified co-optimal.
Opening an STL/OBJ in the Design pane now rebuilds it into a real OCCT B-rep
solid that the face/edge feature tools can operate on, instead of a print mesh.
GeometryEngine::mesh_to_brep is a native C++ port of mesh2step
(github.com/tommasobbianchi/mesh2step): vertices and edges are shared across
triangles at construction time (vertex cache by deduped index, edge cache by
unordered index pair), so no BRepBuilderAPI_Sewing pass is needed to rebuild the
topology afterwards, and watertightness falls out of the edge-usage counts for
free. An open mesh is returned as a shell and reported as such — never dressed up
as a fake solid.
It runs in-process on the OCCT kernel libslic3r already links, so no STEP file is
written or re-read. That is not an optimisation but the whole point: a faceted
STEP of a 62k-triangle mesh is ~149 MB and OCCT's STEPControl_Reader takes >300 s
to parse it back, so routing this through a file would hang the GUI.
Coplanar neighbours are merged (ShapeUpgrade_UnifySameDomain, 5° default) so the
body arrives with pickable CAD faces rather than one face per triangle — on the
20,656-triangle test part that is 20,614 faces down to 4,784. Without it the
import is technically a solid but nothing you can meaningfully fillet or extrude.
- Design pane: "Import mesh" button + Shift+M; warns above 50k triangles.
- MCP: import_mesh {path, tolerance, merge_angle_deg}, returning the full
conversion stats so a caller can tell an honest solid from an open shell.
- Catch2: cube round-trip (exact volume, 12 faceted faces, 6 after merge), open
mesh stays a shell, and the scale-independent sliver rule that a naive
area < tolerance^2 test would get wrong.
Verified end-to-end on the real 20,656-triangle ir3v2 hotend STL: reproduces
mesh2step's Python run exactly (20,614 kept, 42 degenerate, 0 boundary edges,
2 non-manifold edges, not watertight) and the resulting body's bbox matches the
one FreeCAD reports for the same part.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BVzKmX6Y1aEteit1HTXG4Q
- Print::update_filament_maps_to_config takes filament/volume/nozzle
maps, backfills an empty volume map from extruder types, rebuilds
filament_map_2, re-expands the per-filament variant arrays, and
recomputes retract overrides keyed by resolved slots
- grouping writes its result back in every non-sequential mode;
manual multi-nozzle grouping validates the user mapping and raises a
translatable error on deviation; the engine's concrete volume
assignment is deliberately not merged yet (per-filament arrays are
already consumed by filament id, so materializing High Flow now
would change motion before the layer-aware resolvers land)
- Print::apply treats the three map keys as engine outputs in auto
modes (erased from the diff and adopted), compares them against used
filaments in manual mode, and keeps the pre-expansion snapshot in
sync with the late normalization pass so rebuilt headers reflect the
sliced state instead of resurrecting stale values
- volume/nozzle maps and extruder_nozzle_stats join the invalidation
group of filament_map (wipe tower + skirt/brim)
- PresetBundle composes full configs with an optional per-filament
volume map (plate map, else defaults derived from each extruder's
flow type); project config keeps the map sized across filament
count changes
- PartPlate stores per-plate volume/nozzle maps; Plater injects them
at every slice-composition site (incl. g-code reload and wipe-tower
estimation); BackgroundSlicingProcess reads engine results back to
the plate in auto modes
- per-filament map trust guards relaxed to size-match everywhere now
that every producer sizes the map; single-filament explicit flow
assignments are honored
- tests: grouping volume maps stay concrete, merge semantics of
update_used_filament_values, single-filament override honoring
Motion g-code is byte-identical fleet-wide including Hybrid projects
(19-fixture gate + repro determinism double-slice). Header deltas:
the map keys now dump real values, and stale pre-normalization values
(e.g. enable_prime_tower on single-used-filament prints) no longer
leak into the config block.
- Print::get_nozzle_config_index / get_filament_config_indx resolve a
filament's variant slot per layer from the nozzle group result, with
hashed index caches; when no group result is published (sequential
prints), they fall back to the static filament->extruder mapping so
behavior is unchanged
- filament_map_2 caches each filament's resolved print-variant slot;
rebuilt in Print::apply after the filament_map diff handling and in
the filament-map write-back
- filament retract overrides now key by slot indices: apply_override
fallback indexing flips to 0-based, Print::apply passes
filament_map/extruder indices, the write-back passes filament_map_2
(identical resolution while slots equal extruders)
- filament_volume_map/filament_nozzle_map/filament_map_2/
filament_self_index become PrintConfig static members (required for
member access); grouping input guards tightened so their registered
1-element defaults are never mistaken for real per-filament maps
(single-filament manual mode keeps the mix-marker fallback)
- update_filament_self_index_cache refreshed at every full-config
assignment
- tests: 0-based apply_override fallback, get_config_index_base
hit/miss/mixed-type cases
The resolvers are not consumed by the g-code writer yet. Non-Hybrid
g-code is unchanged except the config header, which now serializes the
three new static keys (defaults until the per-filament producer lands);
verified by the 19-fixture byte gate: 3 added header lines per fixture,
zero motion changes.
- get_extruder_nozzle_volume_count derives per-extruder volume-type slot
lists from extruder_nozzle_stats (absent stats = one slot per extruder)
- update_values_to_printer_extruders learns the slot layout: when any
extruder mixes volume types, option arrays keep one slot per
(extruder x volume type), extruder-ascending then volume-ascending;
single-slot resolution takes the filament's volume type on mixed
extruders
- update_values_to_printer_extruders_for_multiple_filaments applies a
per-filament nozzle_volume_type override from filament_volume_map
(when sized to the filament count) and remaps filament_self_index
through the same pipeline as every other filament key
- get_config_index_base + is_auto_filament_map_mode helpers (consumers
land with the per-filament config-index resolvers)
- callers updated: PresetBundle composition paths, PrintApply (counts
hoisted above the extruder_applied guard), Print write-back
- new tests: slot counting, Hybrid slot expansion incl. stride 2,
per-filament override, non-Hybrid degeneracy
Non-Hybrid printers keep their variant layout and values (proven by a
19-fixture byte gate; the only header delta is filament_self_index now
flowing through the same variant pipeline as its sibling filament
keys). Hybrid slices grow the config-block variant arrays to one entry
per sub-nozzle volume type; motion g-code is unchanged until the
g-code writer consumes the new slots.
Gate the entire parametric Design/CAD subsystem behind a single CMake
option so the fork can be built with or without it. With SLIC3R_CAD OFF
the build is behaviour-neutral against upstream OrcaSlicer; this is the
"parallel build" for upstream integration discussion.
Gated surface:
- option(SLIC3R_CAD) + add_definitions(-DSLIC3R_CAD)
- deps/OCCT/OCCT.cmake: BUILD_MODULE_ModelingAlgorithms=${SLIC3R_CAD}
(OFF matches upstream OCCT exactly; ON adds only TKFillet + TKOffset)
- TKFillet/TKOffset link, add_subdirectory(slvs) + libslvs
- CAD kernel + GUI sources, GLGizmoPrimitive (needs GeometryEngine),
CAD Catch2 tests
- 11 upstream C++ hook sites (GLCanvas3D, MainFrame, GLGizmosManager)
- TabPosition and gizmo EType enums switched to implicit numbering so
OFF reproduces upstream indices exactly
Verified on behemoth both ways: OFF and ON link snapmaker-orca +
libslic3r_tests (exit 0); ON shows the Design tab and passes 8 [design]
+ 1 [Deviation] tests, OFF omits them and shows upstream's tab layout.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BVzKmX6Y1aEteit1HTXG4Q
G-code post-processing is now a step of the slicing-pipeline plugin rather than a
separate capability type. One capability class can transform slices at the geometry
seams AND edit the final G-code, behind a single picker/option.
- Add SlicingPipelineStepPlugin::psGCodePostProcess (bound as
orca.slicing.Step.psGCodePostProcess). Unlike the geometry steps it fires from the
GUI export path in PostProcessor.cpp, not from Print::process(): ctx.print/ctx.object
are None and the plugin edits the file at ctx.gcode_path in place. It may run more
than once per slice (file export and/or upload) and its output is not shown in the
preview.
- Extend SlicingPipelineContext with gcode_path/host/output_name and a C++-only
full_config; config_value() falls back to it when there is no live Print.
- PostProcessor.cpp dispatches SlicingPipelinePluginCapability at psGCodePostProcess,
driven by the existing slicing_pipeline_plugin option.
- The exported G-code lives outside data_dir(), so the plugin audit sandbox would
block the write; the trampoline's audit setup grants ctx.gcode_path's folder as a
scoped allowed root, gated on a non-empty gcode_path so the geometry-step hooks gain
no extra filesystem access.
BREAKING CHANGE: the separate G-code post-processing capability type is removed.
- orca.gcode.GCodePluginCapabilityBase and orca.PluginType.PostProcessing are gone;
post-processing plugins migrate to orca.slicing.SlicingPipelineCapabilityBase +
Step.psGCodePostProcess (and gain ctx.params / ctx.config_value()).
- The post_process_plugin config option is removed; use slicing_pipeline_plugin.
Presets carrying the old key degrade to the standard unknown-key warning.
- Manifest type = "post-processing" now maps to Unknown (advisory only; the loader
dispatches on the C++ get_type()).
Also repairs two latent build breaks the branch carried: stale Step enum value usages
in test_slicing_pipeline_hook.cpp and a reference to the removed
ConfigOptionDef::PluginType::None in Tab::on_value_change (now is_plugin_backed()).
Adds the orca_gcode_stamp sample plugin and a psGCodePostProcess binding test.
Filament grouping already consumed per-filament forbidden nozzle volume
types, but every call site passed an empty map, so a variant-restricted
filament (e.g. one limited to "Direct Drive TPU High Flow") could be
auto-grouped onto an incompatible nozzle flow type on multi-variant
printers.
- add convert_to_nvt_type() to parse extruder variant strings
- add Print::get_filament_unprintable_flow(): forbidden volume types =
printer extruder variants minus the filament's declared variants;
filaments declaring no variants stay unrestricted
- feed the map into grouping at the by-object path (Print.cpp) and all
six mapping/planning sites in reorder_extruders_for_minimum_flush_volume
- unit-test the string parser
Non-restricted configurations produce an empty map, so existing
printers' grouping and g-code are unchanged.
round() already had unit coverage; floor() and ceil() had none. Add the missing
positive cases for both signs, plus round()'s half-away-from-zero tie-break, and
one negative case asserting that a name outside the grammar's built-in function
set is treated as an undefined variable and throws, rather than being passed
through to a math library.
* fix: prevent out-of-bounds crash in Arachne beading interpolation
SkeletalTrapezoidation::interpolate() derives an inset index from `left` but
uses it to index the merged beading, which follows the thicker of left/right.
When the thicker side has fewer insets, the index runs past the end and the
slicer crashes during "Generating walls".
Skip the adjustment when the index is out of range, as the adjacent guards
already do. interpolate() uses no instance state, so make it static and add a
regression test that exercises it directly.
Fixes#14584
feat: add regex_replace() string transform to filename templates
The filename template language could test strings (=~, !~, one_of) but
never rewrite one, so there was no supported way to reshape a placeholder
value, such as dropping a file extension from {first_object_name}.
Add regex_replace(subject, /pattern/, replacement), reusing the existing
regex-literal syntax and boost::regex engine. Every placeholder keeps
returning its exact value and the template does the transform explicitly:
{regex_replace(first_object_name, /\.[^.]*$/, "")} strip any extension
The replacement may reference capture groups ($1, $2, ...). It is one
grammar function mirroring digits(), with the name registered as a keyword
so it is not parsed as a variable.
Adds PluginHostSlicing, which registers the print-graph data model (Print,
PrintObject, Layer, LayerRegion, Surface, ExPolygon, extrusions, ...) into the
orca.host submodule in the same raw-class style as PluginHostApi's Model/Preset
graph, with shared helpers in PluginBindingUtils. SlicingPipelinePluginCapability
is trimmed to the capability surface (the standalone SlicingNumpy helper is folded
away). Adds the Twistify example plugin next to Inset and broadens the binding,
hook, and plugin-install tests.
On Linux every account shares /tmp, but slicing builds temp paths there under
fixed, app-owned names via temporary_dir() (model backups, STEP import,
part-skip). The first user to slice creates and owns those dirs, so the next
user cannot write under them and slicing crashes with "No such file or
directory".
Tag the app temp root with the user id at startup (<temp>/orcaslicer_<uid>)
so every temporary_dir() consumer is isolated at once. The id stays at the
top level of the world-writable system temp so each user's dir is created
directly there; a shared parent dir would be owned by whichever user made it
first. The root is pre-created because STEP import writes into it directly.
Windows keeps the plain temp dir since it is already per-user.
Fixes#10108. Same root cause as #5969.
* Support accessing `coFloatsOrPercents` values in gcode template (OrcaSlicer/OrcaSlicer#14522)
* Vector option values are separated by comma
* Fix wrong cast used for checking nullability
gcc reads "\x10cad..." as one escape (c/a/d are hex digits → 0x10CAD > 255).
Split the string literal so the \x10 escape terminates. clang let it slide;
gcc (Linux/Windows CI) errored and blocked the OrcaSlicer build.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BVzKmX6Y1aEteit1HTXG4Q
orca_cad's test_geometry.cpp is upstream Catch2 v3 with no CAD suite, so the
surface_deviation check lives alongside the CAD tests here. Built + ran green
in orca_cad's kernel-test docker: All tests passed (4 assertions). Both forks
now carry the test.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BVzKmX6Y1aEteit1HTXG4Q
Port of the snaporca CAD work to the mainline fork.
- Onshape default planes (XY/XZ/YZ) at the bed centre (transparent, labelled);
modeling origin unified to the bed centre (CadDocument::modeling_origin);
world-axis triad moved to the bed centre on the Design canvas only.
- Datum plane: clickable ghost-plane base pick + draggable offset arrow.
- Slot: dims reassessed to inter-centre distance / radius / angle; fixed the
duplicate cap-arc radius quote.
- Hole: 3D cube move-handle on the face; binds to the face on the first click;
decluttered side-distance construction lines.
- Thread: derive the M spec (diameter/pitch/depth) from a picked cylindrical
surface or circular edge (GeometryEngine::circle_of_edge); fuse the helical
ridge onto the existing body; MakePipeShell fixed-binormal sweep (uniform, no
twist) + self-intersection/param guards.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BVzKmX6Y1aEteit1HTXG4Q
Mirror of snaporca-cad a82cb0d. snaporca-88g — three [CadDocument] tests
proving recompute() replays the whole timeline, so editing any feature (not
just the last) rebuilds downstream: mid-timeline extrude edit -> fillet
rebuilds; first-feature sketch edit propagates the chain; the same survives
serialize_recipe()/deserialize_recipe(); a sketch->extrude->hole->chamfer
chain rebuilds hole+chamfer on a mid-edited extrude. All 3 pass (34 assertions,
Catch2 v3). on_edit_feature edits the tree-selected feature at any position
(13/16 types); Import/Boolean/Cut get a status message instead of a silent
no-op (full dialogs = follow-up snaporca-nu9).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BVzKmX6Y1aEteit1HTXG4Q
CalibPressureAdvancePattern::line_width_first_layer() returned the raw
initial_layer_line_width, so a value of 0 (which means "use the default")
fed 0 into the Flow spacing math and threw FlowErrorNegativeSpacing,
crashing the whole app when slicing a PA pattern calibration.
Mirror the guard already present in the sibling line_width(): when the
configured width is non-positive, fall back to auto_extrusion_width.
Add a libslic3r regression test covering the width resolution.
Fixes#13188
Mirror of snaporca-cad ad822bf. The earlier mirror (2c0140afb9) implemented
persistence in the PrusaSlicer 3mf.cpp, but the GUI saves/loads projects via the
BBS-native backend (store_bbs_3mf / load_bbs_3mf), so the recipe was never
written to nor read from GUI-saved projects.
- bbs_3mf.cpp: BBS_CAD_RECIPE_FILE = "Metadata/SnapOrca_cad.bin"; writer
_add_cad_recipe_file_to_archive (called after layer-height in store_bbs_3mf);
reader branch in _load_model_from_file's metadata dispatch loop (iterate +
iequals on m_filename — mz_zip_reader_locate_file does not work on these
archives).
- Plater.cpp: load_files carries the Model-level cad_recipe onto q->model().
- test_3mf.cpp: [3mf] test asserting store_bbs_3mf embeds the recipe entry
byte-for-byte (read back via miniz, Catch2 v3).
Verified on behemoth: libslic3r_tests + the new [3mf] BBS test pass; orca-slicer
GUI links clean. Round-trip verified live on snaporca-cad (identical code path).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BVzKmX6Y1aEteit1HTXG4Q
Dual-fork mandate: port the parametric-recipe 3MF persistence from
snaporca-cad commits ed19eac+147e1c4 so a saved project reopens with the
editable CAD feature tree, not just the baked mesh.
Shared kernel/format/GUI (identical to snaporca-cad):
- CadDocument serialize_recipe/deserialize_recipe (cereal BinaryArchive,
versioned) + CadFeature split save/load + imported_solid<->BRep string.
- Model::cad_recipe carried through 3MF zip entry Metadata/SnapOrca_cad.bin
(writer + binary-verbatim reader branch).
- DesignPanel on_commit() stamps the recipe; on_tab_shown() rehydrates a
loaded project via load_recipe() (deserialize -> feed_bodies + refresh_tree).
Mainline-only adapters (no snaporca-cad counterpart — Catch2 v3 vs v2):
- tests/libslic3r/test_caddocument.cpp: <catch2/catch_all.hpp> +
`using Catch::Approx;` (v3 scopes Approx under Catch::).
- tests/libslic3r/CMakeLists.txt: register test_caddocument.cpp (the
original CAD port had left it out of the test build).
Verified on behemoth (snaporca-deps toolchain): libslic3r_tests clean;
[CadDocument] 17/18 (only the pre-existing tangent-to-circle SIGABRT fails,
identical to snaporca-tkz); K1 serialize round-trip + version-reject pass
(13 assertions); new [3mf] "CAD recipe blob survives a 3mf save/load cycle"
passes byte-for-byte; orca-slicer GUI links clean (186/186, DesignPanel.cpp
compiled). Interactive :10 click-through pending (no Design-tab automation).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BVzKmX6Y1aEteit1HTXG4Q
* profiles: enforce globally-unique, per-vendor-namespaced setting_id
Many non-Bambu vendors copied Bambu's generic setting_ids (GFSA04 alone
appeared in 1557 files), so setting_id was not globally unique. This
namespaces every vendor's ids and reserves Bambu/OrcaFilamentLibrary space.
- Reserve "G*" (Bambu) and "O*" (OrcaFilamentLibrary) id spaces.
- Assign each other vendor a 2-char prefix (first+last letter, collision
resolved) and renumber every instantiated preset to <PREFIX><NNNN>.
- Strip setting_id from base profiles (instantiation:false) per Bambu's
convention; assign one to instantiated presets that lacked it.
- Remove the pre-existing misspelled "settings_id" key (91 files).
- filament_id is left untouched (it is a per-material id).
- Add one-time migration script scripts/assign_vendor_setting_ids.py with a
persisted registry resources/profiles/vendor_prefixes.json. Re-runs freeze
existing ids; only new vendors/profiles get new ids.
- Bump version in each changed vendor index file.
- Extend scripts/orca_extra_profile_check.py with a CI guard: global
uniqueness, in-namespace, no base setting_id, no gaps, no settings_id typo.
7425 profile files changed across 61 vendors; 0 cross-vendor collisions;
validator clean; migration idempotent. BBL and OrcaFilamentLibrary id spaces
untouched.
* profiles: add setting_id authoring guide for new vendors / profiles
* profiles: drop in-repo README; setting_id guide now lives in the wiki
* profiles: derive setting_id deterministically from vendor/type/name
* bump profile version
* fix(libnest2d): skip the excluded-region alignment pass when there are none
NfpPlacer::finalAlign(), run from clearItems() and the destructor, always
ran the "find a best position inside the NFP of fixed items" pass even when
no items are fixed. With nothing to avoid, calcnfp() computes the inner-fit
NFP of the pile and can feed clipper a coordinate outside its allowed range.
On Linux/clang the value stays in range so it went unnoticed; on MSVC the
clipper "Coordinate outside allowed range" exception escapes the noexcept
destructor and aborts the process (exit 0xC0000409).
Build the excluded set up front and only run the pass when it is non-empty.
The block exists solely to keep the pile clear of fixed items (excluded
regions / wipe tower), so it is a no-op when there are none and the
wipe-tower behaviour is unchanged.
* test(libnest2d): remove dead nesting tests and split the suite by feature
Seven of the suite's hidden [.] test cases drove code paths Orca abandoned
at the BambuStudio fork: BottomLeftPlacer (used nowhere in src/) and the
stock default NfpPlacer backend, which returns zero bins in Orca. They have
been red since the fork and are never registered with ctest. Remove them.
Split the 1,000-line libnest2d_tests_main.cpp into per-feature files, per the
repo convention, sharing a header for the no-fit-polygon backend setup that
every translation unit must agree on (ODR):
libnest2d_tests.cpp Item and nest() basics
test_geometry.cpp geometry primitives
test_nfp.cpp no-fit-polygon machinery
libnest2d_test_utils.hpp shared includes and the NFP backend specialisation
Along the way: drop a debug exportSVG() helper that only wrote a file on test
failure (so the suite never leaves stray assets), convert the deprecated
Catch::Approx to WithinRel/WithinAbs matchers, and give the tests descriptive
names.
* test(libnest2d): add NfpPlacer unit tests
NfpPlacer is the placement engine the arranger drives, but the suite only
covered the geometry primitives. Add a fixture and five tests that exercise
pack()/accept() directly: a single item lands in the bin, an oversized item
is rejected, the first item is seeded for every starting point, many items
pack without overlap, and the rotation candidates are searched. This lifts
nfpplacer.hpp line coverage from 42% to 87% in the libnest2d suite.
* test(libslic3r): add arrangement::arrange() integration coverage
The libnest2d suite cannot reach Orca's real nesting entry point because it
does not link libslic3r. Add test_arrange.cpp driving arrangement::arrange():
items land on the bed and within bounds, do not overlap, are spaced by their
inflation, an oversized item stays unplaced, overflow spills onto virtual beds,
an empty input is a no-op, and the DONT_ALIGN and USER_DEFINED final-alignment
paths are exercised. A self-test guards the overlap check the other cases use.
Fix Unit Tests CI job silently running zero tests
scripts/run_unit_tests.sh selected tests with `ctest -L "Http|PlaceholderParser"`,
but catch_discover_tests() was called without ADD_TAGS_AS_LABELS, so Catch2 tags
were never registered as CTest labels. The -L filter matched nothing and the job
passed green while running no tests ("No tests were found!!!"). Tests have not run
in CI since PR #11485 added that -L line (2025-12-23).
Register tags as labels via a shared orcaslicer_discover_tests() wrapper in
tests/CMakeLists.txt (passing ADD_TAGS_AS_LABELS), routed through all five test
suites. Restore full-suite execution by replacing the narrow -L selection with a
`-LE NotWorking` exclusion, so all reliable tests gate PRs again (the suite ran in
full before #11485).
Tag the two OrcaCloudServiceAgent display-name tests [NotWorking]: their
constructor reaches wxStandardPaths::Get().GetUserDataDir(), which dereferences
the null wxTheApp in the headless test binary and segfaults on every platform.
Excluded until the agent can be constructed without the wx app context.
CI now runs 151 tests (was 0) and passes.
* Add test for Arachne duplicate wall segment detection
Add test cases that reproduce an issue where Arachne generates
duplicate/coinciding extrusion segments at certain min_bead_width settings.
Test configuration:
- Profile: 0.28mm Extra Draft @BBL X1C (0.4mm nozzle, 0.28mm layer)
- outer_wall_line_width: 0.42mm, inner_wall_line_width: 0.45mm
- wall_loops: 2, precise_outer_wall: enabled
- Test polygon: outer rectangle (0,0)-(20,20) with inner cutout (0.5,0.5)-(19.5,19.5)
This creates a 0.5mm wide frame around the perimeter.
Results:
- 50% min_bead_width (0.20mm): FAILS - detects 4 duplicate segments (all 4 sides)
- 60% min_bead_width (0.24mm): PASSES - no duplicates
At 50%, Arachne generates two separate closed loops that share all 4 edges
of the inner square. At 60%, Arachne generates a single closed loop.
SVG output is exported to /tmp/opencode/ for visual debugging.
* Fix Arachne duplicate extrusion caused by bead count mismatch
WideningBeadingStrategy::compute() used optimal_width (inner wall width)
to determine if a thin wall should produce a single bead. However,
getOptimalBeadCount() uses optimal_width_outer (outer wall width) via
RedistributeBeadingStrategy to decide the bead count.
This inconsistency caused situations where getOptimalBeadCount() returned
2 beads, but compute() produced only 1 bead at full thickness. The single
bead was then generated for both inner and outer contours, resulting in
duplicate extrusion paths.
Fix: Use getTransitionThickness(1) instead of optimal_width. This method
returns the exact threshold for the 1-to-2 bead transition, ensuring
consistency between bead count calculation and bead generation.
Reproduces with: 50% min_bead_width, 0.42mm outer wall, 0.45mm inner wall,
0.5mm polygon inset creating ~0.38mm wall thickness.
Fixes#13917
---------
Co-authored-by: SoftFever <softfeverever@gmail.com>
* Update eigen from v3.3.7 to v5.0.1.
This updates eigen from v3.3.7 released on December 11, 2018-12-11 to v5.0.1
released on 2025-11-11. There have be a large number of bug-fixes,
optimizations, and improvements between these releases. See the details at;
https://gitlab.com/libeigen/eigen/-/releases
It retains the previous custom minimal `CMakeLists.txt`, and adds a
README-OrcaSlicer.md that explains what version and parts of the upstream
eigen release have been included, and where the full release can be found.
* Update libigl from v2.0.0 (or older) to v2.6.0.
This updates libigl from what was probably v2.0.0 released on 2018-10-16 to
v2.6.0 released on 2025-05-15. It's possible the old version was even older
than that but there is no version indicators in the code and I ran out of
patience identifying missing changes and only went back as far as v2.0.0.
There have been a large number of bug-fixes, optimizations, and improvements
between these versions. See the following for details;
https://github.com/libigl/libigl/releases
I retained the minimal custom `CMakeLists.txt`, added `README.md` from the
libigl distribution which identifies the version, and added a
README-OrcaSlicer.md that details the version and parts that have been
included.
* Update libslic3r for libigl v2.6.0 changes.
This updates libslic3r for all changes moving to eigen v5.0.1 and libigl
v2.6.0. Despite the large number of updates to both dependencies, no changes
were required for the eigen update, and only one change was required for the
libigl update.
For libigl, `igl::Hit` was changed to a template taking the Scalar type to
use. Previously it was hard-coded to `float`, so to minimize possible impact
I've updated all places it is used from `igl::Hit` to `igl::Hit<float>`.
* Add compiler option `-DNOMINMAX` for libigl with MSVC.
MSVC by default defines `min(()` and `max()` macros that break
`std::numeric_limits<>::max()`. The upstream cmake that we don't include
adds `-DNOMINMAX` for the libigl module when compiling with MSVC, so we need
to add the same thing here.
* Fix src/libslic3r/TriangleMeshDeal.cpp for the unmodified upstream libigl.
This fixes `TriangleMeshDeal.cpp` to work with the unmodified upstream
libigl v2.6.0. loop.{h,cpp} implementation.
This file and feature was added in PR "BBS Port: Mesh Subdivision" (#12150)
which included changes to `loop.{h,cpp}` in the old version of libigl. This PR
avoids modifying the included dependencies, and uses the updated upstream
versions of those files without any modifications, which requires fixing
TriangleMeshDeal.cpp to work with them.
In particular, the modifications made to `loop.{h,cpp}` included changing the
return type from void to bool, adding additional validation checking of the
input meshes, and returning false if they failed validation. These added
checks looked unnecessary and would only have caught problems if the input
mesh was very corrupt.
To make `TriangleMeshDeal.cpp` work without this built-in checking
functionality, I removed checking/handling of any `false` return value.
There was also a hell of a lot of redundant copying and casting back and forth
between float and double, so I cleaned that up. The input and output meshs use
floats for the vertexes, and there would be no accuracy benefits from casting
to and from doubles for the simple weighted average operations done by
igl::loop(). So this just uses `Eigen:Map` to use the original input mesh
vertex data directly without requiring any copy or casting.
* Move eigen from included `deps_src` to externaly fetched `deps`.
This copys what PrusaSlicer did and moved it from an included dependency under
`deps_src` to an externaly fetched dependency under `deps`. This requires
updating some `CMakeList.txt` configs and removing the old and obsolete
`cmake/modules/FindEigen3.cmake`. The details of when this was done in
PrusaSlicer and the followup fixes are at;
* https://github.com/prusa3d/PrusaSlicer/commit/21116995d78b74dee69ce42874b643012a7c14e5
* https://github.com/prusa3d/PrusaSlicer/issues/13608
* https://github.com/prusa3d/PrusaSlicer/pull/13609
* https://github.com/prusa3d/PrusaSlicer/commit/e3c277b9eea0ca25e90e2378bcda198d56c4d304
For some reason I don't fully understand this also required fixing
`src/slic3r/GUI/GUI_App.cpp` by adding `#include <boost/nowide/cstdio.hpp>` to
fix an `error: ‘remove’ is not a member of ‘boost::nowide'`. The main thing I
don't understand is how it worked before. Note that this include is in the
PrusaSlicer version of this file, but it also significantly deviates from what
is currently in OrcaSlicer in many other ways.
* Whups... I missed adding the deps/Eigen/Eigen.cmake file...
* Tidy some whitespace indenting in CMakeLists.txt.
* Ugh... tabs indenting needing fixes.
* Change the include order of deps/Eigen.
It turns out that although Boost includes some references to Eigen, Eigen also
includes some references to Boost for supporting some of it's additional
numeric types.
I don't think it matters much since we are not using these features, but I
think technically its more correct to say Eigen depends on Boost than the
other way around, so I've re-ordered them.
* Add source for Eigen 5.0.1 download to flatpak yml config.
* Add explicit `DEPENDS dep_Boost to deps/Eigen.
I missed this before. This ensures we don't rely on include orders to make
sure Boost is installed before we configure Eigen.
* Add `DEPENDS dep_Boost dep_GMP dep_MPFR` to deps/Eigen.
It turns out Eigen can also use GMP and MPFR for multi-precision and
multi-precision-rounded numeric types if they are available.
Again, I don't think we are using these so it doesn't really matter, but it is
technically correct and ensures they are there if we ever do need them.
* Fix deps DEPENDENCY ordering for GMP, MPFR, Eigen, and CGAL.
I think this is finally correct. Apparently CGAL also optionally depends on
Eigen, so the correct dependency order from lowest to highest is GMP, MPFR, Eigen, and CGAL.
---------
Co-authored-by: Donovan Baarda <dbaarda@google.com>
Co-authored-by: Noisyfox <timemanager.rick@gmail.com>
* Add OrcaCloud sync platform and preset bundle sharing system
Introduce OrcaCloud, a cloud sync platform for user presets, alongside
a preset bundle system that enables sharing printer/filament/process
profiles as local exportable bundles or subscribed cloud bundles.
OrcaCloud platform:
- Auth to Orca Cloud
- Encrypted token storage (file-based or system keychain)
- User preset sync with
- Profile migration from default/bambu folders on first login
- Homepage integration with entrance to cloud.orcaslicer.com
Preset bundles:
- Local bundle import/export with bundle_structure.json metadata
- Subscribed cloud bundles with version-based update checking
- Thread-safe concurrent bundle access with read-write mutex
- Canonical bundle preset naming (_local/<id>/... and _subscribed/<id>/...)
- Bundle presets are read-only; grouped under subheaders in combo boxes
- PresetBundleDialog with auto-sync toggle, refresh, update notifications
- Hyperlinked bundle names to cloud bundle pages
Co-authored-by: Sabriel Koh <sabrielkcr@gmail.com>
Co-authored-by: Derrick <derrick992110@gmail.com>
Co-authored-by: Mykola Nahirnyi <mnahirnyi@amcbridge.com>
Co-authored-by: Ian Chua <iancrb00@gmail.com>
Co-authored-by: Draginraptor <draginraptor@gmail.com>
Co-authored-by: ExPikaPaka <112851715+ExPikaPaka@users.noreply.github.com>
Co-authored-by: Ian Bassi <ian.bassi@outlook.com>
Co-authored-by: Ocraftyone <Ocraftyone@users.noreply.github.com>
Co-authored-by: yw4z <ywsyildiz@gmail.com>
Co-authored-by: peterm-m <101202951+peterm-m@users.noreply.github.com>
* Fixed an issue on Windows it failed to login Orca Cloud with Google account