mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-02 05:11:00 +00:00
4b56c2a5143cba8553a42a532bb0779c9cf641bb
37
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
94266c2819 |
Fill Settings Missing From a CLI Project From Its System Presets (#15953)
* Fill Settings Missing From a CLI Project From Its System Presets A project saved before a printer or process option existed has no value for it. The GUI takes such keys from the project's system preset; the CLI left them at the option default, so e.g. extruder_clearance_dist_to_rod sliced as 40 instead of the P1S's 33. The CLI now resolves the project's system printer and process presets by name and copies the keys the project lacks, skipping preset bookkeeping, print-host keys, the extruder variant layout and keys the legacy handler drops. PresetBundle::resolve_system_preset finds the vendor through its manifest or preset cache, so it also works in release builds, which ship vendors as caches only. * Load a CLI Project's Printer and Process Settings as the GUI Does The CLI filled only the keys a project lacked from its system preset. The GUI builds a project preset differently: the project's values go over the default preset, without the print-host keys, and every key the project does not list in different_settings_to_system is refreshed to its base system preset's current value. After a profile update the two sliced the same project differently. That step now lives in Preset::load_external_config, which takes plain configs and gets the base preset from a callback, so the collection lookup stays in PresetCollection. PresetBundle::project_different_keys builds the kept-key set from a project's escaped entry, adding the preset bookkeeping keys, and is used by both the GUI and the CLI. PresetCollection::load_external_preset calls the shared step with no change in behaviour. The CLI now builds the project's printer and process configs with the same step, passing the system preset from resolve_system_preset. That drops the hand-kept skip list for print-host and variant-layout keys and the legacy-key check: the shared step already excludes print-host keys and maps per-variant values onto the base preset's variant layout. The --uptodate path and a printer or process given on the command line are left as they were. Because the GUI's kept-key set always holds the bookkeeping keys, the refresh runs for every project that names a system preset, so the CLI now loads that preset's vendor on every such run. |
||
|
|
2769b12ce7 |
Give OBJ Quad and Flipped Faces the Texture Coordinates of Their Own Corners (#15977)
load_obj emits the second triangle of a quad from corners 0, 2 and 3, but read its texture coordinates from corners 0, 1 and 2, so half of every textured quad sampled the wrong part of the texture. The corner indices are now passed down to where the coordinates are read. A mesh with inward-facing triangles is flipped after loading, which swaps corners 1 and 2 of every face. The texture coordinates were left as they were. They are now swapped along with the corners. |
||
|
|
203bc63f35 |
Escape Project Metadata in the Project Page and Restrict Accessory Opening (#15956)
* Escape Project Metadata in the Project Page and Restrict Accessory Opening The Project page rendered the model and profile name, author, description and accessory file names from the 3MF as live HTML. Names, authors and file names are now set as text, and the file list is built from DOM nodes with bound click handlers instead of concatenated markup. Descriptions can legitimately carry rich-text HTML, so they are rebuilt from an inert DOMParser document, keeping only plain formatting tags, http(s) links and http(s) images, with every other attribute dropped. Opening an accessory from the page now only launches regular files that lie inside the project's extracted auxiliary directory. The containment check is a new libslic3r helper, is_absolute_path_within_root, built on is_path_within_root so symlinks leading out of the root are rejected too. * Tighten Project Page Description Rendering and Keep More Formatting Link and image URLs in descriptions must now start with an http or https scheme as written and parse as such with the URL parser. Preview images are built as DOM nodes like the file list, and accessory names show their full text as a tooltip. Descriptions keep more plain formatting: del, ins, figure, figcaption, dl, dt, dd, caption, q, abbr, kbd and wbr, plus alt, title, width and height on images, colspan and rowspan on table cells and start on ordered lists. Numeric attributes must be plain integers. Embedded YouTube players become a link to the video. * Confirm Before Opening Program Attachments and Load Only HTTPS Images Opening a project attachment whose type runs as a program or script (executables, installers, shortcuts, shell and PowerShell scripts, macOS command files and apps, Linux desktop entries) now asks for confirmation first. The check lives in libslic3r as is_executable_file_name and ignores the trailing dots and spaces Windows strips from file names. Images in project descriptions are kept only when they load over https, so opening the Project tab no longer issues plain-http requests. * Open Project Attachments Through One Guarded Helper The Edit Project Info view launched attachments directly, without the checks the project page has. Both now call desktop_open_project_attachment, which checks that the file is inside the auxiliary directory, asks for confirmation where needed and then opens it. The auxiliary root was built through encode_path, which returns code page bytes on Windows, while boost::filesystem reads a narrow string as UTF-8. With a non-ASCII temporary directory the root never matched and no attachment opened. It is now built from the UTF-8 path directly. The list of program extensions could not be kept complete and let unknown types open without a prompt. It is replaced by is_safe_to_open_file_name, a list of plain document, image, model and video types that open directly. Everything else asks first. |
||
|
|
e40030cf81 |
Stop Malformed Network Responses from Crashing the App (#15947)
* Stop Malformed Network Responses from Crashing the App Duet, MKS and UltiMaker parsed print host replies with boost read_json inside the HTTP completion callback with no try, so an HTML or truncated reply threw out of the Physical Printer Test button and terminated the app, or killed the upload queue thread. The five identical copies of the parser (ESP3D's and Flashforge's were unused) are replaced by one shared PrintHost::get_err_code_from_body that reports a non-JSON reply as an error. The upload queue now catches a failing job per job, so one bad upload no longer leaves later jobs queued forever. Flashforge read material station slots with nlohmann value(), which throws on off-type fields or non-object entries. The parsing moves into Flashforge::parse_material_slots, which reads fields leniently with the existing try_parse_json_int and skips bad entries. UserManager::parse_json parsed the payload before its try block; the parse now happens inside it. * Keep UploadFinished Paired with UploadStarted When an Upload Throws The exception from a throwing upload was caught around perform_job, so the UploadFinished lifecycle event was skipped and plugins saw an upload start that never finished. The catch now sits around the upload call. The error is reported through the job's error callback and UploadFinished is fired with an error code, as for any other failed upload. The worker keeps running for the next job. The started, upload and finished sequence moved to PrintHostJobQueue::upload_job so it can be tested without the dialog. |
||
|
|
ba468c842d |
Confine Updater and Plugin Archive Extraction to the Target Directory (#15957)
* Confine Updater Archive Extraction to the Target Directory The preset updater extracted downloaded archives by appending each entry name to the cache directory, and the network plugin installer did the same for the plugin folder, without checking that the result stays inside it. Move the updater's extraction into libslic3r as extract_archive_confined, which validates every entry with is_path_within_root before writing anything and fails the whole archive if one entry resolves outside the target. The plugin installer now rejects such an entry the same way. Well formed archives extract exactly as before. * Harden Archive Extraction Against Symlinks The plugin installer now creates a symlink entry only when its target is relative and, joined to the link's own directory, passes is_path_within_root, via the new is_symlink_target_within_root helper. Before writing any entry it checks the destination with symlink_status, so an existing symlink, dangling or not, is replaced rather than followed, and it creates parent directories inside the existing error handling. extract_archive_confined replaces a symlink at a destination file the same way. is_path_within_root now ignores a trailing separator on the root, which previously made every path fail the check. * Validate Plugin Symlink Targets Before Replacing Existing Files A symlink entry's target is now read and checked before anything already at its destination is removed or renamed aside, so an archive rejected for its link target leaves the installed plugin files in place. * Reject Paths with an Embedded NUL When Confining Extraction is_path_within_root compared each component with "..", so a name such as "..\0" passed the check. The filesystem calls stop at the NUL and act on a shorter path than the one that was checked: a symlink target read from a plugin archive as raw bytes was created as "..", pointing out of the plugin directory. A path containing a NUL is now rejected before anything touches the filesystem, which covers every caller, including entry names taken from the Unicode Path extra field. |
||
|
|
e68694dbaf |
Percent-Encode Local File URLs for Embedded Web Pages (#15961)
* Percent-Encode Local File URLs for Embedded Web Pages The Home tab, setup wizard, Project tab and other embedded pages were loaded from file:// URLs built by pasting the resources path into a string. A '#', '%' or '?' in the install path was then read as a URL fragment, escape or query, so the pages failed to load, for example a portable install under D:\#OneDrive showed a directory listing instead of the setup wizard. Add file_url_from_path(), built on wxFileSystem::FileNameToURL, and use it wherever a local page or image URL is built from a path. Queries such as ?lang= are appended after the path is encoded. The wizard's printer cover images are passed to the page as file URLs too. * Encode the Login Error Page URL and Cover More Windows Path Forms The login dialog's error page was still loaded from a raw resources path; it now uses file_url_from_path like the other local pages. The Windows file URL tests now also cover a resources path joined with a forward-slash relative path, as the callers build them, and a UNC path. * Build the Flush Dialog Page URLs with the Shared Helper WipingDialog and NozzleListTable still called wxFileSystem::FileNameToURL directly. They now go through file_url_from_path like every other local page, so the URLs are built in one place. Adds a test for a resources directory with a '#' in its name, which the plugin page check did not recognise before. |
||
|
|
8ffd3e514e |
Harden OBJ and DRC Import Against Malformed Files (#15948)
* Validate OBJ Texture-Coordinate Indices load_obj read the texture coordinates of a face without checking the vt index, so a face referencing a vt past the end of the list read out of bounds and crashed, and a face vertex with no vt read index -1. Out-of-range or missing indices now fall back to a zero UV. The face keeps its entry in the per-face UV list, so the following faces stay aligned, and the geometry loads as before. Negative (relative) vt indices were also rebased by dividing the float count by 3, but each vt stores two floats. * Reject DRC Meshes Without Positions or with Invalid Face Indices load_drc dereferenced the POSITION attribute without checking that the mesh has one, and trusted the decoded face indices, which the Draco decoder does not check against the point count. Both now fail the load cleanly. A failed vertex conversion is treated the same way. The libslic3r tests link Draco so they can encode the malformed meshes in-test. * Keep OBJ Texture Coordinates That Carry a W Component The vt parser stopped reading the optional third component when texture coordinates were cut down to u and v, but the check that nothing is left on the line stayed. A legal "vt u v w" line was therefore rejected and silently dropped, shifting every later texture index. The w component is parsed again and discarded. The texture coordinate stride is now a named constant, OBJ_TEXCOORD_LENGTH, used by the parser and the importer, so the relative-index rebase cannot drift from the storage layout again. |
||
|
|
d35ea27ea5 |
Validate Zip Entry Sizes Before Parsing 3MF XML (#15958)
The 3MF importers read XML entries into a single expat buffer whose size is an int, while the archive extraction used the entry's 64-bit declared size. The two could disagree for entries declaring more than INT_MAX bytes. Reject such entries before allocating, and use one size for the buffer, the extraction and the parse. This applies to the BBS importer, the PrusaSlicer importer and the PrusaSlicer fingerprint probe. The load now fails with an error instead. |
||
|
|
490d134507 |
Harden 3MF Loading Against Malformed Plate IDs and Paint Data (#15959)
* Reject 3MF Plate IDs Below 1 Instead of Indexing Before the Plate List The plate importer copied each plater_id from model_settings.config into the 1-based plate list after checking only the upper bound, so plater_id="0" wrote to plate_data_list[-1] and crashed on load. Both copy sites now reject ids below 1 with the same "invalid plate index" error already used for ids past the end. * Drop Malformed 3MF Paint Data Instead of Reading Past the Bitstream Painted facets are decoded from a bitstream a nibble at a time with no bound check, so a truncated or corrupt paint string in a 3MF (for example split codes with no children behind them) read past the end and crashed on load and slice. A one- or two-side split naming side 3 also indexed past the triangle's vertices. Every nibble read now goes through a bounds-checked reader. Loading validates each triangle's tree and drops a malformed one with a warning, so the stored data, used extruder states and later decoding all agree. deserialize() also unwinds and clears any triangle whose tree is incomplete or malformed, and has_facets() stops at a truncated triangle. Valid streams decode unchanged. |
||
|
|
41eeaf3883 |
Sanitize Server-Supplied Download File Names (#15955)
* Sanitize Server-Supplied Download File Names The URL downloader used the file name from the Content-Disposition header as given, without the cleaning and unused-name search applied to the URL-derived name. Reduce the header name to a sanitized base name with the new sanitize_file_basename helper, which splits on both path separators and rejects names made only of dots and spaces. Run the result through the same unused-name search as the URL-derived name, now shared in find_unused_filename, and fall back to the URL-derived name when nothing usable remains. * Sanitize Download Names Before Choosing an Unused One The unused-name search probed the name as given and sanitized the result afterwards, so a name whose special characters are replaced could be mapped onto a file that already exists. Move the search into libslic3r as find_unused_filename, sanitize first and probe the name that is actually written. The download marker path is shared through download_marker_path. Restore the last tried name in the error reported when no free name is found, and cover the search with unit tests. * Keep Downloads on an Unused Name Until They Complete When the server supplied the name, the download marker stayed under the URL-derived name, so the adopted name was not reserved against other downloads. The final rename also replaced any file that took the name while the download ran. Move the marker to the adopted name before any data is written, and check the name again right before the final rename, picking the next free name if it is taken by then. * Sanitize the File Name of Model Import Links The model import took the file name from the link as given and only avoided an existing file with a substring match on the folder listing. Reduce the name to a sanitized base name, falling back to untitled.3mf, choose the name with the shared unused-name search, and check it again before the final rename. * Handle Filesystem Errors When Finishing a Model Import Download Choosing the final name and moving the downloaded project into place could throw from inside the download callback. Any such error now removes the temporary file and reports the existing import failure message. |
||
|
|
cda1588578 |
Draw the Toolpaths Top-Down When the Camera Looks Down on the Print (#15883)
The segments come in print order, bottom layer first, which seen from above is back to front: every hidden fragment is shaded before the one that covers it, and on an integrated GPU that overdraw is most of the frame. Drawing the instances last to first whenever the camera looks down lets the depth test reject the hidden fragments instead. Side views and views from below keep the print order, and the shadow-caster pass is unchanged. |
||
|
|
db10c719f6 |
Keep the Scarf Seam Free of Sub-Millimetre Segments (#15832)
A scarf joint split the loop at exactly the scarf length, so the remainder of the segment the split landed in became the first flat segment, often a fraction of a millimetre. The seam insertion also leaves segments of a few micrometres at both ends of the loop, which a scarf extrudes through where a plain loop would start or stop. With junction deviation the planner treats such short moves as tight corners, limited by the Z axis acceleration at the end of the ramp, and slows to a third of the wall speed or nearly halts at the seam. Extend the ramp to the next vertex when the remainder would be shorter than half a scarf step, capped at a millimetre, beyond which planners treat a move as ordinary; the scarf only grows, never shrinks. Drop the vertex next to the seam point at either end when that segment is shorter than an eighth of a common line width, so the loop still starts and ends at the seam; a trimmed path loses its arc fitting and prints as line segments. Clamp the scarf length to the trimmed loop so a scarf covering a whole loop still ends at full flow. The descending pass reuses the same path, so both wedges stay consistent, and a flat part that would collapse to a single point is dropped. |
||
|
|
ddf9b85169 |
Reserve the Prime Tower When the CLI Arranges a Project (#15837)
The global arrange branch, taken by --arrange with all plates selected, only reserved the prime tower when filament ids had been given on the command line for STL input. A project carries its filament use per plate and its own tower positions, but that set was empty for it, so the tower was never an obstacle: the arranged pile was centred over it and the slice then failed on a G-code path conflict. When no filament ids were given, count the filaments each plate uses and reserve a tower on every plate that needs one, keeping the project's tower position instead of resetting it to the default. Only a tower the slicer will print is reserved: the prime tower must be enabled, and a by-object print gets none unless a smooth timelapse needs it, as the per-plate arrange decides. Overflow beds are sized for the busiest plate. The STL route is unchanged. |
||
|
|
af52da061f |
Report Per-Object Slicing Errors in the CLI (#15834)
G-code generation collects errors raised per object, such as an empty first layer, into one SlicingErrors exception whose own message is just "Errors". The CLI's generic handler printed that word and recorded the generic slicing error text, so a headless caller had nothing to act on. Let Print render the per-object messages with each object's name, and have the CLI catch SlicingErrors ahead of the generic handler, print that text and record it as the result's error string. The exit code is unchanged. A unit test lifts a cube off the bed and checks the message names the object. |
||
|
|
ef956b995a |
Pick the Parity Build From the Unfiltered Run List and Allow Pinning One (#15823)
The nightly found its build with a filtered run listing (branch=main, status=success) and trusted the first result. GitHub serves filtered listings from a run search index that has intermittently returned weeks-old results, so some nights tested a build from weeks earlier and reported its differences as regressions. The same filter also matched fork PR builds whose branch is named main. The build is now picked from the unfiltered listing, which stays current, and filtered here: a successful build_all run of this repository on the requested branch. Fork PR builds are excluded by repository. A feature branch is normally built only for its PR, so this repository's own PR builds stay eligible, but a PR build compiles the PR merged into its base rather than the head commit the later jobs check out, so a push or dispatch build of the branch is preferred when the same page of the listing has one. A scheduled run fails instead of testing a build more than 48 hours old, and every run names the build it tested in the job summary. Manual runs scan further back, so a branch that last built weeks ago can still be tested, and a new build_run_id input pins one build_all run, read directly rather than through a search. |
||
|
|
89dc4e1b99 |
Add an Align to Y Axis Option to the CLI Arrange (#15836)
The CLI turned "align to Y axis" on for every i3 printer with no way to opt out. With rotations forbidden the pre-rotation is the result, so every object ends up turned 90 degrees from how it was loaded. The GUI defaults the checkbox the same way for i3 printers, but lets the user untick it. Add --align-to-y-axis. When it is not given the printer-structure rule still applies, so existing calls are unchanged; the CLI's own options are filled with defaults after parsing, so the keys the user typed are remembered to tell the two apart. |
||
|
|
12670a6e40 |
Answer the Preview's Per-Frame Lookups From Cached Sums and Draw Segments From an Index Buffer (#15833)
* Answer the Preview's Per-Frame Time Query From a Cached Sum The G-code preview's cost is linear in the number of toolpath vertices, and on a tall multi-filament print the wipe tower dominates that count: it emits a roughly constant 160-180 moves on every layer whatever the object is, measured at 57-61% of all moves on a three-filament print. Four places scanned or allocated across the whole vertex array. None of them needed to. get_estimated_time_at re-accumulated the estimated time from vertex 0 on every call, and its caller is the tool marker tooltip, which ImGui re-renders every frame while the properties panel is unfolded. It now starts from a running sum kept at each layer's first vertex, built at load in vertex order, and adds only that layer's vertices: the same additions in the same order, so the float result is unchanged, at a cost of one float per layer and time mode rather than per vertex. At the 351k vertices of a 636-layer test print the call scanned the whole print (238us); it now scans one layer. update_view_full_range walked from vertex 0 to find where the layer range starts, on every slider tick. It now starts at the first vertex of that layer. The index is derived from the vertices rather than from Layers::Item::range, because Layers::update folds a vertex whose layer_id arrives out of order into whichever bucket is open, which makes that range the wrong answer in general; the index costs four bytes per layer, not per vertex. update_colors_texture allocated one float per vertex of the whole print on every slider tick. It now reuses a buffer. render_legend fetched the layer Zs and the per-layer times from inside loops over the custom G-code items, and built whole vectors only to test them for emptiness. The times are hoisted, the Zs are built lazily so a print with no colour change does not pay for them at all, and the emptiness tests use the existing counters. No rendering behaviour changes. * Draw the Preview's Toolpath Segments From an Index Buffer The preview's frame cost is dominated by one call: a single instanced draw of every visible toolpath segment. On a tall multi-filament print the wipe tower supplies most of those segments, which is why the preview of a large tower is slow and why shrinking the layer range speeds it up again. That draw is not fill bound. Shrinking the model to about a fortieth of its screen area moved the frame from 419 ms to 401 ms, so the cost is per segment, not per pixel, and it is paid in the vertex shader: five texelFetch calls plus several cross/normalize per invocation. Each segment is a box of eight corners, but it was submitted with glDrawArraysInstanced over a 24 entry array, so every corner was transformed once per triangle that touches it and the shader ran 24 times per segment. The same 24 entries are now an element buffer over the eight distinct corners, which lets the post-transform cache reuse them and drops the shader to 8 runs per segment. The triangles, their winding and the vertex_id each corner receives are unchanged. Measured over 100 frames on the 636-layer, 351k-vertex three-filament fixture, the segment draw goes from 381 ms to 322 ms per frame. That is a software rasterizer, where triangle setup dominates and understates the win; the drop in shader invocations is the transferable part. Verified by loading the same project in this build and in a build of the parent commit and comparing the canvas across three states - the default view, a rotated camera, and a reduced layer range: pixel identical in all three. The rotated case matters because the shader picks its corner offsets from the camera direction. The only pixels that differ anywhere on screen are in the G-code text panel, which prints a per-process object id that varies between any two runs. |
||
|
|
19e094a1d1 |
Move the Cube with the Wipe Tower in the Profile Validator (#15799)
The slice check centres its cube on the bed, puts the prime tower beside it, then pulls the tower alone inside the printable outline. On a bed too narrow for the estimated footprint that pull drags the tower back over the cube: Volumic EXO42 IDRE MIRROR MODE (189 mm wide, 87.6 mm estimate) logged "gcode path conflicts found between WipeTower and cube" in every run, and three ~105 mm beds were left with 0.15 to 3.3 mm of clearance. The cube and the tower's footprint are now pulled inside as one rigid pair, so the clearance between them is fixed by construction. A bed too small for the pair keeps the old placement, and presets that were never clamped keep their exact layout. |
||
|
|
2876374b45 |
Add a Dockable HTML Panel API for Plugins (#15736)
orca.host.ui.create_dock_panel(html, title, width, height, on_message, on_close, dock) hosts plugin HTML in a pane of the Plater's dock manager, next to the sidebar, and returns a UiDockPanel handle (post/show/hide/close/is_open). The arguments follow create_window(). The panel uses the window.orca bridge of plugin windows, restores its position and size from the saved window layout, hides with the Plater off the Prepare and Preview tabs when floating, and is closed with its plugin; plugin panes are removed in MainFrame::shutdown(). The web view hosting moves out of PluginPage into a shared WebPanel base: bootstrap page and swap to the plugin HTML, theme, element-default and bridge scripts, window.orca message parsing, delivery to the page, and live re-theming, also re-applied on every load after the swap. Pages tabs and docked panels both derive from it. Pages tabs now re-theme in place on a theme change instead of being reloaded, and a window.orca call a host does not support is logged. What the hosts share no longer lives in one of them: the bootstrap page, the base URL and the plugin-window bridge move to Widgets/WebHosting, used by WebDialog and WebPanel alike. The Plater restores plugin panes with a new saved-layout parser, GUI/AuiPaneLayout, kept in its own small header so slic3rutils can test it without pulling in the Plater. The web hosting classes carry no plugin name, so other hosts can reuse them: PluginWebDialog becomes WebDialog (its bootstrap page moves to resources/web/dialog/WebDialog), and destroy_for_plugin(), load_plugin_content() and plugin_defaults_user_script() become destroy_silently(), load_page_html() and element_defaults_user_script(). Includes a sample plugin (sandboxes/orca_dock_panel_plugin_any.py) and binding and layout-helper tests in slic3rutils. |
||
|
|
8b63628cf9 |
Restore Plugin HTML After a WebKit Reload (#15737)
Plugin dialog content is injected with SetPage, so on the WebKit backends a reload (context menu, keyboard shortcut or location.reload()) re-fetches the SetPage base URL instead of the injected document, and the plugin UI is gone for good: load_plugin_content() returned early once m_content_loaded was set. Re-inject the plugin HTML when a main-frame load after the initial swap is neither that swap nor a page the plugin linked to. m_own_page_load marks the load our own SetPage caused, and the URL test recognises the reload: the injected document and the directory a reload re-fetches both report the base URL, so a load of any other URL is left alone. The test ignores a fragment the page navigated to, and undoes the escaping the web view applies to what the resources path holds. A load reaching the base URL is not enough on its own, because WebKitGTK reports a navigation that never committed against the document that stayed and then finishes that document again: a link to a missing file therefore arrives as a load of the base URL and reads exactly like a reload. So the re-injection also requires a navigation to the base URL to have committed, which a reload always does and a failure never does. A bootstrap page that cannot be loaded is still not recovered from: WebKitGTK substitutes a stock error page for it, and that load supersedes the swap whichever way the swap is ordered around it. The file ships, so this is a broken-install path; nothing here makes it worse than it already was. No separate MSW path is needed: wxWebViewEdge ignores the SetPage base URL, so its documents report about:blank and the test never matches there, and WebView2 reloads NavigateToString content from its own history entry anyway. |
||
|
|
0956b4d8fe |
Add a Nightly Parity Workflow (#15712)
# Description <!-- > Please provide a summary of the changes made in this PR. Include details such as: > * What issue does this PR address or fix? > * What new features or enhancements does this PR introduce? > * Are there any breaking changes or dependencies that need to be considered? --> Adds a nightly workflow that runs the long parity checks from [orca-test-repo](https://github.com/OrcaSlicer/orca-test-repo), which are too slow for the per-build "Run external slicer regression tests" step and are kept out of every PR and merge build. ## What it runs `.github/workflows/parity_nightly.yml`, three jobs: | Job | What it does | |---|---| | Find the build to test | Picks the latest successful `build_all.yml` run for the branch (`main` by default) and records its commit. | | Override sweep effect stage (shard 0 and 1) | Runs orca-test-repo's override sweep with `--effect-full`: every config option that lands on the CLI is re-sliced on its own to check that it actually changes the G-code. Split into 2 shards, each with a 60-minute timeout. | | GUI-vs-CLI parity harness | Slices a set of fixtures in the GUI (headless under Xvfb) and on the CLI, compares the exports, and scores divergences against a known-differences ledger. It reports only and never fails on a divergence. | ## When it runs - **Every night at 21:00 UTC,** after `build_all.yml`'s 17:00 UTC run has finished. - **By hand** through `workflow_dispatch`, with optional inputs: - `build_branch`: the branch whose latest successful build to test; - `test_repo_ref`: the orca-test-repo ref; - `fixtures`: a subset of harness fixtures; - `cli_presets`: `flat` or `raw`. - **No `push` or `pull_request` trigger,** so nothing here runs on PRs or merges. The per-build CI step is unchanged. ## How it tests a build - **Binary:** the Linux x86_64 AppImage from the chosen `build_all` run. - **Source:** OrcaSlicer checked out at that run's exact commit. The AppImage only ships packed preset caches, so profiles and the CLI option list come from this checkout, matched to the binary. - **Output:** each job writes a summary to the run page and uploads its report (`override-report-shard*`, `parity-scorecard`) for 30 days. - **Failures:** a failing effect shard fails the run, and GitHub's usual failure notification for scheduled workflows applies. # Screenshots/Recordings/Graphs <!-- > Please attach relevant screenshots to showcase the UI changes. > Please attach images that can help explain the changes. --> ## Tests <!-- > Please describe the tests that you have conducted to verify the changes made in this PR. --> - Dispatched on this branch against orca-test-repo `main` and #15693's build ([run 34933239912](https://github.com/OrcaSlicer/OrcaSlicer/actions/runs/34933239912)). Every job passed: - **effect shard 0:** 19m31s; 225 options sliced, 151 effective, none crashed or hung, every fixture sliced; - **effect shard 1:** 19m33s; 349 options sliced, 254 effective, same; - **harness:** 4m32s; all 13 fixtures, 0 new divergences, 0 errors. - An earlier dispatch on this branch, testing #15693's build against orca-test-repo's parity branch ([run 34836468900](https://github.com/OrcaSlicer/OrcaSlicer/actions/runs/34836468900)), passed: effect shards in 20m28s and 23m30s, and the harness reported 0 new divergences. - The workflow only runs from the default branch on its schedule, so the nightly trigger itself takes effect once this is merged. <!-- > A guide for users on how to download the artifacts from this PR. --> [How to Download Pull Requests Artifacts for Testing](https://www.orcaslicer.com/wiki/how_to_download_pr_artifacts) |
||
|
|
5514559feb |
Load Each Vendor Tree Once When the CLI Resolves System Presets (#15693)
# Description <!-- > Please provide a summary of the changes made in this PR. Include details such as: > * What issue does this PR address or fix? > * What new features or enhancements does this PR introduce? > * Are there any breaking changes or dependencies that need to be considered? --> Since #15438, every CLI run that loads a system preset spends about a second per preset file re-parsing that vendor's entire profile tree. A slice with a machine, process and filament preset got roughly 2.5 s slower, and a four-filament slice roughly 4 s slower. This PR loads each vendor tree once per run instead. Resolved presets and G-code are unchanged. The GUI never takes this path, and no release contains #15438, so the regression only affects CLI runs on current dev and nightly builds. That includes print farms, slicing services and plugins that call `orca-slicer --slice`, and CI suites. ## Changes ### Why it was slow `PresetBundle::resolve_preset_config` resolves a system preset through its vendor manifest by loading the whole OrcaFilamentLibrary bundle and the whole vendor tree from JSON, then picking the one preset out. The CLI did that separately for every `--load-settings` and `--load-filaments` file, on a fresh `PresetBundle` each time. With BBL presets, a machine + process + filament run opened `BBL.json` three times and read BBL's 2,879 profile files and the library's 512 three times over. ### Load each vendor tree once - `PresetBundle` keeps every vendor bundle its manifest path loads, keyed by source root, vendor and substitution rule, and reuses them for later resolutions on the same bundle. - OrcaFilamentLibrary is cached the same way, so vendors under one root share a single library load and the library's own presets resolve from that same instance. A vendor bundle only reads from its base while loading, so sharing it is safe. - A failed or throwing load is not kept, so error reporting is unchanged. - The key includes the source root, so presets from two different profile roots still resolve separately. - The CLI resolves every system preset through one `PresetBundle` for the whole run, instead of creating one per file. The resolved configurations still come from the same canonical vendor loader, so what a preset resolves to does not change. Only the CLI calls `resolve_preset_config`, so a long-lived GUI bundle cannot end up holding profile trees that later change on disk. # Screenshots/Recordings/Graphs <!-- > Please attach relevant screenshots to showcase the UI changes. > Please attach images that can help explain the changes. --> CLI slice of a 20 mm cube with X1 Carbon system presets. Both builds get the same datadir, best of 3, Linux. "Before" is this PR's base from CI. | System presets loaded | Before | After | |---|---|---| | machine | 0.95 s | 0.87 s | | machine + process | 1.67 s | 0.92 s | | machine + process + 1 filament | 2.51 s | 0.97 s | | machine + process + 4 filaments | 5.00 s | 0.99 s | Files opened during the machine + process + filament run (`strace -e openat`): | | Before | After | |---|---|---| | `BBL.json` | 3 | 1 | | `OrcaFilamentLibrary.json` | 3 | 1 | | `system/BBL/**/*.json` | 8,634 | 2,880 | | `system/OrcaFilamentLibrary/**/*.json` | 1,536 | 512 | Peak memory did not rise: max RSS 306 MB → 286 MB for the three-preset run, and 305 MB → 285 MB for four filaments. The "before" figure is an AppImage, so part of that gap is probably packaging. ## Tests <!-- > Please describe the tests that you have conducted to verify the changes made in this PR. --> - New test "Manifest-backed resolution reuses the vendor tree it already loaded" in `tests/libslic3r/test_preset_bundle_loading.cpp`. It resolves one preset, changes the parent profile on disk, then resolves a sibling. The same bundle returns the value it already loaded, and a fresh bundle picks up the change. - New test "Manifest-backed resolution shares the library between vendors under one root". It resolves through one vendor, changes a library profile on disk, then resolves through a second vendor and a library preset on the same bundle. Both return the value already loaded, and a fresh bundle picks up the change. - All `[Preset][Bundle]` tests pass (87 test cases, 1,069 assertions), including the existing manifest-backed resolution cases for source-root scoping, malformed vendor loads, missing parents and type mismatches. - G-code of the three-preset slice is identical before and after, header lines excluded. - The external CLI regression suite passes. Two cases report as unexpectedly passing because #15639 fixed their bug. They pass the same way on this PR's base without the change. - A GUI-vs-CLI parity run over 10 fixtures shows no new differences. - Builds clean on Linux (Release, with tests). <!-- > A guide for users on how to download the artifacts from this PR. --> [How to Download Pull Requests Artifacts for Testing](https://www.orcaslicer.com/wiki/how_to_download_pr_artifacts) |
||
|
|
37e2b6c928 |
CLI: let --export-settings - write the merged config JSON to stdout (#15698)
`--export-settings` already writes the merged config as JSON at the right point in the CLI flow. Passing `-` now writes that same document to stdout, so scripts can inspect the effective config without a temp file. This replaces #14605. - `ConfigBase::save_to_json` gains a stream overload. The file overload serializes through it before opening the file, so the output format is unchanged, and a config that cannot be serialized (invalid UTF-8) now leaves the existing file untouched instead of truncating it. - On stdout, invalid UTF-8 in string values is written as U+FFFD instead of ending the process with an uncaught `type_error`. Files keep the strict behaviour. - To keep stdout pure JSON, `-` is rejected up front (stderr message, `CLI_INVALID_PARAMS`, shell status 254) when combined with an action or transform that can write to stdout or does real work: `--info`, `--help`, `--orient`, slicing and exporting. Options that do nothing without a slice (`--uptodate`, `--min-save`, `--pipe`, ...) are still accepted. - The one unconditional stdout write on a success path, "skip locked instance" during arrange, now goes to the log. - Every other value, including the default `output.json`, behaves as before. Tests in `tests/libslic3r/test_config.cpp`: the stream output equals the file output and keeps the tab-indented format; invalid UTF-8 throws on the strict path and is replaced when asked; a failed save leaves the previous file intact. |
||
|
|
00429da739 |
Apply the GUI's Mixed Filament Rules on the CLI (#15636)
A valid mixed filament already slices the same on the CLI as in the GUI; these are the places where the CLI still skipped a rule the GUI applies. - Keep the prime tower when a mixed filament is used, even if every --load-filaments preset is the same. A mixed filament swaps between its components every layer, so turning the tower off left the swaps with nothing to purge on. - Leave a mixed slot's row and column of the flush matrix at zero when --filament-colour triggers a recompute, as the GUI does; a mixed slot never reaches a nozzle. - Refuse a mixed slot that has no filament of its own. Feature filament ids aimed at it were past the filament count, got reset to filament 1 and the model silently printed in one colour. - Refuse a plate that uses a mixed filament whose components are different filament types, the type half of the GUI's Sidebar::has_broken_mixed_filament. Missing or out-of-range components are already rejected for the whole project by validate(). get_extruders_under_cli gains an expand_mixed_slots flag so the gate can see mixed slots rather than their components; existing callers keep the expanded list. Both refusals exit with the new CLI_MIXED_FILAMENT_INVALID (-69). |
||
|
|
31f6eb2718 |
Keep the First Value When a Per-Filament Variant Option Is Too Short (#15639)
update_values_to_printer_extruders_for_multiple_filaments picks each filament's value from the flattened (filament x variant) columns of every per-filament variant option. When a column index fell past the end of the option's values, it skipped that filament and left the zero the output vector was created with. The GUI always hands this function full columns, but the CLI does not: - a CLI override of a single value, such as --nozzle-temperature=211 on a four-filament project, came out as 211,0,0,0, so three filaments would print at 0 C; - loading fewer filament presets than the project has filaments left the remaining filaments' columns missing, so filament_cooling_before_tower came out as 10,10,0,0 and filament_ramming_volumetric_speed as -1,-1,0,0. An out-of-range column now keeps the option's first value, the fallback get_at() and the sibling gather step already use. The seven per-type copies of the loop are replaced by that same gather_option_values helper, moved above the function; it now takes its caller's name for its log lines. An empty option, which has no first value, is given one registered default per filament first; it used to be replaced with zeros. On a partial load a filament whose preset was not loaded takes the first filament's value rather than its own preset's, which the CLI does not load; for the options seen in practice those agree. |
||
|
|
f21f062ded |
Cache compiled objects between CI runs (#15611)
# Description <!-- > Please provide a summary of the changes made in this PR. Include details such as: > * What issue does this PR address or fix? > * What new features or enhancements does this PR introduce? > * Are there any breaking changes or dependencies that need to be considered? --> Every CI build leg compiles the whole tree from scratch: 42 to 57 minutes of each build job, on every push and every pull request, roughly 200 runs a week. This PR caches the compiled objects with ccache so that a run only compiles what changed since the last push to main. With a warm cache the compile steps take 1 to 4 minutes on all six legs and a pull-request run finishes in about 30 minutes instead of 75. Three prerequisites landed last week and made this measurable: #15537 took `GIT_COMMIT_HASH` off the compile line, #15552 made a build without the precompiled header work on Windows, and #15501 stopped the Flatpak job from rebuilding its dependencies. ## Changes ### Compiler cache in `build_orca.yml` Each build leg (Linux x86_64/aarch64, Windows x64/arm64, macOS arm64/x86_64) restores a cache entry keyed by that leg, compiles through `ccache` via `CMAKE_<LANG>_COMPILER_LAUNCHER`, and prints its hit statistics at the end of the job. The macOS universal combine does not compile and is left out. Who writes the cache is the important part. Cache entries are immutable and a restore always takes the newest matching one, so every save is a new entry that is never read again once a newer one exists. Therefore: - **Pushes save.** After a successful save, the older entries for the same leg on the same ref are deleted, so a branch holds exactly one entry per leg. The save comes first, so a failed save leaves the previous entry in place. - **Pull requests restore only.** They read main's entries (GitHub lets a PR read the base branch's caches) and keep nothing. Saving from PRs would add about 6 GB per run that no other run can read. The store is therefore a flat ~7 GB (one entry per leg: Linux ~1 GB, Windows ~2 GB, macOS ~0.6 GB), not a growing one. The `hendrikmuhs/ccache-action` only installs and configures ccache; restore and save go through `actions/cache` with one path string, because the cache service only matches entries saved under the identical path and the action spells it differently on Windows. A failed ccache install falls back to an uncached build rather than failing the job. ### Precompiled header off when the cache is on With `SLIC3R_PCH` left on, a warm cache hit only 19 % of compiles: Clang stamps the PCH with the build time, CMake does not pass `-fno-pch-timestamp`, and everything that includes the PCH (libslic3r and libslic3r_gui, ~750 files) missed every run. `build_linux.sh -p` exists for exactly this reason. The workflow now exports `ORCA_EXTRA_BUILD_ARGS=-DSLIC3R_PCH=OFF` whenever ccache is enabled, which brings the warm hit rate to 98.4–98.9 %. The cost is on cold compiles, which are 25–60 % slower than today's PCH build (ccache preprocesses every miss before compiling it, and the miss compiles without PCH). Main pays this once after an image update or a wide header change; PRs pay it only for the files their change invalidates. A change to a header included by half the tree (`PrintConfig.hpp`, `Preset.hpp`, `Model.hpp`) lands a run at 1.2–1.9× today's time. `ccache`'s depend mode would remove the preprocessor pass and is the natural follow-up. ### Includes the precompiled header was supplying on macOS A build without PCH had never been tried on macOS. Three files used what `pchheader.hpp` happened to include: `LocalesUtils.cpp` needs `<sstream>` and `<iomanip>`, and `AmsMappingPopup.cpp` / `PhysicalPrinterDialog.cpp` need `<wx/tooltip.h>`. libstdc++ and the GTK wx port pull these in transitively; libc++ and the Cocoa port do not. This is the macOS counterpart of #15552 and is worth merging on its own. ### `ORCA_EXTRA_BUILD_ARGS` pass-through `build_linux.sh` already forwarded this variable to the slicer configure. `build_release_macos.sh` now reads it into an array (shellcheck-clean), and `build_release_vs.bat` appends it on both configure lines, so CI can add a CMake option without editing three scripts. ## Behaviour reviewers should know about - **Main-only cache writes need `actions: write`** on the workflow token to delete the previous entry. The default token already has it (the nightly deploy steps write with it), so no `permissions:` block was added. A fork PR's read-only token never reaches the delete step. - **A runner image update cold-starts the cache** as configured, because ccache keys the compiler by its mtime and every image rebuild reinstalls it. Images updated 20260819 → 20260828 during this work, about every one to two weeks. Keying on the compiler version string (`compiler_check`) would avoid that; left as a follow-up since it changes every hash. - **What is now the critical path:** the two Flatpak jobs (46–66 min, untouched here), the orca-test-repo regression suite run inline in the Linux job (7 min), and NSIS/PDB/MSIX packaging on Windows (6 min). Those are the next wins. - **Open question:** CI still drives `build_release_vs.bat`. #15552 gave `build_win.bat` a `--cache ccache --no-pch` option; moving the Windows job onto it would replace the batch-file change here. # Screenshots/Recordings/Graphs <!-- > Please attach relevant screenshots to showcase the UI changes. > Please attach images that can help explain the changes. --> Compile step of each build leg, minutes. Main's numbers are from run 34324625046. | Leg | main | cold, PCH on | warm, PCH on | cold, PCH off | warm, PCH off | ~50 % of headers changed | 5 source files changed | |---|---|---|---|---|---|---|---| | Linux x86_64 | 48 | ~75 | (19 % hits) | ~110 | **2.6** | 91.3 (452/928 misses) | 3.4 | | Linux aarch64 | 41.7 | 53.4 | 52.2 (178/927 hits) | 58.8 | **2.5** | 55.8 (452/928) | 2.9 | | Windows x64 | 57 | 85.5 | — | ~105 | **2.4** | 76.8 (449/974) | 2.5 | | Windows arm64 | ~45 | 64.4 | — | ~78 | **4.3** | 59.6 (450/974) | 4.2 | | macOS arm64 | 51 | ~71 | — | — | **0.8** | 79.7 (453/947) | 0.9 | | macOS x86_64 | ~43 | 70.2 | — | — | **1.0** | 68.1 (411/742) | 1.0 | Warm hit rates: 98.4–98.9 % on every leg; the 11–14 misses are what any commit changes (version stamp and its includers). The "50 % of headers" column is a real event: #15251 and #15416 merged into main between two runs, changing 20 headers that reach 453 of 870 translation units. Whole run, before and after (a pull-request run; wall clock to the last non-Flatpak job): | Job | main (run 34324625046) | warm cache (run 34444305385) | what remains | |---|---|---|---| | Windows arm64 | 50.0 | 15.7 | compile 4.3, NSIS 3.5, cache save 1.6, deps restore 1.1, cache restore 1.0 | | Windows x64 | 67.4 | 13.2 | NSIS 3.2, PDB 2.6, compile 2.4, MSIX 0.5 | | Linux x86_64 | 57.5 | 12.6 | orca-test-repo regression 7.6, compile 2.6 | | macOS x86_64 | 46.9 | 6.1 | free disk space 2.3, compile 1.0 | | Linux aarch64 | 43.9 | 4.6 | compile 2.5, apt 0.9 | | macOS arm64 | 54.9 | 4.4 | free disk space 1.7, compile 0.8 | | macOS universal | 7.7 | 2.2 | signing and notarisation only on main | | Flatpak x86_64 / aarch64 | 66.6 / 46.5 | unchanged | full compile inside flatpak-builder | | **Wall clock** | **75 min** | **31 min** (Flatpak excluded; 66 with it) | macOS runner queueing now exceeds job time | Cache storage: one generation per leg is 400–680 MB compressed at PCH on, 0.6–2 GB at PCH off; six legs ≈ 7 GB. Without the delete step, 21 main pushes a week would hold ~80 GB of entries that are never read. ## Tests <!-- > Please describe the tests that you have conducted to verify the changes made in this PR. --> - Thirteen CI runs on this PR, one change per run, with the ccache statistics printed by every leg: cold (34336272234), warm with PCH (34346737197), cold and warm without PCH (34352209577, 34364791732), the macOS include fixes (34435044411, 34435968806 with `ninja -k 0` to list every remaining file, 34439532375), all legs warm (34444305385), the keep-only-newest cleanup (34450381312, then 34452640274 after the Windows CRLF fix), the half-tree invalidation (34452640274), the five-file change (34463517683, 34464720539), and this final shape (34466377763, restore-only). - Unit tests on all five platforms, the profile slice check, the Windows build-script suite, Shellcheck and the universal DMG build all pass on the cached binaries. - The cleanup was verified against the PR's own cache scope: 44 entries from the earlier runs reduced to exactly one per leg, on all three platforms, after fixing the CRLF that made `gh cache delete` fail on Windows. - A libc++ syntax-only pass over all 1986 C++ translation units on Linux found the `LocalesUtils.cpp` include; the two wx includes only surface in a real macOS build and were found with a keep-going build in one round. <!-- > A guide for users on how to download the artifacts from this PR. --> [How to Download Pull Requests Artifacts for Testing](https://www.orcaslicer.com/wiki/how_to_download_pr_artifacts) |
||
|
|
8d874cdc36 |
Register Instance Copies and Moves with Their Plate (#15613)
# Description Each plate keeps a registry of the instances it holds (`PartPlate::obj_to_instance_set`). The plate's filament list (`get_extruders`), its wipe tower preview and position clamp, the object list grouping and the saved project's per-plate instance list all read it. Two paths left it stale: * `Plater::increase_instances` (the `+` key / toolbar) adds the copy to the model but never registers it with any plate. * `GLCanvas3D::do_move` (drag release and arrow keys) ended with `notify_instance_update(-1, 0)`, so only instance 0 of each selected object was re-registered. Rotate, scale and mirror already notify every instance. So a copy created with `+` and dragged onto another plate stayed unknown to that plate: the project saved afterwards listed it on no plate, and a multi-filament copy moved onto a single-filament plate drew no wipe tower there and never got its tower position clamped. The Print side selects instances by geometry, so the plate still sliced, which is why this went unnoticed. This PR * registers new copies with their plate at creation; * has `do_move` notify exactly the instances it moved (every instance of the object when a part was moved in Volume mode), rather than instance 0 or all instances - notifying an instance that stayed put invalidates its plate's slice result, so `(-1, -1)` as used by rotate would have un-sliced every plate holding a sibling copy; * drops the registry entry again when `decrease_instances` removes a copy. A second commit finishes the switch #15532 started with `contain_any_instance_totally()`: `get_extruders_without_support()`, `check_single_extruder_mixed_filament_risk()` and `check_compatible_of_nozzle_and_filament()` still tested instance 0 only, so an object whose copy - not its original - sits on the plate was skipped by all three. No new options, no format change. The `is_new` flag is deliberately not passed for the copies: a copy landing on a spiral-vase plate gets the same "apply spiral mode settings?" prompt a dragged instance gets, instead of a silent rewrite of the object's settings. # Screenshots/Recordings/Graphs Before: <img width="1920" height="1080" alt="05-moved" src="https://github.com/user-attachments/assets/3cf9f5a9-1a4e-41e8-8c57-578f849d8c29" /> After: <img width="1920" height="1080" alt="05-moved" src="https://github.com/user-attachments/assets/1b801a7e-b7cd-4ffb-bd1d-b701f90dade6" /> ## Tests Re-run after the rebase, both binaries driven through the same headless harness (Xvfb 1920x1080, llvmpipe) on the same fixture: `cubeA` (filament 1) alone on plate 1, `cubeB` (a two-part object, filaments 2 and 1) alone on plate 2, so plate 1 shows no wipe tower at load. Select the plate-2 object, press `+`, walk the copy onto plate 1 with 36 x Left (10 mm per press, one `do_move` each), save, slice plate 1. Before is main `8af92214d0` - i.e. with #15532's `contain_any_instance_totally()` already in place, so the only difference is this PR. * **Before:** the saved `model_settings.config` lists plate 1 with `cubeA` only and plate 2 with `cubeB` instance 0. The copy (instance 1) is listed **on no plate at all**, and plate 1 draws no wipe tower even though a two-filament object is sitting on it. * **After:** plate 1 lists `cubeA` **and** `cubeB` instance 1; plate 2 still lists instance 0. The plate-1 tower preview appears, and slicing plate 1 succeeds with the tower actually generated - the filament panel reports 1.10 m / 0.48 m in its Tower column over 51 filament changes, and the G-code carries `EXCLUDE_OBJECT_END NAME=cubeB.stl_id_1_copy_0`. Same camera and fixture on both runs, so the screenshots above are directly comparable. |
||
|
|
8af92214d0 |
Extract and Unify Wipe Tower Estimation (#15532)
# Description
The pre-slice wipe tower size estimate existed twice:
`Print::wipe_tower_data()` (validation) and
`PartPlate::estimate_wipe_tower_size()` (GUI placement clamp, default
placement, preview, arrange, CLI placement) — with a third partial copy
in the CLI, which resolved the brim itself around the second. They were
hand-written twins reading their inputs from different places, so
validation could measure a tower with one number after the clamp had
placed it with another.
This PR extracts the estimate into one function,
`estimate_wipe_tower_footprint()` in
`src/libslic3r/GCode/WipeTowerEstimate.{hpp,cpp}`. It takes a
`ConfigBase&` (static `PrintConfig` and GUI/CLI `DynamicPrintConfig`
both work), the filament count, layer height and tallest object height,
and returns width, depth, height and the resolved brim width.
`Print::wipe_tower_data()` and a new
`PartPlate::estimate_wipe_tower_footprint()` become thin adapters around
it; `PartPlate::estimate_wipe_tower_size()` had no callers left and is
deleted.
**Inputs made to agree** — sharing the arithmetic is not enough when
each caller derives the inputs from its own view of the model:
* **Layer height** — thinnest layer among the objects on the plate,
resolved per object (Print used the first object's, PartPlate the
preset's).
* **Objects setting the height** — `PartPlate::get_extruders` counts an
object if *any* instance is on the plate, matching `PrintApply` (it only
looked at instance 0).
* **Height per object** — per on-plate instance, from the cached convex
hull (same z extent as the mesh). `PrintObject::size()` still measures
the model's first instance, so objects whose instances differ in scale
or x/y tilt can still disagree; that is inherent to the two data
sources.
* **Wipe tower filament** — counted for every caller, since
`Print::extruders()` adds it to the tool ordering even when unused.
* **Rib width cap** — kept for both (Print lacked it).
* **Config source** — everything read from the config passed in
(PartPlate read `m_print->config()`, stale on fresh plates and in the
CLI).
* **Dual-nozzle test** — `nozzle_diameter.size()` from the config for
both.
**One decision about whether a tower exists.** The rectangle branch
sized a tower the generator never builds while the rib branch reported
none for one it does; with rib as the shipped default, a single-filament
plate that still prints a tower (custom G-code tool changes) validated
against depth 0, collapsing the collision/exclusion hull to a point. The
purge volume is computed first, and an empty footprint is returned only
when nothing is purged, there is no tool change, and nothing else puts a
tower on the plate. The reason a single config cannot see arrives as a
resolved input: validation counts `Print::extruders(true)`.
A raft is deliberately **not** one of those reasons.
`DynamicPrintConfig::normalize_fdm_2` clears `enable_prime_tower` for a
plate that purges one filament unless smooth timelapse or wrapping
detection is on, and `Print::apply()` runs it, so a raft alone leaves no
tower to reserve for. (It also keeps the tower for a single *mixed*
filament, which this does not model — `Print::extruders(true)` does not
expand mixed filaments.)
**Two implementation notes:**
* Enums are read **by value**: a preset-built `DynamicPrintConfig` holds
`ConfigOptionEnumGeneric`, so a `dynamic_cast` to `ConfigOptionEnum<T>`
is null for exactly the config the GUI and CLI pass. The tests build
their configs the way `PresetBundle::full_config()` does, so that
storage is what gets tested.
* `PartPlate::estimate_wipe_tower_footprint()` is CLI-reachable, so
`get_extruders(bool)` gained a config-taking core with the identical
body; the GUI wrapper passes the app's presets, the adapter passes the
config it is given. `get_extruders_under_cli()` was not substituted: it
filters the plate's instances differently (skips unprintable ones, keeps
ones the plate flags as outside), so the GUI's filament set would have
changed in edge cases.
**Also fixed here:** `WipeTowerData` carries the effective width — set
by the estimate, and then by both planners at generation, so it never
disagrees with its neighbour `depth`; the preview and the containment
check take *whether there is a tower at all* from the footprint instead
of each re-deriving it; the config-taking `get_extruders` answers for an
object-less (`.gcode.3mf`) plate the way the wx overload does; the
preview takes body *and* brim from the plate's own footprint (an auto
brim drew every plate with the selected plate's brim);
`estimate_wipe_tower_polygon` builds its margin from the resolved brim
("Auto" gave a margin of 0) and no longer calls `std::clamp` with `hi <
lo`; the estimate falls back to declared defaults instead of hand-copied
constants. `estimate_wipe_tower_size()` /
`estimate_wipe_tower_polygon()` lose four parameters every caller took
from the same config.
## Behaviour changes reviewers should know about
G-code is never affected; no 3MF, profile or string changes. But this is
**not** a pure refactor:
1. **Validation now reserves what the placement clamp reserves**, which
is in places larger than before. A saved 3MF with a tower close to an
exclusion area or the rear edge can be rejected where it previously
sliced; dragging resolves it since the clamp agrees. Nothing re-clamps a
stored position on load (out of scope; the CLI side lands with #15518).
2. **`PartPlate::get_extruders` counts any-instance-on-plate**, which
reaches every caller of it, not only the estimate. It is `PrintApply`'s
rule and closes a GUI/CLI divergence.
3. **`estimate_wipe_tower_polygon`'s rear/right bound is looser by one
brim width** (it subtracted the brim twice).
4. **A single-filament plate with a rib wall no longer reserves a
phantom tower.**
5. **A single-filament plate whose tower comes from wrapping detection
is now validated against the bed.** Neither the old estimate (which read
only the wall type and smooth timelapse) nor the old containment gate
(the filament count or smooth timelapse) knew about that tower, so
between them it was never checked. It is printed, so it can be rejected
now.
Not addressed: the estimate still does not read `wipe_tower_type` or
per-filament `filament_prime_volume`, inherited unchanged from both
copies (the generated Type 1 tower is ~10 mm larger than the estimate on
Bambu profiles). #15516 mirrors the planners and folds into this
function on rebase.
## Verification
Before/after on the same fixtures with a main build and this branch, all
numbers read from the CLI (details, method and the real-tower and
arrange-clamp tables in the first comment):
| Fixture (divergence) | Side | Before (w × d, mm) | After (w × d, mm) |
|---|---|---|---|
| control | GUI/CLI · validation | 23.585 × 23.585 · 23.585 × 23.585 |
same |
| per-object layer 0.1 | GUI/CLI · validation | **23.585** · 31.637 |
**31.638** · 31.637 |
| unused `wipe_tower_filament` | GUI/CLI · validation depth | **39.332**
· 44.542 | **44.541** · 44.542 |
| tall object, instance 0 on another plate | GUI/CLI · validation |
**23.585** · 29.391 | **29.390** · 29.391 |
| rib cap binds | GUI/CLI · validation | 11.170 · **13.910** | 11.170 ·
**11.170** |
Before, the two estimates disagree on every divergence fixture; after,
they agree to the 0.001 mm bisection resolution, the control is
unchanged, and G-code is byte-identical. GUI screenshots of the preview
on both binaries are in the same comment.
The table was measured on the first commit; none of its fixtures uses a
raft or a zero purge volume, so the second commit does not move them.
G-code equivalence was re-checked on the final tip: `Cube.3mf` sliced by
a `main` build and by this branch is byte-identical.
# Screenshots/Recordings/Graphs
Before:
No Wipe Tower Preview:
<img width="2068" height="871" alt="image"
src="https://github.com/user-attachments/assets/7875a944-b8db-4bbc-b380-e8188a45caa7"
/>
After:
Has Wipe Tower Preview:
<img width="2551" height="882" alt="image"
src="https://github.com/user-attachments/assets/b4413695-4f24-4aa3-bae4-57304e8b7865"
/>
**Per-object layer height reaching the preview.** One object with a 0.1
mm override against a 0.2 mm preset. `main` sizes the previewed tower
from the preset, so it is smaller than the one validation reserves and
the one that prints; this PR sizes it from the object. Captured
headlessly on both builds from the same project, top view:
<img width="1408" height="596" alt="D_per_object_layer_height"
src="https://github.com/user-attachments/assets/989920fc-9f14-4658-8a3d-681c92a7f754"
/>
Measured over the four evidence fixtures on both builds, this is the
only one of the corrected inputs that changes what is drawn: the others
(an object contributing through a non-zero instance, an unused
`wipe_tower_filament`) change the estimate by amounts confirmed through
the CLI bisection above, but leave the rendered tower pixel-identical.
Arrange is unaffected either way — the tower enters the arranger as a
fixed obstacle (`m_unselected`), so it never moves.
## Tests
* `tests/libslic3r/test_wipe_tower_estimate.cpp` (10 cases / 104
assertions): rectangle and rib sizing, stability floor and auto brim,
single-filament cases (timelapse, wrapping, and a raft *not* reserving
one), a tool change reserving the floor when the purge volumes resolve
to zero, both wall types agreeing on tower existence, dual-nozzle
volume, the shipped flush-matrix path, default fallback for a missing
key, and a {rectangle, cone, rib} × {type1, type2} matrix asserting a
preset-shaped `DynamicPrintConfig` and a static `FullPrintConfig` give
the same footprint.
* `tests/fff_print/test_wipe_tower.cpp`: what `Print` feeds the
estimator — thinnest object layer height, effective width reaching
validation, the width staying current through generation, a
single-filament plate reserving a tower only when one is really printed
(raft no, smooth timelapse yes), and a wrapping-detection tower being
bed-validated. The last two fail on `main` and on the first commit of
this PR.
* Full suites green on this branch: `libslic3r_tests` 342 cases / 58325
assertions, `fff_print_tests` 174 cases / 3152 assertions. `--target
all` builds clean (including `OrcaSlicer_profile_validator`, which needs
`-DORCA_TOOLS=ON`). No new warnings.
* CLI evidence run above; its unused-`wipe_tower_filament` fixture is
also the regression check for the adapter under the CLI, which no unit
test can reach (`PartPlate` needs a GL context).
[How to Download Pull Requests Artifacts for
Testing](https://www.orcaslicer.com/wiki/how_to_download_pr_artifacts)
|
||
|
|
8a291f9d56 |
Confine config import to the preset directory (#15608)
import_presets reduced each zip entry to a basename by stripping only '/', so on Windows an entry named with '\' separators kept its directory components and was extracted wherever they pointed. Strip both separators, and reject any entry whose name still escapes the extraction folder. The preset name from the JSON and the bundle id from bundle_structure.json were joined onto the preset directory unchecked as well, which let either of them write outside it on every platform. Both are now validated before anything is written. The check is the is_path_within_root helper the 3MF importer already had, moved to Utils so both importers share it. It treats '/' and '\' as separators on every platform, so a bundle that would escape on one OS is rejected on all of them. |
||
|
|
4deadc9dce |
Make Tree-Support Deterministic (#15565)
* Make tree support deterministic without giving up its parallelism * Break equal-distance ties in the tree support MST by coordinates * test: cover the determinism this PR fixes The MST unit tests here cover the tie-break, but the drop_nodes rework has no test. Adds two cases to the tree support suite. The thread-scheduling one slices five configs twice each and compares the support point sequence, which is what the node ordering moves. The MST tie one pins the branch diameter and line width that carry Prim's equal-distance ties into the toolpaths. slice_with_tree_support takes an optional config list so the second case can add the tree parameters it needs, and the double-slice comparison is shared rather than written twice. Both fail on main without this PR. The first passes from |
||
|
|
58bf267fdd |
Scale Min Junction Width to Prevent Fuzzy Skin From Failing Slice (#15566)
* Fix fuzzy skin failing the slice: the minimum junction width was unscaled * Unit Tests For Fuzzy Fix * Cover ridged multifractal noise in the fuzzy skin width floor test Its output is not bounded to [-1, 1], so it scales past the configured thickness and drives the junction width negative. The floor has to hold for any noise value, not just an in-range one. |
||
|
|
8e064659ad |
Fix Non-Deterministic Slicing - Order Per-Layer Intersection Lines Canonically (#15563)
Fix nondeterministic slicing: order per-layer intersection lines canonically Facet processing in slice_make_lines() is parallel, so the per-layer line order depended on thread scheduling. make_loops() consumes that order for island order and loop start vertices, so the same model could slice to different G-code run to run. Sort each layer's lines by a topology-based key. edge_type and flags are appended to the key purely to break ties: two lines can share every id and endpoint (a Horizontal facet can emit such a pair) and std::sort is not stable, so without them that pair's order would stay thread-dependent. |
||
|
|
c61d2fe5d1 |
Fix the Folgertech i3 0.6 nozzle printable area (#15577)
The bed was declared as 0x0, 20x0, 200x200, 0x200 - a triangle - where the 0.4 nozzle profile and the printer have the 200 x 200 square. Found by the profile validator once it placed the prime tower beside the test cube: no tower fits inside that outline. |
||
|
|
886d43d37a |
Fix Support Fill Cost Thresholds Being Frozen By The First Call (#15564)
Fix support fill cost thresholds being frozen by the first call |
||
|
|
1170b048e8 |
[CLI]: Fix Plate Config Reading and BuildVolume Height Checks (#15479)
* Fix incorrect early exit for CLI mode no-support preventing parameters from being read * Use PartPlate's m_height to allow CLI to perform proper BuildVolume check * Add safeguard against extruder_pintable_heights and extruder_areas vector size mismatch * Preserve printable_height precision in PartPlate/PartPlateList * Fixed multiple BuildVolume warning issue, and keep check_outside diff minimal |
||
|
|
df30e22427 |
[CLI]: CLI Crash Guards (#15477)
# Description <!-- > Please provide a summary of the changes made in this PR. Include details such as: > * What issue does this PR address or fix? > * What new features or enhancements does this PR introduce? > * Are there any breaking changes or dependencies that need to be considered? --> Part 1 of 3 of the CLI-mode bug sweep, split out of #15452 per review feedback there. This PR contains the crash fixes. ## Fixes - **`--outputdir`/`--datadir` with a missing parent directory aborted** via unguarded `create_directory`. Directories are now created recursively, with a graceful early exit and a specific error message if creation fails. - **`--slice` + `--export-3mf` segfaulted on a from-scratch slice**: `ConfigOptionVector::get_at()` on an empty vector is `.front()` of an empty vector (UB). Guards added for `filament_color`/`filament_id` at the CLI call site, and inside `DynamicPrintConfig::get_filament_type` for `filament_type`/`filament_is_support`/`filament_id`. Only *empty* vectors are treated as missing — the existing clamp-to-front behavior for merely out-of-range indices is preserved, so GUI callers are unaffected. - **OOB heap write from stale `filament_self_index` on `--load-filaments`** (fixes #14181): a 3MF carrying more `filament_self_index` entries than loaded filaments wrote past the end of `old_variant_counts`. The guard validates both bounds — entries `> filament_count` *and* non-positive entries (`< 1`), since a single `0` in an otherwise-valid array indexes `old_variant_counts[-1]`. - **Wrong printable-area check** for non-rectangular beds: use the printable area's bounding box instead of a naive vertex calculation (fixes #15363). - **`nozzle_height` and `align_center` were not read into the arrange config** in CLI mode. # Screenshots/Recordings/Graphs <!-- > Please attach relevant screenshots to showcase the UI changes. > Please attach images that can help explain the changes. --> ## Tests <!-- > Please describe the tests that you have conducted to verify the changes made in this PR. --> - Repro'd each crash on CLI before the fix; all resolved after. - `tests/libslic3r` suite passes; full binary builds clean on Linux. - Added `get_filament_type` unit tests <!-- > A guide for users on how to download the artifacts from this PR. --> [How to Download Pull Requests Artifacts for Testing](https://www.orcaslicer.com/wiki/how_to_download_pr_artifacts) |
||
|
|
57ce18d70d |
[CLI]: CLI Argument Parsing Fixes (#15478)
* Reject invalid CLI argument values instead of silently accepting them * Add read_cli accept/reject tests * Update Option Type for LogFile argument * Add read_cli vector option tests * Accept common bool spellings on the CLI, cover --logfile in tests * Add unit tests for truthy bool parsing |