Compare commits

..
Author SHA1 Message Date
Ian Chua 08dcc0adfc fix: sync per-filament arrays so MM segmentation can't read out of bounds 2026-10-13 20:38:43 +08:00
SoftFever df4c8cd531 Free G-code result and preview memory on reset (#16065)
GCodeProcessorResult declares a copy assignment, which suppresses the
implicit
move assignment, so `*result = std::move(processor.extract_result())`
binds to
the copy and duplicates the whole moves array: 96 bytes per move,
measured at
147.7 MiB in one allocation for a 597k facet model at 0.08 mm, with the
source
staying alive until the export returns.

Add the move assignment. It assigns exactly the same 39 members in the
same
order as the copy, including the ones the copy deliberately leaves
alone, so a
target that carries its own filament maps and nozzle type keeps them. A
move
constructor is intentionally not added: the type holds a std::mutex, so
it is
neither copy nor move constructible today and a partial one would leave
ten
members uninitialised.

reset() now releases the two print sized vectors rather than clearing
them. Its
callers are the paths that discard a result, so the memory went back
only at the
next slice.

ViewerImpl::reset() does the same for the two vectors sized by the
print, which
is what keeps a discarded preview resident, and counts m_vertices_colors
in the
reported CPU memory, where it was missing.
2026-10-13 18:06:46 +08:00
SoftFever bf5a378479 Free G-code result buffers with clear_and_shrink 2026-10-13 18:05:51 +08:00
Lam Wei Lun 01499e536e fix(gui): stop the UV editor pane from hijacking the shared GL context
The texture-displacement gizmo pushes pane state to the UV editor from the
preview rebuild that runs on every frame of a slider drag. Tile size is the
only one of the layer card's sliders whose value appears in that pane state,
so dragging it changes the "N.N mm tile" label, the panel relays out, and the
UV canvas receives a synchronous wxEVT_SIZE. Its on_size() flushes a paint
with Update(), and render() then makes the app's single shared wxGLContext
current on the UV drawable in the middle of the 3D canvas's frame.

On X11/GLX that out-of-turn context activation is fatal: the drawable the
3D canvas later swaps no longer belongs to the current context, the server
returns BadMatch, and GDK turns the unhandled X error into g_error(). EGL on
Wayland reports only a status, so the same theft silently corrupts the frame
instead of aborting - which is why the guard is not conditioned on the
display backend.

Add GLCanvas3D::is_frame_in_progress(), backed by a counter that
_render_frame() maintains through its existing scope guard, and let the UV
canvas skip the synchronous paint while a frame is on the stack, re-queueing
it with Refresh() so nothing is dropped. Rebind the context with
_set_current() before SwapBuffers() as well, so a surface that steals it by
some other route - a modal GL dialog a gizmo opens from its panel - cannot
turn the swap into the same fatal error.
2026-10-13 16:49:06 +08:00
SoftFever f33a9bf999 Merge branch 'main' into perf/gcode-result-move 2026-10-13 16:37:00 +08:00
HanifKoh 869e450981 Keep Per-Nozzle Values When a User Preset Syncs Through the Cloud (#16355)
The cloud upload sent only the keys that differ from the parent, so a
variant layout equal to the parent's was left out. On download the
loader sized every per-variant value by the missing layout, cut it to
one value and kept the parent's values for the other nozzles and nozzle
volume types. On dual-nozzle printers the right nozzle's settings
silently reverted to the parent's after a sync.

Upload the variant layout always, as the local save already does, and
read copies stored without it against the parent's layout when they
carry one value per parent variant.
2026-10-13 13:59:48 +08:00
hikaru0602 b920116fbd Fix manual tool change handling when loading G-code files (#15379)
Opened or drag-dropped G-code files ignored `; MANUAL_TOOL_CHANGE` comments, so the preview showed every move on the first filament. Only `apply_config(const PrintConfig&)` read `manual_filament_change`, and loading a file goes through `apply_config(const DynamicPrintConfig&)`. Read the option there too, initialize the flag to false, and add a regression test.
2026-10-12 20:03:04 -05:00
Downtime 237586675f fix(gui): remove legacy fixed-function OpenGL texture calls (#16270)
Debug builds asserted on startup because these calls raise GL errors on a core-profile context. Both draws already use shaders, so removing the calls changes nothing on compatibility contexts.
2026-10-12 20:01:57 -05:00
Ian Bassi 6d35ff6b9a Rewrite Fix Model on CGAL 6.2, fix Split to parts, remove Win10 SDK leftovers (#16366)
* Add CGAL-based mesh repair, drop Netfabb remnants

Add repair_mesh() in MeshRepair.cpp, a CGAL-based repair that replaces MeshBoolean::cgal::repair(). It welds cracks, cancels coincident triangles, fixes orientation, fills holes (including planar annular regions), removes local self-intersections, and unites crossing shells with libigl's exact boolean while following the nonzero winding rule used by slicing.

FixModelByCgal now repairs each volume in place without splitting it into parts, and reports whether the mesh was modified so unchanged meshes keep their painting and ids. The texture import repair prompt no longer depends on the Windows SDK, and the FIX_THROUGH_CGAL_ALWAYS conditionals are removed. The commented-out Netfabb/WIN10SDK build code is deleted.

libslic3r_cgal now builds with CGAL_LINKED_WITH_TBB and links TBB. Add docs/HLSD/mesh-repair.md and unit tests in test_mesh_repair.cpp.

* Parallelize repair while preserving separate shells

Repair connected mesh units and shells independently in parallel, keep crossing shells separate, and limit self-intersection remeshing to local defects. Update mesh-repair documentation and tests for the revised behavior.

* Make mesh repair deterministic and report its progress

Flags written from parallel loops were std::vector<bool>, whose neighbouring
bits share a word, so shells could be kept or dropped at random; they are now
std::vector<char>. Holes of up to 100 edges are filled with the search over all
triangles: the search restricted to a 3D Delaunay triangulation picks between
equally good fills differently from run to run.

Shells of fewer than four faces are dropped, as the volumes of two triangles
back to back need not cancel exactly, and a shell that self-intersection removal
changed is split again so the pieces it cut off are dropped too.

repair_mesh() takes a progress callback in place of the cancel callback, and
Fix Model reports each stage. The object's bounding box is now invalidated even
when a cancel or an error stops the repair after some volumes were replaced.

The edge-use sort, union-find and box-overlap code shared by several stages is
factored out, the topology analysed for welding is reused to build the work
units, the orientation pass checks only the edges it cut, and the output is
written from the shells without joining them first.

* Fix Split to parts crashing on flat pieces and staying offered after a repair

ModelVolume::split() added every piece to the object and then deleted the
pieces without a convex hull. When the first piece was flat, as often in a
triangle soup, that deleted the volume being split while split() and its
caller kept using it. A part made only of flat pieces lost all its volumes,
and a soup added thousands of pieces, saving a backup of the object for each
one it deleted. Pieces without a convex hull are now dropped before any joins
the object, the volume being split keeps the first remaining piece, and a part
with no solid piece is left whole. Other parts of the object without a convex
hull are no longer deleted as a side effect.

ModelVolume caches whether its mesh can be split, and set_mesh() kept the
cached value, so after Fix Model, Simplify or Emboss replaced a mesh, Split to
parts could still be offered for a single piece. set_mesh() and reset_mesh()
now clear the cache.

* Import STEP surface models with Split compound enabled

Splitting a compound kept only its solids, so a surface model, which OCCT
reads as a compound of loose faces, gave nothing to mesh and the import failed
with "Meshing of a model file failed or no valid shape". Each shell outside a
solid now becomes a part too, and loose faces stay together as one part, as
they do without splitting.

* Add Fix All for plates and re-enable --repair on the command line

ModelVolume::repair() applies repair_mesh() to a part and replaces the mesh
only when the repair changed it. Fix Model, the new Fix All and Fix All Plates
items of the plate context menu, and the command line's --repair option, which
had been commented out since the Win10 SDK repair was dropped, all use it.

With Keep painted feature after mesh change off, Fix Model cleared the painting
of every part of an object before repairing. Now only a part whose mesh changes
loses its painting, so fixing a whole project does not wipe it. The
notification counts the models that needed no repair instead of listing them as
repaired, and objects that did not change are not refreshed.
2026-10-12 21:59:17 -03:00
HMITCHR 075b03975f Read the initial filament start G-code through get_at() (#16080)
A filament_start_gcode list shorter than the filament count made the Bambu start-of-export path throw std::out_of_range. Use get_at() like the other filament_start_gcode reads, and add a regression test.
2026-10-12 19:55:02 -05:00
Kris Austin a379ec4a67 ci(macOS): retry stapler staple after notarization (#16424)
stapler fetches the notarization ticket from Apple's CloudKit server,
and a dropped connection there (NSURLError -1005, exit 68) failed the
whole macOS universal job twice on 2026-10-12, after notarization had
already been accepted. Rerunning the job passed, but repeats the full
build and both notarizations.

Wrap both stapler calls in the existing retry helper already used for
hdiutil in this step.
2026-10-12 19:36:45 -05:00
Alexandre Folle de Menezes 6700d4c5df Verify and improve AI pt_BR translations (#16377) 2026-10-12 20:57:57 -03:00
Downtimeandkenrap a28c9c3a82 fix(gui): guard gizmo actions before deferred initialization (#16268)
GLGizmosManager can be called while its gizmo list is empty, either at startup before the canvas has loaded the gizmos or after their init() failed. reset_all_states(), open_gizmo(), open_gizmo_by_shortcut() and activate_gizmo() now return early in that case. This fixes a Debug-build assert when a new project resets the gizmos at startup, and an out-of-bounds read in release builds when a gizmo shortcut key is pressed after gizmo init failed. activate_gizmo(Undefined) still returns true, since no gizmo is open. The reset_all_states() guard was first proposed in #16074 by kenrap.

Co-authored-by: kenrap <101818165+kenrap@users.noreply.github.com>
2026-10-12 18:25:17 -05:00
packerlschupfer 3dd547c8be CLI: Fix the -50 error reason assuming a 3mf upload (#16339)
The reason recorded for CLI_NO_SUITABLE_OBJECTS (-50) assumed a 3mf being uploaded. The CLI also slices STL, OBJ and STEP input, so the reason now states what both -50 sites check: the plate is empty, or no object on it is both printable and fully inside the print volume. The exit code is unchanged.

Adds tests/cli/test_cli_no_suitable_objects.sh.
2026-10-12 18:23:55 -05:00
f0e9cdc470 raft expansion (#16416)
Update ConfigManipulation.cpp

Co-authored-by: Accelerator <22743445+acceleratorlastorder@users.noreply.github.com>
Co-authored-by: Ian Bassi <12130714+ianalexis@users.noreply.github.com>
2026-10-12 18:24:25 -03:00
Downtime 58a71331d0 fix(gui): balance ImGui style stacks in gizmo windows (#16269)
Pop the FramePadding style var before end() in the move and rotate gizmo windows. It was pushed inside the window and popped after End(), which failed ImGui's style stack check in Debug builds.
2026-10-12 15:32:59 -05:00
packerlschupfer f127b758ed CLI: Don't rotate objects when arranging unless --allow-rotations is passed (#15033)
The CLI's arrange no longer rotates objects unless --allow-rotations is passed, which matches the GUI's default. STL, OBJ and STEP inputs are arranged even without --arrange, so a part that only fits the bed when rotated now exits with -50 unless --allow-rotations is given. The CLI prints that reason to stderr.
2026-10-12 15:17:38 -05:00
Fernando Marino` 1494de66de Bracket a toolchange's own gcode against FanMover splitting its moves (#15904)
FanMover could split a move inside change_filament_gcode to place a delayed fan command. It computed the split point from the last position the G-code set, but on Bambu printers the firmware moves the head during the T command, so the inserted waypoint sent the head to a spot the template never visits (#15789).

Both toolchange paths, set_extruder() and the Type1 append_tcr(), now wrap the template output in ;_FAN_MOVER_SKIP_START/END tags when fan speed-up or kick-start is on. FanMover keeps fan commands inside them where they are and drops the tags from the output. multifilament_config() also sizes the per-filament options that the wipe tower reads by index.
2026-10-12 14:26:20 -05:00
SoftFever 3ca182a98e Fix H2C start G-code record-data comments (#16406)
This fixes the H2C start G-code template by commenting the two
`========== record data ==========` separator lines.

Those lines are currently emitted as raw text inside
`machine_start_gcode`. Since they are not comments or G-code commands,
they can make the generated start sequence invalid or firmware-dependent
when the template is used. The fix preserves the separators as comments.

Fixes #16218.

Validation:
- Parsed the modified JSON profile with Python `json.loads`.
- Checked both `record data` separator lines now start with `;` after
unescaping the G-code string.
2026-10-13 01:07:25 +08:00
SoftFever 5e19fc0e6c Stop leaking layers on re-slice and honor each object's slice closing radius (#16066)
# Description

Re-slicing no longer leaks layers in two places. Tree supports dropped
the support layers above the topmost overhang without freeing them. And
an object that had sliced itself, then became identical to another
object (for example a copy whose original lost a per-object setting or
modifier), leaked its own layers when it switched to sharing that
object's slices; it now frees them once it takes the shared layers over.
If slicing is cancelled before that point, they still leak, as before.
Tree support also stops copying its collision and avoidance areas on
every lookup, and releases its working cache once supports are generated
instead of keeping it until the next slice. Elephant-foot compensation
now uses each object's own slice closing radius, where the first object
sliced in a session used to fix the value for every later slice.

Slicing output changes only on layers with more than one region printed
with elephant-foot compensation and no raft, and only where an object's
slice closing radius differs from the value the session had fixed.

# Screenshots/Recordings/Graphs

<!--
> Please attach relevant screenshots to showcase the UI changes.
> Please attach images that can help explain the changes.
-->

## Tests

Adds a regression test for an object that switches to sharing another
object's slices. The `fff_print` suite passes.

<!--
> 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)

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-10-13 00:28:26 +08:00
SoftFever 3f4709aaa7 Fix stale and hidden highlights in the texture displacement gizmo (#16409)
# Description

Fixes highlights in the texture displacement gizmo that stayed on screen
after the state behind them changed, or were drawn where they could not
be seen. Erase whole model, Select whole model and Capture current view
update the paint highlight at once instead of on the next right-click. A
stroke released outside the 3D view ends on the next mouse move, in
every paint gizmo, rather than leaving the brush stuck in its pressed
colour. The layer textures stop showing an image that has been replaced.
Selecting islands in the UV editor switches a Normal view to Fast, where
their highlight is visible instead of buried under the displaced relief,
and Normal comes back once no island is selected unless another view was
picked in the meantime.

No change to slicing output; the changes are confined to the paint
gizmos and the UV editor.

# 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.
-->

<!--
> 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)
2026-10-13 00:23:33 +08:00
SoftFever c63bd5d474 Switch to the Fast view while UV islands are selected in the texture gizmo
The island highlight was hidden under the Normal view's relief. Normal comes back once no island is selected, unless another view was picked in the meantime.
2026-10-13 00:18:51 +08:00
SoftFever 6263502697 Free a sharing object's own layers only when it takes over the shared ones 2026-10-13 00:15:07 +08:00
HanifKoh 1a9201690d Draw Preview Segments With 16-Bit Indices (#16389)
The segment draw switched to an element buffer of 8-bit indices. GCN 1/2
GPUs cannot read 8-bit indices in hardware, so the driver converts them,
and on a Mac Pro 2013 FirePro under Windows Boot Camp the toolpaths come
out as scattered spikes. 16-bit indices are supported everywhere and keep
the eight shader runs per segment.
2026-10-13 00:09:18 +08:00
SoftFever c6a338f35f Fix stale paint highlights and textures in the texture displacement gizmo
Erase whole model, Select whole model and Capture current view update the
highlight at once. A stroke released outside the 3D view ends on the next mouse
move, in every paint gizmo, instead of leaving the brush in its pressed colour.
The layer textures no longer show an image that was replaced.
2026-10-12 23:18:38 +08:00
HanifKoh 4f2d2ae11a Wait for the PR's Deps Cache in clang-tidy When deps/ Changes (#16374)
* Wait for the PR's Deps Cache in clang-tidy When deps/ Changes

* Fail the clang-tidy Deps Wait When Listing Caches Keeps Failing

* Wait for Deps Through ci_deps_cache.sh in clang-tidy

Reuse the macOS deps wait for Linux: the job names, workflow and cache
ref are passed in, and a new 'own' mode waits for the PR's own build
when it changes deps/. A PR that keeps deps/ now waits for a running base
build with the same deps, and warns when it falls back to older ones. The
wait ends as soon as the deps job fails.
2026-10-12 23:17:41 +08:00
SoftFever c57b5dc6c5 Merge branch 'main' into perf/slicing-core-memory 2026-10-12 22:16:10 +08:00
SoftFever 7149728219 Fix clang-cl dependency builds failing on a precompiled header from the compiler cache 2026-10-12 20:27:37 +08:00
SoftFever f49d49b5a6 Texture displacement: typed values on every panel slider (#16395)
Every slider in the texture displacement panel can now be given an exact
number, either by Ctrl+clicking the track or via a small edit icon at
its right end. Both routes reuse ImGui's own temp-input mode.

Each slider also gets a tooltip with its description and its range, and
the panel's `?` popup lists both ways in.

Draft: description to be refined.
2026-10-12 20:03:43 +08:00
SoftFever 08f2037cbd Texture displacement: save layers, textures and paint in the .3mf (#16148)
## General

Nothing about texture displacement was written to the `.3mf`, so a
painted relief did not survive closing the project.
Everything the user set up is saved now, so a project reopens with the
relief intact and still editable.

### What is saved
- The layer stack: every per-layer setting (depth, midlevel, smoothing,
tiling, projection blend mode, colour) and the per-volume options
- Each layer's texture, as a file in the archive (why? because user
might want to move .3mf to another computer where custom textures are
missing, or he saves .3mf then deletes textures, and returns back to
.3mf)
- The painted region, one mask per layer slot
- The UV work: LSCM seams, island placement, per-vertex overrides
- Editor state: the selected layer and Standard/Pro, so the panel
reopens where it was left

### What is not saved
- The preview mesh. It is a second copy of the model in the file, for
something the app rebuilds quickly on reopen.
- Panel docked/undocked, which goes to the application config instead

## Format

- `<volume id>.json` - the layer stack and the per-volume options
- `<volume id>_<slot>.png` - each layer's texture, referenced from the
JSON through `path_in_3mf`

Paint masks ride along as one per-triangle attribute per slot
(`paint_texture_0..7`), beside
the attributes the existing paint tools already write.

## Compatibility
Both ways:
- an older build ignores what it does not know and still opens the
project, losing only the relief
- a newer build finds nothing in an older project and leaves the volume
as today.
2026-10-12 20:00:17 +08:00
SoftFever d8cae3268a Merge branch 'main' into feature/texdisp-3mf-persistence 2026-10-12 19:47:05 +08:00
chenshj73 7d4ec3494c Fix H2C start G-code record-data comments 2026-10-12 19:30:32 +08:00
HanifKoh cccf53850e Remove Never-Built and Stub Source Files (#16361)
SysInfoDialog, JumpPointSearch and WebUpdatePlugin are in no CMake list on any platform, and nothing includes ConfigExceptions.hpp, wxinit.h or Utils/Profile.hpp. MultiTaskModel holds only empty namespaces. Also drop the empty GUI_App::system_info() and comments that still mention SysInfoDialog.
2026-10-12 18:53:32 +08:00
HanifKoh 5095733323 Fix Include Guards Copied From Other Headers (#16368)
TriangleMeshDeal.hpp reused Timer.hpp's guard and CloneDialog.hpp reused
SingleChoiceDialog.hpp's, so including both headers of a pair silently
dropped the second one. Use #pragma once instead.
2026-10-12 18:52:53 +08:00
SoftFever 3f78959a0c Harden loading and saving of texture displacement in projects
A project no longer reads texture files from outside its archive, and a
malformed value or layer slot drops only the texture layers instead of
failing the whole load. Copies of an object that share a mesh keep their
own texture paint when saved, and projects without texture paint no
longer use extra memory to load.
2026-10-12 17:47:04 +08:00
SoftFever 30e48183e3 Merge branch 'main' into feature/texdisp-3mf-persistence
# Conflicts:
#	src/libslic3r/TextureDisplacement.hpp
#	tests/libslic3r/test_texture_displacement.cpp
2026-10-12 16:58:03 +08:00
ExPikaPaka ed1f3e52c9 Texture displacement: typed values on every panel slider
Every slider in the texture displacement panel now takes an exact number,
either by Ctrl+clicking the track or through a small edit icon at its right
end. Both routes end in ImGui's own temp-input mode: the icon only asks ImGui
to focus the slider, which is what turns it into an input box, so the parsing,
the clamping and the Enter/click-away behaviour stay ImGui's rather than
becoming a second implementation.

The mechanism lives in one helper, typed_row(), with a float and an int form
over it. float_row() and int_row() are thin wrappers on those, and the six
sliders that previously built their widget inline (brush size, smart fill
angle, edge fade amount, remesh sharp angle, refine resolution and the uniform
triangle budget) now go through the same path.

Each slider carries a tooltip with its description and its range. The range is
derived from the bounds and the format string the slider is already given, so
no row states them twice and the tooltip cannot drift from the track. The
tooltip allows hovering while disabled, since several rows are drawn inside
disabled_begin(), where a greyed slider is exactly when the explanation is
wanted.

The panel's "?" popup gains an entry for each of the two ways in, appended to
every tool's shortcut list and shown on their own when no painting tool is
selected, where that popup was previously empty.
2026-10-12 07:57:07 +02:00
ExPikaPaka 8ecfee6a21 Save where the panel was left, and recover a texture the archive lacks
The project carried the layer stack but not the user's place in it: a reopened
project landed on slot 0 in Standard, whatever they had been editing. The active
layer and Standard/Pro now live on the volume, next to the rest of its options,
and come back with it. Docked/undocked goes to the application config instead -
it describes the user's window, not the model, so opening someone else's project
must not move their panel.

A layer whose image_data was empty at save time was written with no texture and
came back with none. It now falls back to the file the layer came from, which the
importer put in the user's own texture library, so a project reopened on the same
machine keeps its pixels.

The texture is always stored as .png: the importer converts whatever the user
picked, so the extension was being taken from a name that no longer described the
bytes.

Also adds v2_flip_edges to the cereal field list, which the undo stack was
silently dropping.
2026-10-09 09:35:35 +02:00
ExPikaPaka fbf4652e12 Fix the build and clang-tidy failures in the persistence change
The round-trip test named a TextureTileMethod enumerator that does not
exist (MirroredRepeat, not Mirror) and used a bare Approx, which Catch2 v3
keeps in its own namespace. The rest of the file compares floats with
WithinAbs, so these follow it rather than reaching for Catch::Approx.

bbs_3mf.cpp reads and writes the layer JSON but picked up the declarations
through another header, and the same for boost::to_lower.
2026-10-09 09:05:42 +02:00
ExPikaPaka be9ff50130 Merge remote-tracking branch 'origin/main' into feature/texdisp-3mf-persistence
# Conflicts:
#	src/libslic3r/TextureDisplacement.cpp
2026-10-09 09:03:51 +02:00
SoftFever dee1f1715f Merge branch 'main' into feature/texdisp-3mf-persistence 2026-10-09 12:15:16 +08:00
ExPikaPaka fcb1f62bd9 Save texture displacement in the project file
A painted relief did not survive closing the project: nothing about the feature
was written to the .3mf, so the layers, their textures and the paint mask were
all lost on save. The layer struct's cereal save()/load() serve the undo/redo
stack only, which is one session and one binary stream.

Write the layer stack and the per-volume options as JSON in the archive, and
each layer's texture as the image file it was loaded from, both referenced by
path - the same split EmbossShape already makes for its SVG. The image stays
out of the XML deliberately: it is binary and routinely megabytes, and base64
in an attribute would bloat the one file every reader parses just to list the
objects. JSON rather than the cereal stream next to it because that one is
positional and unversioned, which would make every future field a
project-breaking change; an unknown key is ignored and a missing one keeps its
default, so a project written by either side keeps loading.

The paint masks go in as one attribute per layer slot, mirroring paint_color.
Older readers ignore attributes they do not know, so a project written here
still opens in a build without the feature - it loses the relief, which is all
it could have done with it anyway.

The layers are restored while the volume is being built rather than after the
archive is walked: volumes are constructed only once the whole archive has been
read, so a hook placed after the walk ran before any volume existed and
restored nothing.
2026-10-05 08:36:22 +02:00
ExPikaPaka bea412fddf Move the G-code processing result instead of copying it
GCodeProcessorResult declares a copy assignment, which suppresses the implicit
move assignment, so `*result = std::move(processor.extract_result())` binds to
the copy and duplicates the whole moves array: 96 bytes per move, measured at
147.7 MiB in one allocation for a 597k facet model at 0.08 mm, with the source
staying alive until the export returns.

Add the move assignment. It assigns exactly the same 39 members in the same
order as the copy, including the ones the copy deliberately leaves alone, so a
target that carries its own filament maps and nozzle type keeps them. A move
constructor is intentionally not added: the type holds a std::mutex, so it is
neither copy nor move constructible today and a partial one would leave ten
members uninitialised.

reset() now releases the two print sized vectors rather than clearing them. Its
callers are the paths that discard a result, so the memory went back only at the
next slice.

ViewerImpl::reset() does the same for the two vectors sized by the print, which
is what keeps a discarded preview resident, and counts m_vertices_colors in the
reported CPU memory, where it was missing.
2026-10-01 09:15:15 +02:00
ExPikaPaka 391c53a8da Take the print config by reference while simplifying extrusions
simplify_path, simplify_multi_path and simplify_loop each copied the whole
PrintConfig, 10520 bytes with around 200 heap owning members, once per extrusion
entity, from inside the parallel_for over all layers. LayerRegion.cpp already
binds a reference a few hundred lines above. Three more sites in Layer.cpp do the
same thing.

The tree support changes in the same area: draw_circles() dropped layers from an
owning vector without deleting them, so every re-slice leaked the layers above
the topmost overhang. std::stable_partition rather than std::remove_if, because
remove_if leaves moved-from duplicates of kept pointers in the tail and deleting
those would be a double free. The collision and avoidance accessors returned
ExPolygons by value out of a cache that hands them out by reference, several
times per node per layer. The preview cache is released when the support step
finishes instead of at the start of the next slice; nothing reads it in between.

set_shared_object() now releases the layers the object still owns before it
starts aliasing another object's. Both clear functions are no-ops once the alias
is set, so this cannot free the sharee's layers.

PrintObjectSlice.cpp: eps was a function local static inside a parallel_for, so
slice_closing_radius was latched once per process. This one changes output for
projects that override it per object, or on a re-slice after editing it. The old
behaviour was undefined: whichever object won the race set it for the session.
2026-10-01 09:15:15 +02:00
174 changed files with 4966 additions and 12962 deletions
+2 -6
View File
@@ -341,12 +341,12 @@ jobs:
# Notarize main DMG
xcrun notarytool submit "OrcaSlicer_Mac_universal_${{ env.ver }}.dmg" --apple-id "${{ secrets.APPLE_DEV_ACCOUNT }}" --team-id "${{ secrets.TEAM_ID }}" --password "${{ secrets.APP_PWD }}" --wait
xcrun stapler staple OrcaSlicer_Mac_universal_${{ env.ver }}.dmg
retry xcrun stapler staple OrcaSlicer_Mac_universal_${{ env.ver }}.dmg
# Notarize profile validator DMG if it exists
if [ -f "OrcaSlicer_profile_validator_Mac_universal_${{ env.ver }}.dmg" ]; then
xcrun notarytool submit "OrcaSlicer_profile_validator_Mac_universal_${{ env.ver }}.dmg" --apple-id "${{ secrets.APPLE_DEV_ACCOUNT }}" --team-id "${{ secrets.TEAM_ID }}" --password "${{ secrets.APP_PWD }}" --wait
xcrun stapler staple OrcaSlicer_profile_validator_Mac_universal_${{ env.ver }}.dmg
retry xcrun stapler staple OrcaSlicer_profile_validator_Mac_universal_${{ env.ver }}.dmg
fi
- name: Create DMG without notary
@@ -460,10 +460,6 @@ jobs:
- name: Build slicer Win
if: runner.os == 'Windows'
working-directory: ${{ github.workspace }}
# Orca: Removed Netfabb STL fixing service support in favor of CGAL.
# env:
# WindowsSdkDir: 'C:\Program Files (x86)\Windows Kits\10\'
# WindowsSDKVersion: '10.0.26100.0\'
# --tests builds the unit tests too; the unit_tests_windows_* jobs run them.
run: |
# cache-path is the install directory inside the deps build directory.
+70 -4
View File
@@ -16,6 +16,15 @@ on:
permissions:
contents: read
# Listing caches and builds while waiting for a deps cache.
actions: read
# Read by scripts/ci_deps_cache.sh.
env:
REPO: ${{ github.repository }}
WORKFLOW: build_all.yml
BASE_REF: ${{ github.base_ref }}
JOB: build_linux (${{ vars.SELF_HOSTED && 'orca-lnx-server' || 'ubuntu-24.04' }})
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
@@ -35,7 +44,7 @@ jobs:
# The PR merge commit plus its first parent, the base it is diffed against.
fetch-depth: 2
- name: Look for changed C++ files
- name: Look for changed files
id: changes
run: |
if git -c core.quotePath=false diff --name-only HEAD^1 -- src tests | grep -qE '\.(cpp|cc|cxx|hpp|h|hxx)$'; then
@@ -43,18 +52,75 @@ jobs:
else
echo "No C++ changes under src/ or tests/."
fi
if ! git diff --quiet HEAD^1 -- deps; then
echo "deps=true" >> "$GITHUB_OUTPUT"
fi
echo "deps-key=linux-clang-cache-orcaslicer_deps-build-${{ hashFiles('deps/**') }}" >> "$GITHUB_OUTPUT"
# Parsing needs the dependency headers, not a build of this PR's deps/, so
# a PR that changes deps/ falls back to the newest cache main has.
# Parsing needs the dependency headers, not a build of this PR's deps/.
# Without an exact match the job waits for the build that saves it: this
# PR's own when it changes deps/, otherwise a running push build of the
# base branch with the same deps. With neither, a PR that keeps deps/
# falls back to the newest cache.
- name: Restore cached deps
id: deps_cache
if: steps.changes.outputs.cpp == 'true'
uses: actions/cache/restore@v6
with:
path: ${{ github.workspace }}/deps/build/OrcaSlicer_dep
key: linux-clang-cache-orcaslicer_deps-build-${{ hashFiles('deps/**') }}
key: ${{ steps.changes.outputs.deps-key }}
- name: Look for a build to wait for
id: wait_check
if: steps.changes.outputs.cpp == 'true' && steps.deps_cache.outputs.cache-hit != 'true'
env:
GH_TOKEN: ${{ github.token }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: bash scripts/ci_deps_cache.sh ${{ steps.changes.outputs.deps == 'true' && 'own' || 'check' }}
# Linux Build Deps takes about 35 minutes on GitHub-hosted runners. The
# wait ends early when the deps job fails.
- name: Wait for the deps cache
if: steps.wait_check.outputs.wait-for != ''
timeout-minutes: 100
env:
GH_TOKEN: ${{ github.token }}
KEY: ${{ steps.changes.outputs.deps-key }}
CACHE_REF: ${{ steps.changes.outputs.deps == 'true' && github.ref || format('refs/heads/{0}', github.base_ref) }}
KEY_INPUTS: ${{ steps.wait_check.outputs.key-inputs }}
WAIT_FOR: ${{ steps.wait_check.outputs.wait-for }}
WAIT_SINCE: ${{ steps.wait_check.outputs.wait-since }}
WAIT_MINUTES: 90
run: bash scripts/ci_deps_cache.sh wait
- name: Restore the deps cache after waiting
id: waited_cache
if: steps.wait_check.outputs.wait-for != ''
uses: actions/cache/restore@v6
with:
path: ${{ github.workspace }}/deps/build/OrcaSlicer_dep
key: ${{ steps.changes.outputs.deps-key }}
- name: Fail without this PR's deps cache
if: steps.changes.outputs.deps == 'true' && steps.wait_check.outcome == 'success' && steps.waited_cache.outputs.cache-hit != 'true'
run: |
echo "::error::No deps cache ${{ steps.changes.outputs.deps-key }}. Check this PR's Linux Build Deps job, then rerun clang-tidy."
exit 1
- name: Restore the newest cached deps
id: newest_cache
if: steps.changes.outputs.deps != 'true' && steps.wait_check.outcome == 'success' && steps.waited_cache.outputs.cache-hit != 'true'
uses: actions/cache/restore@v6
with:
path: ${{ github.workspace }}/deps/build/OrcaSlicer_dep
key: ${{ steps.changes.outputs.deps-key }}
restore-keys: linux-clang-cache-orcaslicer_deps-build-
fail-on-cache-miss: true
- name: Warn about older deps
if: steps.newest_cache.outcome == 'success' && steps.newest_cache.outputs.cache-hit != 'true'
run: echo "::warning title=Older deps::No cache matches this PR's deps/, so clang-tidy parsed against ${{ steps.newest_cache.outputs.cache-matched-key }}. If it reports errors in code that uses a dependency, rerun clang-tidy once the base branch's Build all has saved the new deps."
- name: Apt-Install Dependencies
if: steps.changes.outputs.cpp == 'true' && !vars.SELF_HOSTED
uses: ./.github/actions/apt-install-deps
-41
View File
@@ -497,47 +497,6 @@ endif()
# Once we get rid of Perl and libslic3r is linked statically, we can get rid of -fPIC
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
# WIN10SDK_PATH is used to point CMake to the WIN10 SDK installation directory.
# We pick it from environment if it is not defined in another way
# ORCA: Removed Netfabb STL fixing service support in favor of CGAL.
# if(WIN32)
# if(NOT DEFINED WIN10SDK_PATH)
# if(DEFINED ENV{WIN10SDK_PATH})
# set(WIN10SDK_PATH "$ENV{WIN10SDK_PATH}")
# endif()
# endif()
# if(DEFINED WIN10SDK_PATH)
# #BBS: modify win10sdk_path
# if (EXISTS "${WIN10SDK_PATH}/winrt/windows.graphics.printing3d.h")
# set(WIN10SDK_INCLUDE_PATH "${WIN10SDK_PATH}")
# else()
# message("WIN10SDK_PATH is invalid: ${WIN10SDK_PATH}")
# message("${WIN10SDK_PATH}/winrt/windows.graphics.printing3d.h was not found")
# message("STL fixing by the Netfabb service will not be compiled")
# unset(WIN10SDK_PATH)
# endif()
# else()
# # Try to use the default Windows 10 SDK path.
# if (DEFINED ENV{WindowsSdkDir} AND DEFINED ENV{WindowsSDKVersion})
# set(WIN10SDK_INCLUDE_PATH "$ENV{WindowsSdkDir}/Include/$ENV{WindowsSDKVersion}")
# else ()
# set(WIN10SDK_INCLUDE_PATH "C:/Program Files (x86)/Windows Kits/10/Include/10.0.26100.0")
# endif ()
# if (NOT EXISTS "${WIN10SDK_INCLUDE_PATH}/winrt/windows.graphics.printing3d.h")
# message("${WIN10SDK_INCLUDE_PATH}/winrt/windows.graphics.printing3d.h was not found")
# message("STL fixing by the Netfabb service will not be compiled")
# unset(WIN10SDK_INCLUDE_PATH)
# endif()
# endif()
# if(WIN10SDK_INCLUDE_PATH)
# message("Building with Win10 Netfabb STL fixing service support")
# add_definitions(-DHAS_WIN10SDK)
# include_directories(SYSTEM "${WIN10SDK_INCLUDE_PATH}")
# else()
# message("Building without Win10 Netfabb STL fixing service support")
# endif()
# endif()
if (APPLE)
message("OS X SDK Path: ${CMAKE_OSX_SYSROOT}")
if (CMAKE_OSX_DEPLOYMENT_TARGET)
+8
View File
@@ -1,10 +1,18 @@
# ccache does not parse the -clang: arguments CMake uses for clang-cl's gcc-style
# depfile, so a cache hit writes the object and no depfile, and Ninja then records
# no headers for that object. ccache reproduces /showIncludes output on a hit.
#
# Clang also records the modification time of every input in a precompiled header
# and rejects the header once one differs. ccache caches the compile that creates
# the header, so a tree that regenerates cmake_pch.cxx gets back a header stamped
# with the old file's time, and every compile that uses it fails. The build system
# already rebuilds the header when an input changes. This covers the dependencies,
# such as wxWidgets; PrecompiledHeader.cmake does the same for OrcaSlicer's targets.
foreach (_lang C CXX)
if (CMAKE_${_lang}_COMPILER_ID STREQUAL "Clang" AND
CMAKE_${_lang}_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
set(CMAKE_DEPFILE_FLAGS_${_lang} "/showIncludes")
set(CMAKE_${_lang}_DEPFILE_FORMAT msvc)
list(APPEND CMAKE_${_lang}_COMPILE_OPTIONS_CREATE_PCH -Xclang -fno-pch-timestamp)
endif ()
endforeach ()
-7
View File
@@ -131,13 +131,6 @@
<true/>
<key>NSHumanReadableCopyright</key>
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
<key>NSLocalNetworkUsageDescription</key>
<string>OrcaSlicer uses your local network to discover and connect to printers.</string>
<key>NSBonjourServices</key>
<array>
<string>_octoprint._tcp</string>
<string>_http._tcp</string>
</array>
<key>NSAppTransportSecurity</key>
<dict>
<!-- Disable App Transport Security. Resolves https://github.com/OrcaSlicer/OrcaSlicer/issues/791 -->
+135
View File
@@ -0,0 +1,135 @@
# Mesh repair — High Level Design
## Purpose and scope
Mesh repair turns a broken triangle mesh into closed, outward oriented shells that bound a solid, so that the
mesh slices predictably and the other mesh tools (booleans, cut, painting) get valid input. It runs on demand:
the *Fix Model* command of the context menu of an object or part and of the warning icon in the object list, *Fix
All* and *Fix All Plates* in the context menu of a plate, the prompt the Cut tool shows when a cut leaves open edges,
the *Repair and import* choice of texture import, and the `--repair` option of the command line. It is built on CGAL
and is the only repair the application has, on every platform.
`repair_mesh()` in `src/libslic3r/MeshRepair.cpp` does the work on one `TriangleMesh`, and `ModelVolume::repair()`
applies it to a part: it replaces the mesh only when the repair changed it, and remaps or drops the painting of a
replaced mesh. The GUI side, `fix_model_with_cgal_gui()` in `src/slic3r/Utils/FixModelByCgal.cpp`, runs it on a
worker thread for each volume of an object, or for one volume, behind a progress dialog. `repair_mesh()` reports its
progress between stages through a callback, which cancels the repair by throwing.
## What the repaired mesh is
The repair keeps what the slicer would print from the input. The slicer fills a layer where the winding number of
the input surface is not zero (`MeshSlicingParams::SlicingMode::Regular` uses Clipper's nonzero fill rule), so:
- Shells crossing each other print as their union. The repair keeps them as separate shells, as the parts of an
object are kept, and makes each of them an outward oriented solid.
- A shell inside a solid that is oriented like it adds nothing, and the repair removes it.
- A shell inside a solid that is oriented inward is a cavity, and stays one.
- A whole mesh turned inside out prints as a solid, and the repair turns it outward.
On top of that, it closes holes and cracks, gives inconsistently oriented triangles the orientation of their
neighbours, drops degenerate, duplicate and stray flat geometry, and removes small self-intersections inside a shell,
such as slivers and folds. Where none of this applies, it reports the mesh as unmodified and the caller keeps the
original mesh, its painting and its object id.
A volume is repaired in place and is never split into several parts: splitting by connectivity would turn a cavity
into a separate solid part and fill it. Each solid of the result is a shell of its own, so *Split to parts* still
separates them. Open pieces of the input whose borders meet exactly, which are pieces of one surface rather than
solids, come out as the one shell they form.
## Pipeline
The input is treated as a triangle soup. Every stage after the first only runs on what the previous ones could
not settle, so a valid mesh passes through quickly.
### Valid parts first
Parts of the input that are connected by vertex indices and are already closed, consistently oriented 2-manifolds
skip the soup stages and go straight to the shell stages. Merging their points with coincident points of other
parts would only make non-manifold junctions out of solids that touch, for example parts of an assembly exported
together or voxels written as separate cubes.
### Soup clean-up
The rest of the soup has its coincident points merged. Triangles with repeated corners are dropped. Coincident
triangles of opposite orientation are a wall between two solids touching face to face, so they cancel in pairs;
of the remaining copies of a triangle one is kept. A connected region that would vanish entirely through
cancelling is a two-sided export instead (every face written in both orientations), and keeps one copy of each
triangle.
Open edges then get their vertices welded within a tolerance of 1e-4 of the bounding box diagonal, which closes the
cracks some exporters leave between triangles that should share corners. A vertex is never welded further than a
quarter of its shortest edge, so welding cannot collapse fine detail. A soup that stays mostly open after welding has
cracks wider than that tolerance, and is welded again with ten and then a hundred times the tolerance.
### Work units
The soup is cut into units repaired independently and in parallel: its connected parts, except that open parts whose
bounding boxes touch share a unit, so that cracks and T-junctions between separately exported patches still close.
### Building the mesh
A unit whose edges are each shared by at most two triangles, running in opposite directions, and whose vertices each
have a single fan of triangles, becomes a `Surface_mesh` directly. Any other unit is oriented first. Triangles are
flipped so that neighbours across an edge shared by two of them agree, each part keeping the orientation the
majority of its area had, which keeps inward oriented cavities inward. Edges where that is impossible or that more
than two triangles share are cut, and each fan of triangles around a vertex gets its own copy of the vertex. If that
still leaves an invalid mesh, CGAL's `orient_polygon_soup()` orients the unit instead, and the parts it flipped
against their majority are reversed back. The linear pass exists because `orient_polygon_soup()` is several times
slower on large soups that need only a few fixes.
### Closing
Compatible borders are stitched, parts that can be stitched once reversed are merged, and borders still open are
snapped onto each other within the weld tolerance, which closes T-junctions. Exactly degenerate faces are removed.
Holes are filled last. Planar border loops that face each other in one plane, one inside the other, bound a single
region, such as a missing annulus at the end of a tube or a cap with features standing on it. Such a group is
triangulated at once with a 2D constrained Delaunay triangulation that fills only the faces at odd nesting depth,
so the openings inside the region stay open. The patch is used only if it closes every loop with the orientation
of the triangles around it. Every other loop is filled on its own with `triangulate_hole()`, which uses a 2D
constrained Delaunay triangulation when the loop is planar. Otherwise it searches the best triangulation among all
triangles over the loop for loops of up to 100 edges, and among the triangles of a 3D Delaunay triangulation for
longer ones, as the full search is cubic in the length of the loop. Beyond 200 edges the full search is not even
used as a fallback, and a fan around the loop's centroid closes a loop nothing else could.
The closed unit is split into its shells. The largest stays in place and the others are copied out, as converting
the whole mesh again costs seconds on large models.
### Shells
The shells of all units, together with the valid parts set aside at the start, are handled independently and in
parallel. Shells enclosing no volume, which hole filling makes out of stray flat patches, are dropped.
Self-intersections inside a shell are removed locally with `experimental::remove_self_intersections()`, which
remeshes the region around them. It is used only for local defects: a shell whose intersections involve more than
5 % of its faces is left as it is, and a result that changes the enclosed volume by more than 0.1 % is undone. On
heavily overlapping ornamental models the remeshing otherwise cuts parts down to slivers, while on genuine defects
it changes the volume by thousandths of a percent. Overlaps left in place slice correctly. Remeshing can cut
small pieces off a shell, so a shell it changed is split again and its pieces enclosing no volume are dropped.
Shells crossing others are found through their bounding boxes and an AABB tree per shell, and are made outward
oriented solids. For every other shell the winding number just outside it is summed from the orientation of the
shells containing it, tested from a point on its largest face. The shell bounds the solid only if the winding
number is zero on exactly one of its sides; it is then oriented so that the solid is inside, or dropped otherwise.
Crossing shells are not united. Slicing unites them anyway, uniting them exactly costs tens of seconds on models
made of many overlapping pieces, and it would fuse parts that *Split to parts* should keep apart.
## Robustness
The repair avoids `experimental::autorefine_and_remove_self_intersections()` and corefinement, which can crash on
messy input, and uses only steps whose failures it can detect. A step that fails keeps its input. CGAL precondition
failures surface as `repair_mesh()` returning false with the message as the error. The output is written in float,
as every mesh in the application is.
The result does not depend on thread scheduling. `libslic3r_cgal` is built with `CGAL_LINKED_WITH_TBB`. Units and
shells are processed in parallel, but each on its own data, and their results are gathered in a fixed order;
sorts compare complete keys, and the detected self-intersections are put in order before they are used. Snapping
stays sequential, as its parallel version gives different results from run to run. Holes are filled with the full
search wherever it is affordable, because the search among Delaunay triangles can choose between equally good fills
differently from run to run, so only non-planar holes of more than 100 edges may still vary. On some inputs
`experimental::remove_self_intersections()` itself places a few vertices differently from run to run.
## Verification
`tests/libslic3r/test_mesh_repair.cpp` covers each kind of defect above on small meshes with known volumes.
+10 -44
View File
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-10-12 19:00+0800\n"
"POT-Creation-Date: 2026-10-11 16:21+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -3999,15 +3999,6 @@ msgstr ""
msgid "Go to Plate"
msgstr ""
msgid "Device"
msgstr ""
msgid "Device (Web)"
msgstr ""
msgid "Multi-device"
msgstr ""
msgid "AMS Materials Setting"
msgstr ""
@@ -4056,9 +4047,6 @@ msgstr ""
msgid "You need to select the material type and color first."
msgstr ""
msgid "The minimum temperature cannot be greater than the maximum temperature."
msgstr ""
#, possible-c-format, possible-boost-format
msgid "Please input a valid value (K in %.1f~%.1f)"
msgstr ""
@@ -4067,12 +4055,6 @@ msgstr ""
msgid "Please input a valid value (K in %.1f~%.1f, N in %.1f~%.1f)"
msgstr ""
msgid "Favorite filaments"
msgstr ""
msgid "All filaments"
msgstr ""
msgid ""
"The nozzle flow is not set. Please set the nozzle flow rate before editing the filament.\n"
"'Device -> Print parts'"
@@ -6003,9 +5985,18 @@ msgstr ""
msgid "Preview"
msgstr ""
msgid "Device"
msgstr ""
msgid "Multi-device"
msgstr ""
msgid "Project"
msgstr ""
msgid "Device (Web)"
msgstr ""
msgid "Yes"
msgstr ""
@@ -6679,22 +6670,6 @@ msgstr ""
msgid "File '%s' was lost! Please download it again."
msgstr ""
msgid "gcodes"
msgstr ""
msgid "Failed to delete the file from printer."
msgstr ""
#, possible-c-format, possible-boost-format
msgid "Do you want to print the file '%s' from printer?"
msgstr ""
msgid "Print file"
msgstr ""
msgid "Failed to start the print job."
msgstr ""
#, possible-c-format, possible-boost-format
msgid ""
"File: %s\n"
@@ -10367,12 +10342,6 @@ msgstr ""
msgid "Clean Up"
msgstr ""
msgid "AMS filament mapping is not available for this printer. Clear the AMS mapping before printing."
msgstr ""
msgid "A filament used by this print has no AMS mapping. Assign it before printing."
msgstr ""
msgid "Preparing print job"
msgstr ""
@@ -27238,9 +27207,6 @@ msgstr ""
msgid "An error occurred loading %s"
msgstr ""
msgid "/"
msgstr ""
#, possible-boost-format
msgid "Load %1% to %2%"
msgstr ""
+14 -45
View File
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-10-12 19:00+0800\n"
"POT-Creation-Date: 2026-10-11 16:21+0800\n"
"PO-Revision-Date: 2025-03-15 10:55+0100\n"
"Last-Translator: \n"
"Language-Team: \n"
@@ -4375,16 +4375,6 @@ msgstr "Projectes recents"
msgid "Go to Plate"
msgstr "Anar a la placa"
msgid "Device"
msgstr "Dispositiu"
# AI Translated
msgid "Device (Web)"
msgstr "Dispositiu (Web)"
msgid "Multi-device"
msgstr "Multidispositiu"
msgid "AMS Materials Setting"
msgstr "Configuració de materials AMS"
@@ -4433,9 +4423,6 @@ msgstr "Estàs segur que vols esborrar la informació del filament?"
msgid "You need to select the material type and color first."
msgstr "Primer heu de seleccionar el tipus de material i el color."
msgid "The minimum temperature cannot be greater than the maximum temperature."
msgstr ""
#, c-format, boost-format
msgid "Please input a valid value (K in %.1f~%.1f)"
msgstr "Introduïu un valor vàlid (K a %.1f~%.1f)"
@@ -4444,12 +4431,6 @@ msgstr "Introduïu un valor vàlid (K a %.1f~%.1f)"
msgid "Please input a valid value (K in %.1f~%.1f, N in %.1f~%.1f)"
msgstr "Introduïu un valor vàlid (K a %.1f~%.1f, N a %.1f~%.1f)"
msgid "Favorite filaments"
msgstr ""
msgid "All filaments"
msgstr ""
msgid ""
"The nozzle flow is not set. Please set the nozzle flow rate before editing the filament.\n"
"'Device -> Print parts'"
@@ -6525,9 +6506,19 @@ msgstr "Preparació"
msgid "Preview"
msgstr "Previsualització"
msgid "Device"
msgstr "Dispositiu"
msgid "Multi-device"
msgstr "Multidispositiu"
msgid "Project"
msgstr "Projecte"
# AI Translated
msgid "Device (Web)"
msgstr "Dispositiu (Web)"
msgid "Yes"
msgstr "Sí"
@@ -7234,22 +7225,6 @@ msgstr "El fitxer .gcode.3mf no conté dades de G-code. Lamineu-lo amb Orca Slic
msgid "File '%s' was lost! Please download it again."
msgstr "L'arxiu '%s' s'ha perdut! Si us plau, descarregueu-lo de nou."
msgid "gcodes"
msgstr ""
msgid "Failed to delete the file from printer."
msgstr ""
#, c-format, boost-format
msgid "Do you want to print the file '%s' from printer?"
msgstr ""
msgid "Print file"
msgstr ""
msgid "Failed to start the print job."
msgstr ""
#, c-format, boost-format
msgid ""
"File: %s\n"
@@ -11436,12 +11411,6 @@ msgstr "Cancel·lar el timelapse i imprimir"
msgid "Clean Up"
msgstr "Netejar"
msgid "AMS filament mapping is not available for this printer. Clear the AMS mapping before printing."
msgstr ""
msgid "A filament used by this print has no AMS mapping. Assign it before printing."
msgstr ""
msgid "Preparing print job"
msgstr "Preparant el treball d'impressió"
@@ -31549,9 +31518,6 @@ msgstr "Activar les eines de desenvolupament"
msgid "An error occurred loading %s"
msgstr "S'ha produït un error en carregar %s"
msgid "/"
msgstr "/"
# AI Translated
#, boost-format
msgid "Load %1% to %2%"
@@ -33619,6 +33585,9 @@ msgstr ""
#~ msgid "An error occurred loading "
#~ msgstr "S'ha produït un error en carregar "
#~ msgid "/"
#~ msgstr "/"
# AI Translated
#, c-format, boost-format
#~ msgid "Load %s to "
+15 -46
View File
@@ -3,7 +3,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Orca Slicer\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-10-12 19:00+0800\n"
"POT-Creation-Date: 2026-10-11 16:21+0800\n"
"PO-Revision-Date: \n"
"Last-Translator: Jakub Hencl\n"
"Language-Team: \n"
@@ -4331,16 +4331,6 @@ msgstr "Nedávné projekty"
msgid "Go to Plate"
msgstr "Přejít na desku"
msgid "Device"
msgstr "Zařízení"
# AI Translated
msgid "Device (Web)"
msgstr "Zařízení (Web)"
msgid "Multi-device"
msgstr "Více zařízení"
msgid "AMS Materials Setting"
msgstr "Nastavení materiálů AMS"
@@ -4391,9 +4381,6 @@ msgstr "Opravdu chcete vymazat informace o filamentu?"
msgid "You need to select the material type and color first."
msgstr "Nejprve musíte vybrat typ materiálu a barvu."
msgid "The minimum temperature cannot be greater than the maximum temperature."
msgstr ""
#, c-format, boost-format
msgid "Please input a valid value (K in %.1f~%.1f)"
msgstr "Zadejte platnou hodnotu (K v rozsahu %.1f~%.1f)"
@@ -4402,12 +4389,6 @@ msgstr "Zadejte platnou hodnotu (K v rozsahu %.1f~%.1f)"
msgid "Please input a valid value (K in %.1f~%.1f, N in %.1f~%.1f)"
msgstr "Zadejte platnou hodnotu (K v rozsahu %.1f~%.1f, N v rozsahu %.1f~%.1f)"
msgid "Favorite filaments"
msgstr ""
msgid "All filaments"
msgstr ""
msgid ""
"The nozzle flow is not set. Please set the nozzle flow rate before editing the filament.\n"
"'Device -> Print parts'"
@@ -6501,9 +6482,19 @@ msgstr "Připravit"
msgid "Preview"
msgstr "Náhled"
msgid "Device"
msgstr "Zařízení"
msgid "Multi-device"
msgstr "Více zařízení"
msgid "Project"
msgstr "Projekt"
# AI Translated
msgid "Device (Web)"
msgstr "Zařízení (Web)"
msgid "Yes"
msgstr "Ano"
@@ -7214,22 +7205,6 @@ msgstr "Soubor .gcode.3mf neobsahuje žádná G-code data. Prosím, rozřežte m
msgid "File '%s' was lost! Please download it again."
msgstr "Soubor '%s' byl ztracen! Stáhněte jej prosím znovu."
msgid "gcodes"
msgstr ""
msgid "Failed to delete the file from printer."
msgstr ""
#, c-format, boost-format
msgid "Do you want to print the file '%s' from printer?"
msgstr ""
msgid "Print file"
msgstr ""
msgid "Failed to start the print job."
msgstr ""
#, c-format, boost-format
msgid ""
"File: %s\n"
@@ -11405,12 +11380,6 @@ msgstr "Zrušit časosběr a tisknout"
msgid "Clean Up"
msgstr "Vyčistit"
msgid "AMS filament mapping is not available for this printer. Clear the AMS mapping before printing."
msgstr ""
msgid "A filament used by this print has no AMS mapping. Assign it before printing."
msgstr ""
msgid "Preparing print job"
msgstr "Příprava tiskové úlohy"
@@ -31563,10 +31532,6 @@ msgstr "Povolit vývojářské nástroje"
msgid "An error occurred loading %s"
msgstr "Při načítání %s došlo k chybě"
# AI Translated
msgid "/"
msgstr "/"
# AI Translated
#, boost-format
msgid "Load %1% to %2%"
@@ -33636,6 +33601,10 @@ msgstr ""
#~ msgid "An error occurred loading "
#~ msgstr "Při načítání došlo k chybě: "
# AI Translated
#~ msgid "/"
#~ msgstr "/"
# AI Translated
#, c-format, boost-format
#~ msgid "Load %s to "
+14 -45
View File
@@ -3,7 +3,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Orca Slicer\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-10-12 19:00+0800\n"
"POT-Creation-Date: 2026-10-11 16:21+0800\n"
"PO-Revision-Date: \n"
"Last-Translator: Heiko Liebscher <hliebschergmail.com>\n"
"Language-Team: \n"
@@ -4246,16 +4246,6 @@ msgstr "Zuletzt verwendete Projekte"
msgid "Go to Plate"
msgstr "Zur Druckplatte wechseln"
msgid "Device"
msgstr "Gerät"
# AI Translated
msgid "Device (Web)"
msgstr "Gerät (Web)"
msgid "Multi-device"
msgstr "Multi-Gerät"
msgid "AMS Materials Setting"
msgstr "AMS Material Einstellung"
@@ -4304,9 +4294,6 @@ msgstr "Sind Sie sicher, dass Sie die Filamentinformationen löschen möchten?"
msgid "You need to select the material type and color first."
msgstr "Sie müssen zuerst den Materialtyp und die Farbe auswählen."
msgid "The minimum temperature cannot be greater than the maximum temperature."
msgstr ""
#, c-format, boost-format
msgid "Please input a valid value (K in %.1f~%.1f)"
msgstr "Bitte geben Sie einen gültigen Wert ein (K in %.1f~%.1f)"
@@ -4315,12 +4302,6 @@ msgstr "Bitte geben Sie einen gültigen Wert ein (K in %.1f~%.1f)"
msgid "Please input a valid value (K in %.1f~%.1f, N in %.1f~%.1f)"
msgstr "Bitte geben Sie einen gültigen Wert ein (K in %.1f~%.1f, N in %.1f~%.1f)"
msgid "Favorite filaments"
msgstr ""
msgid "All filaments"
msgstr ""
msgid ""
"The nozzle flow is not set. Please set the nozzle flow rate before editing the filament.\n"
"'Device -> Print parts'"
@@ -6383,9 +6364,19 @@ msgstr "Vorbereiten"
msgid "Preview"
msgstr "Vorschau"
msgid "Device"
msgstr "Gerät"
msgid "Multi-device"
msgstr "Multi-Gerät"
msgid "Project"
msgstr "Projekt"
# AI Translated
msgid "Device (Web)"
msgstr "Gerät (Web)"
msgid "Yes"
msgstr "Ja"
@@ -7081,22 +7072,6 @@ msgstr "Die .gcode.3mf-Datei enthält keine G-Code-Daten. Bitte slicen Sie sie m
msgid "File '%s' was lost! Please download it again."
msgstr "Die Datei '%s' ist verloren gegangen! Bitte laden Sie diese erneut herunter."
msgid "gcodes"
msgstr ""
msgid "Failed to delete the file from printer."
msgstr ""
#, c-format, boost-format
msgid "Do you want to print the file '%s' from printer?"
msgstr ""
msgid "Print file"
msgstr ""
msgid "Failed to start the print job."
msgstr ""
#, c-format, boost-format
msgid ""
"File: %s\n"
@@ -11221,12 +11196,6 @@ msgstr "Zeitraffer abbrechen & Drucken"
msgid "Clean Up"
msgstr "Bereinigen"
msgid "AMS filament mapping is not available for this printer. Clear the AMS mapping before printing."
msgstr ""
msgid "A filament used by this print has no AMS mapping. Assign it before printing."
msgstr ""
msgid "Preparing print job"
msgstr "Druckauftrag vorbereiten"
@@ -30950,9 +30919,6 @@ msgstr "Entwicklertools aktivieren"
msgid "An error occurred loading %s"
msgstr "Fehler beim Laden von %s"
msgid "/"
msgstr "/"
# AI Translated
#, boost-format
msgid "Load %1% to %2%"
@@ -33014,6 +32980,9 @@ msgstr ""
#~ msgid "An error occurred loading "
#~ msgstr "Fehler beim Laden von "
#~ msgid "/"
#~ msgstr "/"
# AI Translated
#, c-format, boost-format
#~ msgid "Load %s to "
+10 -44
View File
@@ -3,7 +3,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Orca Slicer\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-10-12 19:00+0800\n"
"POT-Creation-Date: 2026-10-11 16:21+0800\n"
"PO-Revision-Date: 2026-06-17 15:44-0300\n"
"Last-Translator: Alexandre Folle de Menezes\n"
"Language-Team: \n"
@@ -3995,15 +3995,6 @@ msgstr ""
msgid "Go to Plate"
msgstr ""
msgid "Device"
msgstr ""
msgid "Device (Web)"
msgstr ""
msgid "Multi-device"
msgstr ""
msgid "AMS Materials Setting"
msgstr ""
@@ -4052,9 +4043,6 @@ msgstr ""
msgid "You need to select the material type and color first."
msgstr ""
msgid "The minimum temperature cannot be greater than the maximum temperature."
msgstr ""
#, c-format, boost-format
msgid "Please input a valid value (K in %.1f~%.1f)"
msgstr ""
@@ -4063,12 +4051,6 @@ msgstr ""
msgid "Please input a valid value (K in %.1f~%.1f, N in %.1f~%.1f)"
msgstr ""
msgid "Favorite filaments"
msgstr ""
msgid "All filaments"
msgstr ""
msgid ""
"The nozzle flow is not set. Please set the nozzle flow rate before editing the filament.\n"
"'Device -> Print parts'"
@@ -5999,9 +5981,18 @@ msgstr ""
msgid "Preview"
msgstr ""
msgid "Device"
msgstr ""
msgid "Multi-device"
msgstr ""
msgid "Project"
msgstr ""
msgid "Device (Web)"
msgstr ""
msgid "Yes"
msgstr ""
@@ -6675,22 +6666,6 @@ msgstr ""
msgid "File '%s' was lost! Please download it again."
msgstr ""
msgid "gcodes"
msgstr ""
msgid "Failed to delete the file from printer."
msgstr ""
#, c-format, boost-format
msgid "Do you want to print the file '%s' from printer?"
msgstr ""
msgid "Print file"
msgstr ""
msgid "Failed to start the print job."
msgstr ""
#, c-format, boost-format
msgid ""
"File: %s\n"
@@ -10363,12 +10338,6 @@ msgstr ""
msgid "Clean Up"
msgstr ""
msgid "AMS filament mapping is not available for this printer. Clear the AMS mapping before printing."
msgstr ""
msgid "A filament used by this print has no AMS mapping. Assign it before printing."
msgstr ""
msgid "Preparing print job"
msgstr ""
@@ -27234,9 +27203,6 @@ msgstr ""
msgid "An error occurred loading %s"
msgstr ""
msgid "/"
msgstr ""
#, boost-format
msgid "Load %1% to %2%"
msgstr ""
+14 -45
View File
@@ -3,7 +3,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Orca Slicer\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-10-12 19:00+0800\n"
"POT-Creation-Date: 2026-10-11 16:21+0800\n"
"PO-Revision-Date: \n"
"Last-Translator: Ian A. Bassi <>\n"
"Language-Team: \n"
@@ -4123,16 +4123,6 @@ msgstr "Proyectos recientes"
msgid "Go to Plate"
msgstr "Ir a la cama"
msgid "Device"
msgstr "Dispositivo"
# AI Translated
msgid "Device (Web)"
msgstr "Dispositivo (Web)"
msgid "Multi-device"
msgstr "Multi-dispositivo"
msgid "AMS Materials Setting"
msgstr "Ajustes de materiales AMS"
@@ -4183,9 +4173,6 @@ msgstr "¿Estás seguro que quieres limpiar la información de filamento?"
msgid "You need to select the material type and color first."
msgstr "Necesitas seleccionar el tipo y el color del material primero."
msgid "The minimum temperature cannot be greater than the maximum temperature."
msgstr ""
#, c-format, boost-format
msgid "Please input a valid value (K in %.1f~%.1f)"
msgstr "Por favor, introduzca un valor válido (K in %.1f~%.1f)"
@@ -4194,12 +4181,6 @@ msgstr "Por favor, introduzca un valor válido (K in %.1f~%.1f)"
msgid "Please input a valid value (K in %.1f~%.1f, N in %.1f~%.1f)"
msgstr "Introduzca un valor válido (K en %.1f~%.1f, N en %.1f~%.1f)"
msgid "Favorite filaments"
msgstr ""
msgid "All filaments"
msgstr ""
msgid ""
"The nozzle flow is not set. Please set the nozzle flow rate before editing the filament.\n"
"'Device -> Print parts'"
@@ -6238,9 +6219,19 @@ msgstr "Preparar"
msgid "Preview"
msgstr "Previsualización"
msgid "Device"
msgstr "Dispositivo"
msgid "Multi-device"
msgstr "Multi-dispositivo"
msgid "Project"
msgstr "Proyecto"
# AI Translated
msgid "Device (Web)"
msgstr "Dispositivo (Web)"
msgid "Yes"
msgstr "Sí"
@@ -6934,22 +6925,6 @@ msgstr "El archivo .gcode .3mf no contiene datos de G-Code. Por favor, lamine co
msgid "File '%s' was lost! Please download it again."
msgstr "¡El archivo '%s' se perdió!\" Por favor, vuelva a descargárlo."
msgid "gcodes"
msgstr ""
msgid "Failed to delete the file from printer."
msgstr ""
#, c-format, boost-format
msgid "Do you want to print the file '%s' from printer?"
msgstr ""
msgid "Print file"
msgstr ""
msgid "Failed to start the print job."
msgstr ""
#, c-format, boost-format
msgid ""
"File: %s\n"
@@ -10956,12 +10931,6 @@ msgstr "Cancelar timelapse e imprimir"
msgid "Clean Up"
msgstr "Limpiar"
msgid "AMS filament mapping is not available for this printer. Clear the AMS mapping before printing."
msgstr ""
msgid "A filament used by this print has no AMS mapping. Assign it before printing."
msgstr ""
msgid "Preparing print job"
msgstr "Preparando el trabajo de impresión"
@@ -30509,9 +30478,6 @@ msgstr "Activar herramientas de desarrollo"
msgid "An error occurred loading %s"
msgstr "Se produjo un error al cargar %s"
msgid "/"
msgstr "/"
# AI Translated
#, boost-format
msgid "Load %1% to %2%"
@@ -32569,6 +32535,9 @@ msgstr ""
#~ msgid "An error occurred loading "
#~ msgstr "Se produjo un error al cargar "
#~ msgid "/"
#~ msgstr "/"
# AI Translated
#, c-format, boost-format
#~ msgid "Load %s to "
+14 -45
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-10-12 19:00+0800\n"
"POT-Creation-Date: 2026-10-11 16:21+0800\n"
"PO-Revision-Date: 2026-07-20 13:33+0200\n"
"Last-Translator: Manu Goiogana <mgoiogana@gmail.com>\n"
"Language-Team: \n"
@@ -4165,16 +4165,6 @@ msgstr "Azken proiektuak"
msgid "Go to Plate"
msgstr "Joan plakara"
msgid "Device"
msgstr "Gailua"
# AI Translated
msgid "Device (Web)"
msgstr "Gailua (Web)"
msgid "Multi-device"
msgstr "Gailu anitz"
msgid "AMS Materials Setting"
msgstr "AMS Materialen Ezarpena"
@@ -4226,9 +4216,6 @@ msgstr "Ziur zaude filamentuaren informazioa garbitu nahi duzula?"
msgid "You need to select the material type and color first."
msgstr "Lehenik eta behin, material-mota eta kolorea hautatu behar dituzu."
msgid "The minimum temperature cannot be greater than the maximum temperature."
msgstr ""
#, c-format, boost-format
msgid "Please input a valid value (K in %.1f~%.1f)"
msgstr "Mesedez, sartu baliozko balio bat (K in %.1f~%.1f)"
@@ -4237,12 +4224,6 @@ msgstr "Mesedez, sartu baliozko balio bat (K in %.1f~%.1f)"
msgid "Please input a valid value (K in %.1f~%.1f, N in %.1f~%.1f)"
msgstr "Mesedez, sartu baliozko balio bat (K in %.1f~%.1f, N in %.1f~%.1f)"
msgid "Favorite filaments"
msgstr ""
msgid "All filaments"
msgstr ""
msgid ""
"The nozzle flow is not set. Please set the nozzle flow rate before editing the filament.\n"
"'Device -> Print parts'"
@@ -6288,9 +6269,19 @@ msgstr "Prestatu"
msgid "Preview"
msgstr "Aurrebista"
msgid "Device"
msgstr "Gailua"
msgid "Multi-device"
msgstr "Gailu anitz"
msgid "Project"
msgstr "Proiektua"
# AI Translated
msgid "Device (Web)"
msgstr "Gailua (Web)"
msgid "Yes"
msgstr "Bai"
@@ -6985,22 +6976,6 @@ msgstr ".gcode.3mf fitxategiak ez du G-code daturik. Mesedez, xerratu OrcaSlicer
msgid "File '%s' was lost! Please download it again."
msgstr "'%s' fitxategia galdu da! Deskargatu berriro."
msgid "gcodes"
msgstr ""
msgid "Failed to delete the file from printer."
msgstr ""
#, c-format, boost-format
msgid "Do you want to print the file '%s' from printer?"
msgstr ""
msgid "Print file"
msgstr ""
msgid "Failed to start the print job."
msgstr ""
#, c-format, boost-format
msgid ""
"File: %s\n"
@@ -11062,12 +11037,6 @@ msgstr "Utzi timelapsea eta inprimatu"
msgid "Clean Up"
msgstr "Garbitu"
msgid "AMS filament mapping is not available for this printer. Clear the AMS mapping before printing."
msgstr ""
msgid "A filament used by this print has no AMS mapping. Assign it before printing."
msgstr ""
msgid "Preparing print job"
msgstr "Inprimatze-lana prestatzen"
@@ -30700,9 +30669,6 @@ msgstr "Gaitu garapen-tresnak"
msgid "An error occurred loading %s"
msgstr "Errore bat gertatu da %s kargatzean"
msgid "/"
msgstr "/"
# AI Translated
#, boost-format
msgid "Load %1% to %2%"
@@ -32765,6 +32731,9 @@ msgstr ""
#~ msgid "An error occurred loading "
#~ msgstr "Errore bat gertatu da hau kargatzean: "
#~ msgid "/"
#~ msgstr "/"
# AI Translated
#, c-format, boost-format
#~ msgid "Load %s to "
+14 -45
View File
@@ -6,7 +6,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Orca Slicer\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-10-12 19:00+0800\n"
"POT-Creation-Date: 2026-10-11 16:21+0800\n"
"PO-Revision-Date: \n"
"Last-Translator: \n"
"Language-Team: Guislain Cyril, Thomas Lété\n"
@@ -4195,16 +4195,6 @@ msgstr "Projets récents"
msgid "Go to Plate"
msgstr "Aller à la plaque"
msgid "Device"
msgstr "Appareil"
# AI Translated
msgid "Device (Web)"
msgstr "Appareil (Web)"
msgid "Multi-device"
msgstr "Multi-appareils"
msgid "AMS Materials Setting"
msgstr "Réglage des matériaux AMS"
@@ -4255,9 +4245,6 @@ msgstr "Voulez-vous vraiment effacer les informations du filament ?"
msgid "You need to select the material type and color first."
msgstr "Vous devez d'abord sélectionner le type de matériau et sa couleur."
msgid "The minimum temperature cannot be greater than the maximum temperature."
msgstr ""
#, c-format, boost-format
msgid "Please input a valid value (K in %.1f~%.1f)"
msgstr "Veuillez saisir une valeur valide (K entre %.1f~%.1f)"
@@ -4266,12 +4253,6 @@ msgstr "Veuillez saisir une valeur valide (K entre %.1f~%.1f)"
msgid "Please input a valid value (K in %.1f~%.1f, N in %.1f~%.1f)"
msgstr "Veuillez saisir une valeur valide (K entre %.1f~%.1f, N entre %.1f~%.1f)"
msgid "Favorite filaments"
msgstr ""
msgid "All filaments"
msgstr ""
msgid ""
"The nozzle flow is not set. Please set the nozzle flow rate before editing the filament.\n"
"'Device -> Print parts'"
@@ -6327,9 +6308,19 @@ msgstr "Préparer"
msgid "Preview"
msgstr "Aperçu"
msgid "Device"
msgstr "Appareil"
msgid "Multi-device"
msgstr "Multi-appareils"
msgid "Project"
msgstr "Projet"
# AI Translated
msgid "Device (Web)"
msgstr "Appareil (Web)"
msgid "Yes"
msgstr "Oui"
@@ -7024,22 +7015,6 @@ msgstr "Le fichier G-code .3mf ne contient pas de données G-code. Veuillez le d
msgid "File '%s' was lost! Please download it again."
msgstr "Le fichier « %s » a été perdu ! Veuillez le télécharger à nouveau."
msgid "gcodes"
msgstr ""
msgid "Failed to delete the file from printer."
msgstr ""
#, c-format, boost-format
msgid "Do you want to print the file '%s' from printer?"
msgstr ""
msgid "Print file"
msgstr ""
msgid "Failed to start the print job."
msgstr ""
#, c-format, boost-format
msgid ""
"File: %s\n"
@@ -11078,12 +11053,6 @@ msgstr "Annuler le timelapse et imprimer"
msgid "Clean Up"
msgstr "Nettoyer"
msgid "AMS filament mapping is not available for this printer. Clear the AMS mapping before printing."
msgstr ""
msgid "A filament used by this print has no AMS mapping. Assign it before printing."
msgstr ""
msgid "Preparing print job"
msgstr "Préparation du travail d'impression"
@@ -30446,9 +30415,6 @@ msgstr "Activer les outils de développement"
msgid "An error occurred loading %s"
msgstr "Une erreur s’est produite lors du chargement de %s"
msgid "/"
msgstr "/"
# AI Translated
#, boost-format
msgid "Load %1% to %2%"
@@ -32500,6 +32466,9 @@ msgstr ""
#~ msgid "An error occurred loading "
#~ msgstr "Une erreur s’est produite lors du chargement de "
#~ msgid "/"
#~ msgstr "/"
# AI Translated
#, c-format, boost-format
#~ msgid "Load %s to "
+16 -47
View File
@@ -3,7 +3,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Orca Slicer\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-10-12 19:00+0800\n"
"POT-Creation-Date: 2026-10-11 16:21+0800\n"
"Language: hu\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -4302,17 +4302,6 @@ msgstr "Legutóbbi projektek"
msgid "Go to Plate"
msgstr "Ugrás a tálcára"
msgid "Device"
msgstr "Nyomtató"
# AI Translated
msgid "Device (Web)"
msgstr "Nyomtató (Web)"
# AI Translated
msgid "Multi-device"
msgstr "Több eszköz"
msgid "AMS Materials Setting"
msgstr "AMS anyagok beállítása"
@@ -4365,9 +4354,6 @@ msgstr "Biztosan törölni szeretnéd a filament adatait?"
msgid "You need to select the material type and color first."
msgstr "Először ki kell választanod az anyagtípust és a színt."
msgid "The minimum temperature cannot be greater than the maximum temperature."
msgstr ""
#, c-format, boost-format
msgid "Please input a valid value (K in %.1f~%.1f)"
msgstr "Adj meg érvényes értéket (K tartomány: %.1f~%.1f)"
@@ -4376,12 +4362,6 @@ msgstr "Adj meg érvényes értéket (K tartomány: %.1f~%.1f)"
msgid "Please input a valid value (K in %.1f~%.1f, N in %.1f~%.1f)"
msgstr "Adj meg érvényes értéket (K tartomány: %.1f~%.1f, N tartomány: %.1f~%.1f)"
msgid "Favorite filaments"
msgstr ""
msgid "All filaments"
msgstr ""
msgid ""
"The nozzle flow is not set. Please set the nozzle flow rate before editing the filament.\n"
"'Device -> Print parts'"
@@ -6468,9 +6448,20 @@ msgstr "Előkészítés"
msgid "Preview"
msgstr "Előnézet"
msgid "Device"
msgstr "Nyomtató"
# AI Translated
msgid "Multi-device"
msgstr "Több eszköz"
msgid "Project"
msgstr "Projekt"
# AI Translated
msgid "Device (Web)"
msgstr "Nyomtató (Web)"
msgid "Yes"
msgstr "Igen"
@@ -7176,22 +7167,6 @@ msgstr "A .gcode.3mf fájl nem tartalmaz G-code adatot. Szeleteld újra Orca Sli
msgid "File '%s' was lost! Please download it again."
msgstr "A(z) '%s' fájl elveszett! Kérlek, töltsd le újra."
msgid "gcodes"
msgstr ""
msgid "Failed to delete the file from printer."
msgstr ""
#, c-format, boost-format
msgid "Do you want to print the file '%s' from printer?"
msgstr ""
msgid "Print file"
msgstr ""
msgid "Failed to start the print job."
msgstr ""
#, c-format, boost-format
msgid ""
"File: %s\n"
@@ -11354,12 +11329,6 @@ msgstr "Timelapse megszakítása és nyomtatás"
msgid "Clean Up"
msgstr "Tisztítás"
msgid "AMS filament mapping is not available for this printer. Clear the AMS mapping before printing."
msgstr ""
msgid "A filament used by this print has no AMS mapping. Assign it before printing."
msgstr ""
msgid "Preparing print job"
msgstr "Nyomtatási feladat előkészítése"
@@ -31428,10 +31397,6 @@ msgstr "Fejlesztői eszközök engedélyezése"
msgid "An error occurred loading %s"
msgstr "Hiba történt a(z) %s betöltésekor"
# AI Translated
msgid "/"
msgstr "/"
# AI Translated
#, boost-format
msgid "Load %1% to %2%"
@@ -33494,6 +33459,10 @@ msgstr ""
#~ msgid "An error occurred loading "
#~ msgstr "Hiba történt a betöltés során: "
# AI Translated
#~ msgid "/"
#~ msgstr "/"
# AI Translated
#, c-format, boost-format
#~ msgid "Load %s to "
+14 -45
View File
@@ -3,7 +3,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Orca Slicer\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-10-12 19:00+0800\n"
"POT-Creation-Date: 2026-10-11 16:21+0800\n"
"PO-Revision-Date: \n"
"Last-Translator: \n"
"Language-Team: \n"
@@ -4295,16 +4295,6 @@ msgstr "Progetti recenti"
msgid "Go to Plate"
msgstr "Vai al piatto"
msgid "Device"
msgstr "Dispositivo"
# AI Translated
msgid "Device (Web)"
msgstr "Dispositivo (Web)"
msgid "Multi-device"
msgstr "Multi-dispositivo"
msgid "AMS Materials Setting"
msgstr "Impostazione materiali AMS"
@@ -4357,9 +4347,6 @@ msgstr "Sei sicuro di voler cancellare le informazioni del filamento?"
msgid "You need to select the material type and color first."
msgstr "Devi prima selezionare il tipo e il colore del materiale."
msgid "The minimum temperature cannot be greater than the maximum temperature."
msgstr ""
#, c-format, boost-format
msgid "Please input a valid value (K in %.1f~%.1f)"
msgstr "Si prega di immettere un valore valido (K in %.1f~%.1f)"
@@ -4368,12 +4355,6 @@ msgstr "Si prega di immettere un valore valido (K in %.1f~%.1f)"
msgid "Please input a valid value (K in %.1f~%.1f, N in %.1f~%.1f)"
msgstr "Si prega di immettere un valore valido (K in %.1f~%.1f, N in %.1f~%.1f)"
msgid "Favorite filaments"
msgstr ""
msgid "All filaments"
msgstr ""
msgid ""
"The nozzle flow is not set. Please set the nozzle flow rate before editing the filament.\n"
"'Device -> Print parts'"
@@ -6435,9 +6416,19 @@ msgstr "Prepara"
msgid "Preview"
msgstr "Anteprima"
msgid "Device"
msgstr "Dispositivo"
msgid "Multi-device"
msgstr "Multi-dispositivo"
msgid "Project"
msgstr "Progetto"
# AI Translated
msgid "Device (Web)"
msgstr "Dispositivo (Web)"
msgid "Yes"
msgstr "Sì"
@@ -7141,22 +7132,6 @@ msgstr "Il file .gcode.3mf non contiene dati G-code. Elaboralo con OrcaSlicer ed
msgid "File '%s' was lost! Please download it again."
msgstr "Il file '%s' è stato perso! Si prega di scaricarlo di nuovo."
msgid "gcodes"
msgstr ""
msgid "Failed to delete the file from printer."
msgstr ""
#, c-format, boost-format
msgid "Do you want to print the file '%s' from printer?"
msgstr ""
msgid "Print file"
msgstr ""
msgid "Failed to start the print job."
msgstr ""
#, c-format, boost-format
msgid ""
"File: %s\n"
@@ -11314,12 +11289,6 @@ msgstr "Annulla timelapse e stampa"
msgid "Clean Up"
msgstr "Pulisci"
msgid "AMS filament mapping is not available for this printer. Clear the AMS mapping before printing."
msgstr ""
msgid "A filament used by this print has no AMS mapping. Assign it before printing."
msgstr ""
msgid "Preparing print job"
msgstr "Preparazione della stampa"
@@ -31290,9 +31259,6 @@ msgstr "Abilita strumenti per sviluppatori"
msgid "An error occurred loading %s"
msgstr "Si è verificato un errore durante il caricamento di %s"
msgid "/"
msgstr "/"
# AI Translated
#, boost-format
msgid "Load %1% to %2%"
@@ -33356,6 +33322,9 @@ msgstr ""
#~ msgid "An error occurred loading "
#~ msgstr "Si è verificato un errore durante il caricamento di "
#~ msgid "/"
#~ msgstr "/"
# AI Translated
#, c-format, boost-format
#~ msgid "Load %s to "
+15 -46
View File
@@ -3,7 +3,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Orca Slicer\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-10-12 19:00+0800\n"
"POT-Creation-Date: 2026-10-11 16:21+0800\n"
"PO-Revision-Date: \n"
"Last-Translator: \n"
"Language-Team: \n"
@@ -4301,17 +4301,6 @@ msgstr "最近のプロジェクト"
msgid "Go to Plate"
msgstr "プレートへ移動"
msgid "Device"
msgstr "デバイス"
# AI Translated
msgid "Device (Web)"
msgstr "デバイス (Web)"
# AI Translated
msgid "Multi-device"
msgstr "マルチデバイス"
msgid "AMS Materials Setting"
msgstr "AMS素材設定"
@@ -4360,9 +4349,6 @@ msgstr "フィラメント情報をクリアしてもよろしいですか?"
msgid "You need to select the material type and color first."
msgstr "まず材料タイプと色を選択してください。"
msgid "The minimum temperature cannot be greater than the maximum temperature."
msgstr ""
#, c-format, boost-format
msgid "Please input a valid value (K in %.1f~%.1f)"
msgstr "有効な値を入力してください(Kは%.1f~%.1f)"
@@ -4371,12 +4357,6 @@ msgstr "有効な値を入力してください(Kは%.1f~%.1f)"
msgid "Please input a valid value (K in %.1f~%.1f, N in %.1f~%.1f)"
msgstr "有効な値を入力してください(Kは%.1f~%.1f、Nは%.1f~%.1f)"
msgid "Favorite filaments"
msgstr ""
msgid "All filaments"
msgstr ""
msgid ""
"The nozzle flow is not set. Please set the nozzle flow rate before editing the filament.\n"
"'Device -> Print parts'"
@@ -6453,9 +6433,20 @@ msgstr "準備"
msgid "Preview"
msgstr "プレビュー"
msgid "Device"
msgstr "デバイス"
# AI Translated
msgid "Multi-device"
msgstr "マルチデバイス"
msgid "Project"
msgstr "プロジェクト"
# AI Translated
msgid "Device (Web)"
msgstr "デバイス (Web)"
msgid "Yes"
msgstr "はい"
@@ -7156,22 +7147,6 @@ msgstr ".gcode.3mfファイルにG-codeデータが含まれていません。Or
msgid "File '%s' was lost! Please download it again."
msgstr "ファイル %s が見つかりませんでした、もう一度ダウンロードしてください。"
msgid "gcodes"
msgstr ""
msgid "Failed to delete the file from printer."
msgstr ""
#, c-format, boost-format
msgid "Do you want to print the file '%s' from printer?"
msgstr ""
msgid "Print file"
msgstr ""
msgid "Failed to start the print job."
msgstr ""
#, c-format, boost-format
msgid ""
"File: %s\n"
@@ -11342,12 +11317,6 @@ msgstr "タイムラプスを中止して印刷"
msgid "Clean Up"
msgstr "整理する"
msgid "AMS filament mapping is not available for this printer. Clear the AMS mapping before printing."
msgstr ""
msgid "A filament used by this print has no AMS mapping. Assign it before printing."
msgstr ""
msgid "Preparing print job"
msgstr "造形タスクを準備"
@@ -31810,9 +31779,6 @@ msgstr "開発者ツールを有効化"
msgid "An error occurred loading %s"
msgstr "%s の読み込み中にエラーが発生しました"
msgid "/"
msgstr "/"
# AI Translated
#, boost-format
msgid "Load %1% to %2%"
@@ -33916,6 +33882,9 @@ msgstr ""
#~ msgid "An error occurred loading "
#~ msgstr "読み込み中にエラーが発生しました: "
#~ msgid "/"
#~ msgstr "/"
# AI Translated
#, c-format, boost-format
#~ msgid "Load %s to "
+14 -45
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Orca Slicer\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-10-12 19:00+0800\n"
"POT-Creation-Date: 2026-10-11 16:21+0800\n"
"PO-Revision-Date: 2025-06-02 17:12+0900\n"
"Last-Translator: crwusiz <crwusiz@gmail.com>\n"
"Language-Team: \n"
@@ -4324,16 +4324,6 @@ msgstr "최근 프로젝트"
msgid "Go to Plate"
msgstr "플레이트로 이동"
msgid "Device"
msgstr "장치"
# AI Translated
msgid "Device (Web)"
msgstr "장치 (웹)"
msgid "Multi-device"
msgstr "멀티 디바이스"
msgid "AMS Materials Setting"
msgstr "AMS 재료 설정"
@@ -4384,9 +4374,6 @@ msgstr "정말 필라멘트 정보를 삭제하시겠습니까?"
msgid "You need to select the material type and color first."
msgstr "재료 유형과 색상을 먼저 선택해야 합니다."
msgid "The minimum temperature cannot be greater than the maximum temperature."
msgstr ""
#, c-format, boost-format
msgid "Please input a valid value (K in %.1f~%.1f)"
msgstr "유효한 값을 입력하세요 (%.1f~%.1f 사이의 K)"
@@ -4395,12 +4382,6 @@ msgstr "유효한 값을 입력하세요 (%.1f~%.1f 사이의 K)"
msgid "Please input a valid value (K in %.1f~%.1f, N in %.1f~%.1f)"
msgstr "유효한 값을 입력하세요 (%.1f~%.1f 사이의 K, %.1f~%.1f 사이의 N)"
msgid "Favorite filaments"
msgstr ""
msgid "All filaments"
msgstr ""
msgid ""
"The nozzle flow is not set. Please set the nozzle flow rate before editing the filament.\n"
"'Device -> Print parts'"
@@ -6491,9 +6472,19 @@ msgstr "준비 하기"
msgid "Preview"
msgstr "미리 보기"
msgid "Device"
msgstr "장치"
msgid "Multi-device"
msgstr "멀티 디바이스"
msgid "Project"
msgstr "프로젝트"
# AI Translated
msgid "Device (Web)"
msgstr "장치 (웹)"
msgid "Yes"
msgstr "예"
@@ -7202,22 +7193,6 @@ msgstr ".gcode.3mf 파일에는 G-code 데이터가 없습니다. OrcaSlicer에
msgid "File '%s' was lost! Please download it again."
msgstr "'%s' 파일이 손실되었습니다! 다시 다운로드하세요."
msgid "gcodes"
msgstr ""
msgid "Failed to delete the file from printer."
msgstr ""
#, c-format, boost-format
msgid "Do you want to print the file '%s' from printer?"
msgstr ""
msgid "Print file"
msgstr ""
msgid "Failed to start the print job."
msgstr ""
#, c-format, boost-format
msgid ""
"File: %s\n"
@@ -11484,12 +11459,6 @@ msgstr "타임랩스 취소 후 출력"
msgid "Clean Up"
msgstr "정리"
msgid "AMS filament mapping is not available for this printer. Clear the AMS mapping before printing."
msgstr ""
msgid "A filament used by this print has no AMS mapping. Assign it before printing."
msgstr ""
msgid "Preparing print job"
msgstr "출력 작업 준비 중"
@@ -31828,9 +31797,6 @@ msgstr "개발자 도구 사용"
msgid "An error occurred loading %s"
msgstr "%s 로드 중 오류가 발생했습니다"
msgid "/"
msgstr "/"
# AI Translated
#, boost-format
msgid "Load %1% to %2%"
@@ -33906,6 +33872,9 @@ msgstr ""
#~ msgid "An error occurred loading "
#~ msgstr "로드 중 오류가 발생했습니다: "
#~ msgid "/"
#~ msgstr "/"
# AI Translated
#, c-format, boost-format
#~ msgid "Load %s to "
-1
View File
@@ -147,7 +147,6 @@ src/slic3r/GUI/HMSPanel.cpp
src/slic3r/GUI/MainFrame.cpp
src/slic3r/GUI/MediaPlayCtrl.cpp
src/slic3r/GUI/MediaFilePanel.cpp
src/slic3r/GUI/OrcaFilesPanel.cpp
src/slic3r/GUI/ImageGrid.cpp
src/slic3r/GUI/Widgets/ImageSwitchButton.cpp
src/slic3r/GUI/Printer/PrinterFileSystem.cpp
+15 -47
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-10-12 19:00+0800\n"
"POT-Creation-Date: 2026-10-11 16:21+0800\n"
"PO-Revision-Date: 2026-07-02 14:13+0300\n"
"Last-Translator: Gintaras Kučinskas <sharanchius@gmail.com>\n"
"Language-Team: \n"
@@ -4291,16 +4291,6 @@ msgstr "Paskutinieji projektai"
msgid "Go to Plate"
msgstr "Eiti į plokštę"
msgid "Device"
msgstr "Įrenginys"
# AI Translated
msgid "Device (Web)"
msgstr "Įrenginys (Web)"
msgid "Multi-device"
msgstr "Kelių įrenginių valdymas (Multi-device)"
msgid "AMS Materials Setting"
msgstr "AMS medžiagų nuostatos"
@@ -4351,9 +4341,6 @@ msgstr "Ar jūs tikrai norite išvalyti gijos informaciją?"
msgid "You need to select the material type and color first."
msgstr "Pradžioje jums reikia pasirinkti medžiagos tipą ir spalvą."
msgid "The minimum temperature cannot be greater than the maximum temperature."
msgstr ""
#, c-format, boost-format
msgid "Please input a valid value (K in %.1f~%.1f)"
msgstr "Įveskite tinkamą reikšmę (K %.1f~%.1f)"
@@ -4362,12 +4349,6 @@ msgstr "Įveskite tinkamą reikšmę (K %.1f~%.1f)"
msgid "Please input a valid value (K in %.1f~%.1f, N in %.1f~%.1f)"
msgstr "Įveskite tinkamą reikšmę (K %.1f~%.1f, N %.1f~%.1f)"
msgid "Favorite filaments"
msgstr ""
msgid "All filaments"
msgstr ""
msgid ""
"The nozzle flow is not set. Please set the nozzle flow rate before editing the filament.\n"
"'Device -> Print parts'"
@@ -6454,9 +6435,19 @@ msgstr "Paruošimas"
msgid "Preview"
msgstr "Peržiūra"
msgid "Device"
msgstr "Įrenginys"
msgid "Multi-device"
msgstr "Kelių įrenginių valdymas (Multi-device)"
msgid "Project"
msgstr "Projektas"
# AI Translated
msgid "Device (Web)"
msgstr "Įrenginys (Web)"
msgid "Yes"
msgstr "Taip"
@@ -7162,22 +7153,6 @@ msgstr "Failas „.gcode.3mf“ neturi G-code duomenų. Supjaustykite jį su „
msgid "File '%s' was lost! Please download it again."
msgstr "Failas „%s“ nerastas! Atsisiųskite jį iš naujo."
msgid "gcodes"
msgstr ""
msgid "Failed to delete the file from printer."
msgstr ""
#, c-format, boost-format
msgid "Do you want to print the file '%s' from printer?"
msgstr ""
msgid "Print file"
msgstr ""
msgid "Failed to start the print job."
msgstr ""
#, c-format, boost-format
msgid ""
"File: %s\n"
@@ -11310,12 +11285,6 @@ msgstr "Atšaukti intervalinį fotografavimą ir spausdinti"
msgid "Clean Up"
msgstr "Išvalyti"
msgid "AMS filament mapping is not available for this printer. Clear the AMS mapping before printing."
msgstr ""
msgid "A filament used by this print has no AMS mapping. Assign it before printing."
msgstr ""
msgid "Preparing print job"
msgstr "Spausdinimo užduoties rengimas"
@@ -12985,7 +12954,6 @@ msgstr ""
msgid "Select filtered"
msgstr ""
# AI Translated
msgid "Clear all"
msgstr "Išvalyti visus"
@@ -31164,10 +31132,6 @@ msgstr "Įjungti kūrėjo įrankius"
msgid "An error occurred loading %s"
msgstr "Įvyko klaida įkeliant %s"
# AI Translated
msgid "/"
msgstr "/"
# AI Translated
#, boost-format
msgid "Load %1% to %2%"
@@ -33235,6 +33199,10 @@ msgstr ""
#~ msgid "An error occurred loading "
#~ msgstr "Įvyko klaida įkeliant "
# AI Translated
#~ msgid "/"
#~ msgstr "/"
# AI Translated
#, c-format, boost-format
#~ msgid "Load %s to "
+15 -46
View File
@@ -3,7 +3,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Orca Slicer\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-10-12 19:00+0800\n"
"POT-Creation-Date: 2026-10-11 16:21+0800\n"
"PO-Revision-Date: \n"
"Last-Translator: \n"
"Language-Team: \n"
@@ -4660,17 +4660,6 @@ msgstr "Recente projecten"
msgid "Go to Plate"
msgstr "Naar printbed gaan"
msgid "Device"
msgstr "Apparaat"
# AI Translated
msgid "Device (Web)"
msgstr "Apparaat (Web)"
# AI Translated
msgid "Multi-device"
msgstr "Meerdere apparaten"
msgid "AMS Materials Setting"
msgstr "AMS Materiaal instellingen"
@@ -4727,9 +4716,6 @@ msgstr "Weet u zeker dat u de filamentinformatie wilt wissen?"
msgid "You need to select the material type and color first."
msgstr "U moet eerst het materiaaltype en de kleur selecteren."
msgid "The minimum temperature cannot be greater than the maximum temperature."
msgstr ""
# AI Translated
#, c-format, boost-format
msgid "Please input a valid value (K in %.1f~%.1f)"
@@ -4740,12 +4726,6 @@ msgstr "Voer een geldige waarde in (K tussen %.1f~%.1f)"
msgid "Please input a valid value (K in %.1f~%.1f, N in %.1f~%.1f)"
msgstr "Voer een geldige waarde in (K tussen %.1f~%.1f, N tussen %.1f~%.1f)"
msgid "Favorite filaments"
msgstr ""
msgid "All filaments"
msgstr ""
# AI Translated
msgid ""
"The nozzle flow is not set. Please set the nozzle flow rate before editing the filament.\n"
@@ -6988,10 +6968,21 @@ msgstr "Voorbereiden"
msgid "Preview"
msgstr "Voorvertoning"
msgid "Device"
msgstr "Apparaat"
# AI Translated
msgid "Multi-device"
msgstr "Meerdere apparaten"
# AI Translated
msgid "Project"
msgstr "Project"
# AI Translated
msgid "Device (Web)"
msgstr "Apparaat (Web)"
msgid "Yes"
msgstr "Ja"
@@ -7740,22 +7731,6 @@ msgstr "Het .gcode.3mf-bestand bevat geen G-code-gegevens. Slice het met Orca Sl
msgid "File '%s' was lost! Please download it again."
msgstr "Bestand '%s' is verloren gegaan! Download het opnieuw."
msgid "gcodes"
msgstr ""
msgid "Failed to delete the file from printer."
msgstr ""
#, c-format, boost-format
msgid "Do you want to print the file '%s' from printer?"
msgstr ""
msgid "Print file"
msgstr ""
msgid "Failed to start the print job."
msgstr ""
#, c-format, boost-format
msgid ""
"File: %s\n"
@@ -12197,12 +12172,6 @@ msgstr "Timelapse annuleren en printen"
msgid "Clean Up"
msgstr "Opruimen"
msgid "AMS filament mapping is not available for this printer. Clear the AMS mapping before printing."
msgstr ""
msgid "A filament used by this print has no AMS mapping. Assign it before printing."
msgstr ""
msgid "Preparing print job"
msgstr "Print opdracht voorbereiden"
@@ -33354,9 +33323,6 @@ msgstr "Ontwikkelaarstools inschakelen"
msgid "An error occurred loading %s"
msgstr "Er is een fout opgetreden bij het laden van %s"
msgid "/"
msgstr "/"
# AI Translated
#, boost-format
msgid "Load %1% to %2%"
@@ -35490,6 +35456,9 @@ msgstr ""
#~ msgid "An error occurred loading "
#~ msgstr "Er is een fout opgetreden bij het laden van "
#~ msgid "/"
#~ msgstr "/"
# AI Translated
#, c-format, boost-format
#~ msgid "Load %s to "
+14 -45
View File
@@ -3,7 +3,7 @@ msgid ""
msgstr ""
"Project-Id-Version: OrcaSlicer 2.3.0-rc\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-10-12 19:00+0800\n"
"POT-Creation-Date: 2026-10-11 16:21+0800\n"
"PO-Revision-Date: \n"
"Last-Translator: Krzysztof Morga <<tlumaczeniebs@gmail.com>>\n"
"Language-Team: \n"
@@ -4382,16 +4382,6 @@ msgstr "Ostatnie projekty"
msgid "Go to Plate"
msgstr "Przejdź do płyty"
msgid "Device"
msgstr "Urządzenie"
# AI Translated
msgid "Device (Web)"
msgstr "Urządzenie (Web)"
msgid "Multi-device"
msgstr "Wiele urządzeń"
msgid "AMS Materials Setting"
msgstr "Ustawienia filamentów AMS"
@@ -4445,9 +4435,6 @@ msgstr "Czy na pewno usunąć informacje o filamentach?"
msgid "You need to select the material type and color first."
msgstr "Najpierw należy wybrać rodzaj i kolor filamentu."
msgid "The minimum temperature cannot be greater than the maximum temperature."
msgstr ""
#, c-format, boost-format
msgid "Please input a valid value (K in %.1f~%.1f)"
msgstr "Proszę podać prawidłową wartość (K w zakresie %.1f~%.1f)"
@@ -4456,12 +4443,6 @@ msgstr "Proszę podać prawidłową wartość (K w zakresie %.1f~%.1f)"
msgid "Please input a valid value (K in %.1f~%.1f, N in %.1f~%.1f)"
msgstr "Proszę podać prawidłową wartość (K w zakresie %.1f~%.1f, N in %.1f~%.1f)"
msgid "Favorite filaments"
msgstr ""
msgid "All filaments"
msgstr ""
msgid ""
"The nozzle flow is not set. Please set the nozzle flow rate before editing the filament.\n"
"'Device -> Print parts'"
@@ -6571,9 +6552,19 @@ msgstr "Przygotowanie"
msgid "Preview"
msgstr "Podgląd"
msgid "Device"
msgstr "Urządzenie"
msgid "Multi-device"
msgstr "Wiele urządzeń"
msgid "Project"
msgstr "Projekt"
# AI Translated
msgid "Device (Web)"
msgstr "Urządzenie (Web)"
msgid "Yes"
msgstr "Tak"
@@ -7292,22 +7283,6 @@ msgstr "Plik .gcode.3mf nie zawiera danych G-code. Proszę poddać go obróbce w
msgid "File '%s' was lost! Please download it again."
msgstr "Plik „%s” został utracony! Proszę pobrać go ponownie."
msgid "gcodes"
msgstr ""
msgid "Failed to delete the file from printer."
msgstr ""
#, c-format, boost-format
msgid "Do you want to print the file '%s' from printer?"
msgstr ""
msgid "Print file"
msgstr ""
msgid "Failed to start the print job."
msgstr ""
#, c-format, boost-format
msgid ""
"File: %s\n"
@@ -11583,12 +11558,6 @@ msgstr "Anuluj timelapse i drukuj"
msgid "Clean Up"
msgstr "Wyczyść"
msgid "AMS filament mapping is not available for this printer. Clear the AMS mapping before printing."
msgstr ""
msgid "A filament used by this print has no AMS mapping. Assign it before printing."
msgstr ""
msgid "Preparing print job"
msgstr "Przygotowywanie zadania do druku"
@@ -31809,9 +31778,6 @@ msgstr "Włącz narzędzia deweloperskie"
msgid "An error occurred loading %s"
msgstr "Wystąpił błąd podczas ładowania %s"
msgid "/"
msgstr "/"
# AI Translated
#, boost-format
msgid "Load %1% to %2%"
@@ -33854,6 +33820,9 @@ msgstr ""
#~ msgid "An error occurred loading "
#~ msgstr "Wystąpił błąd podczas ładowania"
#~ msgid "/"
#~ msgstr "/"
# AI Translated
#, c-format, boost-format
#~ msgid "Load %s to "
File diff suppressed because it is too large Load Diff
+14 -46
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: OrcaSlicer V2.5.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-10-12 19:00+0800\n"
"POT-Creation-Date: 2026-10-11 16:21+0800\n"
"PO-Revision-Date: 2026-02-25 13:38+0300\n"
"Last-Translator: Felix14_v2\n"
"Language-Team: Felix14_v2 (ДС/ТГ: @felix14_v2, почта: aleks111001@list.ru), Andylg <andylg@yandex.ru>\n"
@@ -4267,16 +4267,6 @@ msgstr "Недавние проекты"
msgid "Go to Plate"
msgstr "Перейти к столу"
msgid "Device"
msgstr "Принтер"
# AI Translated
msgid "Device (Web)"
msgstr "Принтер (веб)"
msgid "Multi-device"
msgstr "Принтеры"
msgid "AMS Materials Setting"
msgstr "Настройка материалов AMS"
@@ -4327,9 +4317,6 @@ msgstr "Вы действительно хотите удалить информ
msgid "You need to select the material type and color first."
msgstr "Сначала нужно выбрать тип материала и цвет."
msgid "The minimum temperature cannot be greater than the maximum temperature."
msgstr ""
#, c-format, boost-format
msgid "Please input a valid value (K in %.1f~%.1f)"
msgstr "Пожалуйста, введите допустимое значение (K в диапазоне %.1f~%.1f)"
@@ -4338,12 +4325,6 @@ msgstr "Пожалуйста, введите допустимое значени
msgid "Please input a valid value (K in %.1f~%.1f, N in %.1f~%.1f)"
msgstr "Пожалуйста, введите допустимое значение (K в диапазоне %.1f~%.1f, N в диапазоне %.1f~%.1f)"
msgid "Favorite filaments"
msgstr ""
msgid "All filaments"
msgstr ""
# Дичь какая-то бамбуковская с терминами, требуется проверка.
msgid ""
"The nozzle flow is not set. Please set the nozzle flow rate before editing the filament.\n"
@@ -6484,9 +6465,19 @@ msgstr "Подготовка"
msgid "Preview"
msgstr "Просмотр нарезки"
msgid "Device"
msgstr "Принтер"
msgid "Multi-device"
msgstr "Принтеры"
msgid "Project"
msgstr "Проект"
# AI Translated
msgid "Device (Web)"
msgstr "Принтер (веб)"
msgid "Yes"
msgstr "Да"
@@ -7199,22 +7190,6 @@ msgstr "Файл .gcode.3mf не содержит данных G-code. Испо
msgid "File '%s' was lost! Please download it again."
msgstr "Файл '%s' потерян! Пожалуйста, загрузите его снова."
msgid "gcodes"
msgstr ""
msgid "Failed to delete the file from printer."
msgstr ""
#, c-format, boost-format
msgid "Do you want to print the file '%s' from printer?"
msgstr ""
msgid "Print file"
msgstr ""
msgid "Failed to start the print job."
msgstr ""
#, c-format, boost-format
msgid ""
"File: %s\n"
@@ -11323,12 +11298,6 @@ msgstr "Отключить таймлапс и продолжить"
msgid "Clean Up"
msgstr "Очистить"
msgid "AMS filament mapping is not available for this printer. Clear the AMS mapping before printing."
msgstr ""
msgid "A filament used by this print has no AMS mapping. Assign it before printing."
msgstr ""
msgid "Preparing print job"
msgstr "Подготовка задания на печать"
@@ -12999,7 +12968,6 @@ msgstr ""
msgid "Select filtered"
msgstr ""
# AI Translated
msgid "Clear all"
msgstr "Очистить"
@@ -31658,9 +31626,6 @@ msgstr "Включить инструменты разработчика"
msgid "An error occurred loading %s"
msgstr "Ошибка при загрузке %s"
msgid "/"
msgstr "/"
# AI Translated
#, boost-format
msgid "Load %1% to %2%"
@@ -33728,6 +33693,9 @@ msgstr ""
#~ msgid "An error occurred loading "
#~ msgstr "Ошибка при загрузке "
#~ msgid "/"
#~ msgstr "/"
# AI Translated
#, c-format, boost-format
#~ msgid "Load %s to "
+15 -46
View File
@@ -3,7 +3,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Orca Slicer\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-10-12 19:00+0800\n"
"POT-Creation-Date: 2026-10-11 16:21+0800\n"
"Language: sv\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -4728,17 +4728,6 @@ msgstr "Senaste projekt"
msgid "Go to Plate"
msgstr "Gå till platta"
msgid "Device"
msgstr "Enhet"
# AI Translated
msgid "Device (Web)"
msgstr "Enhet (Webb)"
# AI Translated
msgid "Multi-device"
msgstr "Flera enheter"
msgid "AMS Materials Setting"
msgstr "AMS Material Inställning"
@@ -4792,9 +4781,6 @@ msgstr "Är du säker på att du vill rensa filament informationen?"
msgid "You need to select the material type and color first."
msgstr "Du måste först välja materialtyp och färg."
msgid "The minimum temperature cannot be greater than the maximum temperature."
msgstr ""
# AI Translated
#, c-format, boost-format
msgid "Please input a valid value (K in %.1f~%.1f)"
@@ -4805,12 +4791,6 @@ msgstr "Ange ett giltigt värde (K mellan %.1f~%.1f)"
msgid "Please input a valid value (K in %.1f~%.1f, N in %.1f~%.1f)"
msgstr "Ange ett giltigt värde (K mellan %.1f~%.1f, N mellan %.1f~%.1f)"
msgid "Favorite filaments"
msgstr ""
msgid "All filaments"
msgstr ""
# AI Translated
msgid ""
"The nozzle flow is not set. Please set the nozzle flow rate before editing the filament.\n"
@@ -7085,9 +7065,20 @@ msgstr "Förbered"
msgid "Preview"
msgstr "Förhandsvisning"
msgid "Device"
msgstr "Enhet"
# AI Translated
msgid "Multi-device"
msgstr "Flera enheter"
msgid "Project"
msgstr "Projekt"
# AI Translated
msgid "Device (Web)"
msgstr "Enhet (Webb)"
msgid "Yes"
msgstr "Ja"
@@ -7834,22 +7825,6 @@ msgstr "Filen .gcode.3mf innehåller inga G-code-data. Bered den med Orca Slicer
msgid "File '%s' was lost! Please download it again."
msgstr "Filen '%s' förlorades! Ladda ner den igen."
msgid "gcodes"
msgstr ""
msgid "Failed to delete the file from printer."
msgstr ""
#, c-format, boost-format
msgid "Do you want to print the file '%s' from printer?"
msgstr ""
msgid "Print file"
msgstr ""
msgid "Failed to start the print job."
msgstr ""
# AI Translated
#, c-format, boost-format
msgid ""
@@ -12345,12 +12320,6 @@ msgstr "Avbryt timelapse och skriv ut"
msgid "Clean Up"
msgstr "Rensa"
msgid "AMS filament mapping is not available for this printer. Clear the AMS mapping before printing."
msgstr ""
msgid "A filament used by this print has no AMS mapping. Assign it before printing."
msgstr ""
msgid "Preparing print job"
msgstr "Förbereder utskriftsjobb"
@@ -33688,9 +33657,6 @@ msgstr "Aktivera utvecklarverktyg"
msgid "An error occurred loading %s"
msgstr "Ett fel uppstod vid inläsning av %s"
msgid "/"
msgstr "/"
# AI Translated
#, boost-format
msgid "Load %1% to %2%"
@@ -35844,6 +35810,9 @@ msgstr ""
#~ msgid "An error occurred loading "
#~ msgstr "Ett fel uppstod vid inläsning av "
#~ msgid "/"
#~ msgstr "/"
# AI Translated
#, c-format, boost-format
#~ msgid "Load %s to "
+15 -46
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Orca Slicer\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-10-12 19:00+0800\n"
"POT-Creation-Date: 2026-10-11 16:21+0800\n"
"PO-Revision-Date: 2026-06-19 13:40+0700\n"
"Last-Translator: Icezaza\n"
"Language-Team: Thai\n"
@@ -4279,16 +4279,6 @@ msgstr "โปรเจกต์ล่าสุด"
msgid "Go to Plate"
msgstr "ไปที่ฐานพิมพ์"
msgid "Device"
msgstr "อุปกรณ์"
# AI Translated
msgid "Device (Web)"
msgstr "อุปกรณ์ (เว็บ)"
msgid "Multi-device"
msgstr "หลายอุปกรณ์"
msgid "AMS Materials Setting"
msgstr "การตั้งค่าวัสดุ AMS"
@@ -4339,9 +4329,6 @@ msgstr "คุณแน่ใจหรือไม่ว่าต้องกา
msgid "You need to select the material type and color first."
msgstr "คุณต้องเลือกประเภทวัสดุและสีก่อน"
msgid "The minimum temperature cannot be greater than the maximum temperature."
msgstr ""
#, c-format, boost-format
msgid "Please input a valid value (K in %.1f~%.1f)"
msgstr "โปรดป้อนค่าที่ถูกต้อง (K อยู่ในช่วง %.1f~%.1f)"
@@ -4350,12 +4337,6 @@ msgstr "โปรดป้อนค่าที่ถูกต้อง (K อ
msgid "Please input a valid value (K in %.1f~%.1f, N in %.1f~%.1f)"
msgstr "โปรดป้อนค่าที่ถูกต้อง (K ใน %.1f~%.1f, N ใน %.1f~%.1f)"
msgid "Favorite filaments"
msgstr ""
msgid "All filaments"
msgstr ""
msgid ""
"The nozzle flow is not set. Please set the nozzle flow rate before editing the filament.\n"
"'Device -> Print parts'"
@@ -6418,9 +6399,19 @@ msgstr "เตรียมพิมพ์"
msgid "Preview"
msgstr "พรีวิว"
msgid "Device"
msgstr "อุปกรณ์"
msgid "Multi-device"
msgstr "หลายอุปกรณ์"
msgid "Project"
msgstr "โปรเจกต์"
# AI Translated
msgid "Device (Web)"
msgstr "อุปกรณ์ (เว็บ)"
msgid "Yes"
msgstr "ใช่"
@@ -7115,22 +7106,6 @@ msgstr "ไฟล์ .gcode.3mf ไม่มีข้อมูล G-code โป
msgid "File '%s' was lost! Please download it again."
msgstr "ไฟล์ '%s' สูญหาย! กรุณาดาวน์โหลดอีกครั้ง."
msgid "gcodes"
msgstr ""
msgid "Failed to delete the file from printer."
msgstr ""
#, c-format, boost-format
msgid "Do you want to print the file '%s' from printer?"
msgstr ""
msgid "Print file"
msgstr ""
msgid "Failed to start the print job."
msgstr ""
#, c-format, boost-format
msgid ""
"File: %s\n"
@@ -11240,12 +11215,6 @@ msgstr "ยกเลิกไทม์แลปส์และพิมพ์"
msgid "Clean Up"
msgstr "ล้าง"
msgid "AMS filament mapping is not available for this printer. Clear the AMS mapping before printing."
msgstr ""
msgid "A filament used by this print has no AMS mapping. Assign it before printing."
msgstr ""
msgid "Preparing print job"
msgstr "กำลังเตรียมงานพิมพ์"
@@ -31045,10 +31014,6 @@ msgstr "เปิดใช้งานเครื่องมือนักพ
msgid "An error occurred loading %s"
msgstr "เกิดข้อผิดพลาดในการโหลด %s"
# AI Translated
msgid "/"
msgstr "/"
# AI Translated
#, boost-format
msgid "Load %1% to %2%"
@@ -33110,6 +33075,10 @@ msgstr ""
#~ msgid "An error occurred loading "
#~ msgstr "เกิดข้อผิดพลาดในการโหลด "
# AI Translated
#~ msgid "/"
#~ msgstr "/"
# AI Translated
#, c-format, boost-format
#~ msgid "Load %s to "
+15 -46
View File
@@ -3,7 +3,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Orca Slicer\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-10-12 19:00+0800\n"
"POT-Creation-Date: 2026-10-11 16:21+0800\n"
"PO-Revision-Date: 2026-08-21 23:18+0300\n"
"Last-Translator: GlauTech\n"
"Language-Team: \n"
@@ -4331,16 +4331,6 @@ msgstr "Son Projeler"
msgid "Go to Plate"
msgstr "Plakaya Git"
msgid "Device"
msgstr "Cihaz"
# AI Translated
msgid "Device (Web)"
msgstr "Cihaz (Web)"
msgid "Multi-device"
msgstr "Çoklu cihaz"
msgid "AMS Materials Setting"
msgstr "AMS Malzeme Ayarı"
@@ -4391,9 +4381,6 @@ msgstr "Filament bilgisini temizlemek istediğinizden emin misiniz?"
msgid "You need to select the material type and color first."
msgstr "Önce malzeme türünü ve rengini seçmeniz gerekir."
msgid "The minimum temperature cannot be greater than the maximum temperature."
msgstr ""
#, c-format, boost-format
msgid "Please input a valid value (K in %.1f~%.1f)"
msgstr "Lütfen geçerli bir değer girin (K %.1f~%.1f içinde)"
@@ -4402,12 +4389,6 @@ msgstr "Lütfen geçerli bir değer girin (K %.1f~%.1f içinde)"
msgid "Please input a valid value (K in %.1f~%.1f, N in %.1f~%.1f)"
msgstr "Lütfen geçerli bir değer girin (K %.1f~%.1f içinde, N %.1f~%.1f içinde)"
msgid "Favorite filaments"
msgstr ""
msgid "All filaments"
msgstr ""
msgid ""
"The nozzle flow is not set. Please set the nozzle flow rate before editing the filament.\n"
"'Device -> Print parts'"
@@ -6484,9 +6465,19 @@ msgstr "Hazırlık"
msgid "Preview"
msgstr "Ön İzleme"
msgid "Device"
msgstr "Cihaz"
msgid "Multi-device"
msgstr "Çoklu cihaz"
msgid "Project"
msgstr "Proje"
# AI Translated
msgid "Device (Web)"
msgstr "Cihaz (Web)"
msgid "Yes"
msgstr "Evet"
@@ -7188,22 +7179,6 @@ msgstr ".gcode.3mf dosyası hiçbir G-code verisi içermiyor. Lütfen dosyayı B
msgid "File '%s' was lost! Please download it again."
msgstr "'%s' dosyası kayboldu! Lütfen tekrar indirin."
msgid "gcodes"
msgstr ""
msgid "Failed to delete the file from printer."
msgstr ""
#, c-format, boost-format
msgid "Do you want to print the file '%s' from printer?"
msgstr ""
msgid "Print file"
msgstr ""
msgid "Failed to start the print job."
msgstr ""
#, c-format, boost-format
msgid ""
"File: %s\n"
@@ -11372,12 +11347,6 @@ msgstr "Timelapse'i İptal Et ve Yazdır"
msgid "Clean Up"
msgstr "Temizle"
msgid "AMS filament mapping is not available for this printer. Clear the AMS mapping before printing."
msgstr ""
msgid "A filament used by this print has no AMS mapping. Assign it before printing."
msgstr ""
msgid "Preparing print job"
msgstr "Yazdırma için hazırlanıyor"
@@ -31488,10 +31457,6 @@ msgstr "Geliştirici Araçlarını Etkinleştir"
msgid "An error occurred loading %s"
msgstr "%s yüklenirken bir hata oluştu"
# AI Translated
msgid "/"
msgstr "/"
# AI Translated
#, boost-format
msgid "Load %1% to %2%"
@@ -33558,6 +33523,10 @@ msgstr ""
#~ msgid "An error occurred loading "
#~ msgstr "Yüklenirken bir hata oluştu: "
# AI Translated
#~ msgid "/"
#~ msgstr "/"
# AI Translated
#, c-format, boost-format
#~ msgid "Load %s to "
+14 -45
View File
@@ -3,7 +3,7 @@ msgid ""
msgstr ""
"Project-Id-Version: orcaslicerua\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-10-12 19:00+0800\n"
"POT-Creation-Date: 2026-10-11 16:21+0800\n"
"PO-Revision-Date: 2026-07-17 16:25+0300\n"
"Last-Translator: Andrij Mizyk <andm1zyk@proton.me>\n"
"Language-Team: Ukrainian\n"
@@ -4245,16 +4245,6 @@ msgstr "Останні проєкти"
msgid "Go to Plate"
msgstr "Перейти до пластини"
msgid "Device"
msgstr "Пристрій"
# AI Translated
msgid "Device (Web)"
msgstr "Пристрій (Веб)"
msgid "Multi-device"
msgstr "Багато пристроїв"
msgid "AMS Materials Setting"
msgstr "Налаштування матеріалів AMS"
@@ -4305,9 +4295,6 @@ msgstr "Ви впевнені, що хочете видалити інформа
msgid "You need to select the material type and color first."
msgstr "Спочатку потрібно вибрати тип матеріалу та колір."
msgid "The minimum temperature cannot be greater than the maximum temperature."
msgstr ""
#, c-format, boost-format
msgid "Please input a valid value (K in %.1f~%.1f)"
msgstr "Будь ласка введіть коректне значення (K в %.1f~%.1f)"
@@ -4316,12 +4303,6 @@ msgstr "Будь ласка введіть коректне значення (K
msgid "Please input a valid value (K in %.1f~%.1f, N in %.1f~%.1f)"
msgstr "Будь ласка введіть коректне значення (K в %.1f~%.1f, N в %.1f~%.1f)"
msgid "Favorite filaments"
msgstr ""
msgid "All filaments"
msgstr ""
# AI Translated
msgid ""
"The nozzle flow is not set. Please set the nozzle flow rate before editing the filament.\n"
@@ -6448,9 +6429,19 @@ msgstr "Підготовка"
msgid "Preview"
msgstr "Перегляд"
msgid "Device"
msgstr "Пристрій"
msgid "Multi-device"
msgstr "Багато пристроїв"
msgid "Project"
msgstr "Проєкт"
# AI Translated
msgid "Device (Web)"
msgstr "Пристрій (Веб)"
msgid "Yes"
msgstr "Так"
@@ -7157,22 +7148,6 @@ msgstr "Файл .gcode.3mf не містить даних G-code. Будь ла
msgid "File '%s' was lost! Please download it again."
msgstr "Файл '%s' втрачений! Завантажте його знову."
msgid "gcodes"
msgstr ""
msgid "Failed to delete the file from printer."
msgstr ""
#, c-format, boost-format
msgid "Do you want to print the file '%s' from printer?"
msgstr ""
msgid "Print file"
msgstr ""
msgid "Failed to start the print job."
msgstr ""
#, c-format, boost-format
msgid ""
"File: %s\n"
@@ -11389,12 +11364,6 @@ msgstr "Скасувати таймлапс і друкувати"
msgid "Clean Up"
msgstr "Очистити"
msgid "AMS filament mapping is not available for this printer. Clear the AMS mapping before printing."
msgstr ""
msgid "A filament used by this print has no AMS mapping. Assign it before printing."
msgstr ""
msgid "Preparing print job"
msgstr "Підготовка завдання для друку"
@@ -31655,9 +31624,6 @@ msgstr "Увімкнути інструменти розробника"
msgid "An error occurred loading %s"
msgstr "Помилка під час завантаження %s"
msgid "/"
msgstr "/"
# AI Translated
#, boost-format
msgid "Load %1% to %2%"
@@ -33752,6 +33718,9 @@ msgstr ""
#~ msgid "An error occurred loading "
#~ msgstr "Помилка під час завантаження "
#~ msgid "/"
#~ msgstr "/"
# AI Translated
#, c-format, boost-format
#~ msgid "Load %s to "
+15 -46
View File
@@ -3,7 +3,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Orca Slicer\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-10-12 19:00+0800\n"
"POT-Creation-Date: 2026-10-11 16:21+0800\n"
"PO-Revision-Date: 2025-10-02 17:43+0700\n"
"Last-Translator: \n"
"Language-Team: hainguyen.ts13@gmail.com\n"
@@ -4503,16 +4503,6 @@ msgstr "Dự án gần đây"
msgid "Go to Plate"
msgstr "Đi tới plate"
msgid "Device"
msgstr "Thiết bị"
# AI Translated
msgid "Device (Web)"
msgstr "Thiết bị (Web)"
msgid "Multi-device"
msgstr "Nhiều thiết bị"
msgid "AMS Materials Setting"
msgstr "Cài đặt vật liệu AMS"
@@ -4565,9 +4555,6 @@ msgstr "Bạn có chắc muốn xóa thông tin filament?"
msgid "You need to select the material type and color first."
msgstr "Bạn cần chọn loại vật liệu và màu trước."
msgid "The minimum temperature cannot be greater than the maximum temperature."
msgstr ""
#, c-format, boost-format
msgid "Please input a valid value (K in %.1f~%.1f)"
msgstr "Vui lòng nhập giá trị hợp lệ (K trong %.1f~%.1f)"
@@ -4576,12 +4563,6 @@ msgstr "Vui lòng nhập giá trị hợp lệ (K trong %.1f~%.1f)"
msgid "Please input a valid value (K in %.1f~%.1f, N in %.1f~%.1f)"
msgstr "Vui lòng nhập giá trị hợp lệ (K trong %.1f~%.1f, N trong %.1f~%.1f)"
msgid "Favorite filaments"
msgstr ""
msgid "All filaments"
msgstr ""
# AI Translated
msgid ""
"The nozzle flow is not set. Please set the nozzle flow rate before editing the filament.\n"
@@ -6808,9 +6789,19 @@ msgstr "Chuẩn bị"
msgid "Preview"
msgstr "Xem trước"
msgid "Device"
msgstr "Thiết bị"
msgid "Multi-device"
msgstr "Nhiều thiết bị"
msgid "Project"
msgstr "Dự án"
# AI Translated
msgid "Device (Web)"
msgstr "Thiết bị (Web)"
msgid "Yes"
msgstr "Có"
@@ -7522,22 +7513,6 @@ msgstr "File .gcode.3mf không chứa dữ liệu G-code. Vui lòng slice với
msgid "File '%s' was lost! Please download it again."
msgstr "File '%s' đã bị mất! Vui lòng tải lại."
msgid "gcodes"
msgstr ""
msgid "Failed to delete the file from printer."
msgstr ""
#, c-format, boost-format
msgid "Do you want to print the file '%s' from printer?"
msgstr ""
msgid "Print file"
msgstr ""
msgid "Failed to start the print job."
msgstr ""
#, c-format, boost-format
msgid ""
"File: %s\n"
@@ -11912,12 +11887,6 @@ msgstr "Hủy timelapse & In"
msgid "Clean Up"
msgstr "Dọn dẹp"
msgid "AMS filament mapping is not available for this printer. Clear the AMS mapping before printing."
msgstr ""
msgid "A filament used by this print has no AMS mapping. Assign it before printing."
msgstr ""
msgid "Preparing print job"
msgstr "Đang chuẩn bị công việc in"
@@ -32326,10 +32295,6 @@ msgstr "Bật công cụ nhà phát triển"
msgid "An error occurred loading %s"
msgstr "Đã xảy ra lỗi khi tải %s"
# AI Translated
msgid "/"
msgstr "/"
# AI Translated
#, boost-format
msgid "Load %1% to %2%"
@@ -34433,6 +34398,10 @@ msgstr ""
#~ msgid "An error occurred loading "
#~ msgstr "Đã xảy ra lỗi khi tải "
# AI Translated
#~ msgid "/"
#~ msgstr "/"
# AI Translated
#, c-format, boost-format
#~ msgid "Load %s to "
+15 -47
View File
@@ -6,7 +6,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Slic3rPE\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-10-12 19:00+0800\n"
"POT-Creation-Date: 2026-10-11 16:21+0800\n"
"PO-Revision-Date: 2026-06-11 12:37-0300\n"
"Last-Translator: Handle <mail@bysb.net>\n"
"Language-Team: \n"
@@ -4162,16 +4162,6 @@ msgstr "最近的项目"
msgid "Go to Plate"
msgstr "转到盘"
msgid "Device"
msgstr "设备"
# AI Translated
msgid "Device (Web)"
msgstr "设备(网页)"
msgid "Multi-device"
msgstr "多设备"
msgid "AMS Materials Setting"
msgstr "AMS 材料设置"
@@ -4220,9 +4210,6 @@ msgstr "您确定要清除耗材丝信息吗?"
msgid "You need to select the material type and color first."
msgstr "您需要先选择材料类型和颜色。"
msgid "The minimum temperature cannot be greater than the maximum temperature."
msgstr ""
#, c-format, boost-format
msgid "Please input a valid value (K in %.1f~%.1f)"
msgstr "请输入有效的数值(K的范围为%.1f~%.1f)"
@@ -4231,12 +4218,6 @@ msgstr "请输入有效的数值(K的范围为%.1f~%.1f)"
msgid "Please input a valid value (K in %.1f~%.1f, N in %.1f~%.1f)"
msgstr "请输入有效的数值(K的范围为%.1f~%.1f,N的范围为%.1f~%.1f)"
msgid "Favorite filaments"
msgstr ""
msgid "All filaments"
msgstr ""
msgid ""
"The nozzle flow is not set. Please set the nozzle flow rate before editing the filament.\n"
"'Device -> Print parts'"
@@ -6317,9 +6298,19 @@ msgstr "准备"
msgid "Preview"
msgstr "预览"
msgid "Device"
msgstr "设备"
msgid "Multi-device"
msgstr "多设备"
msgid "Project"
msgstr "项目"
# AI Translated
msgid "Device (Web)"
msgstr "设备(网页)"
msgid "Yes"
msgstr "是"
@@ -7015,22 +7006,6 @@ msgstr ".gcode.3mf文件中不包含G-code数据。请使用Orca Slicer进行切
msgid "File '%s' was lost! Please download it again."
msgstr "文件%s丢失,请重新下载。"
msgid "gcodes"
msgstr ""
msgid "Failed to delete the file from printer."
msgstr ""
#, c-format, boost-format
msgid "Do you want to print the file '%s' from printer?"
msgstr ""
msgid "Print file"
msgstr ""
msgid "Failed to start the print job."
msgstr ""
#, c-format, boost-format
msgid ""
"File: %s\n"
@@ -11140,12 +11115,6 @@ msgstr "取消延时摄影,继续打印"
msgid "Clean Up"
msgstr "清理"
msgid "AMS filament mapping is not available for this printer. Clear the AMS mapping before printing."
msgstr ""
msgid "A filament used by this print has no AMS mapping. Assign it before printing."
msgstr ""
msgid "Preparing print job"
msgstr "正在准备打印任务"
@@ -12807,7 +12776,6 @@ msgstr "未勾选"
msgid "Select filtered"
msgstr "选择筛选项"
# AI Translated
msgid "Clear all"
msgstr "清空"
@@ -31179,10 +31147,6 @@ msgstr "启用开发者工具"
msgid "An error occurred loading %s"
msgstr "加载 %s 时出错"
# AI Translated
msgid "/"
msgstr "/"
# AI Translated
#, boost-format
msgid "Load %1% to %2%"
@@ -33250,6 +33214,10 @@ msgstr ""
#~ msgid "An error occurred loading "
#~ msgstr "加载时出错: "
# AI Translated
#~ msgid "/"
#~ msgstr "/"
# AI Translated
#, c-format, boost-format
#~ msgid "Load %s to "
+15 -47
View File
@@ -9,7 +9,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Orca Slicer\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-10-12 19:00+0800\n"
"POT-Creation-Date: 2026-10-11 16:21+0800\n"
"PO-Revision-Date: 2025-11-28 13:48-0600\n"
"Last-Translator: tntchn <15895303+tntchn@users.noreply.github.com>\n"
"Language-Team: \n"
@@ -4237,16 +4237,6 @@ msgstr "最近的專案"
msgid "Go to Plate"
msgstr "前往列印板"
msgid "Device"
msgstr "裝置"
# AI Translated
msgid "Device (Web)"
msgstr "裝置(網頁)"
msgid "Multi-device"
msgstr "多臺裝置"
msgid "AMS Materials Setting"
msgstr "AMS 線材設定"
@@ -4298,9 +4288,6 @@ msgstr "您確認要清除線材資訊嗎?"
msgid "You need to select the material type and color first."
msgstr "您需要先選擇線材類型和顏色。"
msgid "The minimum temperature cannot be greater than the maximum temperature."
msgstr ""
#, c-format, boost-format
msgid "Please input a valid value (K in %.1f~%.1f)"
msgstr "請輸入有效的數值 (K於 %.1f~%.1f 之間)"
@@ -4309,12 +4296,6 @@ msgstr "請輸入有效的數值 (K於 %.1f~%.1f 之間)"
msgid "Please input a valid value (K in %.1f~%.1f, N in %.1f~%.1f)"
msgstr "請輸入有效的數值 (K於 %.1f~%.1f 之間,N於 %.1f~%.1f 之間)"
msgid "Favorite filaments"
msgstr ""
msgid "All filaments"
msgstr ""
msgid ""
"The nozzle flow is not set. Please set the nozzle flow rate before editing the filament.\n"
"'Device -> Print parts'"
@@ -6400,9 +6381,19 @@ msgstr "準備"
msgid "Preview"
msgstr "預覽"
msgid "Device"
msgstr "裝置"
msgid "Multi-device"
msgstr "多臺裝置"
msgid "Project"
msgstr "專案"
# AI Translated
msgid "Device (Web)"
msgstr "裝置(網頁)"
msgid "Yes"
msgstr "是"
@@ -7097,22 +7088,6 @@ msgstr ".gcode.3mf 檔案不包含 G-code 資料。請使用 OrcaSlicer 切片
msgid "File '%s' was lost! Please download it again."
msgstr "檔案 %s 遺失,請重新下載。"
msgid "gcodes"
msgstr ""
msgid "Failed to delete the file from printer."
msgstr ""
#, c-format, boost-format
msgid "Do you want to print the file '%s' from printer?"
msgstr ""
msgid "Print file"
msgstr ""
msgid "Failed to start the print job."
msgstr ""
#, c-format, boost-format
msgid ""
"File: %s\n"
@@ -11228,12 +11203,6 @@ msgstr "取消縮時攝影並列印"
msgid "Clean Up"
msgstr "清理"
msgid "AMS filament mapping is not available for this printer. Clear the AMS mapping before printing."
msgstr ""
msgid "A filament used by this print has no AMS mapping. Assign it before printing."
msgstr ""
msgid "Preparing print job"
msgstr "正在準備列印作業"
@@ -12883,7 +12852,6 @@ msgstr ""
msgid "Select filtered"
msgstr ""
# AI Translated
msgid "Clear all"
msgstr "清空"
@@ -30969,10 +30937,6 @@ msgstr "啟用開發者工具"
msgid "An error occurred loading %s"
msgstr "載入 %s 時發生錯誤"
# AI Translated
msgid "/"
msgstr "/"
# AI Translated
#, boost-format
msgid "Load %1% to %2%"
@@ -33050,6 +33014,10 @@ msgstr ""
#~ msgid "An error occurred loading "
#~ msgstr "載入時發生錯誤: "
# AI Translated
#~ msgid "/"
#~ msgstr "/"
# AI Translated
#, c-format, boost-format
#~ msgid "Load %s to "
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "Bambulab",
"url": "http://www.bambulab.com/Parameters/vendor/BBL.json",
"version": "02.08.00.14",
"version": "02.08.00.15",
"force_update": "0",
"description": "the initial version of BBL configurations",
"machine_model_list": [
File diff suppressed because one or more lines are too long
+43 -17
View File
@@ -1,21 +1,27 @@
#!/usr/bin/env bash
# Lets a pull request's macOS deps build wait for a push build of the base
# branch that builds the same deps, from a Linux job. `check` runs after a cache
# miss and names a queued or running base build with the same deps/ and .github/
# to wait for, unless the pull request changes either. `wait` waits until that
# build saves KEY. actions/cache then confirms the entry can be restored.
# Lets a pull request's macOS deps build, or its clang-tidy job, wait for a
# build that saves the deps cache it needs, from a Linux job. `check` runs after
# a cache miss and names a queued or running push build of the base branch with
# the same deps/ and .github/ to wait for, unless the pull request changes either.
# `own` names the pull request's own build instead, for clang-tidy when the pull
# request changes deps/. `wait` waits until that build saves KEY under CACHE_REF,
# or its deps job fails. actions/cache then confirms the entry can be restored.
#
# Environment: GH_TOKEN, REPO, WORKFLOW_REF and BASE_REF, plus KEY_INPUTS,
# WAIT_FOR, WAIT_SINCE, WAIT_MINUTES, KEY and ARCH for `wait`.
# Environment: GH_TOKEN, REPO, WORKFLOW_REF (or WORKFLOW, the build workflow's
# file name) and BASE_REF, plus HEAD_SHA for `own`, and KEY_INPUTS, WAIT_FOR,
# WAIT_SINCE, WAIT_MINUTES, KEY, CACHE_REF (default the base branch) and ARCH
# (or JOB, the matrix job's name) for `wait`.
# Writes key-inputs, wait-for and wait-since to $GITHUB_OUTPUT.
set -uo pipefail
workflow=${WORKFLOW_REF%%@*}
workflow=${workflow##*/}
workflow=${WORKFLOW_REF:-}
workflow=${workflow%%@*}
workflow=${WORKFLOW:-${workflow##*/}}
# Must match the job names in build_all.yml, build_check_cache.yml and build_deps.yml.
check_job_name="build_macos_arch (${ARCH:-}) / Check Cache"
deps_job_name="build_macos_arch (${ARCH:-}) / Build Deps / Build Deps"
job=${JOB:-build_macos_arch (${ARCH:-})}
check_job_name="$job / Check Cache"
deps_job_name="$job / Build Deps / Build Deps"
out() { echo "$1" >> "${GITHUB_OUTPUT:-/dev/stdout}"; }
@@ -63,8 +69,27 @@ check() {
fi
}
own() {
out "key-inputs=$(local_key_inputs HEAD)"
local id
# The build can be created a moment after this job.
for _ in 1 2 3 4 5; do
id=$(gh api -X GET "repos/$REPO/actions/workflows/$workflow/runs" \
-f head_sha="$HEAD_SHA" -f event=pull_request -f per_page=1 --jq '.workflow_runs[0].id // empty' < /dev/null)
[ -n "$id" ] && break
sleep 30
done
if [ -n "$id" ]; then
echo "Waiting for the deps cache of build $id of this pull request."
out "wait-for=$id"
out "wait-since=$(date +%s)"
else
echo "No build of this pull request is building its deps."
fi
}
key_saved() {
gh cache list -R "$REPO" --ref "refs/heads/$BASE_REF" --key "$KEY" --json key < /dev/null \
gh cache list -R "$REPO" --ref "${CACHE_REF:-refs/heads/$BASE_REF}" --key "$KEY" --json key < /dev/null \
| jq -e --arg key "$KEY" 'any(.[]; .key == $key)' > /dev/null
}
@@ -87,11 +112,11 @@ wait_for() {
key_saved && { echo "Found $KEY."; return; }
sleep 20
done
echo "Build $id of $BASE_REF finished its deps without saving $KEY."
echo "Build $id finished its deps without saving $KEY."
return ;;
"completed cancelled") ;;
completed*)
echo "The deps job of build $id of $BASE_REF did not succeed."
echo "The deps job of build $id did not succeed."
return ;;
# The deps job exists only once the build's cache check has succeeded.
"")
@@ -100,7 +125,7 @@ wait_for() {
completed*)
check=$(jq -r --arg name "$check_job_name" 'first(.jobs[] | select(.name == $name) | .conclusion) // ""' <<< "$jobs")
if [ -z "$warned" ] && { [ -z "$check" ] || [ "$check" = success ]; }; then
echo "::warning title=Deps wait::Build $id of $BASE_REF has no job named \"$deps_job_name\". If the build jobs were renamed, update scripts/ci_deps_cache.sh. This does not affect this pull request."
echo "::warning title=Deps wait::Build $id has no job named \"$deps_job_name\". If the build jobs were renamed, update scripts/ci_deps_cache.sh. This does not affect this pull request."
warned=1
fi ;;
*)
@@ -118,11 +143,12 @@ wait_for() {
seen="$seen $id"
echo "Following build $id of $BASE_REF."
done
echo "No build of $BASE_REF saved $KEY."
echo "No build saved $KEY."
}
case "${1:-}" in
check) check ;;
own) own ;;
wait) wait_for ;;
*) echo "usage: $0 check|wait" >&2; exit 2 ;;
*) echo "usage: $0 check|own|wait" >&2; exit 2 ;;
esac
+28 -6
View File
@@ -99,6 +99,7 @@
#include "libslic3r/Preset.hpp"
#include "libslic3r/Geometry.hpp"
#include "libslic3r/GCode.hpp"
#include "libslic3r/MeshRepair.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/ModelArrange.hpp"
#include "libslic3r/Platform.hpp"
@@ -206,7 +207,7 @@ std::map<int, std::string> cli_errors = {
{CLI_OBJECT_ORIENT_FAILED, "An error occurred when auto-orienting object(s)."},
{CLI_MODIFIED_PARAMS_TO_PRINTER, "Found modified parameter in printer preset in the 3mf file, which should not be changed."},
{CLI_FILE_VERSION_NOT_SUPPORTED, "Unsupported 3MF version. Please make sure the 3MF file was created with the official version of Bambu Studio, not a beta version."},
{CLI_NO_SUITABLE_OBJECTS, "One of the plate is empty or has no object fully inside it. Please check that the 3mf contains no empty plate in Orca Slicer before uploading."},
{CLI_NO_SUITABLE_OBJECTS, "The plate has nothing to slice: it is empty, or no object on it is both printable and fully inside the print volume."},
{CLI_VALIDATE_ERROR, "There are some incorrect slicing parameters in the 3mf. Please verify the slicing of all plates in Orca Slicer before uploading."},
{CLI_OBJECTS_PARTLY_INSIDE, "Some objects are located over the boundary of the heated bed."},
{CLI_EXPORT_CACHE_DIRECTORY_CREATE_FAILED, "Failed creating directory when exporting cache data."},
@@ -1463,7 +1464,7 @@ int CLI::run(int argc, char **argv)
//int arrange_option;
int plate_to_slice = 0, filament_count = 0, duplicate_count = 0, real_duplicate_count = 0, current_extruder_count = 1, new_extruder_count = 1, current_printer_variant_count = 1, current_print_variant_count = 1, new_printer_variant_count = 1;
bool first_file = true, is_bbl_3mf = false, need_arrange = true, has_thumbnails = false, up_config_to_date = false, normative_check = true, duplicate_single_object = false, use_first_fila_as_default = false, minimum_save = false, enable_timelapse = false;
bool allow_rotations = true, skip_modified_gcodes = false, avoid_extrusion_cali_region = false, skip_useless_pick = false, allow_newer_file = false, current_is_multi_extruder = false, new_is_multi_extruder = false, allow_mix_temp = false, enable_wrapping_detect = false;
bool allow_rotations = false, skip_modified_gcodes = false, avoid_extrusion_cali_region = false, skip_useless_pick = false, allow_newer_file = false, current_is_multi_extruder = false, new_is_multi_extruder = false, allow_mix_temp = false, enable_wrapping_detect = false;
Semver file_version;
std::map<size_t, bool> orients_requirement;
std::vector<Preset*> project_presets;
@@ -5130,9 +5131,19 @@ int CLI::run(int argc, char **argv)
}
}
} else if (opt_key == "repair") {
// Models are repaired by default.
//for (auto &model : m_models)
// model.repair();
if (m_config.opt_bool(opt_key))
for (Model &model : m_models)
for (ModelObject *object : model.objects) {
for (ModelVolume *volume : object->volumes) {
MeshRepairStats stats;
std::string error;
if (!volume->repair(true, &stats, &error))
BOOST_LOG_TRIVIAL(warning) << "repair of " << object->name << " / " << volume->name << " failed: " << error;
else if (stats.modified)
BOOST_LOG_TRIVIAL(info) << "repaired " << object->name << " / " << volume->name;
}
object->invalidate_bounding_box();
}
} else {
boost::nowide::cerr << "error: option not implemented yet: " << opt_key << std::endl;
record_exit_reson(outfile_dir, CLI_UNSUPPORTED_OPERATION, 0, cli_errors[CLI_UNSUPPORTED_OPERATION], sliced_info);
@@ -5142,6 +5153,7 @@ int CLI::run(int argc, char **argv)
BOOST_LOG_TRIVIAL(info) << "finished model pre-process commands\n";
bool oriented_or_arranged = false;
bool arranged_without_rotations = false;
//BBS: add orient and arrange logic here
for (auto& model : m_models)
{
@@ -5409,6 +5421,7 @@ int CLI::run(int argc, char **argv)
BOOST_LOG_TRIVIAL(info) << boost::format("start plate %1%'s arranging...") % (i + 1);
arrangement::arrange(selected, unselected, beds, arrange_cfg);
//arrangement::arrange(unprintable, {}, beds, arrange_cfg);
arranged_without_rotations |= !arrange_cfg.allow_rotations;
BOOST_LOG_TRIVIAL(info) << boost::format("finished plate %1%'s arranging") % (i + 1);
//step-4: postprocess the bed index and result
@@ -5881,6 +5894,7 @@ int CLI::run(int argc, char **argv)
BOOST_LOG_TRIVIAL(info) << boost::format("start %1% th arranging...")%arrange_count;
arrangement::arrange(selected, unselected, beds, arrange_cfg);
arrangement::arrange(unprintable, {}, beds, arrange_cfg);
arranged_without_rotations |= !arrange_cfg.allow_rotations;
BOOST_LOG_TRIVIAL(info) << boost::format("finished %1% th arranging...")%arrange_count;
//Step-4:postprocess by partplate list&&apply the result
@@ -6425,7 +6439,15 @@ int CLI::run(int argc, char **argv)
if (count == 0) {
BOOST_LOG_TRIVIAL(error) << "plate "<< index+1<< ": Nothing to be sliced, Either the print is empty or no object is fully inside the print volume before apply." << std::endl;
record_exit_reson(outfile_dir, CLI_NO_SUITABLE_OBJECTS, index+1, cli_errors[CLI_NO_SUITABLE_OBJECTS], sliced_info);
std::string no_objects_reason = cli_errors[CLI_NO_SUITABLE_OBJECTS];
if (arranged_without_rotations) {
//ORCA: result.json is only written on Linux, so also print the reason to stderr.
no_objects_reason = "No object is fully inside the print volume. Arrange ran without rotations."
" If an object only fits the bed when rotated, pass --allow-rotations."
" Otherwise the plate is empty or an object is larger than the printable area or height.";
boost::nowide::cerr << "plate " << index+1 << ": " << no_objects_reason << std::endl;
}
record_exit_reson(outfile_dir, CLI_NO_SUITABLE_OBJECTS, index+1, no_objects_reason, sliced_info);
flush_and_exit(CLI_NO_SUITABLE_OBJECTS);
}
else if ((plate_to_slice != 0) || pre_check) {
-7
View File
@@ -126,13 +126,6 @@
</array>
<key>LSMinimumSystemVersion</key>
<string>10.10</string>
<key>NSLocalNetworkUsageDescription</key>
<string>OrcaSlicer uses your local network to discover and connect to printers.</string>
<key>NSBonjourServices</key>
<array>
<string>_octoprint._tcp</string>
<string>_http._tcp</string>
</array>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>NSHighResolutionCapable</key>
+4 -1
View File
@@ -625,6 +625,7 @@ add_library(libslic3r_cgal STATIC
CutSurface.hpp CutSurface.cpp
IntersectionPoints.hpp IntersectionPoints.cpp
MeshBoolean.hpp MeshBoolean.cpp
MeshRepair.hpp MeshRepair.cpp
TryCatchSignal.hpp TryCatchSignal.cpp
Triangulation.hpp Triangulation.cpp
)
@@ -652,7 +653,9 @@ if (IS_CLANG_CL)
target_compile_options(libslic3r_cgal PRIVATE -Wno-overriding-option)
endif ()
target_link_libraries(libslic3r_cgal PRIVATE ${_cgal_tgt} admesh libigl mcut boost_libs)
# Lets CGAL algorithms given CGAL::Parallel_if_available_tag run on TBB.
target_compile_definitions(libslic3r_cgal PRIVATE CGAL_LINKED_WITH_TBB)
target_link_libraries(libslic3r_cgal PRIVATE ${_cgal_tgt} admesh libigl mcut boost_libs TBB::tbb)
if (MSVC AND "${CMAKE_SIZEOF_VOID_P}" STREQUAL "4") # 32 bit MSVC workaround
target_compile_definitions(libslic3r_cgal PRIVATE CGAL_DO_NOT_USE_MPZF)
+15 -5
View File
@@ -236,19 +236,29 @@ static void getNamedSolids(const TopLoc_Location& location,
int i = 0;
switch (shape_type) {
case TopAbs_COMPOUND:
if (!isSplitCompound) {
namedSolids.emplace_back(TopoDS::Compound(transform.Shape()), fullName);
break;
}
case TopAbs_COMPSOLID:
if (!isSplitCompound) {
namedSolids.emplace_back(TopoDS::CompSolid(transform.Shape()), fullName);
namedSolids.emplace_back(transform.Shape(), fullName);
} else {
for (explorer.Init(transform.Shape(), TopAbs_SOLID); explorer.More(); explorer.Next()) {
i++;
const TopoDS_Shape& currentShape = explorer.Current();
namedSolids.emplace_back(TopoDS::Solid(currentShape), fullName + "-SOLID-" + std::to_string(i));
}
// Surface models have no solids: each shell outside a solid is a part, and loose faces stay together.
i = 0;
for (explorer.Init(transform.Shape(), TopAbs_SHELL, TopAbs_SOLID); explorer.More(); explorer.Next())
namedSolids.emplace_back(explorer.Current(), fullName + "-SHELL-" + std::to_string(++i));
TopoDS_Compound loose_faces;
TopoDS_Builder builder;
builder.MakeCompound(loose_faces);
bool has_loose_faces = false;
for (explorer.Init(transform.Shape(), TopAbs_FACE, TopAbs_SHELL); explorer.More(); explorer.Next()) {
builder.Add(loose_faces, explorer.Current());
has_loose_faces = true;
}
if (has_loose_faces)
namedSolids.emplace_back(loose_faces, fullName);
}
break;
case TopAbs_SOLID:
+166 -2
View File
@@ -22,6 +22,7 @@
#include "libslic3r/BrimEarsPoint.hpp"
#include "libslic3r/Config.hpp"
#include "libslic3r/CustomGCode.hpp"
#include "libslic3r/TextureDisplacement.hpp"
#include "libslic3r_version.h"
#include "bbs_3mf.hpp"
@@ -354,6 +355,13 @@ static constexpr const char* CUSTOM_SUPPORTS_ATTR = "paint_supports";
static constexpr const char* CUSTOM_FUZZY_SKIN_ATTR = "paint_fuzzy_skin";
static constexpr const char* CUSTOM_SEAM_ATTR = "paint_seam";
static constexpr const char* MMU_SEGMENTATION_ATTR = "paint_color";
// Texture displacement. One paint mask per layer slot, mirroring paint_color; the layer stack itself is
// a JSON file in the archive, named by the volume metadata key below (see add_texture_displacement()).
static constexpr const char* TEXTURE_DISPLACEMENT_ATTRS[Slic3r::TEXTURE_DISPLACEMENT_MAX_LAYERS] = {
"paint_texture_0", "paint_texture_1", "paint_texture_2", "paint_texture_3",
"paint_texture_4", "paint_texture_5", "paint_texture_6", "paint_texture_7" };
static constexpr const char* TEXTURE_DISPLACEMENT_KEY = "texture_displacement";
static constexpr const char* TEXTURE_DISPLACEMENT_DIR = "Metadata/texture_displacement/";
// BBS
static constexpr const char* FACE_PROPERTY_ATTR = "face_property";
@@ -847,6 +855,9 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
std::vector<std::string> custom_seam;
std::vector<std::string> mmu_segmentation;
std::vector<std::string> fuzzy_skin;
// One per texture displacement layer slot, indexed like `triangles` but stored only as far as the
// slot's last painted triangle: empty when the slot has no paint, which is nearly every file.
std::vector<std::string> texture_displacement[TEXTURE_DISPLACEMENT_MAX_LAYERS];
// BBS
std::vector<std::string> face_properties;
@@ -867,6 +878,8 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
custom_seam.clear();
mmu_segmentation.clear();
fuzzy_skin.clear();
for (std::vector<std::string> &slot : texture_displacement)
slot.clear();
}
};
@@ -1200,6 +1213,11 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
/*IdToSlaSupportPointsMap m_sla_support_points;
IdToSlaDrainHolesMap m_sla_drain_holes;*/
PathToEmbossShapeFileMap m_path_to_emboss_shape_files;
// Texture displacement: every file under Metadata/texture_displacement/, by archive path. Volumes
// are built only after the whole archive has been walked, so by the time a volume names its JSON
// every file it could refer to is already in here, whatever order the archive happened to be in.
std::map<std::string, std::string> m_texture_displacement_files;
void _apply_texture_displacement(ModelVolume &volume, const std::string &json_path);
std::string m_curr_metadata_name;
std::string m_curr_characters;
std::string m_name;
@@ -2073,6 +2091,13 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
else if (_is_svg_shape_file(name)) {
_extract_embossed_svg_shape_file(name, archive, stat);
}
else if (boost::algorithm::istarts_with(name, TEXTURE_DISPLACEMENT_DIR)) {
std::string contents(stat.m_uncomp_size, '\0');
if (mz_zip_reader_extract_to_mem(&archive, stat.m_file_index, contents.data(), stat.m_uncomp_size, 0))
m_texture_displacement_files.emplace(name, std::move(contents));
else
add_error("Error while reading texture displacement data");
}
else if (!dont_load_config && boost::algorithm::iequals(name, SLICE_INFO_CONFIG_FILE)) {
m_parsing_slice_info = true;
//extract slice info from archive
@@ -3310,6 +3335,45 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
}
}*/
// Restores one volume's texture displacement stack from the JSON it named. Called while the volume is
// being built, which is after the whole archive has been walked - so every file it can refer to is
// already extracted, and no deferral is needed.
//
// A missing or malformed JSON leaves the volume with no layers but keeps the paint mask it already
// loaded, which is the same state as a project saved by a build without the feature: recoverable by
// picking the texture again rather than a hard failure.
void _BBS_3MF_Importer::_apply_texture_displacement(ModelVolume &volume, const std::string &json_path)
{
const auto json = m_texture_displacement_files.find(json_path);
if (json == m_texture_displacement_files.end()) {
add_error("Missing texture displacement data: " + json_path);
return;
}
std::vector<TextureDisplacementLayer> layers;
TextureDisplacementOptions options;
if (!texture_displacement_layers_from_json(json->second, layers, options)) {
add_error("Malformed texture displacement data: " + json_path);
return;
}
// The archive is the only source of a layer's pixels. `path` comes from the project file, so loading
// the project must never read the file it names: that would let whoever made the project pick any
// local (or, on Windows, network) file to read, and the next save would embed it in the archive.
// `path` stays a label of where the texture came from; a layer the archive carries no image for
// loads without one, its settings intact, until the user picks a texture again.
for (TextureDisplacementLayer &layer : layers) {
if (layer.path_in_3mf.empty())
continue;
const auto image = m_texture_displacement_files.find(layer.path_in_3mf);
if (image == m_texture_displacement_files.end()) {
add_error("Missing texture displacement image: " + layer.path_in_3mf);
continue;
}
layer.image_data = std::make_shared<std::vector<unsigned char>>(image->second.begin(), image->second.end());
}
volume.texture_displacement_layers = std::move(layers);
volume.texture_displacement_options = options;
}
void _BBS_3MF_Importer::_extract_embossed_svg_shape_file(const std::string &filename, mz_zip_archive &archive, const mz_zip_archive_file_stat &stat){
assert(m_path_to_emboss_shape_files.find(filename) == m_path_to_emboss_shape_files.end());
auto file = std::make_unique<std::string>(stat.m_uncomp_size, '\0');
@@ -3966,6 +4030,12 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
m_curr_object->geometry.custom_seam.push_back(bbs_get_attribute_value_string(attributes, num_attributes, CUSTOM_SEAM_ATTR));
m_curr_object->geometry.mmu_segmentation.push_back(bbs_get_attribute_value_string(attributes, num_attributes, MMU_SEGMENTATION_ATTR));
m_curr_object->geometry.fuzzy_skin.push_back(bbs_get_attribute_value_string(attributes, num_attributes, CUSTOM_FUZZY_SKIN_ATTR));
for (int slot = 0; slot < int(TEXTURE_DISPLACEMENT_MAX_LAYERS); ++slot)
if (const char *paint = bbs_get_attribute_value_charptr(attributes, num_attributes, TEXTURE_DISPLACEMENT_ATTRS[slot])) {
std::vector<std::string> &mask = m_curr_object->geometry.texture_displacement[slot];
mask.resize(m_curr_object->geometry.triangles.size());
mask.back() = paint;
}
// BBS
m_curr_object->geometry.face_properties.push_back(bbs_get_attribute_value_string(attributes, num_attributes, FACE_PROPERTY_ATTR));
}
@@ -5317,6 +5387,18 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
volume->mmu_segmentation_facets.touch();
volume->fuzzy_skin_facets.shrink_to_fit();
volume->fuzzy_skin_facets.touch();
for (int slot = 0; slot < int(TEXTURE_DISPLACEMENT_MAX_LAYERS); ++slot) {
const std::vector<std::string> &mask = sub_object->geometry.texture_displacement[slot];
if (mask.empty())
continue; // no triangle painted on this slot
FacetsAnnotation &facets = volume->texture_displacement_facet(slot);
facets.reserve(triangles_count);
for (size_t i = 0; i < triangles_count && i < mask.size(); ++i)
if (!mask[i].empty())
facets.set_triangle_from_string(i, mask[i]);
facets.shrink_to_fit();
facets.touch();
}
}
volume->set_type(volume_data->part_type);
@@ -5332,6 +5414,8 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
for (const Metadata& metadata : volume_data->metadata) {
if (metadata.key == NAME_KEY)
volume->name = metadata.value;
else if (metadata.key == TEXTURE_DISPLACEMENT_KEY)
_apply_texture_displacement(*volume, metadata.value);
//else if ((metadata.key == MODIFIER_KEY) && (metadata.value == "1"))
// volume->set_type(ModelVolumeType::PARAMETER_MODIFIER);
//for old format
@@ -5496,12 +5580,28 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
volume->seam_facets.shrink_to_fit();
volume->mmu_segmentation_facets.shrink_to_fit();
for (int slot = 0; slot < int(TEXTURE_DISPLACEMENT_MAX_LAYERS); ++slot) {
const std::vector<std::string> &mask = geometry.texture_displacement[slot];
if (mask.empty())
continue; // written by a build without the feature
FacetsAnnotation &facets = volume->texture_displacement_facet(slot);
facets.reserve(triangles_count);
for (size_t i = 0; i < triangles_count; ++i) {
const size_t index = volume_data.first_triangle_id + i;
if (index < mask.size() && !mask[index].empty())
facets.set_triangle_from_string(i, mask[index]);
}
facets.shrink_to_fit();
facets.touch();
}
volume->set_type(volume_data.part_type);
// apply the remaining volume's metadata
for (const Metadata& metadata : volume_data.metadata) {
if (metadata.key == NAME_KEY)
volume->name = metadata.value;
else if (metadata.key == TEXTURE_DISPLACEMENT_KEY)
_apply_texture_displacement(*volume, metadata.value);
//else if ((metadata.key == MODIFIER_KEY) && (metadata.value == "1"))
// volume->set_type(ModelVolumeType::PARAMETER_MODIFIER);
//for old format
@@ -5805,6 +5905,12 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
current_object->geometry.custom_seam.push_back(bbs_get_attribute_value_string(attributes, num_attributes, CUSTOM_SEAM_ATTR));
current_object->geometry.mmu_segmentation.push_back(bbs_get_attribute_value_string(attributes, num_attributes, MMU_SEGMENTATION_ATTR));
current_object->geometry.fuzzy_skin.push_back(bbs_get_attribute_value_string(attributes, num_attributes, CUSTOM_FUZZY_SKIN_ATTR));
for (int slot = 0; slot < int(TEXTURE_DISPLACEMENT_MAX_LAYERS); ++slot)
if (const char *paint = bbs_get_attribute_value_charptr(attributes, num_attributes, TEXTURE_DISPLACEMENT_ATTRS[slot])) {
std::vector<std::string> &mask = current_object->geometry.texture_displacement[slot];
mask.resize(current_object->geometry.triangles.size());
mask.back() = paint;
}
// BBS
current_object->geometry.face_properties.push_back(bbs_get_attribute_value_string(attributes, num_attributes, FACE_PROPERTY_ATTR));
}
@@ -6808,6 +6914,9 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
stream << " <Default Extension=\"rels\" ContentType=\"application/vnd.openxmlformats-package.relationships+xml\"/>\n";
stream << " <Default Extension=\"model\" ContentType=\"application/vnd.ms-package.3dmanufacturing-3dmodel+xml\"/>\n";
stream << " <Default Extension=\"png\" ContentType=\"image/png\"/>\n";
stream << " <Default Extension=\"jpg\" ContentType=\"image/jpeg\"/>\n";
stream << " <Default Extension=\"jpeg\" ContentType=\"image/jpeg\"/>\n";
stream << " <Default Extension=\"json\" ContentType=\"application/json\"/>\n";
stream << " <Default Extension=\"gcode\" ContentType=\"text/x.gcode\"/>\n";
stream << "</Types>";
@@ -7232,10 +7341,14 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
if (iter != m_shared_meshes.end())
{
const ModelVolume* shared_volume = iter->second.second;
bool same_texture_paint = true;
for (int slot = 0; slot < int(TEXTURE_DISPLACEMENT_MAX_LAYERS) && same_texture_paint; ++slot)
same_texture_paint = shared_volume->texture_displacement_facet(slot).equals(volume->texture_displacement_facet(slot));
if ((shared_volume->supported_facets.equals(volume->supported_facets))
&& (shared_volume->seam_facets.equals(volume->seam_facets))
&& (shared_volume->mmu_segmentation_facets.equals(volume->mmu_segmentation_facets))
&& (shared_volume->fuzzy_skin_facets.equals(volume->fuzzy_skin_facets)))
&& (shared_volume->fuzzy_skin_facets.equals(volume->fuzzy_skin_facets))
&& same_texture_paint)
{
auto data = iter->second.first;
const_cast<_BBS_3MF_Exporter *>(this)->m_volume_paths.insert({volume, {data->sub_path, data->volumes_objectID.find(iter->second.second)->second}});
@@ -7651,6 +7764,20 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
output_buffer += "\"";
}
// One attribute per texture displacement layer slot. Older readers ignore attributes they do
// not know, so a project written here still opens in a build without the feature - it just
// loses the paint, which is also all it could have done with it.
for (int slot = 0; slot < TEXTURE_DISPLACEMENT_MAX_LAYERS; ++slot) {
const std::string texture_paint = volume->texture_displacement_facet(slot).get_triangle_as_string(i);
if (texture_paint.empty())
continue;
output_buffer += " ";
output_buffer += TEXTURE_DISPLACEMENT_ATTRS[slot];
output_buffer += "=\"";
output_buffer += texture_paint;
output_buffer += "\"";
}
// BBS
if (i < its.properties.size()) {
std::string prop_str = its.properties[i].to_string();
@@ -8100,7 +8227,42 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
return file_path;
}
bool _BBS_3MF_Exporter::_add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model, PlateDataPtrs& plate_data_list, const ObjectToObjectDataMap &objects_data, const DynamicPrintConfig& config, int export_plate_idx, bool save_gcode, bool use_loaded_id)
// Writes a volume's texture displacement stack into the archive and points the volume's metadata at it.
// The layer settings go in as JSON and each layer's texture as the image file it was loaded from, both
// referenced by path - the same split EmbossShape makes for its SVG. The image deliberately stays out of
// the XML: it is binary and routinely megabytes, and base64 in an attribute would bloat the one file
// every reader has to parse just to list the objects.
static void add_texture_displacement(std::stringstream &stream, const ModelVolume &volume, mz_zip_archive &archive,
const std::string &id)
{
if (volume.texture_displacement_layers.empty())
return;
// A copy, because path_in_3mf is only meaningful inside the archive being written and the volume
// being exported is const (and may be saved again, elsewhere, with different paths).
std::vector<TextureDisplacementLayer> layers = volume.texture_displacement_layers;
for (TextureDisplacementLayer &layer : layers) {
layer.path_in_3mf.clear();
if (!layer.image_data || layer.image_data->empty())
continue;
// Always .png: the importer converts whatever the user picked (jpg, bmp, ...) to an 8-bit
// greyscale PNG, so image_data is PNG whatever layer.path happens to be named.
const std::string path = std::string(TEXTURE_DISPLACEMENT_DIR) + id + "_" + std::to_string(layer.slot) + ".png";
// No deflate: a PNG is already compressed, so a second pass only costs time.
if (mz_zip_writer_add_mem(&archive, path.c_str(), layer.image_data->data(), layer.image_data->size(),
MZ_NO_COMPRESSION))
layer.path_in_3mf = path;
}
const std::string json = texture_displacement_layers_to_json(layers, volume.texture_displacement_options);
const std::string json_path = std::string(TEXTURE_DISPLACEMENT_DIR) + id + ".json";
if (!mz_zip_writer_add_mem(&archive, json_path.c_str(), json.data(), json.size(), MZ_DEFAULT_COMPRESSION))
return;
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << TEXTURE_DISPLACEMENT_KEY << "\" "
<< VALUE_ATTR << "=\"" << xml_escape(json_path) << "\"/>\n";
}
bool _BBS_3MF_Exporter::_add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model, PlateDataPtrs& plate_data_list, const ObjectToObjectDataMap &objects_data, const DynamicPrintConfig& config, int export_plate_idx, bool save_gcode, bool use_loaded_id)
{
std::stringstream stream;
// Store mesh transformation in full precision, as the volumes are stored transformed and they need to be transformed back
@@ -8211,6 +8373,8 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
if (const std::optional<EmbossShape> &es = volume->emboss_shape; es.has_value()) {
to_xml(stream, *es, *volume, archive, m_fullpath_sources);
}
add_texture_displacement(stream, *volume, archive, std::to_string(volume->id().id));
if (const std::optional<TextConfiguration> &tc = volume->text_configuration;
tc.has_value())
+15 -2
View File
@@ -1033,6 +1033,15 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
return buf;
}
// FanMover consumes these markers (and drops them) to avoid splitting a G1 inside custom gcode.
// Only written when FanMover runs, the same gate as its construction in GCode::process_layers.
static std::string wrap_custom_gcode_for_fan_mover(const FullPrintConfig &config, const std::string &gcode)
{
if (config.fan_speedup_time.value == 0 && config.fan_kickstart.value <= 0)
return gcode;
return ";_FAN_MOVER_SKIP_START\n" + gcode + ";_FAN_MOVER_SKIP_END\n";
}
std::string WipeTowerIntegration::append_tcr(GCode& gcodegen, const WipeTower::ToolChangeResult& tcr, int new_filament_id, double z) const
{
if (new_filament_id != -1 && new_filament_id != tcr.new_tool)
@@ -1384,6 +1393,9 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
toolchange_gcode_str = gcodegen.placeholder_parser_process("change_filament_gcode", change_filament_gcode, new_filament_id, &config);
check_add_eol(toolchange_gcode_str);
// Type1 wipe tower path (every BBL printer): bracket the same way set_extruder() does,
// so FanMover doesn't split a G1 inside this custom gcode either.
toolchange_gcode_str = wrap_custom_gcode_for_fan_mover(gcodegen.config(), toolchange_gcode_str);
//BBS
{
@@ -4250,7 +4262,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
config.set_key_value("nozzle_diameter_at_nozzle_id", new ConfigOptionFloats(get_nozzle_diameters_by_nozzle_id(group_result.get())));
config.set_key_value("nozzle_volume_types", new ConfigOptionStrings(get_nozzle_volume_types_by_nozzle_id(group_result.get())));
config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index));
std::string filament_start_gcode = this->placeholder_parser_process("filament_start_gcode", print.config().filament_start_gcode.values.at(initial_non_support_extruder_id), initial_non_support_extruder_id,&config);
std::string filament_start_gcode = this->placeholder_parser_process("filament_start_gcode", print.config().filament_start_gcode.get_at(initial_non_support_extruder_id), initial_non_support_extruder_id,&config);
file.writeln(filament_start_gcode);
// Mark the first filament used in print. Multi-nozzle printers (H2C) get ";VT%d H%d" where
// H = dynamic ? nozzle_id : -1; existing single-nozzle printers keep the bare ";VT%d" so their
@@ -10895,7 +10907,8 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo
toolchange_gcode_parsed = placeholder_parser_process("change_filament_gcode", change_filament_gcode, new_filament_id, &dyn_config);
check_add_eol(toolchange_gcode_parsed);
gcode += toolchange_gcode_parsed;
// toolchange_gcode_parsed is still read below (get_last_z_from_gcode, custom_gcode_changes_tool).
gcode += wrap_custom_gcode_for_fan_mover(m_config, toolchange_gcode_parsed);
//BBS
{
+5
View File
@@ -445,6 +445,11 @@ void FanMover::_process_gcode_line(GCodeReader& reader, const GCodeReader::GCode
std::string extrusion_string = line.raw().substr(6, line.raw().size() - 6);
current_role = ExtrusionEntity::string_to_role(extrusion_string);
}
if (line.raw().rfind(";_FAN_MOVER_SKIP_", 0) == 0) {
// Internal markers: read here, then dropped from the output.
m_is_custom_gcode = line.raw().rfind(";_FAN_MOVER_SKIP_START", 0) == 0;
time = -1;
}
if (line.raw().size() > 16) {
if (line.raw().rfind("; custom gcode", 0) != std::string::npos) {
if (line.raw().rfind("; custom gcode end", 0) != std::string::npos)
+7 -2
View File
@@ -2602,12 +2602,13 @@ void GCodeProcessorResult::reset() {
//BBS: add mutex for protection of gcode result
lock();
moves.clear();
// moves and lines_ends grow with the print: free them, as no reset result reuses the capacity.
clear_and_shrink(moves);
plate_mass = {};
object_masses.clear();
body_masses.clear();
support_masses.clear();
lines_ends.clear();
clear_and_shrink(lines_ends);
printable_area = Pointfs();
//BBS: add bed exclude area
bed_exclude_area = Pointfs();
@@ -3598,6 +3599,10 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config)
if (has_scarf_joint_seam != nullptr)
m_detect_layer_based_on_tag = m_detect_layer_based_on_tag || has_scarf_joint_seam->value;
const ConfigOptionBool* manual_filament_change = config.option<ConfigOptionBool>("manual_filament_change");
if (manual_filament_change != nullptr)
m_manual_filament_change = manual_filament_change->value;
const ConfigOptionEnumGeneric *bed_type = config.option<ConfigOptionEnumGeneric>("curr_bed_type");
if (bed_type != nullptr)
m_result.bed_type = (BedType)bed_type->value;
+1 -1
View File
@@ -1228,7 +1228,7 @@ inline constexpr float DEFAULT_FILAMENT_DENSITY = 1.245f;
// m_result.nozzle_group_result, which is handed over only after the stream for the
// pre-heat injector's second pass and gates the richer change-time model.
std::shared_ptr<MultiNozzleUtils::NozzleGroupResultBase> m_nozzle_group_result;
bool m_manual_filament_change;
bool m_manual_filament_change{ false };
//BBS: x, y offset for gcode generated
double m_x_offset{ 0 };
-349
View File
@@ -1,349 +0,0 @@
#include "JumpPointSearch.hpp"
#include "BoundingBox.hpp"
#include "ExPolygon.hpp"
#include "Point.hpp"
#include "libslic3r/AStar.hpp"
#include "libslic3r/KDTreeIndirect.hpp"
#include "libslic3r/Polygon.hpp"
#include "libslic3r/Polyline.hpp"
#include "libslic3r/libslic3r.h"
#include <algorithm>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <iterator>
#include <limits>
#include <optional>
#include <string>
#include <unordered_map>
#include <vector>
#include <oneapi/tbb/scalable_allocator.h>
//#define DEBUG_FILES
#ifdef DEBUG_FILES
#include "libslic3r/SVG.hpp"
#endif
namespace Slic3r {
// execute fn for each pixel on the line. If fn returns false, terminate the iteration
template<typename PointFn> void dda(coord_t x0, coord_t y0, coord_t x1, coord_t y1, const PointFn &fn)
{
coord_t dx = abs(x1 - x0);
coord_t dy = abs(y1 - y0);
coord_t x = x0;
coord_t y = y0;
coord_t n = 1 + dx + dy;
coord_t x_inc = (x1 > x0) ? 1 : -1;
coord_t y_inc = (y1 > y0) ? 1 : -1;
coord_t error = dx - dy;
dx *= 2;
dy *= 2;
for (; n > 0; --n) {
if (!fn(x, y)) return;
if (error > 0) {
x += x_inc;
error -= dy;
} else {
y += y_inc;
error += dx;
}
}
}
// will draw the line twice, second time with and offset of 1 in the direction of normal
// may call the fn on the same coordiantes multiple times!
template<typename PointFn> void double_dda_with_offset(coord_t x0, coord_t y0, coord_t x1, coord_t y1, const PointFn &fn)
{
Vec2d normal = Point{y1 - y0, x1 - x0}.cast<double>().normalized();
normal.x() = ceil(normal.x());
normal.y() = ceil(normal.y());
Point start_offset = Point(x0, y0) + (normal).cast<coord_t>();
Point end_offset = Point(x1, y1) + (normal).cast<coord_t>();
dda(x0, y0, x1, y1, fn);
dda(start_offset.x(), start_offset.y(), end_offset.x(), end_offset.y(), fn);
}
template<typename CellPositionType, typename CellQueryFn> class JPSTracer
{
public:
// Use incoming_dir [0,0] for starting points, so that all directions are checked from that point
struct Node
{
CellPositionType position;
CellPositionType incoming_dir;
};
JPSTracer(CellPositionType target, CellQueryFn is_passable) : target(target), is_passable(is_passable) {}
private:
CellPositionType target;
CellQueryFn is_passable; // should return boolean whether the cell is passable or not
CellPositionType find_jump_point(CellPositionType start, CellPositionType forward_dir) const
{
CellPositionType next = start + forward_dir;
while (next != target && is_passable(next) && !(is_jump_point(next, forward_dir))) { next = next + forward_dir; }
if (is_passable(next)) {
return next;
} else {
return start;
}
}
bool is_jump_point(CellPositionType pos, CellPositionType forward_dir) const
{
if (abs(forward_dir.x()) + abs(forward_dir.y()) == 2) {
// diagonal
CellPositionType horizontal_check_dir = CellPositionType{forward_dir.x(), 0};
CellPositionType vertical_check_dir = CellPositionType{0, forward_dir.y()};
if (!is_passable(pos - horizontal_check_dir) && is_passable(pos + forward_dir - 2 * horizontal_check_dir)) { return true; }
if (!is_passable(pos - vertical_check_dir) && is_passable(pos + forward_dir - 2 * vertical_check_dir)) { return true; }
if (find_jump_point(pos, horizontal_check_dir) != pos) { return true; }
if (find_jump_point(pos, vertical_check_dir) != pos) { return true; }
return false;
} else { // horizontal or vertical
CellPositionType side_dir = CellPositionType(forward_dir.y(), forward_dir.x());
if (!is_passable(pos + side_dir) && is_passable(pos + forward_dir + side_dir)) { return true; }
if (!is_passable(pos - side_dir) && is_passable(pos + forward_dir - side_dir)) { return true; }
return false;
}
}
public:
template<class Fn> void foreach_reachable(const Node &from, Fn &&fn) const
{
const CellPositionType & pos = from.position;
const CellPositionType & forward_dir = from.incoming_dir;
std::vector<CellPositionType> dirs_to_check{};
if (abs(forward_dir.x()) + abs(forward_dir.y()) == 0) { // special case for starting point
dirs_to_check = all_directions;
} else if (abs(forward_dir.x()) + abs(forward_dir.y()) == 2) {
// diagonal
CellPositionType horizontal_check_dir = CellPositionType{forward_dir.x(), 0};
CellPositionType vertical_check_dir = CellPositionType{0, forward_dir.y()};
if (!is_passable(pos - horizontal_check_dir) && is_passable(pos + forward_dir - 2 * horizontal_check_dir)) {
dirs_to_check.push_back(forward_dir - 2 * horizontal_check_dir);
}
if (!is_passable(pos - vertical_check_dir) && is_passable(pos + forward_dir - 2 * vertical_check_dir)) {
dirs_to_check.push_back(forward_dir - 2 * vertical_check_dir);
}
dirs_to_check.push_back(horizontal_check_dir);
dirs_to_check.push_back(vertical_check_dir);
dirs_to_check.push_back(forward_dir);
} else { // horizontal or vertical
CellPositionType side_dir = CellPositionType(forward_dir.y(), forward_dir.x());
if (!is_passable(pos + side_dir) && is_passable(pos + forward_dir + side_dir)) { dirs_to_check.push_back(forward_dir + side_dir); }
if (!is_passable(pos - side_dir) && is_passable(pos + forward_dir - side_dir)) { dirs_to_check.push_back(forward_dir - side_dir); }
dirs_to_check.push_back(forward_dir);
}
for (const CellPositionType &dir : dirs_to_check) {
CellPositionType jp = find_jump_point(pos, dir);
if (jp != pos) fn(Node{jp, dir});
}
}
float distance(Node a, Node b) const { return (a.position - b.position).template cast<double>().norm(); }
float goal_heuristic(Node n) const { return n.position == target ? -1.f : (target - n.position).template cast<double>().norm(); }
size_t unique_id(Node n) const { return (static_cast<size_t>(uint16_t(n.position.x())) << 16) + static_cast<size_t>(uint16_t(n.position.y())); }
const std::vector<CellPositionType> all_directions{{1, 0}, {1, 1}, {0, 1}, {-1, 1}, {-1, 0}, {-1, -1}, {0, -1}, {1, -1}};
};
void JPSPathFinder::clear()
{
inpassable.clear();
max_search_box.max = Pixel(std::numeric_limits<coord_t>::min(), std::numeric_limits<coord_t>::min());
max_search_box.min = Pixel(std::numeric_limits<coord_t>::max(), std::numeric_limits<coord_t>::max());
add_obstacles(bed_shape);
}
void JPSPathFinder::add_obstacles(const Lines &obstacles)
{
auto store_obstacle = [&](coord_t x, coord_t y) {
max_search_box.max.x() = std::max(max_search_box.max.x(), x);
max_search_box.max.y() = std::max(max_search_box.max.y(), y);
max_search_box.min.x() = std::min(max_search_box.min.x(), x);
max_search_box.min.y() = std::min(max_search_box.min.y(), y);
inpassable.insert(Pixel{x, y});
return true;
};
for (const Line &l : obstacles) {
Pixel start = pixelize(l.a);
Pixel end = pixelize(l.b);
double_dda_with_offset(start.x(), start.y(), end.x(), end.y(), store_obstacle);
}
}
Polyline JPSPathFinder::find_path(const Point &p0, const Point &p1)
{
Pixel start = pixelize(p0);
Pixel end = pixelize(p1);
if (inpassable.empty() || (start - end).cast<float>().norm() < 3.0) { return Polyline{p0, p1}; }
if (inpassable.find(start) != inpassable.end()) {
dda(start.x(), start.y(), end.x(), end.y(), [&](coord_t x, coord_t y) {
if (inpassable.find(Pixel(x, y)) == inpassable.end() || start == end) { // new start not found yet, and xy passable
start = Pixel(x, y);
return false;
}
return true;
});
}
if (inpassable.find(end) != inpassable.end()) {
dda(end.x(), end.y(), start.x(), start.y(), [&](coord_t x, coord_t y) {
if (inpassable.find(Pixel(x, y)) == inpassable.end() || start == end) { // new start not found yet, and xy passable
end = Pixel(x, y);
return false;
}
return true;
});
}
BoundingBox search_box = max_search_box;
search_box.max -= Pixel(1, 1);
search_box.min += Pixel(1, 1);
BoundingBox bounding_square(Points{start, end});
bounding_square.max += Pixel(5, 5);
bounding_square.min -= Pixel(5, 5);
coord_t bounding_square_size = 2 * std::max(bounding_square.size().x(), bounding_square.size().y());
bounding_square.max.x() += (bounding_square_size - bounding_square.size().x()) / 2;
bounding_square.min.x() -= (bounding_square_size - bounding_square.size().x()) / 2;
bounding_square.max.y() += (bounding_square_size - bounding_square.size().y()) / 2;
bounding_square.min.y() -= (bounding_square_size - bounding_square.size().y()) / 2;
// Intersection - limit the search box to a square area around the start and end, to fasten the path searching
search_box.max = search_box.max.cwiseMin(bounding_square.max);
search_box.min = search_box.min.cwiseMax(bounding_square.min);
auto cell_query = [&](Pixel pixel) { return search_box.contains(pixel) && (pixel == start || pixel == end || inpassable.find(pixel) == inpassable.end()); };
JPSTracer<Pixel, decltype(cell_query)> tracer(end, cell_query);
using QNode = astar::QNode<JPSTracer<Pixel, decltype(cell_query)>>;
std::unordered_map<size_t, QNode> astar_cache{};
std::vector<Pixel, PointsAllocator<Pixel>> out_path;
std::vector<decltype(tracer)::Node> out_nodes;
if (!astar::search_route(tracer, {start, {0, 0}}, std::back_inserter(out_nodes), astar_cache)) {
// path not found - just reconstruct the best path from astar cache.
// Note that astar_cache is NOT empty - at least the starting point should always be there
auto coordiante_func = [&astar_cache](size_t idx, size_t dim) { return float(astar_cache[idx].node.position[dim]); };
std::vector<size_t> keys;
keys.reserve(astar_cache.size());
for (const auto &pair : astar_cache) { keys.push_back(pair.first); }
KDTreeIndirect<2, float, decltype(coordiante_func)> kd_tree(coordiante_func, keys);
size_t closest_qnode = find_closest_point(kd_tree, end.cast<float>());
out_path.push_back(end);
while (closest_qnode != astar::Unassigned) {
out_path.push_back(astar_cache[closest_qnode].node.position);
closest_qnode = astar_cache[closest_qnode].parent;
}
} else {
for (const auto &node : out_nodes) { out_path.push_back(node.position); }
out_path.push_back(start);
}
#ifdef DEBUG_FILES
auto scaled_points = [](const Points &ps) {
Points r;
for (const Point &p : ps) { r.push_back(Point::new_scale(p.x(), p.y())); }
return r;
};
auto scaled_point = [](const Point &p) { return Point::new_scale(p.x(), p.y()); };
::Slic3r::SVG svg(debug_out_path(("path_jps" + std::to_string(print_z) + "_" + std::to_string(rand() % 1000)).c_str()).c_str(),
BoundingBox(scaled_point(search_box.min), scaled_point(search_box.max)));
for (const auto &p : inpassable) { svg.draw(scaled_point(p), "black", scale_(0.4)); }
for (const auto &qn : astar_cache) { svg.draw(scaled_point(qn.second.node.position), "blue", scale_(0.3)); }
svg.draw(Polyline(scaled_points(out_path)), "yellow", scale_(0.25));
svg.draw(scaled_point(end), "purple", scale_(0.4));
svg.draw(scaled_point(start), "green", scale_(0.4));
#endif
std::vector<Pixel, PointsAllocator<Pixel>> tmp_path;
tmp_path.reserve(out_path.size());
// Some path found, reverse and remove points that do not change direction
std::reverse(out_path.begin(), out_path.end());
{
tmp_path.push_back(out_path.front()); // first point
for (size_t i = 1; i < out_path.size() - 1; i++) {
if ((out_path[i] - out_path[i - 1]).cast<float>().normalized() != (out_path[i + 1] - out_path[i]).cast<float>().normalized()) { tmp_path.push_back(out_path[i]); }
}
tmp_path.push_back(out_path.back()); // last_point
out_path = tmp_path;
}
#ifdef DEBUG_FILES
svg.draw(Polyline(scaled_points(out_path)), "orange", scale_(0.20));
#endif
tmp_path.clear();
// remove redundant jump points - there are points that change direction but are not needed - this inefficiency arises from the
// usage of grid search The removal alg tries to find the longest Px Px+k path without obstacles. If Px Px+k+1 is blocked, it will
// insert the Px+k point to result and continue search from Px+k
{
tmp_path.push_back(out_path.front()); // first point
size_t index_of_last_stored_point = 0;
for (size_t i = 1; i < out_path.size(); i++) {
if (i - index_of_last_stored_point < 2) continue;
bool passable = true;
auto store_obstacle = [&](coord_t x, coord_t y) {
if (Pixel(x, y) != start && Pixel(x, y) != end && inpassable.find(Pixel(x, y)) != inpassable.end()) {
passable = false;
return false;
}
return true;
};
dda(tmp_path.back().x(), tmp_path.back().y(), out_path[i].x(), out_path[i].y(), store_obstacle);
if (!passable) {
tmp_path.push_back(out_path[i - 1]);
index_of_last_stored_point = i - 1;
}
}
tmp_path.push_back(out_path.back()); // last_point
out_path = tmp_path;
}
#ifdef DEBUG_FILES
svg.draw(Polyline(scaled_points(out_path)), "red", scale_(0.15));
svg.Close();
#endif
// before returing the path, transform it from pixels back to points.
// Also replace the first and last pixel by input points so that result path patches input params exactly.
for (Pixel &p : out_path) { p = unpixelize(p); }
out_path.front() = p0;
out_path.back() = p1;
return Polyline(out_path);
}
} // namespace Slic3r
-39
View File
@@ -1,39 +0,0 @@
#pragma once
#include "Line.hpp"
#ifndef SRC_LIBSLIC3R_JUMPPOINTSEARCH_HPP_
#define SRC_LIBSLIC3R_JUMPPOINTSEARCH_HPP_
#include "BoundingBox.hpp"
#include "Polygon.hpp"
#include "libslic3r/Layer.hpp"
#include "libslic3r/Point.hpp"
#include "libslic3r/Polyline.hpp"
#include "libslic3r/libslic3r.h"
#include <unordered_map>
#include <unordered_set>
namespace Slic3r {
class JPSPathFinder
{
using Pixel = Point;
std::unordered_set<Pixel, PointHash> inpassable;
coordf_t print_z;
BoundingBox max_search_box;
Lines bed_shape;
const coord_t resolution = scaled(1.5);
Pixel pixelize(const Point &p) { return p / resolution; }
Point unpixelize(const Pixel &p) { return p * resolution; }
public:
JPSPathFinder() = default;
void init_bed_shape(const Points &bed_shape) { this->bed_shape = (to_lines(Polygon{bed_shape})); };
void clear();
void add_obstacles(const Lines &obstacles);
Polyline find_path(const Point &start, const Point &end);
};
} // namespace Slic3r
#endif /* SRC_LIBSLIC3R_JUMPPOINTSEARCH_HPP_ */
-88
View File
@@ -29,8 +29,6 @@
#include <CGAL/Polygon_mesh_processing/triangulate_hole.h>
#include <CGAL/Polygon_mesh_processing/stitch_borders.h>
#include <CGAL/Polygon_mesh_processing/polygon_soup_to_polygon_mesh.h>
#include <CGAL/Polygon_mesh_processing/repair_degeneracies.h>
#include <CGAL/Polygon_mesh_processing/manifoldness.h>
#include <cstdint>
#include <map>
#include <algorithm>
@@ -50,7 +48,6 @@
#include <CGAL/Polygon_mesh_processing/orient_polygon_soup.h>
#include <CGAL/Polygon_mesh_processing/repair.h>
#include <CGAL/Polygon_mesh_processing/remesh.h>
#include <CGAL/Polygon_mesh_processing/repair_polygon_soup.h>
#include <CGAL/Polygon_mesh_processing/orientation.h>
// BBS: for segment
#include <CGAL/mesh_segmentation.h>
@@ -658,91 +655,6 @@ bool empty(const CGALMesh &mesh)
return mesh.m.is_empty();
}
bool repair(TriangleMesh& mesh, RepairedMeshErrors* repaired_errors, std::string* error)
{
namespace PMP = CGAL::Polygon_mesh_processing;
if (mesh.empty())
return true;
try {
// 1) Convert to polygon soup
std::vector<_EpicMesh::Point> points;
std::vector<std::vector<std::size_t>> polygons;
points.reserve(mesh.its.vertices.size());
polygons.reserve(mesh.its.indices.size());
for (const auto& v : mesh.its.vertices)
points.emplace_back(v.x(), v.y(), v.z());
for (const auto& f : mesh.its.indices)
polygons.push_back({size_t(f[0]), size_t(f[1]), size_t(f[2])});
// 2) Aggressive soup cleanup
PMP::repair_polygon_soup(points, polygons);
// 3) Convert soup → mesh
_EpicMesh cgal_mesh;
PMP::polygon_soup_to_polygon_mesh(points, polygons, cgal_mesh);
// 4) Remove degenerate geometry
PMP::remove_degenerate_faces(cgal_mesh);
PMP::remove_isolated_vertices(cgal_mesh);
// 5) Fix remaining non-manifold vertices
PMP::duplicate_non_manifold_vertices(cgal_mesh);
// 6) Boolean union (keeps only outer shell)
_EpicMesh tmp;
if (PMP::corefine_and_compute_union(cgal_mesh, cgal_mesh, tmp)) {
cgal_mesh = std::move(tmp);
}
// If it fails, continue anyway with previous mesh
// 7) Fill holes
if (!CGAL::is_closed(cgal_mesh)) {
using halfedge_descriptor = boost::graph_traits<_EpicMesh>::halfedge_descriptor;
std::vector<halfedge_descriptor> borders;
CGAL::extract_boundary_cycles(cgal_mesh, std::back_inserter(borders));
for (halfedge_descriptor h : borders) {
PMP::triangulate_and_refine_hole(cgal_mesh, h);
}
}
// 8) Final validity check
if (!CGAL::is_closed(cgal_mesh)) {
if (error)
*error = "Repair failed: mesh still open after hole filling.";
return false;
}
// 9) Ensure outward orientation
if (!PMP::does_bound_a_volume(cgal_mesh))
PMP::orient_to_bound_a_volume(cgal_mesh);
// 10) Convert back
indexed_triangle_set its = cgal_to_indexed_triangle_set(cgal_mesh);
RepairedMeshErrors errs{};
errs.facets_removed = 0;
errs.edges_fixed = 0;
mesh = TriangleMesh(std::move(its), errs);
if (repaired_errors)
*repaired_errors = errs;
return true;
} catch (const std::exception& e) {
if (error)
*error = e.what();
return false;
}
}
CGALMeshPtr clone(const CGALMesh &m)
{
return CGALMeshPtr{new CGALMesh{m}};
-3
View File
@@ -77,9 +77,6 @@ TriangleMesh merge(std::vector<TriangleMesh> meshes);
bool does_bound_a_volume(const CGALMesh &mesh);
bool empty(const CGALMesh &mesh);
// Repair a mesh using CGAL. Returns true on success. Optionally returns a summary of repairs and an error string.
bool repair(TriangleMesh &mesh, RepairedMeshErrors *repaired_errors = nullptr, std::string *error = nullptr);
// Real UV unwrap of an open mesh patch via CGAL's LSCM (Least Squares Conformal Maps) surface
// parameterization. Returns one UV coordinate per input vertex (same indexing as `mesh.vertices`),
// or nullopt if `mesh` isn't a single topological disk -- LSCM needs exactly one connected
File diff suppressed because it is too large Load Diff
+28
View File
@@ -0,0 +1,28 @@
#pragma once
#include <cstddef>
#include <functional>
#include <string>
namespace Slic3r {
class TriangleMesh;
struct MeshRepairStats
{
std::size_t welded_vertices = 0;
std::size_t removed_triangles = 0;
std::size_t reversed_shells = 0;
std::size_t filled_holes = 0;
std::size_t removed_shells = 0;
std::size_t self_intersections = 0;
// False when the input already was closed, consistently oriented and free of self-intersections.
bool modified = false;
};
// Returns false, leaving the mesh untouched, if no solid can be built. progress gets the percentage done and may
// throw to cancel. See docs/HLSD/mesh-repair.md.
bool repair_mesh(TriangleMesh &mesh, MeshRepairStats *stats = nullptr, std::string *error = nullptr,
const std::function<void(int)> &progress = {});
} // namespace Slic3r
+39 -22
View File
@@ -29,6 +29,7 @@
#include "TriangleMeshSlicer.hpp"
#include "TriangleSelector.hpp"
#include "MaterialType.hpp"
#include "MeshRepair.hpp"
#include "Format/AMF.hpp"
#include "Format/svg.hpp"
@@ -2946,6 +2947,18 @@ size_t ModelVolume::split(unsigned int max_extruders, bool remap_paint)
if (text_configuration.has_value())
text_configuration.reset();
// Parts without a convex hull enclose no volume; drop them here, as deleting volumes later could delete this one.
std::vector<std::unique_ptr<ModelVolume>> parts;
for (TriangleMesh &mesh : meshes)
if (!mesh.empty()) {
std::unique_ptr<ModelVolume> part(new ModelVolume(object, *this, std::move(mesh)));
if (const std::shared_ptr<const TriangleMesh> &hull = part->get_convex_hull_shared_ptr();
hull != nullptr && !hull->its.vertices.empty() && !hull->its.indices.empty())
parts.emplace_back(std::move(part));
}
if (parts.empty())
return 1;
std::optional<TriangleSelector::SavedPainting> saved_painting = remap_paint ? save_painting() :
std::optional<TriangleSelector::SavedPainting>{};
@@ -2956,26 +2969,22 @@ size_t ModelVolume::split(unsigned int max_extruders, bool remap_paint)
unsigned int extruder_counter = 0;
const Vec3d offset = this->get_offset();
for (TriangleMesh &mesh : meshes) {
if (mesh.empty())
// Repair may have removed unconnected triangles, thus emptying the mesh.
continue;
for (std::unique_ptr<ModelVolume> &part : parts) {
if (idx == 0) {
this->set_mesh(std::move(mesh));
this->calculate_convex_hull();
this->set_mesh(part->m_mesh);
m_convex_hull = part->m_convex_hull;
this->invalidate_convex_hull_2d();
// Assign a new unique ID, so that a new GLVolume will be generated.
this->set_new_unique_id();
// reset the source to disable reload from disk
this->source = ModelVolume::Source();
// BBS: reset facet annotations
this->reset_extra_facets();
}
else
this->object->volumes.insert(this->object->volumes.begin() + (++ivolume), new ModelVolume(object, *this, std::move(mesh)));
this->object->volumes.insert(this->object->volumes.begin() + (++ivolume), part.release());
// reset the source to disable reload from disk
this->object->volumes[ivolume]->source = ModelVolume::Source();
this->object->volumes[ivolume]->set_offset(Vec3d::Zero());
this->object->volumes[ivolume]->center_geometry_after_creation();
this->object->volumes[ivolume]->translate(offset);
@@ -2989,21 +2998,29 @@ size_t ModelVolume::split(unsigned int max_extruders, bool remap_paint)
++ idx;
}
// discard volumes for which the convex hull was not generated or is degenerate
size_t i = 0;
while (i < this->object->volumes.size()) {
const std::shared_ptr<const TriangleMesh> &hull = this->object->volumes[i]->get_convex_hull_shared_ptr();
if (hull == nullptr || hull->its.vertices.empty() || hull->its.indices.empty()) {
this->object->delete_volume(i);
--idx;
--i;
}
++i;
}
return idx;
}
bool ModelVolume::repair(bool keep_painting, MeshRepairStats *stats_out, std::string *error, const std::function<void(int)> &progress)
{
TriangleMesh mesh = this->mesh();
MeshRepairStats stats;
const bool ok = repair_mesh(mesh, &stats, error, progress);
if (stats_out)
*stats_out = stats;
if (!ok || !stats.modified)
return ok;
const std::optional<TriangleSelector::SavedPainting> saved_painting = keep_painting ? save_painting() : std::nullopt;
this->set_mesh(std::move(mesh));
this->calculate_convex_hull();
this->invalidate_convex_hull_2d();
// Assign a new unique ID, so that a new GLVolume will be generated.
this->set_new_unique_id();
this->restore_painting(saved_painting);
return true;
}
void ModelVolume::translate(const Vec3d& displacement)
{
set_offset(get_offset() + displacement);
+11 -7
View File
@@ -77,6 +77,7 @@ class Preset;
class BBLProject;
class KeyStore;
struct MeshRepairStats;
namespace UndoRedo {
class StackImpl;
@@ -887,13 +888,13 @@ public:
// The triangular model.
const TriangleMesh& mesh() const { return *m_mesh.get(); }
std::shared_ptr<const TriangleMesh> mesh_ptr() const { return m_mesh; }
void set_mesh(const TriangleMesh &mesh) { m_mesh = std::make_shared<const TriangleMesh>(mesh); }
void set_mesh(TriangleMesh &&mesh) { m_mesh = std::make_shared<const TriangleMesh>(std::move(mesh)); }
void set_mesh(const indexed_triangle_set &mesh) { m_mesh = std::make_shared<const TriangleMesh>(mesh); }
void set_mesh(indexed_triangle_set &&mesh) { m_mesh = std::make_shared<const TriangleMesh>(std::move(mesh)); }
void set_mesh(std::shared_ptr<const TriangleMesh> &mesh) { m_mesh = mesh; }
void set_mesh(std::unique_ptr<const TriangleMesh> &&mesh) { m_mesh = std::move(mesh); }
void reset_mesh() { m_mesh = std::make_shared<const TriangleMesh>(); }
void set_mesh(const TriangleMesh &mesh) { m_mesh = std::make_shared<const TriangleMesh>(mesh); m_is_splittable = -1; }
void set_mesh(TriangleMesh &&mesh) { m_mesh = std::make_shared<const TriangleMesh>(std::move(mesh)); m_is_splittable = -1; }
void set_mesh(const indexed_triangle_set &mesh) { m_mesh = std::make_shared<const TriangleMesh>(mesh); m_is_splittable = -1; }
void set_mesh(indexed_triangle_set &&mesh) { m_mesh = std::make_shared<const TriangleMesh>(std::move(mesh)); m_is_splittable = -1; }
void set_mesh(std::shared_ptr<const TriangleMesh> &mesh) { m_mesh = mesh; m_is_splittable = -1; }
void set_mesh(std::unique_ptr<const TriangleMesh> &&mesh) { m_mesh = std::move(mesh); m_is_splittable = -1; }
void reset_mesh() { m_mesh = std::make_shared<const TriangleMesh>(); m_is_splittable = -1; }
const std::shared_ptr<const TriangleMesh>& get_mesh_shared_ptr() const { return m_mesh; }
// Configuration parameters specific to an object model geometry or a modifier volume,
// overriding the global Slic3r settings and the ModelObject settings.
@@ -1043,6 +1044,9 @@ public:
// Return the number of volumes created from this one.
// This is useful to assign different materials to different volumes of an object.
size_t split(unsigned int max_extruders, bool remap_paint);
// Replaces the mesh by its repair_mesh() result if that changed it. False if no solid can be built.
bool repair(bool keep_painting, MeshRepairStats *stats = nullptr, std::string *error = nullptr,
const std::function<void(int)> &progress = {});
void translate(double x, double y, double z) { translate(Vec3d(x, y, z)); }
void translate(const Vec3d& displacement);
void scale(const Vec3d& scaling_factors);
+35 -6
View File
@@ -2164,6 +2164,16 @@ int PresetCollection::get_differed_values_to_update(Preset& preset, std::map<std
DynamicPrintConfig temp_config;
std::vector<std::string> dirty_options = preset.config.diff(parent_preset->config);
// Send the variant layout even when it matches the parent's, as the local save does: the loader
// sizes the per-variant values by it and would otherwise keep only the first one.
std::string extruder_id_name, extruder_variant_name;
std::set<std::string> *key_set1 = nullptr, *key_set2 = nullptr;
Preset::get_extruder_names_and_keysets(m_type, extruder_id_name, extruder_variant_name, &key_set1, &key_set2);
if (!extruder_id_name.empty())
dirty_options.emplace_back(extruder_id_name);
if (!extruder_variant_name.empty())
dirty_options.emplace_back(extruder_variant_name);
for (auto option: dirty_options)
{
ConfigOption *opt_src = preset.config.option(option);
@@ -2628,15 +2638,34 @@ bool PresetCollection::load_user_preset(std::string name, std::map<std::string,
new_config = default_preset.config;
}
std::string extruder_id_name, extruder_variant_name;
std::set<std::string> *key_set1 = nullptr, *key_set2 = nullptr;
Preset::get_extruder_names_and_keysets(m_type, extruder_id_name, extruder_variant_name, &key_set1, &key_set2);
// Older uploads left out a variant layout equal to the parent's but still sent one value per
// parent variant. Read those values against the parent's layout instead of cutting them to one.
if (inherit_preset && !cloud_config.has(extruder_variant_name)) {
const auto *parent_variants = inherit_preset->config.option<ConfigOptionStrings>(extruder_variant_name);
const size_t variant_count = parent_variants ? parent_variants->size() : 0;
bool values_per_variant = false;
for (const std::string &key : cloud_config.keys()) {
const size_t stride = key_set2->count(key) ? 2 : key_set1->count(key) ? 1 : 0;
const auto *values = dynamic_cast<const ConfigOptionVectorBase *>(cloud_config.option(key));
if (stride > 0 && key != extruder_id_name && values && variant_count > 1 && values->size() == variant_count * stride) {
values_per_variant = true;
break;
}
}
if (values_per_variant)
for (const std::string &key : {extruder_id_name, extruder_variant_name})
if (!key.empty() && !cloud_config.has(key) && inherit_preset->config.has(key))
cloud_config.set_key_value(key, inherit_preset->config.option(key)->clone());
}
extend_default_config_length(cloud_config, false, {});
if (inherit_preset) {
std::string extruder_id_name, extruder_variant_name;
std::set<std::string> *key_set1 = nullptr, *key_set2 = nullptr;
Preset::get_extruder_names_and_keysets(m_type, extruder_id_name, extruder_variant_name, &key_set1, &key_set2);
if (inherit_preset)
new_config.update_diff_values_to_child_config(cloud_config, extruder_id_name, extruder_variant_name, *key_set1, *key_set2);
}
else{
new_config.apply(std::move(cloud_config));
extend_default_config_length(new_config, true, default_preset.config);
+79 -118
View File
@@ -3905,14 +3905,8 @@ void PresetBundle::export_selections(AppConfig &config)
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": printer %1%, print %2%, filaments[0] %3% ")%printers.get_selected_preset_name() % prints.get_selected_preset_name() %filament_presets[0];
}
void PresetBundle::set_num_filaments(unsigned int n, std::string new_color)
size_t PresetBundle::resize_project_filament_slots(size_t n)
{
unsigned old_filament_count = this->filament_presets.size();
if (n > old_filament_count && old_filament_count != 0)
filament_presets.resize(n, filament_presets.back());
else {
filament_presets.resize(n);
}
ConfigOptionStrings* filament_color = project_config.option<ConfigOptionStrings>("filament_colour");
ConfigOptionStrings *filament_multi_color = project_config.option<ConfigOptionStrings>("filament_multi_colour");
ConfigOptionStrings* filament_color_type = project_config.option<ConfigOptionStrings>("filament_colour_type");
@@ -3938,9 +3932,25 @@ void PresetBundle::set_num_filaments(unsigned int n, std::string new_color)
ams_multi_color_filment.resize(n);
resize_mixed_filament_metadata(project_config, old_slot_count, n);
return old_slot_count;
}
void PresetBundle::set_num_filaments(unsigned int n, std::string new_color)
{
unsigned old_filament_count = this->filament_presets.size();
if (n > old_filament_count && old_filament_count != 0)
filament_presets.resize(n, filament_presets.back());
else {
filament_presets.resize(n);
}
const size_t old_slot_count = resize_project_filament_slots(n);
//BBS set new filament color to new_color
if (!new_color.empty()) {
ConfigOptionStrings* filament_color = project_config.option<ConfigOptionStrings>("filament_colour");
ConfigOptionStrings *filament_multi_color = project_config.option<ConfigOptionStrings>("filament_multi_colour");
ConfigOptionStrings* filament_color_type = project_config.option<ConfigOptionStrings>("filament_colour_type");
for (size_t i = old_slot_count; i < n; i++) {
filament_color->values[i] = new_color;
filament_multi_color->values[i] = new_color;
@@ -4102,11 +4112,7 @@ void PresetBundle::get_ams_cobox_infos(AMSComboInfo& combox_info)
auto ams_name = ams.opt_string("tray_name", 0u);
auto filament_changed = !ams.has("filament_changed") || ams.opt_bool("filament_changed");
auto filament_multi_color = ams.opt<ConfigOptionStrings>("filament_multi_colour")->values;
auto filament_type = ams.opt_string("filament_type", 0u);
auto is_placeholder = ams.has("filament_slot_placeholder") && ams.opt_bool("filament_slot_placeholder", 0u);
// A printer-set tray carries a material type but no preset id; resolve it to the
// matching Generic preset so it is not dropped and the combo array keeps aligning.
if (filament_id.empty() && (is_placeholder || filament_type.empty())) {
if (filament_id.empty()) {
continue;
}
if (!filament_changed && this->filament_presets.size() > combox_info.ams_filament_presets.size()) {
@@ -4116,48 +4122,16 @@ void PresetBundle::get_ams_cobox_infos(AMSComboInfo& combox_info)
combox_info.ams_names.push_back(ams_name);
continue;
}
auto iter = filaments.end();
if (!filament_id.empty()) {
iter = std::find_if(filaments.begin(), filaments.end(),
[this, &filament_id](auto &f) { return f.is_compatible && filaments.get_preset_base(f) == &f && f.filament_id == filament_id; });
warn_ambiguous_filament_id_match(filaments, iter, filament_id);
}
auto iter = std::find_if(filaments.begin(), filaments.end(),
[this, &filament_id](auto &f) { return f.is_compatible && filaments.get_preset_base(f) == &f && f.filament_id == filament_id; });
warn_ambiguous_filament_id_match(filaments, iter, filament_id);
if (iter == filaments.end()) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": filament_id %1% not found or system or compatible") % filament_id;
auto filament_type = ams.opt_string("filament_type", 0u);
if (!filament_type.empty()) {
auto original_type = filament_type;
filament_type = "Generic " + filament_type;
iter = std::find_if(filaments.begin(), filaments.end(),
[&filament_type](auto &f) { return f.is_compatible && f.is_system && boost::algorithm::starts_with(f.name, filament_type); });
if (iter == filaments.end()) {
// Similarity fallback: find a generic preset whose filament_type
// appears as a whole word in the AMS type (e.g. "ASA" in "ASA Sparkle").
auto upper_type = boost::to_upper_copy(original_type);
auto contains_word = [](const std::string& haystack, const std::string& needle) {
auto pos = haystack.find(needle);
while (pos != std::string::npos) {
bool start_ok = (pos == 0 || !std::isalnum(static_cast<unsigned char>(haystack[pos - 1])));
bool end_ok = (pos + needle.size() >= haystack.size() ||
!std::isalnum(static_cast<unsigned char>(haystack[pos + needle.size()])));
if (start_ok && end_ok)
return true;
pos = haystack.find(needle, pos + 1);
}
return false;
};
// Find the longest-matching preset type to prefer e.g. "PA-CF" over "PA".
size_t best_len = 0;
for (auto it = filaments.begin(); it != filaments.end(); ++it) {
if (!it->is_compatible || !it->is_system || !boost::algorithm::starts_with(it->name, "Generic "))
continue;
auto preset_type = boost::to_upper_copy(it->config.opt_string("filament_type", 0u));
if (preset_type.size() > best_len && contains_word(upper_type, preset_type)) {
iter = it;
best_len = preset_type.size();
filament_type = "Generic " + it->config.opt_string("filament_type", 0u);
}
}
}
}
if (iter == filaments.end()) {
// Prefer old selection
@@ -4168,11 +4142,7 @@ void PresetBundle::get_ams_cobox_infos(AMSComboInfo& combox_info)
combox_info.ams_names.push_back(ams_name);
continue;
}
iter = std::find_if(filaments.begin(), filaments.end(), [](auto &f) {
return f.is_compatible && f.is_system && boost::algorithm::starts_with(f.name, "Generic ");
});
if (iter == filaments.end())
iter = std::find_if(filaments.begin(), filaments.end(), [](auto &f) { return f.is_compatible && f.is_system; });
iter = std::find_if(filaments.begin(), filaments.end(), [](auto &f) { return f.is_compatible && f.is_system; });
if (iter == filaments.end())
continue;
}
@@ -4210,7 +4180,6 @@ unsigned int PresetBundle::sync_ams_list(std::vector<std::pair<DynamicPrintConfi
for (auto &entry : filament_ams_list) {
auto & ams = entry.second;
auto filament_id = ams.opt_string("filament_id", 0u);
const bool printer_set_tray = filament_id.empty();
auto filament_color = ams.opt_string("filament_colour", 0u);
auto filament_color_type = ams.opt_string("filament_colour_type", 0u);
auto filament_changed = !ams.has("filament_changed") || ams.opt_bool("filament_changed");
@@ -4223,58 +4192,42 @@ unsigned int PresetBundle::sync_ams_list(std::vector<std::pair<DynamicPrintConfi
ams_array_maps.push_back(temp);
index++;
if (filament_id.empty()) {
// A tray written by the printer UI carries a material type but no
// OrcaSlicer preset id. Resolve it to the matching Generic preset with
// the tray's own color instead of dropping it (direct sync) or forcing
// "Generic PLA" (mapping sync). Placeholders and typeless trays keep
// the previous behavior.
const auto tray_type = ams.opt_string("filament_type", 0u);
if (is_placeholder || tray_type.empty()) {
if (use_map) {
for (int j = maps.size() - 1; j >= 0; j--) {
if (maps[j].slot_id == slot_id && maps[j].ams_id == ams_id) {
maps.erase(j);
}
if (use_map) {
for (int j = maps.size() - 1; j >= 0; j--) {
if (maps[j].slot_id == slot_id && maps[j].ams_id == ams_id) {
maps.erase(j);
}
ams_filament_presets.push_back("Generic PLA");//for unknow matieral
auto default_unknown_color = "#CECECE";
ams_filament_colors.push_back(default_unknown_color);
ams_filament_color_types.push_back("1");
if (filament_multi_color.size() == 0) {
filament_multi_color.push_back(default_unknown_color);
}
ams_multi_color_filment.push_back(filament_multi_color);
} else if (is_placeholder) {
// Orca: push placeholders to keep index alignment with ams_infos
ams_filament_presets.push_back("");
ams_filament_colors.push_back("");
ams_filament_color_types.push_back("");
ams_multi_color_filment.push_back({});
}
continue;
ams_filament_presets.push_back("Generic PLA");//for unknow matieral
auto default_unknown_color = "#CECECE";
ams_filament_colors.push_back(default_unknown_color);
ams_filament_color_types.push_back("1");
if (filament_multi_color.size() == 0) {
filament_multi_color.push_back(default_unknown_color);
}
ams_multi_color_filment.push_back(filament_multi_color);
} else if (is_placeholder) {
// Orca: push placeholders to keep index alignment with ams_infos
ams_filament_presets.push_back("");
ams_filament_colors.push_back("");
ams_filament_color_types.push_back("");
ams_multi_color_filment.push_back({});
}
continue;
}
if (!filament_changed && this->filament_presets.size() > ams_filament_presets.size()) {
ams_filament_presets.push_back(this->filament_presets[ams_filament_presets.size()]);
ams_filament_colors.push_back(filament_color);
ams_filament_color_types.push_back(filament_color_type);
ams_multi_color_filment.push_back(filament_multi_color);
ams_infos.back().valid = true;
continue;
}
bool has_type = false;
auto filament_type = ams.opt_string("filament_type", 0u);
auto iter = filaments.end();
if (!filament_id.empty()) {
iter = std::find_if(filaments.begin(), filaments.end(), [this, &filament_id, &has_type, filament_type](auto &f) {
has_type |= f.config.opt_string("filament_type", 0u) == filament_type;
return f.is_compatible && filaments.get_preset_base(f) == &f && f.filament_id == filament_id; });
warn_ambiguous_filament_id_match(filaments, iter, filament_id);
} else {
// The material type is the only identity a printer-set tray carries.
has_type = std::any_of(filaments.begin(), filaments.end(), [&filament_type](auto &f) {
return f.is_compatible && f.config.opt_string("filament_type", 0u) == filament_type; });
}
auto iter = std::find_if(filaments.begin(), filaments.end(), [this, &filament_id, &has_type, filament_type](auto &f) {
has_type |= f.config.opt_string("filament_type", 0u) == filament_type;
return f.is_compatible && filaments.get_preset_base(f) == &f && f.filament_id == filament_id; });
warn_ambiguous_filament_id_match(filaments, iter, filament_id);
if (iter == filaments.end()) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": filament_id %1% not found or system or compatible") % filament_id;
if (!filament_type.empty()) {
@@ -4314,7 +4267,6 @@ unsigned int PresetBundle::sync_ams_list(std::vector<std::pair<DynamicPrintConfi
}
}
}
const bool resolved_by_material_type = printer_set_tray && iter != filaments.end();
if (iter == filaments.end()) {
// Prefer old selection
if (ams_filament_presets.size() < this->filament_presets.size()) {
@@ -4322,7 +4274,6 @@ unsigned int PresetBundle::sync_ams_list(std::vector<std::pair<DynamicPrintConfi
ams_filament_colors.push_back(filament_color);
ams_filament_color_types.push_back(filament_color_type);
ams_multi_color_filment.push_back(filament_multi_color);
ams_infos.back().valid = true;
unknowns.emplace_back(&ams, has_type ? L("The filament may not be compatible with the current machine settings. Generic filament presets will be used.") :
L("The filament model is unknown. Still using the previous filament preset."));
continue;
@@ -4338,8 +4289,7 @@ unsigned int PresetBundle::sync_ams_list(std::vector<std::pair<DynamicPrintConfi
if (iter == filaments.end())
continue;
}
if (!resolved_by_material_type)
unknowns.emplace_back(&ams, boost::algorithm::starts_with(iter->name, filament_type) ?
unknowns.emplace_back(&ams, boost::algorithm::starts_with(iter->name, filament_type) ?
(has_type ? L("The filament may not be compatible with the current machine settings. Generic filament presets will be used.") :
L("The filament model is unknown. Generic filament presets will be used.")) :
(has_type ? L("The filament may not be compatible with the current machine settings. A random filament preset will be used.") :
@@ -4350,7 +4300,6 @@ unsigned int PresetBundle::sync_ams_list(std::vector<std::pair<DynamicPrintConfi
ams_filament_colors.push_back(filament_color);
ams_filament_color_types.push_back(filament_color_type);
ams_multi_color_filment.push_back(filament_multi_color);
ams_infos.back().valid = true;
}
if (ams_filament_presets.empty())
return 0;
@@ -4790,21 +4739,9 @@ std::vector<std::vector<DynamicPrintConfig>> PresetBundle::get_extruder_filament
return filament_infos;
}
std::string PresetBundle::get_printer_model_display_name(const std::string &model_id) const
{
if (model_id.empty())
return {};
for (const auto &vendor_entry : vendors) {
for (const auto &model : vendor_entry.second.models) {
if (model.model_id == model_id)
return model.name;
}
}
return {};
}
// ORCA TODO: this assumes printer names follow "<printer_model> <nozzle_diameter>", e.g.
// "Bambu Lab X2D 0.4 nozzle". Other naming schemes may not resolve correctly.
// ORCA TODO: currently, this function assumes the printer name follows the pattern of "<printer_model> <nozzle_diameter>", e.g.
// printer_type: "Bambu Lab X2D", nozzle_diameter_str: "0.4 nozzle" => printer_name: "Bambu Lab X2D 0.4 nozzle". If the printer name does
// not follow this pattern, the function may not work correctly.
std::set<std::string> PresetBundle::get_printer_names_by_printer_type_and_nozzle(const std::string &printer_type, std::string nozzle_diameter_str, bool system_only)
{
std::set<std::string> printer_names;
@@ -4826,9 +4763,7 @@ std::set<std::string> PresetBundle::get_printer_names_by_printer_type_and_nozzle
if (printer_it->name.find(nozzle_diameter_str) != std::string::npos) printer_names.insert(printer_it->name);
}
// No match is normal for a connected machine the user has not installed; only an
// ambiguous match is a bug (the caller assumes one preset per model and nozzle).
assert(printer_names.size() <= 1);
assert(printer_names.size() == 1);
for (auto& printer_name : printer_names) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " " << __LINE__ << " printer name: " << printer_name;
@@ -4841,8 +4776,8 @@ std::vector<Preset *> PresetBundle::get_filament_presets_for_machine(const std::
const std::string &nozzle_diameter_str,
bool include_user_presets)
{
// Printer model plus nozzle diameter normally resolves to a single system printer preset.
// Zero matches is normal for a connected machine the user never installed.
// Printer model plus nozzle diameter is expected to resolve to a single system printer preset;
// get_printer_names_by_printer_type_and_nozzle asserts as much in debug builds.
const std::set<std::string> printer_names = get_printer_names_by_printer_type_and_nozzle(printer_type, nozzle_diameter_str);
const Preset *printer = printer_names.empty() ? nullptr : printers.find_preset(*printer_names.begin());
if (printer == nullptr)
@@ -5452,6 +5387,23 @@ DynamicPrintConfig PresetBundle::full_fff_config(bool apply_extruder, std::optio
add_if_some_non_empty(std::move(print_compatible_printers), "print_compatible_printers");
out.option<ConfigOptionStrings>("extruder_ams_count", true)->values = save_extruder_ams_count_to_string(this->extruder_ams_counts);
// The slice config is consumed by code that indexes per-slot arrays by material id
// (MultiMaterialSegmentation sizes its state count from filament_colour). After the
// producer-side lockstep in update_multi_material_filament_presets() these project-scoped
// arrays are expected to be exactly num_filaments; the property the pipeline relies on is
// that none is shorter (a short filament_colour was the OOB in #16432). Pad if needed.
for (const char* per_slot_key : { "filament_colour", "filament_colour_type", "filament_multi_colour" }) {
auto* per_slot_opt = dynamic_cast<ConfigOptionVectorBase*>(out.option(per_slot_key, true));
if (per_slot_opt == nullptr)
continue;
if (per_slot_opt->size() < num_filaments) {
BOOST_LOG_TRIVIAL(warning) << "full_fff_config: per-slot array " << per_slot_key
<< " has " << per_slot_opt->size() << " entries, padding to num_filaments=" << num_filaments;
per_slot_opt->resize(num_filaments, FullPrintConfig::defaults().option(per_slot_key));
}
assert(per_slot_opt->size() >= num_filaments);
}
out.option<ConfigOptionEnumGeneric>("printer_technology", true)->value = ptFFF;
return out;
}
@@ -7986,6 +7938,15 @@ void PresetBundle::update_multi_material_filament_presets(size_t to_delete_filam
this->filament_presets.resize(num_extruders, this->filament_presets.empty() ? this->filaments.first_visible().name :
this->filament_presets.back());
num_filaments = this->filament_presets.size();
// Keep the project-scoped per-slot arrays in lockstep with the padded preset list, so
// consumers that size from filament_colour (MultiMaterialSegmentation) agree with those
// that size from the preset list / filament_diameter (PrintObjectSlice). Grow only: a
// colour array longer than the preset list is a valid state (a project with more slots
// than nozzles) and must not be truncated here.
const auto* filament_colour = this->project_config.option<ConfigOptionStrings>("filament_colour");
if (filament_colour == nullptr || filament_colour->values.size() < num_filaments)
this->resize_project_filament_slots(num_filaments);
}
if (to_delete_filament_id == -1)
to_delete_filament_id = num_filaments;
+6 -3
View File
@@ -394,6 +394,12 @@ public:
// slots live. A caller adding physical filaments has to add num_mixed_filaments() on top and
// then move the new slots ahead of the mixed tail, as Sidebar::add_custom_filament does.
void set_num_filaments(unsigned int n, std::string new_col = "");
// Grow or shrink the project-scoped per-slot arrays (filament_colour, filament_colour_type,
// filament_multi_colour, the filament maps and the mixed metadata) to n, without touching
// filament_presets and without rebuilding flush volumes. Callers that resize filament_presets
// must keep these arrays in lockstep with it. Returns the array size before the resize so the
// caller can seed the new tail.
size_t resize_project_filament_slots(size_t n);
void update_num_filaments(unsigned int to_del_flament_id);
void get_ams_cobox_infos(AMSComboInfo &combox_info);
@@ -413,9 +419,6 @@ public:
std::vector<std::vector<DynamicPrintConfig>> get_extruder_filament_info() const;
// Resolve a vendor model id (an agent-reported machine identity) to the display name
// printer profiles use as printer_model. Empty when no loaded vendor declares it.
std::string get_printer_model_display_name(const std::string &model_id) const;
std::set<std::string> get_printer_names_by_printer_type_and_nozzle(const std::string &printer_type, std::string nozzle_diameter_str, bool system_only = true);
// Orca: the root filament presets a connected machine can use, resolved with the rule the rest
// of the app applies (is_compatible_with_printer): an empty compatible_printers means every
+9
View File
@@ -2991,6 +2991,15 @@ void PrintObject::clear_shared_object()
void PrintObject::copy_layers_from_shared_object()
{
if (m_shared_object) {
// Orca: Print::process() calls this once per slice for each sharing object, after clear_shared_object() has
// emptied any earlier alias, so the vectors hold only layers this object sliced itself before it started sharing
// (or nothing). Free them here, as clear_support_layers() / clear_layers() would; those skip deletion while
// m_shared_object is set. The belt-only data they also free is always empty here, because belt printers never
// share layers.
for (SupportLayer *layer : m_support_layers)
delete layer;
for (Layer *layer : m_layers)
delete layer;
m_layers.clear();
m_support_layers.clear();
+5 -4
View File
@@ -12866,10 +12866,11 @@ CLITransformConfigDef::CLITransformConfigDef()
//def->cli = "orient|o";
def->set_default_value(new ConfigOptionInt(0));
/*def = this->add("repair", coBool);
def = this->add("repair", coBool);
def->label = L("Repair");
def->tooltip = L("Repair the model's meshes if it is non-manifold mesh.");
def->set_default_value(new ConfigOptionBool(false));*/
def->tooltip = L("Repair the meshes of the models as Fix Model does: close holes and cracks, orient them outward and "
"remove self-intersections.");
def->set_default_value(new ConfigOptionBool(false));
def = this->add("rotate", coFloat);
def->label = L("Rotate");
@@ -13085,7 +13086,7 @@ CLIMiscConfigDef::CLIMiscConfigDef()
def = this->add("allow_rotations", coBool);
def->label = L("Allow rotation when arranging");
def->tooltip = L("If enabled, Arrange will allow rotation when placing objects.");
def->set_default_value(new ConfigOptionBool(true));
def->set_default_value(new ConfigOptionBool(false));
def = this->add("align_to_y_axis", coBool);
def->label = L("Align to Y axis when arranging");
+3
View File
@@ -982,6 +982,9 @@ void PrintObject::generate_support_material(bool with_belt_brim)
this->_generate_support_material();
m_print->throw_if_canceled();
}
// Orca: the tree support collision/avoidance caches and support nodes are only used while this step runs
// (detect_overhangs() rebuilds them from scratch), so don't keep them resident until the next slice.
this->clear_tree_support_preview_cache();
// Belt brim rides here rather than in the brim step because its apron
// prologue introduces print_z values below the object's first layer, and
// those must exist before ToolOrdering is built at psWipeTower - one step
+10 -3
View File
@@ -1073,8 +1073,14 @@ static inline void apply_mm_segmentation(PrintObject &print_object, ThrowOnCance
const auto &layer_ranges = print_object.shared_regions()->layer_ranges;
double z = print_object.get_layer(int(range.begin()))->slice_z;
auto it_layer_range = layer_range_first(layer_ranges, z);
// The per-layer row width of the segmentation is the number of material channels it
// was built with (filament_colour.size()). Size the bound from the data itself rather
// than from filament_diameter.size(): those two arrays can diverge (the project's
// colour array can end up shorter than the padded preset list), and trusting the
// larger count reads past the end of every row.
// BBS
const size_t num_extruders = print_object.print()->config().filament_diameter.size();
const size_t num_extruders = segmentation.empty() ? 0 : segmentation.front().size();
assert(num_extruders == print_object.print()->config().filament_diameter.size());
struct ByExtruder {
ExPolygons expolygons;
@@ -1097,7 +1103,8 @@ static inline void apply_mm_segmentation(PrintObject &print_object, ThrowOnCance
by_extruder.assign(num_extruders, ByExtruder());
by_region.assign(layer.region_count(), ByRegion());
bool layer_split = false;
for (size_t extruder_id = 0; extruder_id < num_extruders; ++ extruder_id) {
const size_t layer_channels = segmentation[layer_id].size();
for (size_t extruder_id = 0; extruder_id < std::min(num_extruders, layer_channels); ++ extruder_id) {
ByExtruder &region = by_extruder[extruder_id];
append(region.expolygons, std::move(segmentation[layer_id][extruder_id]));
if (! region.expolygons.empty()) {
@@ -1532,7 +1539,7 @@ void PrintObject::slice_volumes()
if (min_growth < 0.f || elfoot > 0.f) {
// Apply the negative XY compensation. (the ones that is <0)
ExPolygons trimming;
static const float eps = float(scale_(m_config.slice_closing_radius.value) * 1.5);
const float eps = float(scale_(m_config.slice_closing_radius.value) * 1.5);
if (elfoot > 0.f) {
ExPolygons expolygons_to_compensate = offset_ex(layer->merged(eps), -eps);
lslices_elfoot_uncompensated[layer_id] = expolygons_to_compensate;
+13 -30
View File
@@ -1976,37 +1976,15 @@ coordf_t TreeSupport::get_radius(const SupportNode* node)
return node->radius;
}
ExPolygons TreeSupport::get_avoidance(coordf_t radius, size_t obj_layer_nr)
// Orca: drop_nodes() calls these several times per node per layer, so they return a reference into the
// TreeSupportData cache. The cache only ever inserts, so its entries never move while nodes drop in parallel.
const ExPolygons& TreeSupport::get_avoidance(coordf_t radius, size_t obj_layer_nr)
{
#if USE_SUPPORT_3D
if (m_model_volumes) {
bool on_build_plate = m_object_config->support_on_build_plate_only.value;
const Polygons& avoid_polys = m_model_volumes->getAvoidance(radius, obj_layer_nr, TreeSupport3D::TreeModelVolumes::AvoidanceType::FastSafe, on_build_plate, true);
ExPolygons expolys;
for (auto& poly : avoid_polys)
expolys.emplace_back(std::move(poly));
return expolys;
}
return ExPolygons();
#else
return m_ts_data->get_avoidance(radius, obj_layer_nr);
#endif
}
ExPolygons TreeSupport::get_collision(coordf_t radius, size_t layer_nr)
const ExPolygons& TreeSupport::get_collision(coordf_t radius, size_t layer_nr)
{
#if USE_SUPPORT_3D
if (m_model_volumes) {
bool on_build_plate = m_object_config->support_on_build_plate_only.value;
const Polygons& collision_polys = m_model_volumes->getCollision(radius, layer_nr, true);
ExPolygons expolys;
for (auto& poly : collision_polys)
expolys.emplace_back(std::move(poly));
return expolys;
}
#else
return m_ts_data->get_collision(radius, layer_nr);
#endif
return ExPolygons();
}
Polygons TreeSupport::get_collision_polys(coordf_t radius, size_t layer_nr)
{
@@ -2827,7 +2805,12 @@ void TreeSupport::draw_circles()
#endif // SUPPORT_TREE_DEBUG_TO_SVG
SupportLayerPtrs& ts_layers = m_object->support_layers();
auto iter = std::remove_if(ts_layers.begin(), ts_layers.end(), [](SupportLayer* ts_layer) { return ts_layer->height < EPSILON; });
// Orca: the vector owns its layers, so the dropped ones are deleted before they are erased.
// std::stable_partition (unlike std::remove_if) leaves exactly the dropped layers in the tail.
// Only heights below EPSILON are dropped; written as !(height < EPSILON), a NaN height is kept.
auto iter = std::stable_partition(ts_layers.begin(), ts_layers.end(), [](SupportLayer* ts_layer) { return !(ts_layer->height < EPSILON); });
for (auto it = iter; it != ts_layers.end(); ++it)
delete *it;
ts_layers.erase(iter, ts_layers.end());
for (int layer_nr = 0; layer_nr < ts_layers.size(); layer_nr++) {
ts_layers[layer_nr]->upper_layer = layer_nr != ts_layers.size() - 1 ? ts_layers[layer_nr + 1] : nullptr;
@@ -3071,7 +3054,7 @@ void TreeSupport::drop_nodes()
//Insert a completely new node and let both original nodes fade.
Point next_position = (node.position + neighbours[0]) / 2; //Average position of the two nodes.
coordf_t next_radius = calc_radius(node.dist_mm_to_top+height_next);
auto avoid_layer = get_avoidance(next_radius, obj_layer_nr_next);
const ExPolygons& avoid_layer = get_avoidance(next_radius, obj_layer_nr_next);
if (group_index == 0)
{
//Avoid collisions.
@@ -3275,7 +3258,7 @@ void TreeSupport::drop_nodes()
}
#endif
coordf_t next_radius = calc_radius(node.dist_mm_to_top + height_next);
auto avoidance_next = get_avoidance(next_radius, obj_layer_nr_next);
const ExPolygons& avoidance_next = get_avoidance(next_radius, obj_layer_nr_next);
Point to_outside = projection_onto(avoidance_next, node.position);
Point direction_to_outer = to_outside - node.position;
@@ -3337,7 +3320,7 @@ void TreeSupport::drop_nodes()
p_node->to_buildplate = false;
return; // from parallel_for_each lambda
}
auto next_collision = get_collision(0, obj_layer_nr_next);
const ExPolygons& next_collision = get_collision(0, obj_layer_nr_next);
const bool to_buildplate = !is_inside_ex(m_ts_data->m_layer_outlines[obj_layer_nr_next], next_layer_vertex);
// don't increase radius if next node will collide partially with the object (STUDIO-7883)
to_outside = projection_onto(next_collision, next_layer_vertex);
+2 -2
View File
@@ -536,9 +536,9 @@ private:
coordf_t calc_branch_radius(coordf_t base_radius, coordf_t mm_to_top, double diameter_angle_scale_factor, bool use_min_distance=true);
coordf_t calc_radius(coordf_t mm_to_top);
coordf_t get_radius(const SupportNode* node);
ExPolygons get_avoidance(coordf_t radius, size_t obj_layer_nr);
const ExPolygons& get_avoidance(coordf_t radius, size_t obj_layer_nr);
// layer's expolygon expanded by radius+m_xy_distance
ExPolygons get_collision(coordf_t radius, size_t layer_nr);
const ExPolygons& get_collision(coordf_t radius, size_t layer_nr);
// get Polygons instead of ExPolygons
Polygons get_collision_polys(coordf_t radius, size_t layer_nr);
+256
View File
@@ -19,6 +19,7 @@
#include <mutex>
#include <numeric>
#include <optional>
#include "nlohmann/json.hpp"
#include <queue>
#include <ratio>
#include <string>
@@ -5166,5 +5167,260 @@ indexed_triangle_set cut_mesh_at_steps(const indexed_triangle_set &mesh, const s
return out;
}
// ---------------------------------------------------------------------------------------------
// Project persistence. See the declarations in TextureDisplacement.hpp for why this is JSON rather
// than the cereal stream next to them.
// ---------------------------------------------------------------------------------------------
namespace {
nlohmann::json vec2_to_json(const Vec2f &v) { return nlohmann::json::array({ v.x(), v.y() }); }
nlohmann::json vec3_to_json(const Vec3f &v) { return nlohmann::json::array({ v.x(), v.y(), v.z() }); }
Vec2f vec2_from_json(const nlohmann::json &j, const Vec2f &fallback)
{
if (!j.is_array() || j.size() != 2)
return fallback;
return Vec2f(j[0].get<float>(), j[1].get<float>());
}
Vec3f vec3_from_json(const nlohmann::json &j, const Vec3f &fallback)
{
if (!j.is_array() || j.size() != 3)
return fallback;
return Vec3f(j[0].get<float>(), j[1].get<float>(), j[2].get<float>());
}
// Reads key `k` into `out` when it is present and of the expected type, leaving `out` alone otherwise.
// That "leave it alone" is the whole point: every member starts at its struct default, so a project
// written by an older build simply keeps the defaults for whatever it did not know about.
template<class T> void read(const nlohmann::json &j, const char *k, T &out)
{
const auto it = j.find(k);
if (it == j.end())
return;
try {
out = it->get<T>();
} catch (...) {
}
}
void read_enum(const nlohmann::json &j, const char *k, int &out)
{
const auto it = j.find(k);
if (it != j.end() && it->is_number_integer())
out = it->get<int>();
}
} // namespace
std::string texture_displacement_layers_to_json(const std::vector<TextureDisplacementLayer> &layers,
const TextureDisplacementOptions &options)
{
nlohmann::json root;
root["version"] = 1;
nlohmann::json &opt = root["options"];
opt["displace_border"] = options.displace_border;
opt["smooth_enabled"] = options.smooth_enabled;
opt["smooth_strength"] = options.smooth_strength;
opt["smooth_iterations"] = options.smooth_iterations;
opt["smooth_skip_border"] = options.smooth_skip_border;
opt["pipeline_v2"] = options.pipeline_v2;
opt["v2_refine_mm"] = options.v2_refine_mm;
opt["v2_regularize"] = options.v2_regularize;
opt["v2_max_triangles_k"] = options.v2_max_triangles_k;
opt["v2_relocate"] = options.v2_relocate;
opt["v2_flip_edges"] = options.v2_flip_edges;
opt["color_mix_enabled"] = options.color_mix_enabled;
opt["color_despeckle"] = options.color_despeckle;
opt["color_mix_count"] = options.color_mix_count;
opt["active_slot"] = options.active_slot;
opt["panel_mode"] = options.panel_mode;
nlohmann::json &arr = root["layers"];
arr = nlohmann::json::array();
for (const TextureDisplacementLayer &l : layers) {
nlohmann::json j;
j["slot"] = l.slot;
j["name"] = l.name;
j["path"] = l.path;
j["path_in_3mf"] = l.path_in_3mf;
j["depth_mm"] = l.depth_mm;
j["tiling_scale"] = l.tiling_scale;
j["rotation_deg"] = l.rotation_deg;
j["offset"] = vec2_to_json(l.offset);
j["invert"] = l.invert;
j["midlevel"] = l.midlevel;
j["smoothing"] = l.smoothing;
j["edge_smoothing"] = l.edge_smoothing;
j["edge_smoothing_amount"] = l.edge_smoothing_amount;
j["auto_connect_islands"] = l.auto_connect_islands;
j["tile_enabled"] = l.tile_enabled;
j["tile_method"] = int(l.tile_method);
j["projection_method"] = int(l.projection_method);
j["blend_mode"] = int(l.blend_mode);
j["color_enabled"] = l.color_enabled;
j["lscm_seam_angle_deg"] = l.lscm_seam_angle_deg;
j["island_padding_mm"] = l.island_padding_mm;
j["view_project_right"] = vec3_to_json(l.view_project_right);
j["view_project_up"] = vec3_to_json(l.view_project_up);
j["view_project_projective"] = l.view_project_projective;
j["view_project_matrix"] = l.view_project_matrix;
// Flat pairs rather than nested arrays: shorter, and the reader can simply ignore a trailing
// odd element instead of having to validate every sub-array.
nlohmann::json &seams = j["lscm_seam_edges"];
seams = nlohmann::json::array();
for (const auto &[a, b] : l.lscm_seam_edges) {
seams.push_back(a);
seams.push_back(b);
}
nlohmann::json &islands = j["islands"];
islands = nlohmann::json::array();
for (const TextureIsland &island : l.islands)
islands.push_back(nlohmann::json::array(
{ island.offset.x(), island.offset.y(), island.rotation_deg, island.scale }));
j["island_groups"] = l.island_groups;
nlohmann::json &overrides = j["lscm_uv_overrides"];
overrides = nlohmann::json::array();
for (const auto &[key, uv] : l.lscm_uv_overrides)
overrides.push_back(nlohmann::json::array({ key, uv.x(), uv.y() }));
arr.push_back(std::move(j));
}
return root.dump();
}
bool texture_displacement_layers_from_json(const std::string &text,
std::vector<TextureDisplacementLayer> &layers,
TextureDisplacementOptions &options)
{
nlohmann::json root;
try {
root = nlohmann::json::parse(text);
} catch (...) {
return false;
}
if (!root.is_object())
return false;
TextureDisplacementOptions out_options;
if (const auto it = root.find("options"); it != root.end() && it->is_object()) {
const nlohmann::json &opt = *it;
read(opt, "displace_border", out_options.displace_border);
read(opt, "smooth_enabled", out_options.smooth_enabled);
read(opt, "smooth_strength", out_options.smooth_strength);
read(opt, "smooth_iterations", out_options.smooth_iterations);
read(opt, "smooth_skip_border", out_options.smooth_skip_border);
read(opt, "pipeline_v2", out_options.pipeline_v2);
read(opt, "v2_refine_mm", out_options.v2_refine_mm);
read(opt, "v2_regularize", out_options.v2_regularize);
read(opt, "v2_max_triangles_k", out_options.v2_max_triangles_k);
read(opt, "v2_relocate", out_options.v2_relocate);
read(opt, "v2_flip_edges", out_options.v2_flip_edges);
read(opt, "color_mix_enabled", out_options.color_mix_enabled);
read(opt, "color_despeckle", out_options.color_despeckle);
read(opt, "color_mix_count", out_options.color_mix_count);
read(opt, "active_slot", out_options.active_slot);
read(opt, "panel_mode", out_options.panel_mode);
out_options.active_slot = std::clamp(out_options.active_slot, 0, int(TEXTURE_DISPLACEMENT_MAX_LAYERS) - 1);
out_options.panel_mode = std::clamp(out_options.panel_mode, 0, 1);
}
std::vector<TextureDisplacementLayer> out_layers;
// read() skips a value of the wrong type, but the vector, matrix, seam, island and UV-override arrays
// below read their numbers with get<>(), which throws on one; that rejects the document, like text that
// does not parse.
try {
if (const auto it = root.find("layers"); it != root.end() && it->is_array()) {
for (const nlohmann::json &j : *it) {
if (!j.is_object())
continue;
TextureDisplacementLayer l;
read(j, "slot", l.slot);
// The panel and the paint masks are indexed by slot, so a layer without a valid one rejects the
// document rather than reaching them.
if (l.slot < 0 || l.slot >= int(TEXTURE_DISPLACEMENT_MAX_LAYERS))
return false;
read(j, "name", l.name);
read(j, "path", l.path);
read(j, "path_in_3mf", l.path_in_3mf);
read(j, "depth_mm", l.depth_mm);
read(j, "tiling_scale", l.tiling_scale);
read(j, "rotation_deg", l.rotation_deg);
if (const auto o = j.find("offset"); o != j.end())
l.offset = vec2_from_json(*o, l.offset);
read(j, "invert", l.invert);
read(j, "midlevel", l.midlevel);
read(j, "smoothing", l.smoothing);
read(j, "edge_smoothing", l.edge_smoothing);
read(j, "edge_smoothing_amount", l.edge_smoothing_amount);
read(j, "auto_connect_islands", l.auto_connect_islands);
read(j, "tile_enabled", l.tile_enabled);
read(j, "color_enabled", l.color_enabled);
int tile_method = int(l.tile_method), projection = int(l.projection_method), blend = int(l.blend_mode);
read_enum(j, "tile_method", tile_method);
read_enum(j, "projection_method", projection);
read_enum(j, "blend_mode", blend);
l.tile_method = TextureTileMethod(tile_method);
l.projection_method = TextureProjectionMethod(std::clamp(projection, 0, 4));
l.blend_mode = TextureBlendMode(blend);
read(j, "lscm_seam_angle_deg", l.lscm_seam_angle_deg);
read(j, "island_padding_mm", l.island_padding_mm);
if (const auto v = j.find("view_project_right"); v != j.end())
l.view_project_right = vec3_from_json(*v, l.view_project_right);
if (const auto v = j.find("view_project_up"); v != j.end())
l.view_project_up = vec3_from_json(*v, l.view_project_up);
read(j, "view_project_projective", l.view_project_projective);
if (const auto v = j.find("view_project_matrix"); v != j.end() && v->is_array() && v->size() == 12)
for (size_t i = 0; i < 12; ++i)
l.view_project_matrix[i] = (*v)[i].get<float>();
if (const auto v = j.find("lscm_seam_edges"); v != j.end() && v->is_array())
for (size_t i = 0; i + 1 < v->size(); i += 2)
l.lscm_seam_edges.emplace_back((*v)[i].get<int>(), (*v)[i + 1].get<int>());
if (const auto v = j.find("islands"); v != j.end() && v->is_array())
for (const nlohmann::json &e : *v) {
if (!e.is_array() || e.size() != 4)
continue;
TextureIsland island;
island.offset = Vec2f(e[0].get<float>(), e[1].get<float>());
island.rotation_deg = e[2].get<float>();
island.scale = e[3].get<float>();
l.islands.push_back(island);
}
read(j, "island_groups", l.island_groups);
if (const auto v = j.find("lscm_uv_overrides"); v != j.end() && v->is_array())
for (const nlohmann::json &e : *v) {
if (!e.is_array() || e.size() != 3)
continue;
l.lscm_uv_overrides.emplace_back(e[0].get<int>(), Vec2f(e[1].get<float>(), e[2].get<float>()));
}
out_layers.push_back(std::move(l));
}
}
} catch (const nlohmann::json::exception &) {
return false;
}
layers = std::move(out_layers);
options = out_options;
return true;
}
} // namespace Slic3r
+28 -1
View File
@@ -397,14 +397,41 @@ struct TextureDisplacementOptions
// finer than the mesh leaves behind, without eating features that are genuinely a facet wide.
int color_despeckle = 2;
// Where the panel was left, rather than anything the bake reads. It lives with the volume because
// that is what it describes: reopening a project should put the user back on the layer they were
// editing, in the mode they were editing it in, not on slot 0 in Standard.
int active_slot = 0;
int panel_mode = 0; // 0 Standard, 1 Pro
template<class Archive> void serialize(Archive &ar)
{
ar(displace_border, smooth_enabled, smooth_strength, smooth_iterations, smooth_skip_border,
pipeline_v2, v2_refine_mm, v2_regularize, v2_max_triangles_k,
v2_relocate, color_mix_enabled, color_despeckle, color_mix_count);
v2_relocate, v2_flip_edges, color_mix_enabled, color_despeckle, color_mix_count,
active_slot, panel_mode);
}
};
// Project persistence (see bbs_3mf.cpp). The layer stack and the per-volume options are written to the
// .3mf as JSON rather than through the cereal save()/load() above: those two are positional and
// unversioned, which is right for the undo/redo stack they serve (one session, one binary) but would
// make every future field a project-breaking change. A JSON object tolerates both directions - an
// unknown key is ignored, a missing one keeps the member's default - so old projects keep loading and
// new ones degrade gracefully in older builds.
//
// The texture image itself is *not* in here. It is a binary blob that belongs in the archive as a file
// of its own, exactly as EmbossShape stores its SVG; `path_in_3mf` names that file, and the caller is
// responsible for writing it and for filling `image_data` back in on load.
std::string texture_displacement_layers_to_json(const std::vector<TextureDisplacementLayer> &layers,
const TextureDisplacementOptions &options);
// Returns false and leaves both outputs untouched when the text is not a valid JSON object, a number in a
// layer's vector, matrix, seam, island or UV-override array has the wrong type, or a layer's slot is
// missing or outside [0, TEXTURE_DISPLACEMENT_MAX_LAYERS). Any other value of the wrong type or shape
// keeps its default.
bool texture_displacement_layers_from_json(const std::string &text,
std::vector<TextureDisplacementLayer> &layers,
TextureDisplacementOptions &options);
// How much detail a height texture carries: central differences of the grey image, the mean gradient
// and the share of texels steeper than 30 grey levels, mapped to how many texels one mesh edge may
// span (1 for a hard-edged image, 4 for a smooth one). Cached per image, like the decode.
@@ -673,14 +673,14 @@ static bool repair_cluster_smooth(
[&]() { return cancelled(); }, &repair_error);
if (repaired) {
if (cancelled()) return false;
BOOST_LOG_TRIVIAL(info) << log_prefix << ": Windows 3D mesh repair finished.";
BOOST_LOG_TRIVIAL(info) << log_prefix << ": mesh repair finished.";
if (!resample_face_colors(TriMesh(std::move(repaired_its))))
return false;
} else {
BOOST_LOG_TRIVIAL(warning) << log_prefix << ": Windows 3D mesh repair failed: " << repair_error;
BOOST_LOG_TRIVIAL(warning) << log_prefix << ": mesh repair failed: " << repair_error;
}
} else {
BOOST_LOG_TRIVIAL(info) << log_prefix << ": importing mesh without Windows 3D repair.";
BOOST_LOG_TRIVIAL(info) << log_prefix << ": importing mesh without repair.";
}
}
}
+1 -4
View File
@@ -1,5 +1,4 @@
#ifndef libslic3r_Timer_hpp_
#define libslic3r_Timer_hpp_
#pragma once
#include "TriangleMesh.hpp"
@@ -10,5 +9,3 @@ public:
static TriangleMesh smooth_triangle_mesh(const TriangleMesh &mesh,bool& ok);
};
} // namespace Slic3r
#endif // libslic3r_Timer_hpp_
-1
View File
@@ -109,7 +109,6 @@ extern std::string format_memsize_MB(size_t n);
extern std::string format_memsize(size_t bytes, unsigned int decimals = 1);
// Return string to be added to the boost::log output to inform about the current process memory allocation.
// The string is non-empty if the loglevel >= info (3) or ignore_loglevel==true.
// Latter is used to get the memory info from SysInfoDialog.
extern std::string log_memory_info(bool ignore_loglevel = false);
extern void disable_multi_threading();
// Returns the size of physical memory (RAM) in bytes.
+1 -3
View File
@@ -1744,9 +1744,7 @@ std::string format_diameter_to_str(double diameter, int precision)
return oss.str();
}
// Returns platform-specific string to be used as log output or parsed in SysInfoDialog.
// The latter parses the string with (semi)colons as separators, it should look about as
// "desc1: value1; desc2: value2" or similar (spaces should not matter).
// Returns platform-specific string to be used as log output.
std::string log_memory_info(bool ignore_loglevel)
{
std::string out;
+6 -4
View File
@@ -21,7 +21,9 @@ namespace libvgcode {
// the triangles that share it: the shader runs 8 times per segment instead of 24.
static constexpr const std::array<uint8_t, 8> VERTEX_DATA = { 0, 1, 2, 3, 4, 5, 6, 7 };
static constexpr const std::array<uint8_t, 24> INDEX_DATA = {
// 16-bit indices: GCN 1/2 GPUs have no 8-bit index support, and some of their drivers
// emulate it wrongly in instanced draws.
static constexpr const std::array<uint16_t, 24> INDEX_DATA = {
0, 1, 2, // front spike
0, 2, 3, // front spike
0, 3, 4, // right/bottom body
@@ -37,7 +39,7 @@ void SegmentTemplate::init()
if (m_vao_id != 0)
return;
m_size_in_bytes_gpu += (VERTEX_DATA.size() + INDEX_DATA.size()) * sizeof(uint8_t);
m_size_in_bytes_gpu += VERTEX_DATA.size() * sizeof(uint8_t) + INDEX_DATA.size() * sizeof(uint16_t);
int curr_vertex_array;
glsafe(glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &curr_vertex_array));
@@ -61,7 +63,7 @@ void SegmentTemplate::init()
// and restored together with the vao.
glsafe(glGenBuffers(1, &m_ibo_id));
glsafe(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_ibo_id));
glsafe(glBufferData(GL_ELEMENT_ARRAY_BUFFER, INDEX_DATA.size() * sizeof(uint8_t), INDEX_DATA.data(), GL_STATIC_DRAW));
glsafe(glBufferData(GL_ELEMENT_ARRAY_BUFFER, INDEX_DATA.size() * sizeof(uint16_t), INDEX_DATA.data(), GL_STATIC_DRAW));
glsafe(glBindBuffer(GL_ARRAY_BUFFER, curr_array_buffer));
glsafe(glBindVertexArray(curr_vertex_array));
@@ -94,7 +96,7 @@ void SegmentTemplate::render(size_t count)
glsafe(glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &curr_vertex_array));
glsafe(glBindVertexArray(m_vao_id));
glsafe(glDrawElementsInstanced(GL_TRIANGLES, static_cast<GLsizei>(INDEX_DATA.size()), GL_UNSIGNED_BYTE,
glsafe(glDrawElementsInstanced(GL_TRIANGLES, static_cast<GLsizei>(INDEX_DATA.size()), GL_UNSIGNED_SHORT,
nullptr, static_cast<GLsizei>(count)));
glsafe(glBindVertexArray(curr_vertex_array));
}
+6 -3
View File
@@ -927,15 +927,17 @@ void ViewerImpl::reset()
m_used_extruders.clear();
m_total_time = { 0.0f, 0.0f };
m_travels_time = { 0.0f, 0.0f };
m_vertices.clear();
m_vertices_colors.clear();
// swap rather than clear: these are sized by the print, and a reset means the memory
// should go back, not sit reserved until the next load
std::vector<PathVertex>().swap(m_vertices);
std::vector<float>().swap(m_vertices_colors);
for (std::vector<float>& times : m_layer_start_times)
std::vector<float>().swap(times);
std::vector<uint32_t>().swap(m_layer_first_vertex);
m_layers_in_vertex_order = false;
m_valid_lines_bitset.clear();
// BitSet::clear() only zeroes the bits, it keeps the blocks allocated; load() builds a new
// bitset anyway and it is never read while m_vertices is empty
m_valid_lines_bitset = BitSet<>();
#if VGCODE_ENABLE_COG_AND_TOOL_MARKERS
m_cog_marker.reset();
#endif // VGCODE_ENABLE_COG_AND_TOOL_MARKERS
@@ -1834,6 +1836,7 @@ size_t ViewerImpl::get_used_cpu_memory() const
ret += sizeof(m_extrusion_roles_colors);
ret += sizeof(m_options_colors);
ret += STDVEC_MEMSIZE(m_vertices, PathVertex);
ret += STDVEC_MEMSIZE(m_vertices_colors, float);
for (const std::vector<float>& times : m_layer_start_times)
ret += STDVEC_MEMSIZE(times, float);
ret += STDVEC_MEMSIZE(m_layer_first_vertex, uint32_t);
+1 -17
View File
@@ -372,10 +372,6 @@ set(SLIC3R_GUI_SOURCES
GUI/MarkdownTip.hpp
GUI/MediaFilePanel.cpp
GUI/MediaFilePanel.h
GUI/OrcaFilesPanel.cpp
GUI/OrcaFilesPanel.h
GUI/StoragePanel.cpp
GUI/StoragePanel.h
GUI/MediaPlayCtrl.cpp
GUI/MediaPlayCtrl.h
GUI/WebRtcMediaController.cpp
@@ -420,8 +416,6 @@ set(SLIC3R_GUI_SOURCES
GUI/MultiSendMachineModel.hpp
GUI/MultiTaskManagerPage.cpp
GUI/MultiTaskManagerPage.hpp
GUI/MultiTaskModel.cpp
GUI/MultiTaskModel.hpp
GUI/NetworkTestDialog.cpp
GUI/NetworkTestDialog.hpp
GUI/Notebook.cpp
@@ -467,11 +461,6 @@ set(SLIC3R_GUI_SOURCES
GUI/PresetHints.hpp
GUI/PrinterCloudAuthDialog.cpp
GUI/PrinterCloudAuthDialog.hpp
GUI/Printer/BambuFileGridModel.cpp
GUI/Printer/BambuFileGridModel.h
GUI/Printer/IFileGridModel.h
GUI/Printer/RemoteFileGridModel.cpp
GUI/Printer/RemoteFileGridModel.h
GUI/Printer/PrinterFileSystem.cpp
GUI/Printer/PrinterFileSystem.h
GUI/PrinterWebView.cpp
@@ -785,12 +774,8 @@ set(SLIC3R_GUI_SOURCES
Utils/ICameraSignalingChannel.hpp
Utils/OrcaCloudServiceAgent.cpp
Utils/OrcaCloudServiceAgent.hpp
Utils/OrcaMqttConnection.cpp
Utils/OrcaMqttConnection.hpp
Utils/OrcaPrinterAgent.cpp
Utils/OrcaPrinterAgent.hpp
Utils/OrcaCloudSignalingChannel.cpp
Utils/OrcaCloudSignalingChannel.hpp
Utils/QidiPrinterAgent.cpp
Utils/QidiPrinterAgent.hpp
Utils/SnapmakerPrinterAgent.cpp
@@ -815,7 +800,6 @@ set(SLIC3R_GUI_SOURCES
Utils/PrintHost.hpp
Utils/Process.cpp
Utils/Process.hpp
Utils/Profile.hpp
Utils/RaycastManager.cpp
Utils/RaycastManager.hpp
Utils/Repetier.cpp
@@ -929,7 +913,7 @@ target_include_directories(libslic3r_gui PRIVATE Utils ${CMAKE_CURRENT_BINARY_DI
if (WIN32)
target_include_directories(libslic3r_gui SYSTEM PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../../deps/WebView2/include)
target_link_libraries(libslic3r_gui Advapi32 Crypt32)
target_link_libraries(libslic3r_gui Advapi32)
endif()
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SLIC3R_GUI_SOURCES})
+1 -10
View File
@@ -7,7 +7,6 @@
#include "slic3r/GUI/DeviceCore/DevExtruderSystem.h"
#include "slic3r/GUI/DeviceCore/DevManager.h"
#include "slic3r/GUI/DeviceManager.hpp"
#include "slic3r/GUI/MsgDialog.hpp"
@@ -1634,16 +1633,8 @@ int AMSDryCtrWin::update_filament_list(DevAms* dev_ams, MachineObject* obj)
stream << std::fixed << std::setprecision(1) << obj->GetExtderSystem()->GetNozzleDiameter(extruder_id);
std::string nozzle_diameter_str = stream.str();
// The connected device's model may not resolve (OrcaSonar's is optional); the
// helper falls back to the selected profile so the list is never empty.
const std::string filament_printer_model = resolve_filament_printer_model(obj->printer_type, preset_bundle);
if (filament_printer_model.empty()) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << " cannot resolve a printer model for the filament list";
return false;
}
for (Preset *filament_it : preset_bundle->get_filament_presets_for_machine(
filament_printer_model, nozzle_diameter_str, obj->is_support_user_preset)) {
DevPrinterConfigUtil::get_printer_display_name(obj->printer_type), nozzle_diameter_str, obj->is_support_user_preset)) {
if (!filament_id_set.insert(filament_it->filament_id).second)
continue;
const std::string filament_alias = filaments.get_preset_alias(*filament_it, true);
+43 -98
View File
@@ -4,12 +4,10 @@
#include "GUI_App.hpp"
#include "GUI.hpp"
#include "slic3r/Utils/NetworkAgent.hpp"
#include "slic3r/Utils/NetworkAgentFactory.hpp"
#include "libslic3r/Preset.hpp"
#include "I18N.hpp"
#include <algorithm>
#include <boost/log/trivial.hpp>
#include <slic3r/GUI/DeviceManager.hpp>
#include <string>
#include <sstream>
#include <ios>
@@ -498,13 +496,6 @@ void AMSMaterialsSetting::update_filament_editing(bool is_printing)
m_button_confirm->Show(true);
}
// A third-party tray owns its temp range; BBL RFID trays keep the read-only preset values.
// Only the OrcaSonar agent accepts the temp fields, so other agents stay read-only.
const bool is_orca_agent = obj && obj->printer_agent_id == ORCA_PRINTER_AGENT_ID;
const bool can_edit = !is_printing || obj->is_support_filament_setting_inprinting;
m_input_nozzle_min->Enable(m_is_third && can_edit && is_orca_agent);
m_input_nozzle_max->Enable(m_is_third && can_edit && is_orca_agent);
if (!m_is_third) {
m_tip_readonly->SetLabelText(wxEmptyString);
m_tip_readonly->Hide();
@@ -527,8 +518,6 @@ void AMSMaterialsSetting::update_filament_editing(bool is_printing)
if (m_view_only) { // Orca: view-only (2D laser/cut) — lock every edit control and hide apply/reset
m_comboBox_filament->Enable(false);
m_comboBox_cali_result->Enable(false);
m_input_nozzle_min->Enable(false);
m_input_nozzle_max->Enable(false);
m_input_k_val->Enable(false);
m_input_n_val->Enable(false);
m_button_confirm->Hide();
@@ -577,7 +566,7 @@ void AMSMaterialsSetting::on_select_reset(wxCommandEvent& event) {
}
// set k / n value
if (obj->supports_extrusion_cali() && obj->cali_version <= -1 && obj->get_printer_series() == PrinterSeries::SERIES_P1P) {
if (obj->cali_version <= -1 && obj->get_printer_series() == PrinterSeries::SERIES_P1P) {
// set extrusion cali ratio
int cali_tray_id = ams_id * 4 + slot_id;
@@ -598,7 +587,7 @@ void AMSMaterialsSetting::on_select_reset(wxCommandEvent& event) {
}
obj->command_extrusion_cali_set(cali_tray_id, "", "", k, n);
}
else if (obj->supports_extrusion_cali()) {
else {
PACalibIndexInfo select_index_info;
int tray_id = ams_id * 4 + slot_id;
if (is_virtual_tray()) {
@@ -758,21 +747,6 @@ void AMSMaterialsSetting::on_select_ok(wxCommandEvent &event)
return;
}
if (nozzle_temp_min_int < FILAMENT_MIN_TEMP || nozzle_temp_min_int > FILAMENT_MAX_TEMP ||
nozzle_temp_max_int < FILAMENT_MIN_TEMP || nozzle_temp_max_int > FILAMENT_MAX_TEMP) {
MessageDialog msg_dlg(nullptr,
wxString::Format(_L("The input value should be greater than %1% and less than %2%"), FILAMENT_MIN_TEMP, FILAMENT_MAX_TEMP),
wxEmptyString, wxICON_WARNING | wxOK);
msg_dlg.ShowModal();
return;
}
if (nozzle_temp_min_int > nozzle_temp_max_int) {
MessageDialog msg_dlg(nullptr, _L("The minimum temperature cannot be greater than the maximum temperature."),
wxEmptyString, wxICON_WARNING | wxOK);
msg_dlg.ShowModal();
return;
}
// Orca: log the tray payload this dialog hands the printer, so the filament_id resolved from the
// dropdown selection can be checked against the tray_info_idx the AMS actually receives. A
@@ -796,7 +770,7 @@ void AMSMaterialsSetting::on_select_ok(wxCommandEvent &event)
wxString k_text = m_input_k_val->GetTextCtrl()->GetValue();
wxString n_text = m_input_n_val->GetTextCtrl()->GetValue();
if (obj->supports_extrusion_cali() && obj->cali_version <= -1 && (obj->get_printer_series() != PrinterSeries::SERIES_X1) && !ExtrusionCalibration::check_k_validation(k_text)) {
if (obj->cali_version <= -1 && (obj->get_printer_series() != PrinterSeries::SERIES_X1) && !ExtrusionCalibration::check_k_validation(k_text)) {
wxString k_tips = wxString::Format(_L("Please input a valid value (K in %.1f~%.1f)"), MIN_PA_K_VALUE, MAX_PA_K_VALUE);
wxString kn_tips = wxString::Format(_L("Please input a valid value (K in %.1f~%.1f, N in %.1f~%.1f)"), MIN_PA_K_VALUE, MAX_PA_K_VALUE, 0.6, 2.0);
MessageDialog msg_dlg(nullptr, k_tips, wxEmptyString, wxICON_WARNING | wxOK);
@@ -826,7 +800,7 @@ void AMSMaterialsSetting::on_select_ok(wxCommandEvent &event)
vt_tray = VIRTUAL_TRAY_DEPUTY_ID;
}
if (obj->supports_extrusion_cali() && obj->cali_version >= 0) {
if (obj->cali_version >= 0) {
PACalibIndexInfo select_index_info;
select_index_info.tray_id = vt_tray;
select_index_info.ams_id = ams_id;
@@ -845,7 +819,7 @@ void AMSMaterialsSetting::on_select_ok(wxCommandEvent &event)
CalibUtils::select_PA_calib_result(select_index_info);
}
else if (obj->supports_extrusion_cali()) {
else {
obj->command_extrusion_cali_set(vt_tray, "", "", k, n);
}
}
@@ -867,7 +841,7 @@ void AMSMaterialsSetting::on_select_ok(wxCommandEvent &event)
;
}
if (obj->supports_extrusion_cali() && obj->cali_version >= 0) {
if (obj->cali_version >= 0) {
PACalibIndexInfo select_index_info;
select_index_info.tray_id = cali_tray_id;
select_index_info.ams_id = ams_id;
@@ -886,7 +860,7 @@ void AMSMaterialsSetting::on_select_ok(wxCommandEvent &event)
CalibUtils::select_PA_calib_result(select_index_info);
}
else if (obj->supports_extrusion_cali()) {
else {
obj->command_extrusion_cali_set(cali_tray_id, "", "", k, n);
}
}
@@ -982,12 +956,7 @@ bool AMSMaterialsSetting::is_virtual_tray()
void AMSMaterialsSetting::update_widgets()
{
if (obj && !obj->supports_extrusion_cali()) {
// No printer-side K/N records: do not offer them.
m_panel_normal->Show();
m_panel_kn->Hide();
}
else if (obj && obj->get_printer_series() == PrinterSeries::SERIES_X1 && obj->cali_version <= -1) {
if (obj && obj->get_printer_series() == PrinterSeries::SERIES_X1 && obj->cali_version <= -1) {
// Low version firmware does not display k value
m_panel_kn->Hide();
}
@@ -1175,9 +1144,6 @@ void AMSMaterialsSetting::Popup(wxString filament, wxString sn, wxString temp_mi
m_input_k_val->GetTextCtrl()->SetValue(k);
m_input_n_val->GetTextCtrl()->SetValue(n);
// Tray values are re-supplied by every caller; clear so a previous popup cannot leak in.
m_input_nozzle_min->GetTextCtrl()->SetValue(wxEmptyString);
m_input_nozzle_max->GetTextCtrl()->SetValue(wxEmptyString);
wxString bambu_filament_name;
std::unordered_map<wxString, wxString> query_filament_vendors;// some information for sort
@@ -1203,14 +1169,11 @@ void AMSMaterialsSetting::Popup(wxString filament, wxString sn, wxString temp_mi
}
stream << std::fixed << std::setprecision(1) << machine_diameter;
std::string nozzle_diameter_str = stream.str();
// OrcaSonar's model id is optional, so the device model alone can resolve to nothing;
// the helper falls back to the selected profile so the dropdown is never empty.
const std::string filament_printer_model = resolve_filament_printer_model(obj->printer_type, preset_bundle);
if (preset_bundle && !filament_printer_model.empty()) {
if (preset_bundle) {
BOOST_LOG_TRIVIAL(trace) << "system_preset_bundle filament number=" << preset_bundle->filaments.size();
for (Preset *filament_it : preset_bundle->get_filament_presets_for_machine(
filament_printer_model, nozzle_diameter_str, obj->is_support_user_preset)) {
DevPrinterConfigUtil::get_printer_display_name(obj->printer_type), nozzle_diameter_str, obj->is_support_user_preset)) {
if (!filament_id_set.insert(filament_it->filament_id).second)
continue;
const std::string alias = preset_bundle->filaments.get_preset_alias(*filament_it, true);
@@ -1276,12 +1239,6 @@ void AMSMaterialsSetting::Popup(wxString filament, wxString sn, wxString temp_mi
else {
m_comboBox_filament->Show();
m_readonly_filament->Hide();
// A printer-set tray carries its own temps; the preset selection only fills
// them when the tray has none.
if (!temp_min.IsEmpty() && temp_min != "0")
m_input_nozzle_min->GetTextCtrl()->SetValue(temp_min);
if (!temp_max.IsEmpty() && temp_max != "0")
m_input_nozzle_max->GetTextCtrl()->SetValue(temp_max);
}
if (obj->cali_version >= 0) {
@@ -1405,19 +1362,17 @@ void AMSMaterialsSetting::Popup(wxString filament, wxString sn, wxString temp_mi
std::sort(m_sorted_filament_items.begin(), m_sorted_filament_items.end(), _filament_sorter);
}
if (m_sorted_filament_items.empty())
BOOST_LOG_TRIVIAL(warning) << "ams_materials_setting: no filament presets for printer model \""
<< filament_printer_model << "\" at nozzle " << nozzle_diameter_str;
rebuild_filament_dropdown_items(ams_filament_id);
int selection_idx = m_comboBox_filament->GetSelection();
m_comboBox_from_printer = true;
post_select_event(selection_idx);
if (selection_idx < 0) {
m_comboBox_filament->SetValue(wxEmptyString);
}
// Set the flag whether to open the filament setting dialog from the device page
m_comboBox_filament->SetClientData(new int(1));
update();
Layout();
Fit();
@@ -1461,18 +1416,18 @@ int AMSMaterialsSetting::get_filament_variant_index(const Preset &filament, cons
void AMSMaterialsSetting::on_select_filament(wxCommandEvent &evt)
{
// True for the popup's own initial selection: the dialog pre-filled the tray's
// temps, so the matching preset must not overwrite them.
const bool initial_printer_selection = m_comboBox_from_printer;
// Get the flag whether to open the filament setting dialog from the device page
int* from_printer = static_cast<int*>(m_comboBox_filament->GetClientData());
m_filament_type = "";
const FilamentInfos* selected_info = selected_filament_info();
PresetBundle* preset_bundle = wxGetApp().preset_bundle;
std::string filament_printer_model;
if (preset_bundle) {
std::ostringstream stream;
if (obj) {
// Use the selected profile's nozzle diameter until the connected device reports one.
// Defensive: this dialog is opened only from StatusPanel (BBL-only) today, so the fallback fires
// only during the brief BBL startup window before firmware reports nozzle info. Without this,
// the "0.0" lookup string returns an empty set and filament lookup yields no results.
float machine_diameter = obj->GetExtderSystem()->GetNozzleDiameter(0);
if (machine_diameter == 0.0f) {
const ConfigOption *opt = preset_bundle->printers.get_selected_preset().config.option("nozzle_diameter");
@@ -1482,35 +1437,32 @@ void AMSMaterialsSetting::on_select_filament(wxCommandEvent &evt)
}
}
stream << std::fixed << std::setprecision(1) << machine_diameter;
filament_printer_model = resolve_filament_printer_model(obj->printer_type, preset_bundle);
}
std::string nozzle_diameter_str = stream.str();
// Resolve the selection against the same list Popup() built the dropdown from, so the two
// halves of the dialog cannot disagree about which filaments this machine can use.
if (selected_info && !filament_printer_model.empty()) {
if (selected_info) {
const std::string& filament_id = selected_info->filament_id;
for (Preset *it : preset_bundle->get_filament_presets_for_machine(
filament_printer_model, nozzle_diameter_str, obj->is_support_user_preset)) {
DevPrinterConfigUtil::get_printer_display_name(obj->printer_type), nozzle_diameter_str, obj->is_support_user_preset)) {
if (it->filament_id != filament_id)
continue;
if (!initial_printer_selection) {
// ) if nozzle_temperature_range is found
const int variant_index = get_filament_variant_index(*it, nozzle_diameter_str);
ConfigOption* opt_min = it->config.option("nozzle_temperature_range_low");
if (opt_min) {
ConfigOptionInts* opt_min_ints = dynamic_cast<ConfigOptionInts*>(opt_min);
if (opt_min_ints) {
wxString text_nozzle_temp_min = wxString::Format("%d", opt_min_ints->get_at(variant_index));
m_input_nozzle_min->GetTextCtrl()->SetValue(text_nozzle_temp_min);
}
// ) if nozzle_temperature_range is found
const int variant_index = get_filament_variant_index(*it, nozzle_diameter_str);
ConfigOption* opt_min = it->config.option("nozzle_temperature_range_low");
if (opt_min) {
ConfigOptionInts* opt_min_ints = dynamic_cast<ConfigOptionInts*>(opt_min);
if (opt_min_ints) {
wxString text_nozzle_temp_min = wxString::Format("%d", opt_min_ints->get_at(variant_index));
m_input_nozzle_min->GetTextCtrl()->SetValue(text_nozzle_temp_min);
}
ConfigOption* opt_max = it->config.option("nozzle_temperature_range_high");
if (opt_max) {
ConfigOptionInts* opt_max_ints = dynamic_cast<ConfigOptionInts*>(opt_max);
if (opt_max_ints) {
wxString text_nozzle_temp_max = wxString::Format("%d", opt_max_ints->get_at(variant_index));
m_input_nozzle_max->GetTextCtrl()->SetValue(text_nozzle_temp_max);
}
}
ConfigOption* opt_max = it->config.option("nozzle_temperature_range_high");
if (opt_max) {
ConfigOptionInts* opt_max_ints = dynamic_cast<ConfigOptionInts*>(opt_max);
if (opt_max_ints) {
wxString text_nozzle_temp_max = wxString::Format("%d", opt_max_ints->get_at(variant_index));
m_input_nozzle_max->GetTextCtrl()->SetValue(text_nozzle_temp_max);
}
}
ConfigOption* opt_type = it->config.option("filament_type");
@@ -1549,16 +1501,13 @@ void AMSMaterialsSetting::on_select_filament(wxCommandEvent &evt)
m_button_confirm->Disable(); // ORCA No need to change style
m_comboBox_cali_result->Clear();
m_comboBox_cali_result->SetValue(wxEmptyString);
if (!initial_printer_selection) {
m_input_k_val->GetTextCtrl()->SetValue(wxEmptyString);
m_input_n_val->GetTextCtrl()->SetValue(wxEmptyString);
}
m_comboBox_from_printer = false;
m_input_k_val->GetTextCtrl()->SetValue(wxEmptyString);
m_input_n_val->GetTextCtrl()->SetValue(wxEmptyString);
m_comboBox_filament->SetClientData(new int(0));
return;
}
else {
if (!m_view_only)
m_button_confirm->Enable(true); // ORCA No need to change style
m_button_confirm->Enable(true); // ORCA No need to change style
}
//filament id
@@ -1570,9 +1519,6 @@ void AMSMaterialsSetting::on_select_filament(wxCommandEvent &evt)
ams_setting_id = selected_info->setting_id;
}
if (!ams_filament_id.empty())
m_clr_picker->is_empty(false);
wxArrayString items;
m_pa_profile_items.clear();
m_comboBox_cali_result->SetValue(wxEmptyString);
@@ -1628,7 +1574,7 @@ void AMSMaterialsSetting::on_select_filament(wxCommandEvent &evt)
m_comboBox_cali_result->Set(items);
if (ams_id == VIRTUAL_TRAY_MAIN_ID || ams_id == VIRTUAL_TRAY_DEPUTY_ID) {
if (initial_printer_selection) {
if (from_printer && (*from_printer == 1)) {
for (auto slot : obj->vt_slot) {
if (slot.id == std::to_string(ams_id))
cali_select_idx = CalibUtils::get_selected_calib_idx(m_pa_profile_items, slot.cali_idx);
@@ -1645,11 +1591,10 @@ void AMSMaterialsSetting::on_select_filament(wxCommandEvent &evt)
}
}
else {
if (initial_printer_selection) {
if (from_printer && (*from_printer == 1)) {
DevAmsTray* selected_tray = this->obj->GetFilaSystem()->GetAmsTray(std::to_string(ams_id), std::to_string(slot_id));
if (!selected_tray)
{
m_comboBox_from_printer = false;
return;
}
@@ -1680,7 +1625,7 @@ void AMSMaterialsSetting::on_select_filament(wxCommandEvent &evt)
else {
if (!ams_filament_id.empty()) {
//m_input_k_val->GetTextCtrl()->SetValue("0.00");
m_input_k_val->Enable(!m_view_only);
m_input_k_val->Enable(true);
}
else {
//m_input_k_val->GetTextCtrl()->SetValue("0.00");
@@ -1688,7 +1633,7 @@ void AMSMaterialsSetting::on_select_filament(wxCommandEvent &evt)
}
}
m_comboBox_from_printer = false;
m_comboBox_filament->SetClientData(new int(0));
}
void AMSMaterialsSetting::on_dpi_changed(const wxRect &suggested_rect)
+1 -4
View File
@@ -138,7 +138,7 @@ public:
std::string ams_filament_id;
std::string ams_setting_id;
bool m_is_third = false;
bool m_is_third;
// Orca: view-only mode (laser/cut). When set, the dialog is inspectable but every
// editing control is disabled and no command is sent.
bool m_view_only = false;
@@ -206,9 +206,6 @@ protected:
wxStaticText* m_n_param;
TextInput* m_input_n_val;
int m_filament_selection;
// True while the popup's initial, printer-driven selection is being handled; it
// keeps that selection from overwriting the tray's own temps with preset temps.
bool m_comboBox_from_printer = false;
int m_pa_cali_select_id = 0;
+4 -7
View File
@@ -530,8 +530,7 @@ void AMSSetting::on_insert_material_read(wxCommandEvent &event)
bool tray_read_opt = m_checkbox_Insert_material_auto_read->GetValue();
bool remain_opt = m_checkbox_remain->GetValue();
if (m_obj->command_ams_user_settings(start_read_opt, tray_read_opt, remain_opt) != 0)
UpdateByObj(m_obj);
m_obj->command_ams_user_settings(start_read_opt, tray_read_opt, remain_opt);
m_sizer_Insert_material_tip_inline->Layout();
Layout();
@@ -557,8 +556,7 @@ void AMSSetting::on_starting_read(wxCommandEvent &event)
bool tray_read_opt = m_checkbox_Insert_material_auto_read->GetValue();
bool remain_opt = m_checkbox_remain->GetValue();
if (m_obj->command_ams_user_settings(start_read_opt, tray_read_opt, remain_opt) != 0)
UpdateByObj(m_obj);
m_obj->command_ams_user_settings(start_read_opt, tray_read_opt, remain_opt);
m_sizer_starting_tip_inline->Layout();
Layout();
@@ -572,8 +570,7 @@ void AMSSetting::on_remain(wxCommandEvent& event)
bool start_read_opt = m_checkbox_starting_auto_read->GetValue();
bool tray_read_opt = m_checkbox_Insert_material_auto_read->GetValue();
bool remain_opt = m_checkbox_remain->GetValue();
if (m_obj->command_ams_user_settings(start_read_opt, tray_read_opt, remain_opt) != 0)
UpdateByObj(m_obj);
m_obj->command_ams_user_settings(start_read_opt, tray_read_opt, remain_opt);
event.Skip();
}
@@ -809,4 +806,4 @@ void AMSSettingArrangeAMSOrder::OnBtnRearrangeClicked(wxCommandEvent& event)
}
#endif
}} // namespace Slic3r::GUI
}} // namespace Slic3r::GUI
+1 -8
View File
@@ -10,7 +10,6 @@
#include "Widgets/DialogButtons.hpp"
#include "libslic3r/Config.hpp"
#include <boost/log/trivial.hpp>
#include <slic3r/GUI/DeviceManager.hpp>
#include <string>
#include <vector>
#include "libslic3r/calib.hpp"
@@ -746,15 +745,9 @@ wxArrayString NewCalibrationHistoryDialog::get_all_filaments(const MachineObject
std::string nozzle_diameter_str = stream.str();
if (preset_bundle) {
// OrcaSonar's model id is optional; the helper falls back to the selected profile
// so the list is never empty.
const std::string filament_printer_model = resolve_filament_printer_model(obj->printer_type, preset_bundle);
if (filament_printer_model.empty())
return filament_items;
BOOST_LOG_TRIVIAL(trace) << "system_preset_bundle filament number=" << preset_bundle->filaments.size();
for (Preset *filament_it : preset_bundle->get_filament_presets_for_machine(
filament_printer_model, nozzle_diameter_str, obj->is_support_user_preset)) {
DevPrinterConfigUtil::get_printer_display_name(obj->printer_type), nozzle_diameter_str, obj->is_support_user_preset)) {
if (!filament_id_set.insert(filament_it->filament_id).second)
continue;
const std::string alias = preset_bundle->filaments.get_preset_alias(*filament_it, true);
+1 -4
View File
@@ -1,5 +1,4 @@
#ifndef slic3r_GUI_SingleChoice_hpp_
#define slic3r_GUI_SingleChoice_hpp_
#pragma once
#include "GUI_Utils.hpp"
#include "Plater.hpp"
@@ -33,5 +32,3 @@ private:
};
}} // namespace Slic3r::GUI
#endif
-19
View File
@@ -1,19 +0,0 @@
#include <exception>
#include <stdexcept>
#include "libslic3r/Exception.hpp"
namespace Slic3r {
class ConfigError : public Slic3r::RuntimeError {
using Slic3r::RuntimeError::RuntimeError;
};
namespace GUI {
class ConfigGUITypeError : public ConfigError {
using ConfigError::ConfigError;
};
} // namespace GUI
} // namespace Slic3r
+4
View File
@@ -1105,6 +1105,10 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
toggle_field("support_filament", have_support_material || have_skirt);
toggle_line("raft_contact_distance", have_raft && !have_support_soluble);
// Orca:Hide raft expansion when raft is not enabled
toggle_line("raft_expansion", have_raft);
// Orca: Slim, Strong and Hybrid tree supports size their raft by first layer expansion only.
toggle_field("raft_expansion", !(is_tree(support_type) && (support_style == smsTreeSlim || support_style == smsTreeStrong || support_style == smsTreeHybrid)));
// Orca: First-layer density is available for supports broadly.
toggle_field("raft_first_layer_density", have_support_material);
+1 -16
View File
@@ -1,4 +1,3 @@
#include <algorithm>
#include <array>
#include <cstddef>
#include <cassert>
@@ -9,7 +8,6 @@
#include <cstdlib>
#include <chrono>
#include <nlohmann/json.hpp>
#include <wx/app.h>
#include <wx/colour.h>
#include <string>
#include <wx/string.h>
@@ -21,7 +19,6 @@
#include <utility>
#include "DevFilaSystem.h"
#include "slic3r/Utils/NetworkAgent.hpp"
#include "slic3r/Utils/NetworkAgentFactory.hpp" // ORCA_PRINTER_AGENT_ID
#include "DevNozzleSystem.h" // DevNozzle / DevNozzleSystem for GetNozzleFlowStringByAmsId
// TODO: remove this include
@@ -64,15 +61,6 @@ void DevAmsTray::UpdateColorFromStr(const std::string& color)
}
}
void DevAmsTray::UpdateEmptyState(bool material_fields_present, bool classify_empty)
{
const auto is_zero_or_empty = [](const std::string& value) {
return value.empty() || std::all_of(value.begin(), value.end(), [](char c) { return c == '0'; });
};
is_empty = material_fields_present && classify_empty && setting_id.empty() && m_fila_type.empty() &&
is_zero_or_empty(color) && is_zero_or_empty(tag_uid);
}
void DevAmsTray::reset()
{
tag_uid = "";
@@ -94,7 +82,6 @@ void DevAmsTray::reset()
k = 0.0f;
n = 0.0f;
is_bbl = false;
is_empty = false;
hold_count = 0;
remain = 0;
}
@@ -694,7 +681,7 @@ void DevFilaSystemParser::ParseV1_0(const json& jj, MachineObject* obj, DevFilaS
//std::string type = (*tray_it)["tray_type"].get<std::string>();
std::string type = MachineObject::setting_id_to_type(curr_tray->setting_id, (*tray_it)["tray_type"].get<std::string>());
// curr_tray->setting_id is our OF id; GFS00/GFS01 are the printer's own support-filament ids.
auto* agent = wxTheApp != nullptr ? GUI::wxGetApp().getAgent() : nullptr;
auto* agent = GUI::wxGetApp().getAgent();
const std::string printer_filament_id = agent ? agent->from_orca_filament_id(curr_tray->setting_id) : curr_tray->setting_id;
if (printer_filament_id == "GFS00")
{
@@ -794,8 +781,6 @@ void DevFilaSystemParser::ParseV1_0(const json& jj, MachineObject* obj, DevFilaS
{
curr_tray->remain = -1;
}
curr_tray->UpdateEmptyState(tray_it->contains("tray_info_idx") && tray_it->contains("tray_type"),
obj->printer_agent_id == ORCA_PRINTER_AGENT_ID);
// The tray objects are reused across status updates. Reset this
// state when a previously empty slot receives a filament again.
curr_tray->is_slot_placeholder = tray_it->contains("tray_slot_placeholder");
+1 -4
View File
@@ -82,7 +82,6 @@ public:
wxColour wx_color;
bool is_bbl;
bool is_exists = false;
bool is_empty = false; // Explicitly reported as having no filament.
bool is_slot_placeholder = false; // Orca: True for empty tray slots from pull-mode agents
int hold_count = 0;
int remain = 0; // filament remain: 0 ~ 100
@@ -91,15 +90,13 @@ public:
// operators
bool operator==(DevAmsTray const& o) const
{
return id == o.id && m_fila_type == o.m_fila_type && filament_setting_id == o.filament_setting_id &&
color == o.color && is_empty == o.is_empty;
return id == o.id && m_fila_type == o.m_fila_type && filament_setting_id == o.filament_setting_id && color == o.color;
}
bool operator!=(DevAmsTray const& o) const { return !operator==(o); }
// setters
void reset();
void UpdateColorFromStr(const std::string& color);
void UpdateEmptyState(bool material_fields_present, bool classify_empty);
void set_hold_count() { hold_count = HOLD_COUNT_MAX; }
// getter
+11 -110
View File
@@ -64,7 +64,6 @@
#include <unordered_set>
#include <utility>
#include <vector>
#include <wx/app.h>
#include <wx/colour.h>
#include <tuple>
#include <wx/dir.h>
@@ -432,32 +431,6 @@ NozzleVolumeType convert_to_nozzle_type(const std::string &str)
return res;
}
std::string resolve_filament_printer_model(const std::string& printer_type, PresetBundle* preset_bundle)
{
if (!preset_bundle)
return {};
// Devices with a shipped printer config (Bambu) resolve directly.
if (const std::string display_name = DevPrinterConfigUtil::get_printer_display_name(printer_type); !display_name.empty())
return display_name;
// A vendor model id the device reported (SSDP modelNumber, manual binding).
if (!printer_type.empty()) {
if (const std::string model_name = preset_bundle->get_printer_model_display_name(printer_type); !model_name.empty())
return model_name;
}
// OrcaSonar's model id is optional; a generic device resolves against the selected profile.
if (const ConfigOptionString* model = preset_bundle->printers.get_selected_preset().config.opt<ConfigOptionString>("printer_model");
model && !model->value.empty()) {
BOOST_LOG_TRIVIAL(info) << "resolve_filament_printer_model: device type \"" << printer_type
<< "\" has no installed model; using the selected profile \"" << model->value << "\"";
return model->value;
}
return {};
}
wxString MachineObject::get_printer_type_display_str() const
{
std::string display_name = DevPrinterConfigUtil::get_printer_display_name(printer_type);
@@ -1760,10 +1733,6 @@ int MachineObject::check_resume_condition()
}
int MachineObject::command_ams_change_filament(bool load, std::string ams_id, std::string slot_id, int old_temp, int new_temp, std::optional<int> extruder_id)
{
if (!printer_supports_command("print.ams_change_filament")) {
BOOST_LOG_TRIVIAL(warning) << "command_ams_change_filament: printer agent does not support the command";
return command_with_dialog(ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED);
}
json j;
try {
auto tray_id = 0;
@@ -1807,10 +1776,6 @@ int MachineObject::command_ams_change_filament(bool load, std::string ams_id, st
int MachineObject::command_ams_user_settings(bool start_read_opt, bool tray_read_opt, bool remain_flag)
{
if (!printer_supports_command("print.ams_user_setting")) {
BOOST_LOG_TRIVIAL(warning) << "command_ams_user_settings: printer agent does not support the command";
return command_with_dialog(ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED);
}
json j;
j["print"]["command"] = "ams_user_setting";
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
@@ -1819,59 +1784,22 @@ int MachineObject::command_ams_user_settings(bool start_read_opt, bool tray_read
j["print"]["tray_read_option"] = tray_read_opt;
j["print"]["calibrate_remain_flag"] = remain_flag;
const int rc = this->publish_json(j);
if (rc == 0) {
m_fila_system->GetAmsSystemSetting().SetDetectOnInsertEnabled(tray_read_opt);
m_fila_system->GetAmsSystemSetting().SetDetectOnPowerupEnabled(start_read_opt);
m_fila_system->GetAmsSystemSetting().SetDetectRemainEnabled(remain_flag);
ams_user_setting_start = time(nullptr);
}
return rc;
m_fila_system->GetAmsSystemSetting().SetDetectOnInsertEnabled(tray_read_opt);
m_fila_system->GetAmsSystemSetting().SetDetectOnPowerupEnabled(start_read_opt);
m_fila_system->GetAmsSystemSetting().SetDetectRemainEnabled(remain_flag);
ams_user_setting_start = time(nullptr);
return this->publish_json(j);
}
int MachineObject::command_ams_calibrate(int ams_id)
{
if (!m_agent) return -1;
if (!m_agent->owns_agent(printer_agent_id))
return command_with_dialog(ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED);
return command_with_dialog(m_agent->command_ams_calibrate(get_dev_id(), ams_id, MachineObject::m_sequence_id++, is_lan_mode_printer()));
}
bool MachineObject::printer_supports_command(const char* command) const
{
if (!command || !m_agent)
return false;
return m_agent->supports_command(printer_agent_id, get_dev_id(), command);
}
bool MachineObject::printer_uses_filament_mapping() const
{
if (!m_agent)
return false;
// An unrecorded owner goes through owns_agent() like any other, because
// whichever agent is active is also the one formatting the payload. Only a
// recorded owner that differs from the active agent is a mismatch.
return m_agent->uses_filament_mapping(printer_agent_id);
}
bool MachineObject::printer_supports_feature(const char* feature) const
{
return feature && m_agent && m_agent->supports_feature(printer_agent_id, get_dev_id(), feature);
}
bool MachineObject::supports_extrusion_cali() const
{
// Devices with no agent id predate the agent split and keep the Bambu path.
return printer_agent_id.empty() || printer_agent_id == BBL_PRINTER_AGENT_ID;
}
int MachineObject::command_ams_filament_settings(int ams_id, int slot_id, std::string filament_id, std::string setting_id, std::string tray_color, std::string tray_type, int nozzle_temp_min, int nozzle_temp_max)
{
if (!printer_supports_command("print.ams_filament_setting")) {
BOOST_LOG_TRIVIAL(warning) << "command_ams_filament_settings: printer agent does not support slot metadata updates";
return command_with_dialog(ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED);
}
int tag_tray_id = 0;
int tag_ams_id = ams_id;
int tag_slot_id = slot_id;
@@ -1906,10 +1834,6 @@ int MachineObject::command_ams_filament_settings(int ams_id, int slot_id, std::s
int MachineObject::command_ams_refresh_rfid(int ams_id, int slot_id)
{
if (!m_agent) return -1;
if (!printer_supports_command("print.ams_get_rfid")) {
BOOST_LOG_TRIVIAL(warning) << "command_ams_refresh_rfid: printer agent does not support the command";
return command_with_dialog(ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED);
}
return command_with_dialog(m_agent->command_ams_refresh_rfid(get_dev_id(), ams_id, slot_id, MachineObject::m_sequence_id++, is_lan_mode_printer()));
}
@@ -1923,19 +1847,11 @@ int MachineObject::command_start_camera()
int MachineObject::command_ams_select_tray(std::string tray_id)
{
if (!m_agent) return -1;
if (!printer_supports_command("print.ams_change_filament")) {
BOOST_LOG_TRIVIAL(warning) << "command_ams_select_tray: printer agent does not support the command";
return command_with_dialog(ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED);
}
return command_with_dialog(m_agent->command_ams_select_tray(get_dev_id(), tray_id, MachineObject::m_sequence_id++, is_lan_mode_printer()));
}
int MachineObject::command_ams_control(std::string action)
{
if (!printer_supports_command("print.ams_control")) {
BOOST_LOG_TRIVIAL(warning) << "command_ams_control: printer agent does not support the command";
return command_with_dialog(ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED);
}
if (action == "resume" && check_resume_condition()) return 0;
//valid actions
@@ -1951,10 +1867,6 @@ int MachineObject::command_ams_control(std::string action)
int MachineObject::command_ams_drying_stop()
{
if (!printer_supports_command("print.auto_stop_ams_dry")) {
BOOST_LOG_TRIVIAL(warning) << "command_ams_drying_stop: printer agent does not support the command";
return command_with_dialog(ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED);
}
json j;
j["print"]["command"] = "auto_stop_ams_dry";
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
@@ -2905,9 +2817,7 @@ bool MachineObject::is_camera_busy_off()
int MachineObject::publish_json(const json& json_item, int qos, int flag)
{
int rtn = 0;
if (m_agent && !m_agent->owns_agent(printer_agent_id)) {
rtn = ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED;
} else if (is_lan_mode_printer()) {
if (is_lan_mode_printer()) {
rtn = local_publish_json(json_item.dump(), qos, flag);
} else {
rtn = cloud_publish_json(json_item.dump(), qos, flag);
@@ -2951,7 +2861,6 @@ int MachineObject::local_publish_json(std::string json_str, int qos, int flag)
std::string MachineObject::setting_id_to_type(std::string setting_id, std::string tray_type)
{
std::string type;
if (wxTheApp == nullptr) return tray_type;
PresetBundle* preset_bundle = GUI::wxGetApp().preset_bundle;
if (preset_bundle) {
for (auto it = preset_bundle->filaments.begin(); it != preset_bundle->filaments.end(); it++) {
@@ -4288,9 +4197,6 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
vt_slot[0].setting_id = jj["tray_info_idx"].get<std::string>();
//vt_tray.type = jj["tray_type"].get<std::string>();
vt_slot[0].m_fila_type = setting_id_to_type(vt_slot[0].setting_id, jj["tray_type"].get<std::string>());
// The ack carries the whole slot; re-derive empty so the panel flips off
// "Empty" without waiting out the hold.
vt_slot[0].UpdateEmptyState(true, printer_agent_id == ORCA_PRINTER_AGENT_ID);
// delay update
vt_slot[0].set_hold_count();
} else {
@@ -4316,9 +4222,6 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
tray_it->second->setting_id = jj["tray_info_idx"].get<std::string>();
tray_it->second->m_fila_type = setting_id_to_type(tray_it->second->setting_id, jj["tray_type"].get<std::string>());
// The ack carries the whole slot; re-derive empty so the panel flips off
// "Empty" without waiting out the hold.
tray_it->second->UpdateEmptyState(true, printer_agent_id == ORCA_PRINTER_AGENT_ID);
// delay update
tray_it->second->set_hold_count();
} else {
@@ -5257,7 +5160,7 @@ DevAmsTray MachineObject::parse_vt_tray(json vtray)
//std::string type = vtray["tray_type"].get<std::string>();
std::string type = setting_id_to_type(vt_tray.setting_id, vtray["tray_type"].get<std::string>());
// vt_tray.setting_id is our OF id (translated on the way in); the two support ids below are the printer's own.
auto* agent = wxTheApp != nullptr ? GUI::wxGetApp().getAgent() : nullptr;
auto* agent = GUI::wxGetApp().getAgent();
const std::string printer_filament_id = agent ? agent->from_orca_filament_id(vt_tray.setting_id) : vt_tray.setting_id;
if (printer_filament_id == "GFS00") {
vt_tray.m_fila_type = "PLA-S";
@@ -5352,8 +5255,6 @@ DevAmsTray MachineObject::parse_vt_tray(json vtray)
else {
vt_tray.remain = -1;
}
vt_tray.UpdateEmptyState(vtray.contains("tray_info_idx") && vtray.contains("tray_type"),
printer_agent_id == ORCA_PRINTER_AGENT_ID);
}
return vt_tray;
@@ -5647,11 +5548,11 @@ void MachineObject::parse_new_info2(const json& info)
if (capabilities_it == info.end() || !capabilities_it->is_object())
return;
const auto flags_it = capabilities_it->find("flags");
const bool has_flags = flags_it != capabilities_it->end() && flags_it->is_object();
if (!has_flags)
if (flags_it == capabilities_it->end() || !flags_it->is_object())
return;
const json& flags = *flags_it;
BOOST_LOG_TRIVIAL(info) << "parse_new_info2: capability flags=" << flags.dump();
BOOST_LOG_TRIVIAL(info) << "parse_new_info2: OrcaSonar capability flags=" << flags.dump();
auto parse_bool = [&flags](const char* name, bool& target) {
const auto it = flags.find(name);
-14
View File
@@ -14,7 +14,6 @@
#include <string>
#include <memory>
#include <chrono>
#include <set>
#include <unordered_set>
#include <optional>
#include <boost/thread.hpp>
@@ -73,7 +72,6 @@ class DeviceErrorDialog; // Previous definitions
class NetworkAgent;
// Orca: ManualPaCaliMethod now provided by DeviceCore/DevCalib.h (enum class)
class PresetBundle;
#define UpgradeNoError 0
#define UpgradeDownloadFailed -1
@@ -109,11 +107,6 @@ struct DevPrintTaskRatingInfo;
// given nozzle diameter (mm), bucketed per nozzle size to mirror the printer firmware.
bool is_stringing_prone_filament(const std::string& filament_id, float nozzle_diameter);
// The printer model filament presets are matched against for a connected machine. OrcaSonar's
// model id is optional, so a device with no installed vendor model falls back to the selected
// printer profile rather than resolving to no filament list at all.
std::string resolve_filament_printer_model(const std::string& printer_type, PresetBundle* preset_bundle);
class MachineObject
{
private:
@@ -802,13 +795,6 @@ public:
int command_refresh_nozzle();
int command_set_chamber(int temp);
int check_resume_condition();
bool printer_supports_command(const char* command) const;
bool printer_supports_feature(const char* feature) const;
bool printer_uses_filament_mapping() const;
// The per-tray flow-dynamics K/N records are a Bambu firmware feature. Other
// agents have no printer-side calibration to read or write, so the AMS UI must
// not offer K/N for them.
bool supports_extrusion_cali() const;
// ams controls
//int command_ams_switch(int tray_index, int old_temp = 210, int new_temp = 210);
int command_ams_change_filament(bool load, std::string ams_id, std::string slot_id, int old_temp = 210, int new_temp = 210, std::optional<int> extruder_id = std::nullopt);
@@ -970,11 +970,7 @@ int ReselectMachineDialog::CaculateSwitcherDistribution(MachineObject* obj, cons
const auto& can = ams.cans[j];
auto id = getTrayID(obj, ams.ams_id, can.can_id);
auto material = can.material_name;
if (can.is_empty || can.material_state == AMSCanType::AMS_CAN_TYPE_EMPTY)
{
material = L("Empty");
}
else if (can.material_state == AMSCanType::AMS_CAN_TYPE_THIRDBRAND ||
if (can.material_state == AMSCanType::AMS_CAN_TYPE_THIRDBRAND ||
can.material_state == AMSCanType::AMS_CAN_TYPE_BRAND ||
can.material_state == AMSCanType::AMS_CAN_TYPE_VIRTUAL)
{
@@ -983,6 +979,10 @@ int ReselectMachineDialog::CaculateSwitcherDistribution(MachineObject* obj, cons
material = L("?");
}
}
if (can.material_state == AMSCanType::AMS_CAN_TYPE_EMPTY)
{
material = L("Empty");
}
auto itOK = std::find_if(posOK.begin(), posOK.end(), [&](const trayHelper& tray){
auto amsID = std::get<0>(tray);
-105
View File
@@ -1,105 +0,0 @@
#pragma once
#include <string>
#include <vector>
#include <nlohmann/json.hpp>
#include "libslic3r/ProjectTask.hpp"
#include "DeviceManager.hpp"
namespace Slic3r {
namespace GUI {
// True when the normalized ams_mapping2 carries any entry the agent's serializer
// would put on the wire: every integer pair except the {255,255} unmatched
// sentinel, external slots ({255,0}/{254,0}) included. This mirrors
// OrcaPrinterAgent::build_filament_mapping exactly, so the GUI gate and the
// agent gate agree; a mismatch lets an entry reach the agent and be refused late
// with a generic publish error instead of the designed message.
inline bool has_engaged_filament_mapping(const std::string& ams_mapping2)
{
const nlohmann::json mapping = nlohmann::json::parse(ams_mapping2, nullptr, false);
if (mapping.is_discarded() || !mapping.is_array())
return false;
for (const auto& entry : mapping) {
if (!entry.is_object())
continue;
const auto ams_id_it = entry.find("ams_id");
const auto slot_id_it = entry.find("slot_id");
if (ams_id_it == entry.end() || slot_id_it == entry.end())
continue;
if (!ams_id_it->is_number_integer() || !slot_id_it->is_number_integer())
continue;
if (ams_id_it->get<int>() == 255 && slot_id_it->get<int>() == 255)
continue; // unmatched/unused sentinel: the serializer drops it
return true;
}
return false;
}
// A device with no AMS units has a single source: the external spool. OrcaSlicer's
// auto-mapping force-selects it for every filament (DevMapping.cpp), but that is not
// a lane choice: keep it out of the send gate and off print.gcode_file.
inline void drop_forced_external_selection(bool device_has_ams, std::string& ams_mapping2)
{
if (!device_has_ams)
ams_mapping2.clear();
}
// A used filament with no target would be silently dropped from the wire
// mapping, so the print must be refused rather than run the wrong material.
// m_ams_mapping_result carries exactly the filaments the slice uses.
inline bool has_used_filament_without_target(const std::vector<FilamentInfo>& result)
{
for (const auto& f : result) {
if (f.get_ams_id() < 0 || f.get_slot_id() < 0)
return true;
}
return false;
}
// True when at least one used filament already has a target. The used-unmapped
// refusal applies to a partially mapped print; a print with no mapping at all
// is handled by the existing all-invalid send-path flow.
inline bool has_any_mapped_target(const std::vector<FilamentInfo>& result)
{
for (const auto& f : result) {
if (f.get_ams_id() >= 0 && f.get_slot_id() >= 0)
return true;
}
return false;
}
// Refusal reason for a printer agent that serializes lane selection into
// print.gcode_file's per-print filament_mapping field.
enum class MappingSendError {
none, // no refusal; ams_mapping2 is normalized for send
unsupported, // a mapping is engaged but the connector did not advertise it
incomplete, // a used filament has no target while others do
};
// Applies the send-time mapping policy and normalizes ams_mapping2 in place.
// Agents that do not speak filament_mapping keep their legacy payload untouched.
inline MappingSendError prepare_filament_mapping_for_send(MachineObject* obj,
std::string& ams_mapping2,
const std::vector<FilamentInfo>& mapping_result)
{
if (!obj || !obj->printer_uses_filament_mapping())
return MappingSendError::none;
// A device with no AMS units has one source, the external spool:
// auto-mapping force-selects it, which is not a lane choice. Drop it before
// the capability gate so it cannot refuse a print nobody mapped.
drop_forced_external_selection(obj->HasAms(), ams_mapping2);
if (!obj->printer_supports_feature("filament_mapping") && has_engaged_filament_mapping(ams_mapping2))
return MappingSendError::unsupported;
if (has_any_mapped_target(mapping_result) && has_used_filament_without_target(mapping_result))
return MappingSendError::incomplete;
return MappingSendError::none;
}
} // namespace GUI
} // namespace Slic3r
+22 -3
View File
@@ -1514,6 +1514,8 @@ wxDEFINE_EVENT(EVT_GLCANVAS_PRINTABLE, SimpleEvent);
const double GLCanvas3D::DefaultCameraZoomToBoxMarginFactor = 1.25;
const double GLCanvas3D::DefaultCameraZoomToBedMarginFactor = 2.00;
const double GLCanvas3D::DefaultCameraZoomToPlateMarginFactor = 1.25;
// Main-thread only: _render_frame never runs off it, so no atomic is needed.
int GLCanvas3D::s_frames_in_progress = 0;
void GLCanvas3D::load_arrange_settings()
{
@@ -2468,7 +2470,8 @@ void GLCanvas3D::_render_frame(bool scene_dirty, bool only_init)
}
m_in_render = true;
Slic3r::ScopeGuard in_render_guard([this]() { m_in_render = false; });
++s_frames_in_progress;
Slic3r::ScopeGuard in_render_guard([this]() { m_in_render = false; --s_frames_in_progress; });
(void)in_render_guard;
if (m_canvas == nullptr)
@@ -2713,15 +2716,31 @@ void GLCanvas3D::_render_frame(bool scene_dirty, bool only_init)
// On Wayland, eglSwapBuffers blocks when the canvas is hidden or
// occluded. Skip the swap to avoid stalling the render loop.
if (m_canvas->IsShownOnScreen()) {
// Code run from inside this frame - a modal dialog a gizmo opened from its ImGui panel, a
// synchronous paint of another canvas - may have made a different surface current. Bind this one
// again before presenting, so the swap is not issued against a window the context no longer
// belongs to: on Mesa/X11 that is a fatal BadMatch, not the no-op it is on other platforms. Draws
// already issued before this point cannot be redirected - only this swap is protected.
if (m_canvas->IsShownOnScreen() && _set_current()) {
m_canvas->SwapBuffers();
m_frame_profiler.mark("swap");
if (!overlay_tick)
m_render_stats.increment_fps_counter();
m_presented_signature = signature;
}
else
else {
if (m_canvas->IsShownOnScreen()) {
// Logged once: if the rebind fails it does so every frame, and a warning per frame would
// bury everything else.
static bool rebind_failure_logged = false;
if (!rebind_failure_logged) {
rebind_failure_logged = true;
BOOST_LOG_TRIVIAL(warning) << "GLCanvas3D: could not rebind the GL context to this canvas before "
"SwapBuffers(); the frame is not presented";
}
}
m_presented_signature.reset();
}
}
// Everything drawn into the 3D scene, from the clear to the post processing passes, ending in the
+7
View File
@@ -643,6 +643,8 @@ private:
#endif
unsigned int m_last_w, m_last_h;
bool m_in_render;
// How many canvases are inside _render_frame() right now - see is_frame_in_progress().
static int s_frames_in_progress;
wxTimer m_timer;
wxTimer m_timer_set_color;
// Armed by each frame that draws the FPS overlay; its tick requests an overlay-only frame.
@@ -974,6 +976,11 @@ public:
void on_change_color_mode(bool is_dark, bool reinit = true);
const bool get_dark_mode_status() { return m_is_dark; }
void set_as_dirty() { m_dirty = true; }
// True while any GLCanvas3D is inside _render_frame(). The other surfaces that share the app's one
// GL context (the UV editor canvas) check this before making themselves current: a frame interrupted
// by another surface finishes drawing into that surface and then presents a window the context no
// longer belongs to, which on Mesa/X11 is a fatal X error (BadMatch) rather than a no-op.
static bool is_frame_in_progress() { return s_frames_in_progress > 0; }
void requires_check_outside_state() { m_requires_check_outside_state = true; }
unsigned int get_volumes_count() const { return (unsigned int)m_volumes.volumes.size(); }
-4
View File
@@ -719,9 +719,6 @@ void GLTexture::render_sub_texture(unsigned int tex_id, float left, float right,
// does not treat anti-aliased icon edges as window transparency.
glsafe(::glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA));
glsafe(::glEnable(GL_TEXTURE_2D));
glsafe(::glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE));
glsafe(::glBindTexture(GL_TEXTURE_2D, (GLuint)tex_id));
GLModel::Geometry init_data;
@@ -753,7 +750,6 @@ void GLTexture::render_sub_texture(unsigned int tex_id, float left, float right,
glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
glsafe(::glDisable(GL_TEXTURE_2D));
glsafe(::glDisable(GL_BLEND));
}
+2 -22
View File
@@ -1160,8 +1160,7 @@ void GUI_App::post_init()
m_agent->set_on_http_error_fn([this](CloudEvent event, unsigned int status, std::string body) {
this->handle_http_error(status, body, event.provider);
});
if (should_start_ssdp_discovery())
m_agent->start_discovery(true, false);
m_agent->start_discovery(true, false);
}
//update the plugin tips
@@ -4173,18 +4172,6 @@ std::string GUI_App::resolve_printer_agent_id(const std::string& stored_id) cons
return (preset_bundle && preset_bundle->is_bbl_vendor()) ? BBL_PRINTER_AGENT_ID : ORCA_PRINTER_AGENT_ID;
}
bool GUI_App::should_start_ssdp_discovery()
{
// Non-Orca agents (BBL, plugins) keep their own discovery; OrcaSonar only scans in the
// background when one of its machines is already configured, otherwise the popup drives it.
const std::string agent_id = resolve_printer_agent_id(
preset_bundle ? preset_bundle->printers.get_edited_preset().config.opt_string("printer_agent")
: std::string());
if (agent_id != ORCA_PRINTER_AGENT_ID)
return true;
return m_device_manager && !m_device_manager->get_my_machine_list(ORCA_PRINTER_AGENT_ID).empty();
}
std::string GUI_App::canonical_printer_agent_id(const std::string& picked_id)
{
return picked_id == resolve_printer_agent_id("") ? std::string() : picked_id;
@@ -4256,8 +4243,7 @@ void GUI_App::switch_printer_agent()
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": printer agent switched to " << effective_agent_id;
// Start discovery so Python agents can populate the device list via SSDP callback
if (should_start_ssdp_discovery())
m_agent->start_discovery(true, false);
m_agent->start_discovery(true, false);
// Auto-switch MachineObject (new agent has empty device_info, so always re-select)
select_machine(effective_agent_id);
@@ -4876,12 +4862,6 @@ void GUI_App::recreate_GUI(const wxString &msg_name)
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "recreate_GUI exit";
}
void GUI_App::system_info()
{
//SysInfoDialog dlg;
//dlg.ShowModal();
}
void GUI_App::keyboard_shortcuts(ShortcutContext page, wxWindow* parent)
{
KBShortcutsDialog dlg(parent != nullptr ? parent : mainframe, page);
-4
View File
@@ -403,9 +403,6 @@ public:
// Reconcile the live printer agent with the stored preset selection.
void switch_printer_agent();
// Whether the background SSDP scan should run for the effective printer agent.
bool should_start_ssdp_discovery();
std::string resolve_printer_agent_id(const std::string& stored_id) const;
// ORCA TODO: in the future, bbl presets should specify "bbl" printer agent id
// then, all resolve and canonical would just be ORCA<->""
@@ -522,7 +519,6 @@ public:
void check_printer_presets();
void recreate_GUI(const wxString& message);
void system_info();
void keyboard_shortcuts(ShortcutContext page, wxWindow* parent = nullptr); // the main frame when null
void troubleshoot();
void load_project(wxWindow *parent, wxString& input_file) const;
+10
View File
@@ -1952,6 +1952,16 @@ void MenuFactory::create_plate_menu()
},
"", nullptr, []() { return !plater()->get_partplate_list().get_selected_plate()->get_objects().empty(); }, m_parent);
// fix objects on current plate
append_menu_item(menu, wxID_ANY, _L("Fix All"), _L("Fix all models on the current plate"),
[](wxCommandEvent&) { obj_list()->fix_all_through_cgal(false); }, "", nullptr,
[]() { return !plater()->get_partplate_list().get_selected_plate()->empty(); }, m_parent);
// fix objects on all plates
append_menu_item(menu, wxID_ANY, _L("Fix All Plates"), _L("Fix all models on all plates"),
[](wxCommandEvent&) { obj_list()->fix_all_through_cgal(true); }, "", nullptr,
[]() { return !plater()->model().objects.empty(); }, m_parent);
// orient objects on current plate
append_menu_item(menu, wxID_ANY, _L("Auto Rotate"), _L("Auto rotate current plate"),
[](wxCommandEvent&) {
+65 -55
View File
@@ -6297,30 +6297,44 @@ void ObjectList::rename_item()
}
void ObjectList::fix_through_cgal()
{
std::vector<int> obj_idxs, vol_idxs;
get_selection_indexes(obj_idxs, vol_idxs);
fix_through_cgal(obj_idxs, vol_idxs);
}
void ObjectList::fix_all_through_cgal(bool all_plates)
{
std::vector<int> obj_idxs;
if (all_plates) {
for (int obj_idx = 0; obj_idx < int(m_objects->size()); ++obj_idx)
obj_idxs.push_back(obj_idx);
} else {
for (const ModelObject *mo : wxGetApp().plater()->get_partplate_list().get_selected_plate()->get_objects_on_this_plate())
obj_idxs.push_back(int(std::find(m_objects->begin(), m_objects->end(), mo) - m_objects->begin()));
std::sort(obj_idxs.begin(), obj_idxs.end());
obj_idxs.erase(std::unique(obj_idxs.begin(), obj_idxs.end()), obj_idxs.end());
}
fix_through_cgal(obj_idxs, {});
}
void ObjectList::fix_through_cgal(const std::vector<int> &obj_idxs, const std::vector<int> &vol_idxs)
{
// Do not fix anything when a gizmo is open. There might be issues with updates
// and what is worse, the snapshot time would refer to the internal stack.
if (!wxGetApp().plater()->get_view3D_canvas3D()->get_gizmos_manager().check_gizmos_closed_except(GLGizmosManager::Undefined))
if (obj_idxs.empty() || !wxGetApp().plater()->get_view3D_canvas3D()->get_gizmos_manager().check_gizmos_closed_except(GLGizmosManager::Undefined))
return;
// model_name
std::vector<std::string> succes_models;
// model_name failing reason
std::vector<std::pair<std::string, std::string>> failed_models;
std::vector<int> obj_idxs, vol_idxs;
get_selection_indexes(obj_idxs, vol_idxs);
size_t unchanged_models = 0;
bool painting_removed = false;
std::vector<std::string> model_names;
// clear selections from the non-broken models if any exists
// and than fill names of models to repairing
if (vol_idxs.empty()) {
#if !FIX_THROUGH_CGAL_ALWAYS
for (int i = int(obj_idxs.size())-1; i >= 0; --i)
if (object(obj_idxs[i])->get_repaired_errors_count() == 0)
obj_idxs.erase(obj_idxs.begin()+i);
#endif // FIX_THROUGH_CGAL_ALWAYS
for (int obj_idx : obj_idxs)
if (object(obj_idx))
model_names.push_back(object(obj_idx)->name);
@@ -6328,31 +6342,26 @@ void ObjectList::fix_through_cgal()
else {
ModelObject* obj = object(obj_idxs.front());
if (obj) {
#if !FIX_THROUGH_CGAL_ALWAYS
for (int i = int(vol_idxs.size()) - 1; i >= 0; --i)
if (obj->get_repaired_errors_count(vol_idxs[i]) == 0)
vol_idxs.erase(vol_idxs.begin() + i);
#endif // FIX_THROUGH_CGAL_ALWAYS
for (int vol_idx : vol_idxs)
model_names.push_back(obj->volumes[vol_idx]->name);
}
}
auto plater = wxGetApp().plater();
const bool keep_painting = GUI::wxGetApp().app_config->get_bool("keep_painting");
auto fix_and_update_progress = [this, plater, model_names](const int obj_idx, const int vol_idx,
int model_idx,
ProgressDialog& progress_dlg,
std::vector<std::string>& succes_models,
std::vector<std::pair<std::string, std::string>>& failed_models)
auto fix_and_update_progress = [&](const int obj_idx, const int vol_idx, int model_idx, ProgressDialog& progress_dlg)
{
if (!object(obj_idx))
ModelObject *mo = object(obj_idx);
if (!mo)
return false;
const std::string& model_name = model_names[model_idx];
wxString msg = _L("Repairing model object");
if (model_names.size() == 1)
msg += ": " + from_u8(model_name) + "\n";
else if (model_names.size() > 10)
msg += ": " + from_u8(model_name) + wxString::Format(" (%d/%d)", model_idx + 1, int(model_names.size())) + "\n";
else {
msg += ":\n";
for (int i = 0; i < int(model_names.size()); ++i)
@@ -6360,33 +6369,29 @@ void ObjectList::fix_through_cgal()
msg += "\n";
}
const bool keep_painting = GUI::wxGetApp().app_config->get_bool("keep_painting");
if (!keep_painting) {
plater->clear_before_change_mesh(obj_idx);
}
const size_t volumes_before = object(obj_idx)->volumes.size();
auto count_painted = [mo]() { return std::count_if(mo->volumes.begin(), mo->volumes.end(), [](const ModelVolume *mv) { return mv->is_any_painted(); }); };
const auto painted_before = count_painted();
std::string res;
if (!fix_model_with_cgal_gui(*(object(obj_idx)), vol_idx, progress_dlg, msg, res, keep_painting))
bool modified = false;
if (!fix_model_with_cgal_gui(*mo, vol_idx, progress_dlg, msg, res, keep_painting, &modified))
return false;
//wxGetApp().plater()->changed_mesh(obj_idx);
object(obj_idx)->ensure_on_bed();
plater->changed_mesh(obj_idx);
const size_t volumes_after = object(obj_idx)->volumes.size();
if (volumes_after != volumes_before)
add_volumes_to_object_in_list(obj_idx);
if (modified) {
painting_removed = painting_removed || count_painted() < painted_before;
mo->ensure_on_bed();
plater->changed_mesh(obj_idx);
plater->get_partplate_list().notify_instance_update(obj_idx, 0);
plater->sidebar().obj_list()->update_plate_values_for_items();
update_item_error_icon(obj_idx, vol_idx);
update_info_items(obj_idx);
}
plater->get_partplate_list().notify_instance_update(obj_idx, 0);
plater->sidebar().obj_list()->update_plate_values_for_items();
if (res.empty())
if (!res.empty())
failed_models.push_back({ model_name, res });
else if (modified)
succes_models.push_back(model_name);
else
failed_models.push_back({ model_name, res });
update_item_error_icon(obj_idx, vol_idx);
update_info_items(obj_idx);
++unchanged_models;
return true;
};
@@ -6399,11 +6404,7 @@ void ObjectList::fix_through_cgal()
if (vol_idxs.empty()) {
int vol_idx{ -1 };
for (int obj_idx : obj_idxs) {
#if !FIX_THROUGH_CGAL_ALWAYS
if (object(obj_idx)->get_repaired_errors_count(vol_idx) == 0)
continue;
#endif // FIX_THROUGH_CGAL_ALWAYS
if (!fix_and_update_progress(obj_idx, vol_idx, model_idx, progress_dlg, succes_models, failed_models))
if (!fix_and_update_progress(obj_idx, vol_idx, model_idx, progress_dlg))
break;
model_idx++;
}
@@ -6411,7 +6412,7 @@ void ObjectList::fix_through_cgal()
else {
int obj_idx{ obj_idxs.front() };
for (int vol_idx : vol_idxs) {
if (!fix_and_update_progress(obj_idx, vol_idx, model_idx, progress_dlg, succes_models, failed_models))
if (!fix_and_update_progress(obj_idx, vol_idx, model_idx, progress_dlg))
break;
model_idx++;
}
@@ -6419,20 +6420,29 @@ void ObjectList::fix_through_cgal()
// Close the progress dialog
progress_dlg.Update(100, "");
if (painting_removed)
plater->notify_painting_removed();
// Show info notification
wxString msg;
std::vector<wxString> blocks;
wxString bullet_suf = "\n - ";
if (!succes_models.empty()) {
msg = _L_PLURAL("The following model object has been repaired", "The following model objects have been repaired", succes_models.size()) + ":";
wxString block = _L_PLURAL("The following model object has been repaired", "The following model objects have been repaired", succes_models.size()) + ":";
for (auto& model : succes_models)
msg += bullet_suf + from_u8(model);
msg += "\n\n";
block += bullet_suf + from_u8(model);
blocks.push_back(block);
}
if (!failed_models.empty()) {
msg += _L_PLURAL("Failed to repair the following model object", "Failed to repair the following model objects", failed_models.size()) + ":\n";
wxString block = _L_PLURAL("Failed to repair the following model object", "Failed to repair the following model objects", failed_models.size()) + ":";
for (auto& model : failed_models)
msg += bullet_suf + from_u8(model.first) + ": " + _(model.second);
block += bullet_suf + from_u8(model.first) + ": " + _(model.second);
blocks.push_back(block);
}
if (unchanged_models > 0)
blocks.push_back(format_wxstr(_L_PLURAL("%1$d model object needed no repair.", "%1$d model objects needed no repair.", unchanged_models), unchanged_models));
wxString msg;
for (const wxString &block : blocks)
msg += (msg.IsEmpty() ? "" : "\n\n") + block;
if (msg.IsEmpty())
msg = _L("Repairing was canceled");
plater->get_notification_manager()->push_notification(NotificationType::CgalFinished, NotificationManager::NotificationLevel::PrintInfoShortNotificationLevel, into_u8(msg));
+3 -3
View File
@@ -47,9 +47,6 @@ typedef double coordf_t;
typedef std::pair<coordf_t, coordf_t> t_layer_height_range;
typedef std::map<t_layer_height_range, ModelConfig> t_layer_config_ranges;
// Manifold mesh may contain self-intersections, so we want to always allow fixing the mesh.
#define FIX_THROUGH_CGAL_ALWAYS 1
namespace GUI {
enum class Shortcut : uint8_t;
@@ -463,6 +460,9 @@ public:
void split_instances();
void rename_item();
void fix_through_cgal();
// Fixes every object on the selected plate, or on all plates.
void fix_all_through_cgal(bool all_plates);
void fix_through_cgal(const std::vector<int> &obj_idxs, const std::vector<int> &vol_idxs);
void simplify();
void smooth_mesh();
void update_item_error_icon(const int obj_idx, int vol_idx) const ;
@@ -1036,6 +1036,11 @@ bool GLGizmoPainterBase::on_mouse(const wxMouseEvent &mouse_event)
Vec2d mouse_pos = mouse_coord.cast<double>();
if (mouse_event.Moving()) {
// No button is down, yet a stroke is still open: its release went elsewhere, as a paint drag does not
// capture the mouse. End the stroke as that release would have.
if (m_button_down != Button::None)
gizmo_event(m_button_down == Button::Left ? SLAGizmoEventType::LeftUp : SLAGizmoEventType::RightUp, mouse_pos,
mouse_event.ShiftDown(), mouse_event.AltDown(), false);
gizmo_event(SLAGizmoEventType::Moving, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), false);
return false;
}
@@ -1205,6 +1210,10 @@ void GLGizmoPainterBase::on_set_state()
m_old_mo_id = -1;
//m_iva.release_geometry();
m_triangle_selectors.clear();
// A stroke still open goes with the selectors. Its state must not carry into the next session, where the
// first move would end it as if released and the first press would paint a line from its last position.
m_button_down = Button::None;
m_last_mouse_click = Vec2d::Zero();
//Camera& camera = wxGetApp().plater()->get_camera();
//camera.look_at(camera.get_position(), m_previous_target, Vec3d::UnitZ());
@@ -494,7 +494,17 @@ bool GLGizmoTextureDisplacement::on_init()
{ctrl + _L("Mouse wheel"), m_desc["gap_area"]},
toggle_wireframe_shortcut
};
// Both ways into the same input box on any slider in the panel: ImGui turns a slider into one when
// it is Ctrl+clicked or when it is given the keyboard focus, which is what the edit icon asks for.
m_desc["type_value"] = _L("Type an exact value into a slider");
m_shortcuts_sliders = {
{ctrl + _L("Left mouse button"), m_desc["type_value"]},
{_L("Edit icon at a slider's right end"), m_desc["type_value"]},
};
for (std::vector<std::pair<wxString, wxString>> *list : {&m_shortcuts_brush, &m_shortcuts_bucket_fill, &m_shortcuts_gap_fill})
list->insert(list->end(), m_shortcuts_sliders.begin(), m_shortcuts_sliders.end());
return true;
}
@@ -552,6 +562,11 @@ void GLGizmoTextureDisplacement::on_shutdown()
m_shaded_island_delta = Eigen::Matrix<float, 2, 3>::Identity();
m_island_drag_active = false;
m_island_move_set.clear();
// The pane closes below, taking the highlight with it, and the view must not open the next session in a
// Fast the user never picked. Set directly: nothing is to be queued against a gizmo that is closing.
if (m_island_view.switched)
m_use_shaded_preview = false;
m_island_view = IslandViewFollow{};
m_adjust_texture_mode = false;
m_seam_edit_mode = false;
m_adjust_drag_handle = AdjustHandle::None;
@@ -717,18 +732,13 @@ void GLGizmoTextureDisplacement::render_painter_gizmo()
// The UV editor's island selection, shown on the model. Polled here rather than pushed: the pane
// changes its selection in its own mouse handling, and a compare of a few ints per frame is free.
{
const TextureDisplacementLayer *al = active_layer();
const UVEditorCanvas *uv_canvas = wxGetApp().plater()->get_uv_editor_canvas();
if (m_show_uv_editor && al != nullptr && al->projection_method == TextureProjectionMethod::LSCM &&
uv_canvas != nullptr && !m_uv_editor_unwrap.empty()) {
if (uv_canvas->selected_islands() != m_island_overlay_selection)
rebuild_island_overlay(uv_canvas->selected_islands());
render_island_overlay();
} else if (m_island_overlay_glmodel.is_initialized()) {
m_island_overlay_glmodel.reset();
m_island_overlay_selection.clear();
}
if (const std::vector<int> *islands = island_highlight_selection()) {
if (*islands != m_island_overlay_selection)
rebuild_island_overlay(*islands);
render_island_overlay();
} else if (m_island_overlay_glmodel.is_initialized()) {
m_island_overlay_glmodel.reset();
m_island_overlay_selection.clear();
}
// Diagnostic overlays, drawn on top of whatever preview is active (both pull toward the camera
@@ -1510,7 +1520,7 @@ void GLGizmoTextureDisplacement::render_shaded_preview_mesh()
// is box-filtered down to 128 px for a ~48 px row, and feeding it to the shader cost the preview
// three quarters of the height map's detail - and, since height_tex_texel is derived from it, also
// flattened the shading gradient and made the parallax march skip itself at angles where it should
// run. Cached on the image_data pointer + smoothing, so no PNG is decoded per frame.
// run. Cached per image_data and smoothing, so no PNG is decoded per frame.
GLTexture *tex = get_layer_height_texture(*layer);
if (tex == nullptr || tex->get_width() <= 0 || tex->get_height() <= 0)
return;
@@ -1916,6 +1926,16 @@ void GLGizmoTextureDisplacement::render_paint_overlay(GLModel &overlay)
shader->stop_using();
}
const std::vector<int> *GLGizmoTextureDisplacement::island_highlight_selection()
{
const TextureDisplacementLayer *al = active_layer();
const UVEditorCanvas *uv_canvas = wxGetApp().plater()->get_uv_editor_canvas();
if (m_show_uv_editor && al != nullptr && al->projection_method == TextureProjectionMethod::LSCM &&
uv_canvas != nullptr && !m_uv_editor_unwrap.empty())
return &uv_canvas->selected_islands();
return nullptr;
}
void GLGizmoTextureDisplacement::rebuild_island_overlay(const std::vector<int> &selection)
{
m_island_overlay_glmodel.reset();
@@ -2641,6 +2661,37 @@ void GLGizmoTextureDisplacement::apply_view_mode(int mode)
m_parent.set_as_dirty();
}
int GLGizmoTextureDisplacement::view_mode() const
{
return m_use_shaded_preview ? 1 :
m_uv_check_mode == UVCheckMode::Checker ? 2 :
m_uv_check_mode == UVCheckMode::Distortion ? 3 : 0;
}
int GLGizmoTextureDisplacement::IslandViewFollow::step(bool shown_now, int view, bool debug_open)
{
shown = shown_now;
if (!shown) {
// The view is back to Normal already if a debug stage took it, and Normal applied over a stage would
// queue a preview job that replaces the stage being inspected.
const bool restore = switched && view == 1;
switched = keep_normal = false;
return restore ? 0 : -1;
}
if (view != 0 || keep_normal || debug_open)
return -1;
switched = true;
return 1;
}
void GLGizmoTextureDisplacement::follow_island_highlight()
{
const std::vector<int> *islands = island_highlight_selection();
const int view = m_island_view.step(islands != nullptr && !islands->empty(), view_mode(), m_debug_stage >= 0);
if (view >= 0)
apply_view_mode(view);
}
void GLGizmoTextureDisplacement::push_uv_pane_state()
{
UVEditorCanvas *canvas = wxGetApp().plater()->get_uv_editor_canvas();
@@ -2881,7 +2932,8 @@ void GLGizmoTextureDisplacement::run_uv_command(int cmd, float value)
rebuild_preview();
}
}
// FrameAll/ToggleSnap are handled inside the canvas; ProjectFromView is not wired yet.
// FrameAll/ToggleSnap are handled inside the canvas; ProjectFromView is not wired yet. SelectionChanged
// only asks for this frame: follow_island_highlight() and the overlay read the selection back themselves.
m_parent.set_as_dirty();
}
@@ -3558,6 +3610,7 @@ const ModelVolume* GLGizmoTextureDisplacement::texture_volume() const
void GLGizmoTextureDisplacement::update_model_object()
{
store_panel_state();
bool updated = false;
ModelObject *mo = m_c->selection_info()->model_object();
int idx = -1;
@@ -3593,6 +3646,24 @@ void GLGizmoTextureDisplacement::update_from_model_object(bool first_update)
{
wxBusyCursor wait;
// Opening the gizmo puts the panel back where it was left: the layer and the mode come off the
// volume, because they are the user's place in this model's stack, and the dock state off the
// application config, because it is about their window. Only on open - this also runs on an
// undo/redo and on a layer switch, where overriding what the user is doing now would be wrong.
if (first_update) {
if (const ModelVolume *mv = texture_volume(); mv != nullptr) {
const TextureDisplacementOptions &opts = mv->texture_displacement_options;
m_panel_mode = std::clamp(opts.panel_mode, 0, 1);
// Only if that slot still holds a layer: the stack can have been edited by anything that
// does not go through the panel, and a selection pointing at nothing shows an empty panel.
const auto &stack = mv->texture_displacement_layers;
if (std::any_of(stack.begin(), stack.end(),
[&opts](const TextureDisplacementLayer &l) { return l.slot == opts.active_slot; }))
m_active_layer_slot = opts.active_slot;
}
m_undocked = wxGetApp().app_config->get_bool(UNDOCKED_CONFIG_KEY);
}
const ModelObject *mo = m_c->selection_info()->model_object();
m_triangle_selectors.clear();
// The base keeps the last mesh a fill tool hovered, and render_painter_gizmo() reads it as a hover that is
@@ -3641,6 +3712,14 @@ void GLGizmoTextureDisplacement::update_from_model_object(bool first_update)
rebuild_preview();
}
void GLGizmoTextureDisplacement::store_panel_state()
{
if (ModelVolume *mv = texture_volume(); mv != nullptr) {
mv->texture_displacement_options.active_slot = m_active_layer_slot;
mv->texture_displacement_options.panel_mode = m_panel_mode;
}
}
void GLGizmoTextureDisplacement::set_active_layer(int slot)
{
if (slot == m_active_layer_slot)
@@ -3649,6 +3728,7 @@ void GLGizmoTextureDisplacement::set_active_layer(int slot)
// reflect - otherwise they would be silently lost.
update_model_object();
m_active_layer_slot = slot;
store_panel_state();
update_from_model_object(false);
// The on-canvas gizmo (if on) is anchored to whichever layer is active - keep it in sync
// instead of leaving it pointing at the previous layer's (now stale) paint patch.
@@ -3680,6 +3760,8 @@ void GLGizmoTextureDisplacement::ensure_panel_icons()
"texture_displacement_adjust.svg", "canvas_drag.svg", "texture_displacement_move_up.svg",
"texture_displacement_move_down.svg", "texture_displacement_drag.svg",
"texture_displacement_select_all.svg", "texture_displacement_erase_all.svg",
// The "type a value instead" icon at the right end of every slider.
"edit.svg",
};
std::vector<std::string> paths;
paths.reserve(names.size());
@@ -3998,8 +4080,8 @@ void GLGizmoTextureDisplacement::remove_texture_layer(int slot)
if (slot >= 0 && slot < int(TEXTURE_DISPLACEMENT_MAX_LAYERS)) {
mv->texture_displacement_facet(slot).reset();
m_thumbnails[size_t(slot)].reset();
m_thumbnail_source[size_t(slot)] = nullptr;
m_layer_expanded[size_t(slot)] = false;
m_thumbnail_source[size_t(slot)].reset();
m_layer_expanded[size_t(slot)] = false;
}
if (m_picker_slot == slot)
m_picker_slot = -1;
@@ -4173,7 +4255,7 @@ int GLGizmoTextureDisplacement::select_visible_faces(const std::array<float, 12>
selector.reset();
for (unsigned i : unobscured)
selector.set_facet(int(front_facing[i]), EnforcerBlockerType::ENFORCER);
selector.request_update_render_data();
selector.request_update_render_data(true);
update_model_object();
m_parent.set_as_dirty();
@@ -4196,7 +4278,7 @@ void GLGizmoTextureDisplacement::select_whole_model()
const size_t facet_count = v->mesh().its.indices.size();
for (size_t i = 0; i < facet_count; ++i)
m_triangle_selectors[idx]->set_facet(int(i), EnforcerBlockerType::ENFORCER);
m_triangle_selectors[idx]->request_update_render_data();
m_triangle_selectors[idx]->request_update_render_data(true);
}
update_model_object();
m_parent.set_as_dirty();
@@ -5302,7 +5384,7 @@ GLTexture *GLGizmoTextureDisplacement::get_layer_thumbnail(const TextureDisplace
return nullptr;
const size_t slot = size_t(layer.slot);
if (m_thumbnails[slot] && m_thumbnail_source[slot] == layer.image_data.get() &&
if (m_thumbnails[slot] && m_thumbnail_source[slot].lock() == layer.image_data &&
m_thumbnail_smoothing[slot] == layer.smoothing)
return m_thumbnails[slot].get();
@@ -5315,7 +5397,7 @@ GLTexture *GLGizmoTextureDisplacement::get_layer_thumbnail(const TextureDisplace
return nullptr;
m_thumbnails[slot] = std::move(texture);
m_thumbnail_source[slot] = layer.image_data.get();
m_thumbnail_source[slot] = layer.image_data;
m_thumbnail_smoothing[slot] = layer.smoothing;
return m_thumbnails[slot].get();
}
@@ -5324,17 +5406,17 @@ GLTexture *GLGizmoTextureDisplacement::get_layer_color_texture(const TextureDisp
{
if (layer.empty() || !layer.color_enabled)
return nullptr;
if (m_color_tex && m_color_tex_source == layer.image_data.get() && m_color_tex_smoothing == layer.smoothing)
if (m_color_tex && m_color_tex_source.lock() == layer.image_data && m_color_tex_smoothing == layer.smoothing)
return m_color_tex.get();
std::unique_ptr<GLTexture> texture = upload_color_texture(decode_height_texture(layer), HEIGHT_TEX_MAX_PX);
if (!texture) {
m_color_tex.reset();
m_color_tex_source = nullptr;
m_color_tex_source.reset();
return nullptr;
}
m_color_tex = std::move(texture);
m_color_tex_source = layer.image_data.get();
m_color_tex_source = layer.image_data;
m_color_tex_smoothing = layer.smoothing;
return m_color_tex.get();
}
@@ -5346,7 +5428,7 @@ GLTexture *GLGizmoTextureDisplacement::get_layer_height_texture(const TextureDis
// One slot, not one per layer: the preview shader only ever shades the *active* layer, so a single
// full-resolution upload is enough and the VRAM cost stays at one texture rather than eight.
if (m_height_tex && m_height_tex_source == layer.image_data.get() && m_height_tex_smoothing == layer.smoothing)
if (m_height_tex && m_height_tex_source.lock() == layer.image_data && m_height_tex_smoothing == layer.smoothing)
return m_height_tex.get();
std::unique_ptr<GLTexture> texture = upload_height_thumbnail(decode_height_texture(layer), HEIGHT_TEX_MAX_PX);
@@ -5354,7 +5436,7 @@ GLTexture *GLGizmoTextureDisplacement::get_layer_height_texture(const TextureDis
return nullptr;
m_height_tex = std::move(texture);
m_height_tex_source = layer.image_data.get();
m_height_tex_source = layer.image_data;
m_height_tex_smoothing = layer.smoothing;
return m_height_tex.get();
}
@@ -5795,6 +5877,9 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
GizmoImguiBegin(get_name(), flags);
ensure_panel_icons();
process_uv_commands(); // clicks from the UV editor pane, run here where the GL context is current
// After the pane's commands, which can close it or change the view, and before the View row, so the row
// lights the view this frame ends up in and a click on it has the last word.
follow_island_highlight();
const float previous_body_h = m_panel_body_h;
const float previous_footer_h = m_panel_footer_h;
@@ -5919,8 +6004,8 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
}
return s + "...";
};
// Label column + a slider filling the rest of the row, value printed inside the track. Ctrl+click on
// any of these types a value in. `right_inset` keeps a layer card's inner padding.
// Label column + a slider filling the rest of the row, value printed inside the track.
// `right_inset` keeps a layer card's inner padding.
const auto slider_label = [&](const wxString &label) {
const float x0 = ImGui::GetCursorPosX();
ImGui::AlignTextToFramePadding();
@@ -5928,17 +6013,107 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
ImGui::SameLine();
ImGui::SetCursorPosX(std::max(ImGui::GetCursorPosX(), x0 + label_w));
};
// ---- Typed values on sliders ----
// Every slider in the panel goes through typed_slider(), so all of them take a number the same
// two ways: Ctrl+click on the track, or the edit icon at the right end for the same box without
// the modifier. Both end up in ImGui's own "temp input" mode - the icon only asks ImGui to focus
// the slider, which is what turns it into an input box (see SliderScalar) - so the parsing,
// the clamping and the Enter/click-away behaviour are ImGui's, not a second implementation.
// Retires a click whose row is no longer being drawn - the panel changed mode in between - so that
// it cannot fire later when that row comes back. Before any row is drawn, so a live one still wins.
if (!m_slider_type_request.empty() && ImGui::GetFrameCount() > m_slider_type_request_frame + 1)
m_slider_type_request.clear();
// Exactly as tall as the slider it sits beside, so it lines up with the track without the row
// having to be re-centred around it; icon_button() pads the glyph itself down to about 0.7 of this.
const float edit_icon_w = frame_h;
// What the slider's own width has to give up for the icon beside it.
const float edit_icon_room = edit_icon_w + gap_s;
// One end of a range, through the slider's own format, so the bounds in the tooltip read exactly
// like the value in the track. A parenthesised note in the format - "(auto)" - is dropped: it
// describes the current state, not the bound.
const auto fmt_bound = [](const char *format, auto v) {
char buf[64];
std::snprintf(buf, sizeof(buf), format, v);
std::string out(buf);
const size_t paren = out.find('(');
if (paren != std::string::npos)
out.erase(paren);
while (!out.empty() && out.back() == ' ')
out.pop_back();
return out;
};
const auto range_text = [](const std::string &lo, const std::string &hi) {
return Slic3r::format(_u8L("Range: %1% to %2%"), lo, hi);
};
const auto edit_icon_tip = [&](const std::string &range) {
return _u8L("Type a value instead of dragging. Ctrl+click on the slider does the same.") + "\n\n" + range;
};
// Draws `slider` at `item_w` (SetNextItemWidth's convention: negative measures back from the right
// edge, and the caller has already left edit_icon_room in it), then the edit icon. `tip` and the
// range go on both. Returns whatever the slider returned.
const auto typed_row = [&](const char *id, float item_w, const wxString &tip, const std::string &range,
const std::function<bool()> &slider) -> bool {
// Deferred from the click a frame ago: SetKeyboardFocusHere() is a request for the *next*
// frame, so it has to be issued before the slider it names, not from the icon beside it.
if (m_slider_type_request == id) {
ImGui::SetKeyboardFocusHere();
m_slider_type_request.clear();
m_parent.set_as_dirty(); // the focus only lands on a frame that actually gets drawn
}
ImGui::SetNextItemWidth(item_w);
const bool changed = slider();
// AllowWhenDisabled: several of these rows are drawn inside disabled_begin(), and a greyed
// slider is exactly when the user needs to be told what it is and why it is not moving.
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
wxString full = tip;
if (!full.IsEmpty())
full += "\n\n";
m_imgui->tooltip(full + from_u8(range), wrap_w);
}
// SameLine() puts the cursor back at the slider's own top (ItemSize records the Y the item was
// drawn at), so an icon of the same height lands on the track wherever the row placed it.
ImGui::SameLine(0.f, gap_s);
if (icon_button(int(ImGui::GetID(id)), "edit.svg", edit_icon_w, _L("Edit"), from_u8(edit_icon_tip(range)))) {
m_slider_type_request = id;
m_slider_type_request_frame = ImGui::GetFrameCount();
m_parent.set_as_dirty(); // the next frame is where the request above is turned into focus
}
return changed;
};
// The two forms every slider in the panel is one of. They derive the range from the bounds they are
// already given, so no row states its bounds or its format twice and the tooltip cannot drift from
// the track. `item_w` is as typed_row() takes it.
const auto typed_slider = [&](const char *id, float item_w, float *v, float v_min, float v_max, const char *format,
bool log, const wxString &tip) -> bool {
return typed_row(id, item_w, tip, range_text(fmt_bound(format, v_min), fmt_bound(format, v_max)), [&] {
return ImGui::SliderFloat(id, v, v_min, v_max, format,
ImGuiSliderFlags_AlwaysClamp | (log ? ImGuiSliderFlags_Logarithmic : 0));
});
};
const auto typed_slider_int = [&](const char *id, float item_w, int *v, int v_min, int v_max, const char *format,
bool log, const wxString &tip) -> bool {
return typed_row(id, item_w, tip, range_text(fmt_bound(format, v_min), fmt_bound(format, v_max)), [&] {
return ImGui::SliderInt(id, v, v_min, v_max, format,
ImGuiSliderFlags_AlwaysClamp | (log ? ImGuiSliderFlags_Logarithmic : 0));
});
};
// Label column + the slider filling the rest of the row.
const auto float_row = [&](const char *id, const wxString &label, float *v, float v_min, float v_max, const char *format,
bool log, float right_inset) -> bool {
bool log, float right_inset, const wxString &tip) -> bool {
slider_label(label);
ImGui::SetNextItemWidth(-std::max(right_inset, 1.f));
return ImGui::SliderFloat(id, v, v_min, v_max, format, ImGuiSliderFlags_AlwaysClamp | (log ? ImGuiSliderFlags_Logarithmic : 0));
return typed_slider(id, -(std::max(right_inset, 1.f) + edit_icon_room), v, v_min, v_max, format, log, tip);
};
const auto int_row = [&](const char *id, const wxString &label, int *v, int v_min, int v_max, const char *format,
float right_inset) -> bool {
float right_inset, const wxString &tip) -> bool {
slider_label(label);
ImGui::SetNextItemWidth(-std::max(right_inset, 1.f));
return ImGui::SliderInt(id, v, v_min, v_max, format, ImGuiSliderFlags_AlwaysClamp);
return typed_slider_int(id, -(std::max(right_inset, 1.f) + edit_icon_room), v, v_min, v_max, format, false, tip);
};
const auto layer_name = [](const TextureDisplacementLayer &l) {
return l.name.empty() ? Slic3r::format(_u8L("Layer %1%"), l.slot + 1) : l.name;
@@ -5986,6 +6161,7 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
"not what you wanted and you know why."));
if (clicked && !on) {
m_panel_mode = m;
store_panel_state();
if (!pro_mode()) {
// Leaving the subdivision preview open would strand a wireframe whose controls just
// disappeared, so close it as part of the switch.
@@ -6008,6 +6184,7 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
_L("Detach this panel so it can be dragged anywhere over the 3D view, or dock it back beside "
"the toolbar.")))
m_undocked = !m_undocked;
wxGetApp().app_config->set_bool(UNDOCKED_CONFIG_KEY, m_undocked);
}
ImGui::Separator();
@@ -6149,7 +6326,7 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
if (v->is_model_part()) {
++idx;
m_triangle_selectors[idx]->reset();
m_triangle_selectors[idx]->request_update_render_data();
m_triangle_selectors[idx]->request_update_render_data(true);
}
update_model_object();
m_parent.set_as_dirty();
@@ -6160,11 +6337,10 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
const auto centre_on_row = [&](float h) { ImGui::SetCursorPosY(row_y + std::round((icon_md - h) * 0.5f)); };
vsep(icon_md);
if (is_brush_mode) {
ImGui::SetNextItemWidth(std::max(1.f, row_end - ImGui::GetCursorPosX() - (3.f * gap_s + 1.f + 2.f * icon_sm)));
centre_on_row(frame_h);
ImGui::SliderFloat("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f mm",
ImGuiSliderFlags_AlwaysClamp);
hover_tip(m_desc.at("cursor_size"));
typed_slider("##cursor_radius",
std::max(1.f, row_end - ImGui::GetCursorPosX() - (3.f * gap_s + 1.f + 2.f * icon_sm) - edit_icon_room),
&m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f mm", false, m_desc.at("cursor_size"));
vsep(icon_sm);
const bool is_circle = m_cursor_type == TriangleSelector::CursorType::CIRCLE;
centre_on_row(icon_sm);
@@ -6177,11 +6353,10 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
_L("Sphere - paints only within a ball around the point under the cursor")))
m_cursor_type = TriangleSelector::CursorType::SPHERE;
} else if (is_area_mode) {
ImGui::SetNextItemWidth(std::max(1.f, row_end - ImGui::GetCursorPosX()));
centre_on_row(frame_h);
ImGui::SliderFloat("##smart_fill_angle", &m_smart_fill_angle, SmartFillAngleMin, SmartFillAngleMax, "%.0f°",
ImGuiSliderFlags_AlwaysClamp);
hover_tip(_u8L("Angle threshold - the fill stops at edges sharper than this"));
typed_slider("##smart_fill_angle", std::max(1.f, row_end - ImGui::GetCursorPosX() - edit_icon_room),
&m_smart_fill_angle, SmartFillAngleMin, SmartFillAngleMax, "%.0f°", false,
_L("Angle threshold - the fill stops at edges sharper than this"));
} else {
centre_on_row(frame_h);
ImGui::AlignTextToFramePadding();
@@ -6193,10 +6368,9 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
// ---- View: Normal / Fast / Checker / Distortion as one group, Wireframe on its own ----
// The underlying state stays m_use_shaded_preview + m_uv_check_mode.
{
const int cur_mode = m_use_shaded_preview ? 1 :
m_uv_check_mode == UVCheckMode::Checker ? 2 :
m_uv_check_mode == UVCheckMode::Distortion ? 3 : 0;
const int cur_mode = view_mode();
int new_mode = cur_mode;
int picked = -1; // the button clicked, the active one included
bool wf_toggle = false;
bool open_uv_editor = false;
// Checker and Distortion both draw *the unwrap* - the first the texture grid laid over it, the second
@@ -6219,19 +6393,19 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
ImGui::SetCursorPosX(std::max(ImGui::GetCursorPosX(), x0 + m_imgui->scaled(2.6f)));
if (icon_toggle(701, "texture_displacement_real_preview.svg", cur_mode == 0, icon_md, _L("Normal"),
_L("Normal - the real geometry, exactly what Bake will produce. Slower to update")))
new_mode = 0;
new_mode = picked = 0;
ImGui::SameLine(0.f, gap_s);
if (icon_toggle(702, "texture_displacement_fast_preview.svg", cur_mode == 1, icon_md, _L("Fast"),
_L("Fast - the relief is only shaded on, not built, and only for the layer you are "
"editing. Updates instantly while you paint")))
new_mode = 1;
new_mode = picked = 1;
ImGui::SameLine(0.f, gap_s);
if (icon_toggle(703, "texture_displacement_checker.svg", cur_mode == 2, icon_md, _L("Checker"),
_L("Checker - a test grid instead of the texture. Where the squares stay square the "
"texture is undistorted; where they stretch, it will too. Opens the UV editor if "
"it is closed"),
uv_view_na)) {
new_mode = 2;
new_mode = picked = 2;
open_uv_editor = true;
}
ImGui::SameLine(0.f, gap_s);
@@ -6239,7 +6413,7 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
_L("Distortion - blue-to-red stretch heatmap over the unwrap. Opens the UV editor if "
"it is closed"),
uv_view_na)) {
new_mode = 3;
new_mode = picked = 3;
open_uv_editor = true;
}
vsep(icon_md);
@@ -6263,6 +6437,8 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
if (new_mode == cur_mode)
update_uv_editor(); // otherwise apply_view_mode() below does it
}
if (picked >= 0)
m_island_view.picked(picked);
if (new_mode != cur_mode)
apply_view_mode(new_mode);
if (wf_toggle) {
@@ -6451,16 +6627,16 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
// The three controls nearly every layer needs; everything else waits behind "More settings".
// Depth and tile size are logarithmic, so both ends of their wide ranges stay usable.
m_preview_params_dirty |= float_row("##depth", _L("Depth"), &layer.depth_mm, 0.01f, 10.f, "%.3f mm", true, card_pad);
hover_tip(_u8L("Height of the relief: how far white in the texture lifts the surface, in "
"millimetres. Black does not move it at all, unless Midlevel says otherwise."));
m_preview_params_dirty |= float_row("##tile_size", _L("Tile size"), &layer.tiling_scale, 0.2f, 200.f, "%.2f mm", true, card_pad);
hover_tip(_u8L("How wide one copy of the texture is on the model. Smaller repeats the pattern "
"more often and makes its detail finer; with Tile off, this is the size of the "
"single copy."));
m_preview_params_dirty |= float_row("##rotation", _L("Rotation"), &layer.rotation_deg, 0.f, 360.f, "%.0f°", false, card_pad);
hover_tip(_u8L("Turns the texture on the surface, in degrees - for lining a pattern up with an "
"edge of the model."));
m_preview_params_dirty |= float_row("##depth", _L("Depth"), &layer.depth_mm, 0.01f, 10.f, "%.3f mm", true, card_pad,
_L("Height of the relief: how far white in the texture lifts the surface, in "
"millimetres. Black does not move it at all, unless Midlevel says otherwise."));
m_preview_params_dirty |= float_row("##tile_size", _L("Tile size"), &layer.tiling_scale, 0.2f, 200.f, "%.2f mm", true, card_pad,
_L("How wide one copy of the texture is on the model. Smaller repeats the pattern "
"more often and makes its detail finer; with Tile off, this is the size of the "
"single copy."));
m_preview_params_dirty |= float_row("##rotation", _L("Rotation"), &layer.rotation_deg, 0.f, 360.f, "%.0f°", false, card_pad,
_L("Turns the texture on the surface, in degrees - for lining a pattern up with an "
"edge of the model."));
if (m_layer_expanded[size_t(layer.slot)]) {
{
@@ -6475,22 +6651,22 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
// Midlevel: the height that means "don't move". At 0 the surface only ever bulges outwards;
// at 0.5 mid-grey is neutral and darker texels cut inwards.
m_preview_params_dirty |= float_row("##midlevel", _L("Midlevel"), &layer.midlevel, 0.f, 10.f, "%.2f", false, card_pad);
hover_tip(_u8L("Which gray stays where the surface already is. At 0 the texture only pushes "
"outwards; at 0.5 mid-gray stays put, so darker grays cut in and lighter ones "
"still push out - one image both embosses and engraves.\n\n"
"What cuts in has to fit: inside a sharp corner or through a thin wall, a deep "
"cut can pass through the other side."));
m_preview_params_dirty |= float_row("##midlevel", _L("Midlevel"), &layer.midlevel, 0.f, 10.f, "%.2f", false, card_pad,
_L("Which gray stays where the surface already is. At 0 the texture only pushes "
"outwards; at 0.5 mid-gray stays put, so darker grays cut in and lighter ones "
"still push out - one image both embosses and engraves.\n\n"
"What cuts in has to fit: inside a sharp corner or through a thin wall, a deep "
"cut can pass through the other side."));
if (layer.midlevel > 0.f && layer.depth_mm > 1.f) {
ImGui::PushTextWrapPos(content_rx - card_pad);
m_imgui->warning_text(_L("Deep inward displacement may self-intersect."));
ImGui::PopTextWrapPos();
}
m_preview_params_dirty |= float_row("##smoothing", _L("Smoothing"), &layer.smoothing, 0.f, 1.f, "%.2f", false, card_pad);
hover_tip(_u8L("Blurs the image before it is used, which rounds off hard steps and removes "
"speckle from a noisy photo. Raise it if the relief looks harsh or grainy; it "
"costs fine detail."));
m_preview_params_dirty |= float_row("##smoothing", _L("Smoothing"), &layer.smoothing, 0.f, 1.f, "%.2f", false, card_pad,
_L("Blurs the image before it is used, which rounds off hard steps and removes "
"speckle from a noisy photo. Raise it if the relief looks harsh or grainy; it "
"costs fine detail."));
// Edge fade: the relief flattens toward the boundary of the painted area.
m_preview_params_dirty |= m_imgui->bbl_checkbox(_L("Edge fade") + "##edge_smoothing", layer.edge_smoothing);
@@ -6498,12 +6674,11 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
"blends into the bare surface instead of stopping at a step."));
ImGui::SameLine();
m_imgui->disabled_begin(!layer.edge_smoothing);
ImGui::SetNextItemWidth(-card_pad);
m_preview_params_dirty |= ImGui::SliderFloat("##edge_amount", &layer.edge_smoothing_amount, 0.02f, 1.f, "%.2f",
ImGuiSliderFlags_AlwaysClamp);
m_preview_params_dirty |= typed_slider("##edge_amount", -(card_pad + edit_icon_room),
&layer.edge_smoothing_amount, 0.02f, 1.f, "%.2f", false,
_L("How far in the fade reaches, as a share of the painted area. Small "
"values soften a narrow band at the edge; 1 flattens almost all of it."));
m_imgui->disabled_end();
hover_tip(_u8L("How far in the fade reaches, as a share of the painted area. Small values "
"soften a narrow band at the edge; 1 flattens almost all of it."));
// Invert and Colours share a row.
{
@@ -6548,19 +6723,19 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
// Every mix can become a filament slot, and there are only so many beside the
// physical filaments.
const int max_mixes = std::max(1, int(EnforcerBlockerType::ExtruderMax) - int(m_palette_filaments.size()));
if (int_row("##color_mix_count", _L("Mixed colors"), &opts.color_mix_count, 1, max_mixes, "%d", card_pad))
if (int_row("##color_mix_count", _L("Mixed colors"), &opts.color_mix_count, 1, max_mixes, "%d", card_pad,
_L("The most mixed filaments a bake may add. They are picked from the "
"texture's colors, and only the ones the bake actually uses are created.")))
m_preview_params_dirty = true;
hover_tip(_u8L("The most mixed filaments a bake may add. They are picked from the "
"texture's colors, and only the ones the bake actually uses are created."));
// After the slider, so a change shows in the same frame.
ImGui::TextDisabled("%s", Slic3r::format(_u8L("%1% printable colors from %2% filaments"),
int(cached_palette().size()), int(m_palette_filaments.size())).c_str());
}
if (int_row("##color_despeckle", _L("Denoise"), &opts.color_despeckle, 0, 6, "%d", card_pad))
m_preview_params_dirty = true;
hover_tip(_u8L("Cleans up single stray triangles of the wrong color, which detail finer "
if (int_row("##color_despeckle", _L("Denoise"), &opts.color_despeckle, 0, 6, "%d", card_pad,
_L("Cleans up single stray triangles of the wrong color, which detail finer "
"than the mesh leaves behind. Raise it if the result looks speckled, "
"lower it if small features are being swallowed."));
"lower it if small features are being swallowed.")))
m_preview_params_dirty = true;
}
}
@@ -6665,10 +6840,10 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
"gets the texture, and its border becomes the edge of the projection."));
if (projector_open) {
if (int_row("##projector_opacity", _L("Opacity"), &m_projector_opacity, 20, 255, "%d", card_pad))
if (int_row("##projector_opacity", _L("Opacity"), &m_projector_opacity, 20, 255, "%d", card_pad,
_L("How solid the frame window looks while you place it. Lower to see the "
"model through it; it does not affect the result.")))
m_projector_frame->set_opacity(m_projector_opacity);
hover_tip(_u8L("How solid the frame window looks while you place it. Lower to see the "
"model through it; it does not affect the result."));
if (m_imgui->button(_u8L("Apply projection frame"))) {
const int painted_count = apply_projection_frame();
@@ -6969,57 +7144,57 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
// pattern can sample four points that all happen to land at similar heights, report no error,
// and stall before refinement ever starts.
if (float_row("##subdiv_target", m_subdivide_feature ? _L("Max edge") : _L("Target edge"), &m_subdivide_target_mm,
0.001f, 20.f, "%.3f mm", true, 0.f))
0.001f, 20.f, "%.3f mm", true, 0.f,
m_subdivide_feature ?
_L("No triangle in the painted area stays larger than this, even where the texture "
"is flat. Keep it near the size of the smallest feature you want to come out.") :
_L("Triangles in the painted area are split until none is larger than this. Smaller "
"means finer detail and, fast, a lot more triangles.")))
preview_live();
hover_tip(m_subdivide_feature ?
_u8L("No triangle in the painted area stays larger than this, even where the texture "
"is flat. Keep it near the size of the smallest feature you want to come out.") :
_u8L("Triangles in the painted area are split until none is larger than this. Smaller "
"means finer detail and, fast, a lot more triangles."));
if (m_subdivide_feature) {
if (float_row("##subdiv_detail", _L("Detail"), &m_subdivide_detail_mm, 0.001f, 1.f, "%.3f mm", true, 0.f))
if (float_row("##subdiv_detail", _L("Detail"), &m_subdivide_detail_mm, 0.001f, 1.f, "%.3f mm", true, 0.f,
_L("How far the mesh may sit from the shape the texture describes, in millimetres. "
"Smaller follows fine detail and costs triangles; larger only chases the big "
"features.")))
preview_live();
hover_tip(_u8L("How far the mesh may sit from the shape the texture describes, in millimetres. "
"Smaller follows fine detail and costs triangles; larger only chases the big "
"features."));
if (float_row("##subdiv_min", _L("Min edge"), &m_subdivide_min_edge_mm, 0.001f, 20.f, "%.3f mm", true, 0.f))
if (float_row("##subdiv_min", _L("Min edge"), &m_subdivide_min_edge_mm, 0.001f, 20.f, "%.3f mm", true, 0.f,
_L("A floor on triangle size, so a sharp step in the texture cannot be chased "
"forever. Lower it for finer relief, raise it if the count runs away at hard "
"edges.")))
preview_live();
hover_tip(_u8L("A floor on triangle size, so a sharp step in the texture cannot be chased "
"forever. Lower it for finer relief, raise it if the count runs away at hard "
"edges."));
}
// Applies in both adaptive sub-modes: it is about the step the bake puts at the paint's edge, which
// exists whether or not "Follow texture detail" is on. 0 turns the band off.
if (float_row("##subdiv_border", _L("Edge detail"), &m_subdivide_border_mm, 0.f, 5.f, "%.3f mm", false, 0.f))
if (float_row("##subdiv_border", _L("Edge detail"), &m_subdivide_border_mm, 0.f, 5.f, "%.3f mm", false, 0.f,
_L("Triangle size along the outline of the painted area, where the relief drops back to the "
"bare surface. Lower it if that rim looks jagged; it only costs triangles along the "
"outline. 0 turns it off.")))
preview_live();
hover_tip(_u8L("Triangle size along the outline of the painted area, where the relief drops back to the "
"bare surface. Lower it if that rim looks jagged; it only costs triangles along the "
"outline. 0 turns it off."));
// Only worth showing when a layer is actually colouring: with no colour there is no boundary for it
// to refine and the control would do nothing whatever it is set to.
if (mv != nullptr && any_layer_colors(*mv)) {
if (float_row("##subdiv_color", _L("Color detail"), &m_subdivide_color_mm, 0.f, 5.f, "%.3f mm", false, 0.f))
if (float_row("##subdiv_color", _L("Color detail"), &m_subdivide_color_mm, 0.f, 5.f, "%.3f mm", false, 0.f,
_L("Triangle size where two colors meet. Each triangle prints in one filament, so a "
"color edge can only be as sharp as the triangles along it - and nothing else "
"refines there, since the surface is flat across a change of color. 0 turns it off.")))
preview_live();
hover_tip(_u8L("Triangle size where two colors meet. Each triangle prints in one filament, so a "
"color edge can only be as sharp as the triangles along it - and nothing else "
"refines there, since the surface is flat across a change of color. 0 turns it off."));
}
ImGui::TextDisabled("%s", _u8L("Triangle budget: set with Triangles, below.").c_str());
if (m_subdivide_editing && m_subdivide_preview_tris > 0)
m_imgui->text(Slic3r::format(_u8L("Preview: %1% triangles"), m_subdivide_preview_tris));
} else {
if (int_row("##subdiv_steps", _L("Steps"), &m_subdivide_count, 0, 5, "%d", 0.f)) {
if (int_row("##subdiv_steps", _L("Steps"), &m_subdivide_count, 0, 5, "%d", 0.f,
_L("Splits every triangle of the model into four, this many times over. Each step "
"quadruples the count - 3 turns 100 k triangles into 6.4 M - so prefer refining "
"only the painted area unless you need the whole model finer."))) {
if (m_subdivide_editing)
rebuild_subdivide_preview(); // a count of 0 clears the preview, it doesn't compute one
m_parent.set_as_dirty();
}
hover_tip(_u8L("Splits every triangle of the model into four, this many times over. Each step "
"quadruples the count - 3 turns 100 k triangles into 6.4 M - so prefer refining "
"only the painted area unless you need the whole model finer."));
}
{
@@ -7081,9 +7256,9 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
}
m_remesh_target_edge_mm = cnt > 0 ? std::clamp(float(sum / double(cnt)), 0.1f, 20.f) : 1.f;
}
float_row("##remesh_edge", _L("Target edge"), &m_remesh_target_edge_mm, 0.1f, 20.f, "%.2f mm", true, 0.f);
hover_tip(_u8L("Triangle size the whole model is rebuilt with, in millimetres. Displacement works best on an "
"even mesh; this is what makes one out of an uneven import."));
float_row("##remesh_edge", _L("Target edge"), &m_remesh_target_edge_mm, 0.1f, 20.f, "%.2f mm", true, 0.f,
_L("Triangle size the whole model is rebuilt with, in millimetres. Displacement works best on an "
"even mesh; this is what makes one out of an uneven import."));
m_imgui->bbl_checkbox(_L("Keep sharp edges") + "##remesh_sharp", m_remesh_keep_sharp_edges);
hover_tip(_u8L("Holds hard edges and open borders in place while the rest is remeshed. Without it "
@@ -7091,11 +7266,10 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
"a cube comes back with wobbly edges."));
ImGui::SameLine();
m_imgui->disabled_begin(!m_remesh_keep_sharp_edges);
ImGui::SetNextItemWidth(-1.f);
ImGui::SliderFloat("##remesh_sharp_angle", &m_remesh_sharp_angle_deg, 5.f, 90.f, "%.0f°", ImGuiSliderFlags_AlwaysClamp);
m_imgui->disabled_end();
hover_tip(_u8L("How sharp a fold has to be, in degrees, before the remesher treats it as an edge worth "
typed_slider("##remesh_sharp_angle", -(1.f + edit_icon_room), &m_remesh_sharp_angle_deg, 5.f, 90.f, "%.0f°", false,
_L("How sharp a fold has to be, in degrees, before the remesher treats it as an edge worth "
"keeping. Lower protects more of the model's shape; higher rebuilds more of it evenly."));
m_imgui->disabled_end();
m_imgui->disabled_begin(mv == nullptr || busy);
if (ImGui::Button(_u8L("Remesh").c_str()))
@@ -7123,16 +7297,16 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
"on a layer is a different thing: it blurs the image before it is used."));
if (opts.smooth_enabled) {
float percent = opts.smooth_strength * 100.f;
if (float_row("##dispsmooth", _L_CONTEXT("Strength", "Texture Displacement"), &percent, 1.f, 100.f, "%.0f %%", false, 0.f)) {
if (float_row("##dispsmooth", _L_CONTEXT("Strength", "Texture Displacement"), &percent, 1.f, 100.f, "%.0f %%", false, 0.f,
_L("How far each pass pulls a vertex towards its neighbours. High values round the "
"relief off quickly; low values need more passes but keep more of the detail."))) {
opts.smooth_strength = std::clamp(percent / 100.f, 0.01f, 1.f);
m_preview_params_dirty = true;
}
hover_tip(_u8L("How far each pass pulls a vertex towards its neighbours. High values round the "
"relief off quickly; low values need more passes but keep more of the detail."));
if (int_row("##dispsmoothit", _L("Passes"), &opts.smooth_iterations, 1, 10, "%d", 0.f))
if (int_row("##dispsmoothit", _L("Passes"), &opts.smooth_iterations, 1, 10, "%d", 0.f,
_L("How many smoothing passes to run. More passes spread the smoothing further across "
"the surface; Strength decides how much each one moves.")))
m_preview_params_dirty = true;
hover_tip(_u8L("How many smoothing passes to run. More passes spread the smoothing further across "
"the surface; Strength decides how much each one moves."));
m_preview_params_dirty |= m_imgui->bbl_checkbox(_L("Ignore outer ring"), opts.smooth_skip_border);
hover_tip(_u8L("Keeps the outer ring of the painted area out of the smoothing. Its neighbours "
@@ -7175,16 +7349,16 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
int shown_k = auto_budget ? v2_recommendation(*mv).budget_k : opts.v2_max_triangles_k;
m_imgui->disabled_begin(auto_budget);
// TRN Slider value: %d is the triangle budget in thousands
if (int_row("##v2budget", _L("Budget"), &shown_k, 0, 4000, auto_budget ? _u8L("%d k (auto)").c_str() : "%d k", 0.f)) {
if (int_row("##v2budget", _L("Budget"), &shown_k, 0, 4000, auto_budget ? _u8L("%d k (auto)").c_str() : "%d k", 0.f,
_L("How many thousand triangles this bake may spend on the area you painted. "
"Relief already baked into the rest of the model is kept on top of it, so a "
"second bake somewhere else gets the same budget as the first. Raise it if the "
"warning below Resolution says the detail will not fit; 0 keeps every triangle "
"the refinement produced, however many that is."))) {
opts.v2_max_triangles_k = shown_k;
m_preview_params_dirty = true;
}
m_imgui->disabled_end();
hover_tip(_u8L("How many thousand triangles this bake may spend on the area you painted. "
"Relief already baked into the rest of the model is kept on top of it, so a "
"second bake somewhere else gets the same budget as the first. Raise it if the "
"warning below Resolution says the detail will not fit; 0 keeps every triangle "
"the refinement produced, however many that is."));
}
if (SHOW_PIPELINE_DEV_CONTROLS && opts.pipeline_v2) {
// Keeping the relief above the plate is not a checkbox: it is unconditional, in both pipelines
@@ -7291,26 +7465,28 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
"default. Untick to set both yourself - worth doing for a texture much finer or "
"much coarser than the part it sits on."));
ImGui::SameLine();
ImGui::SetNextItemWidth(x0 + panel_w - ImGui::GetCursorPosX());
float shown = auto_res ? rec.edge_mm : opts.v2_refine_mm;
// Held in a local: the format outlives the call that reads it, and the slider and the
// range in its tooltip are then formatted the same way.
const std::string v2edge_format = auto_res ? _u8L("%.2f mm (auto)") : std::string("%.2f mm");
const wxString v2edge_tip =
auto_res && rec.edge_mm > 0.f ?
from_u8(Slic3r::format(_u8L("Chosen from the size of the model: %1$.2f mm triangles, "
"budget %2% k. Untick Auto to set them yourself."),
rec.edge_mm, rec.budget_k)) :
_L("How fine the mesh is made under the paint, in millimetres. It has to be "
"smaller than the detail you want out of the texture - a 0.5 mm groove needs "
"triangles well under 0.5 mm. Smaller costs triangles fast: halving it needs "
"four times as many, and once they no longer fit the budget the bake simplifies "
"back down and the detail goes with it.");
m_imgui->disabled_begin(busy || auto_res);
if (ImGui::SliderFloat("##v2edge", &shown, 0.02f, 2.f, auto_res ? _u8L("%.2f mm (auto)").c_str() : "%.2f mm",
ImGuiSliderFlags_AlwaysClamp | ImGuiSliderFlags_Logarithmic)) {
if (typed_slider("##v2edge", x0 + panel_w - ImGui::GetCursorPosX() - edit_icon_room, &shown, 0.02f, 2.f,
v2edge_format.c_str(), true, v2edge_tip)) {
opts.v2_refine_mm = shown;
m_preview_params_dirty = true;
m_parent.set_as_dirty();
}
m_imgui->disabled_end();
if (auto_res && rec.edge_mm > 0.f)
hover_tip(Slic3r::format(_u8L("Chosen from the size of the model: %1$.2f mm triangles, "
"budget %2% k. Untick Auto to set them yourself."),
rec.edge_mm, rec.budget_k));
else
hover_tip(_u8L("How fine the mesh is made under the paint, in millimetres. It has to be "
"smaller than the detail you want out of the texture - a 0.5 mm groove needs "
"triangles well under 0.5 mm. Smaller costs triangles fast: halving it needs "
"four times as many, and once they no longer fit the budget the bake simplifies "
"back down and the detail goes with it."));
// What this resolution costs over what is painted, against what the budget allows. Refining
// past the budget is not an error - the bake simplifies back down to it - but the result
@@ -7342,18 +7518,17 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
m_imgui->text(_L("Triangles"));
ImGui::SameLine();
ImGui::SetCursorPosX(std::max(ImGui::GetCursorPosX(), x0 + label_w));
ImGui::SetNextItemWidth(x0 + panel_w - ImGui::GetCursorPosX());
m_imgui->disabled_begin(busy);
if (ImGui::SliderInt("##subdivbudget", &m_subdivide_budget_k, 10, 2000, "+%d k",
ImGuiSliderFlags_AlwaysClamp | ImGuiSliderFlags_Logarithmic)) {
if (typed_slider_int("##subdivbudget", x0 + panel_w - ImGui::GetCursorPosX() - edit_icon_room,
&m_subdivide_budget_k, 10, 2000, "+%d k", true,
_L("How many thousand triangles the refinement may add to the model. The triangle "
"that fits worst is always split first, so even a run that spends the lot has "
"spent it where it shows most. Raise it if the relief still looks coarse."))) {
if (m_subdivide_editing)
rebuild_subdivide_preview();
m_parent.set_as_dirty();
}
m_imgui->disabled_end();
hover_tip(_u8L("How many thousand triangles the refinement may add to the model. The triangle "
"that fits worst is always split first, so even a run that spends the lot has "
"spent it where it shows most. Raise it if the relief still looks coarse."));
}
const float button_h = std::round(frame_h * 1.25f);
@@ -7372,7 +7547,7 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
: m_tool_type == ToolType::SMART_FILL ? m_shortcuts_bucket_fill
: m_tool_type == ToolType::BRUSH ? m_shortcuts_brush
: m_tool_type == ToolType::GAP_FILL ? m_shortcuts_gap_fill
: std::vector<std::pair<wxString, wxString>>{};
: m_shortcuts_sliders; // the sliders are there whether or not a painting tool is
ImGui::SetCursorPosY(row_y + (row_h - icon_h) * .5f); // center vertically
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(int(m_imgui->scaled(.5f)), style.ItemSpacing.y));
@@ -197,6 +197,27 @@ public:
};
static PaintedColors painted_colors(const TriangleMesh &mesh, const TriangleSelector::TriangleSplittingData &paint);
// The view while the UV editor's selected islands are highlighted on the model. The highlight lies on the
// undisplaced surface, so the Normal view's relief hides it wherever it rises: Normal gives way to Fast
// while islands are selected and comes back once none are. A view the user clicks in the View row is
// theirs - the pane's background buttons are not a pick, Height only meaning "the texture view".
struct IslandViewFollow
{
bool shown = false; // the highlight, as of the last step()
bool switched = false; // Fast was switched on for the highlight, so Normal comes back once it is gone
bool keep_normal = false; // the user picked Normal in the View row while the highlight was shown
// The view to apply this frame (0 Normal, 1 Fast), or -1 for none. `view` is the one on screen, numbered as
// apply_view_mode() takes it; `debug_open` a debug stage on screen, which holds Normal itself.
int step(bool shown_now, int view, bool debug_open);
// A View row button clicked, the active one included. The row is drawn after step() in the same frame,
// so `shown` is this frame's.
void picked(int view)
{
switched = false;
keep_normal = shown && view == 0;
}
};
void render_painter_gizmo() override;
// Intercepts mouse input while "Adjust Texture" mode is on (dragging the on-canvas offset/
@@ -262,6 +283,9 @@ private:
// Standard is active so what Preview shows is always what Bake will do. Returns true if it actually
// changed something, so the caller can invalidate the preview.
bool apply_standard_mode_presets(ModelVolume *mv);
// Copies the panel's own state (active layer, Standard/Pro) onto the volume, so the project file
// can carry it. Cheap and idempotent - called wherever either of the two changes.
void store_panel_state();
// Standard mode's Bake: remesh to an even density, refine where the texture bends, then displace.
// The order matters and is the whole reason this is one button - a height map can only move
// existing vertices, so the mesh has to be prepared first, and remeshing after painting would drop
@@ -404,6 +428,12 @@ private:
// Per slot: whether the layer's card shows every setting or only Depth, Tile size and Rotation.
// Panel state only, not saved with the project.
std::array<bool, TEXTURE_DISPLACEMENT_MAX_LAYERS> m_layer_expanded{};
// The slider whose edit icon was just clicked, as its ImGui id, and the frame it was clicked on.
// The row acts on it on the next frame - ImGui's keyboard-focus request only takes effect a frame
// later - and clears it; the frame number is what retires a request whose row has gone off screen
// in the meantime. Panel state only, not saved with the project.
std::string m_slider_type_request;
int m_slider_type_request_frame = 0;
// "Adjust Texture" mode: instead of painting, dragging an on-canvas handle changes the active
// layer's offset. The handle is a flat panel lying in the paint patch's own tangent plane
@@ -504,6 +534,11 @@ private:
// The panel's view modes: 0 Normal, 1 Fast, 2 Checker, 3 Distortion. Shared by the View row and the pane's
// background buttons, so both switch views the same way.
void apply_view_mode(int mode);
// The view on screen, numbered as apply_view_mode() takes it.
int view_mode() const;
// Run once a frame, after the pane's commands: switches the view as m_island_view says.
void follow_island_highlight();
IslandViewFollow m_island_view;
// The pane header's thumbnail of the active layer's texture, rebuilt only when its image changes.
static constexpr int UV_THUMB_PX = 64;
const void *m_uv_thumb_source = nullptr;
@@ -735,6 +770,8 @@ private:
// in place. Rebuilt whenever the pane's selection differs from the one it was built for.
GLModel m_island_overlay_glmodel;
std::vector<int> m_island_overlay_selection;
// The pane's selection while the overlay applies - the pane open over an unwrapped LSCM layer - else null.
const std::vector<int> *island_highlight_selection();
void rebuild_island_overlay(const std::vector<int> &selection);
void render_island_overlay();
// Set on every paint event, cleared when the overlay is rebuilt in render_painter_gizmo(). Kept
@@ -915,25 +952,26 @@ private:
bool m_preview_job_pending = false;
void queue_preview_job();
// Per-slot GPU thumbnail cache for the layer list panel, keyed by the image_data pointer that
// was current the last time each thumbnail was built (see get_layer_thumbnail()).
std::array<std::unique_ptr<GLTexture>, TEXTURE_DISPLACEMENT_MAX_LAYERS> m_thumbnails;
std::array<const void *, TEXTURE_DISPLACEMENT_MAX_LAYERS> m_thumbnail_source{};
// Per-slot GPU thumbnail cache for the layer list panel, each keyed by the image_data it was built from
// (see get_layer_thumbnail()). Held weakly rather than by address, as decode_height_texture()'s cache is:
// an image freed and replaced by another at the same address must not pass for it.
std::array<std::unique_ptr<GLTexture>, TEXTURE_DISPLACEMENT_MAX_LAYERS> m_thumbnails;
std::array<std::weak_ptr<const std::vector<unsigned char>>, TEXTURE_DISPLACEMENT_MAX_LAYERS> m_thumbnail_source;
// The smoothing each cached thumbnail was built at, so a smoothing change re-uploads it.
std::array<float, TEXTURE_DISPLACEMENT_MAX_LAYERS> m_thumbnail_smoothing{};
std::array<float, TEXTURE_DISPLACEMENT_MAX_LAYERS> m_thumbnail_smoothing{};
// Full-resolution height texture for the preview shader, keyed the same way (see
// get_layer_height_texture()). A smoothing change re-uploads it, so the fast preview shows the
// blur the bake will apply.
std::unique_ptr<GLTexture> m_height_tex;
const void *m_height_tex_source = nullptr;
float m_height_tex_smoothing = -1.f;
std::unique_ptr<GLTexture> m_height_tex;
std::weak_ptr<const std::vector<unsigned char>> m_height_tex_source;
float m_height_tex_smoothing = -1.f;
// The same, for the layer's *colour*, which the fast preview quantizes per fragment so it shows
// the image at texel resolution rather than at the mesh's. Null for a grayscale texture.
std::unique_ptr<GLTexture> m_color_tex;
const void *m_color_tex_source = nullptr;
float m_color_tex_smoothing = -1.f;
std::unique_ptr<GLTexture> m_color_tex;
std::weak_ptr<const std::vector<unsigned char>> m_color_tex_source;
float m_color_tex_smoothing = -1.f;
// Library textures the picker has shown at least once, keyed by file path (see LibraryTexture).
std::map<std::string, LibraryTexture> m_library_textures;
@@ -971,9 +1009,12 @@ private:
PatchUnwrap m_uv_editor_unwrap;
// When set, the panel is a free-floating window the user can drag anywhere (with a title bar to
// grab), instead of being pinned to the right of the gizmo toolbar. Persisted across gizmo
// open/close within a session, so the choice sticks while working.
// grab), instead of being pinned to the right of the gizmo toolbar. Kept in the application
// config rather than in the project: it describes the user's window, not the model, so opening
// someone else's project must not move their panel.
bool m_undocked = false;
// The config key behind m_undocked.
static constexpr const char *UNDOCKED_CONFIG_KEY = "texture_displacement_panel_undocked";
// Smooth scrolling for the panel body (everything between the header and the pinned Bake footer).
// ImGui jumps a fixed number of lines per wheel notch, which on tall layer cards reads as a hard
@@ -1016,6 +1057,9 @@ private:
std::vector<std::pair<wxString, wxString>> m_shortcuts_bucket_fill;
// Contains all shortcuts in the format of {shortcut, description}, e.g. {alt + _L("Left mouse button"), _L("Part_selection")}
std::vector<std::pair<wxString, wxString>> m_shortcuts_gap_fill;
// The two ways every slider in the panel takes a typed value. They hold whatever tool is selected,
// so they are tacked onto each of the lists above and stand on their own when no tool is active.
std::vector<std::pair<wxString, wxString>> m_shortcuts_sliders;
// Icons for the panel's icon buttons (tools, views, mapping, tiling, layer actions). Loaded through IconManager with
// the same colour/monochrome variants the main toolbar uses, so an inactive button shows the icon in
+13 -5
View File
@@ -420,7 +420,10 @@ void GLGizmosManager::refresh_on_off_state()
void GLGizmosManager::reset_all_states()
{
if (! m_enabled || m_serializing)
// The restore/new-project event can run before deferred OpenGL resource
// loading has populated the gizmo list. There is nothing to reset until
// initialization completes.
if (! m_enabled || m_serializing || m_gizmos.empty())
return;
const EType current = get_current_type();
@@ -435,6 +438,9 @@ void GLGizmosManager::reset_all_states()
bool GLGizmosManager::open_gizmo(EType type)
{
if (m_gizmos.empty())
return false;
int idx = static_cast<int>(type);
// re-open same type cause closing
@@ -522,7 +528,7 @@ bool GLGizmosManager::is_running() const
bool GLGizmosManager::open_gizmo_by_shortcut(Shortcut shortcut)
{
if (!m_enabled)
if (!m_enabled || m_gizmos.empty())
return false;
// The text tool opens without a selection because it creates its own object.
@@ -1427,10 +1433,12 @@ void GLGizmosManager::update_hover_state(const EType &type)
bool GLGizmosManager::activate_gizmo(EType type)
{
assert(!m_gizmos.empty());
// already activated
if (m_current == type) return true;
if (m_current == type)
return true;
if (m_gizmos.empty())
return false;
if (m_current != Undefined) {
// clean up previous gizmo
@@ -925,8 +925,9 @@ void GizmoObjectManipulation::do_render_move_window(ImGuiWrapper *imgui_wrapper,
m_last_active_item = current_active_id;
last_move_input_window_width = ImGui::GetWindowWidth();
ImGui::PopStyleVar();
imgui_wrapper->end();
ImGui::PopStyleVar(2);
ImGui::PopStyleVar();
ImGuiWrapper::pop_toolbar_style();
}
@@ -1131,10 +1132,11 @@ void GizmoObjectManipulation::do_render_rotate_window(ImGuiWrapper *imgui_wrappe
m_last_active_item = current_active_id;
last_rotate_input_window_width = ImGui::GetWindowWidth();
ImGui::PopStyleVar();
imgui_wrapper->end();
// BBS
ImGui::PopStyleVar(2);
ImGui::PopStyleVar();
ImGuiWrapper::pop_toolbar_style();
}
+90 -119
View File
@@ -1,16 +1,13 @@
#include "ImageGrid.h"
#include "Printer/PrinterFileSystem.h"
#include "Printer/BambuFileGridModel.h"
#include "wxExtensions.hpp"
#include "Widgets/Label.hpp"
#include "I18N.hpp"
#include "GUI_App.hpp"
#include "GUI.hpp"
#include <slic3r/GUI/Printer/IFileGridModel.h>
#include <utility>
#include <boost/smart_ptr/shared_ptr.hpp>
#include <boost/smart_ptr/make_shared_object.hpp>
#include <wx/colour.h>
#include <cstddef>
#include <wx/dcclient.h>
@@ -79,25 +76,19 @@ ImageGrid::ImageGrid(wxWindow * parent)
void ImageGrid::SetFileSystem(boost::shared_ptr<PrinterFileSystem> file_sys)
{
if (m_file_sys) {
m_file_sys->Unbind(EVT_MODE_CHANGED, &ImageGrid::changedEvent, this);
m_file_sys->Unbind(EVT_FILE_CHANGED, &ImageGrid::changedEvent, this);
m_file_sys->Unbind(EVT_THUMBNAIL, &ImageGrid::changedEvent, this);
m_file_sys->Unbind(EVT_DOWNLOAD, &ImageGrid::changedEvent, this);
}
m_file_sys = file_sys;
m_model.reset();
if (file_sys) {
m_model = boost::make_shared<BambuFileGridModel>(file_sys);
m_model->SetChangeHandler([this](FileGridChange c) { onModelChange(c); });
if (m_file_sys) {
m_file_sys->Bind(EVT_MODE_CHANGED, &ImageGrid::changedEvent, this);
m_file_sys->Bind(EVT_FILE_CHANGED, &ImageGrid::changedEvent, this);
m_file_sys->Bind(EVT_THUMBNAIL, &ImageGrid::changedEvent, this);
m_file_sys->Bind(EVT_DOWNLOAD, &ImageGrid::changedEvent, this);
}
m_row_count = 0;
m_col_count = 1;
m_row_offset = 0;
m_scroll_offset = 0;
UpdateFileSystem();
}
void ImageGrid::SetModel(boost::shared_ptr<IFileGridModel> model)
{
m_file_sys.reset();
m_model = std::move(model);
if (m_model)
m_model->SetChangeHandler([this](FileGridChange c) { onModelChange(c); });
m_row_count = 0;
m_col_count = 1;
m_row_offset = 0;
@@ -107,7 +98,7 @@ void ImageGrid::SetModel(boost::shared_ptr<IFileGridModel> model)
void ImageGrid::SetStatus(ScalableBitmap const & icon, wxString const &msg)
{
int code = m_model ? m_model->GetLastError() : 1;
int code = m_file_sys ? m_file_sys->GetLastError() : 1;
m_status_icon = icon;
m_status_msg = wxString::Format(msg, code);
BOOST_LOG_TRIVIAL(info) << "ImageGrid::SetStatus: " << m_status_msg.ToUTF8().data();
@@ -116,26 +107,26 @@ void ImageGrid::SetStatus(ScalableBitmap const & icon, wxString const &msg)
void Slic3r::GUI::ImageGrid::SetFileType(int type, std::string const &storage)
{
if (!m_model)
if (!m_file_sys)
return;
m_model->SetFileType((FileGridType) type, storage);
m_file_sys->SetFileType((PrinterFileSystem::FileType) type, storage);
}
void Slic3r::GUI::ImageGrid::SetGroupMode(int mode)
{
if (!m_model)
if (!m_file_sys)
return;
if (m_model->GetCount() == 0) {
m_model->SetGroupMode((FileGridGroup) mode);
if (m_file_sys->GetCount() == 0) {
m_file_sys->SetGroupMode((PrinterFileSystem::GroupMode) mode);
return;
}
wxSize size = GetClientSize();
int index = (m_row_offset + 1 < m_row_count || m_row_count == 0)
? m_row_offset / 4 * m_col_count
: ((m_model->GetCount() + m_col_count - 1) / m_col_count - (size.y + m_border_size.GetHeight() - 1) / m_cell_size.GetHeight()) * m_col_count;
time_t time = m_model->GetFile(index).time;
m_model->SetGroupMode((FileGridGroup) mode);
index = m_model->GetIndexAtTime(time);
: ((m_file_sys->GetCount() + m_col_count - 1) / m_col_count - (size.y + m_border_size.GetHeight() - 1) / m_cell_size.GetHeight()) * m_col_count;
auto & file = m_file_sys->GetFile(index);
m_file_sys->SetGroupMode((PrinterFileSystem::GroupMode) mode);
index = m_file_sys->GetIndexAtTime(file.time);
// UpdateFileSystem(); call by changed event
m_row_offset = index / m_col_count * 4;
if (m_row_offset >= m_row_count)
@@ -146,8 +137,8 @@ void Slic3r::GUI::ImageGrid::SetGroupMode(int mode)
void Slic3r::GUI::ImageGrid::SetSelecting(bool selecting)
{
m_selecting = selecting;
if (m_model)
m_model->SelectAll(false);
if (m_file_sys)
m_file_sys->SelectAll(false);
Refresh();
}
@@ -175,14 +166,14 @@ void Slic3r::GUI::ImageGrid::Rescale()
void Slic3r::GUI::ImageGrid::Select(size_t index)
{
if (m_selecting) {
m_model->ToggleSelect(index);
m_file_sys->ToggleSelect(index);
Refresh();
return;
}
if (m_model->GetGroupMode() == FileGridGroup::All) {
if (m_file_sys->GetGroupMode() == PrinterFileSystem::G_NONE) {
return;
}
index = m_model->EnterSubGroup(index);
index = m_file_sys->EnterSubGroup(index);
// UpdateFileSystem(); call by changed event
m_row_offset = index / m_col_count * 4;
if (m_row_offset >= m_row_count)
@@ -202,9 +193,9 @@ void Slic3r::GUI::ImageGrid::DoAction(size_t index, int action)
void Slic3r::GUI::ImageGrid::UpdateFileSystem()
{
if (!m_model) return;
if (m_model->GetFileType() < FileGridType::Model) {
if (m_model->GetGroupMode() == FileGridGroup::All) {
if (!m_file_sys) return;
if (m_file_sys->GetFileType() < PrinterFileSystem::F_MODEL) {
if (m_file_sys->GetGroupMode() == PrinterFileSystem::G_NONE) {
m_cell_size.Set(396, 228);
m_border_size.Set(384, 216);
}
@@ -225,10 +216,10 @@ void Slic3r::GUI::ImageGrid::UpdateFileSystem()
void ImageGrid::UpdateLayout()
{
if (!m_model) return;
if (!m_file_sys) return;
wxSize size = GetClientSize();
wxSize title_mask_size{0, 60 * em_unit(this) / 10};
if (m_model->GetGroupMode() == FileGridGroup::All) {
if (m_file_sys->GetGroupMode() == PrinterFileSystem::G_NONE) {
title_mask_size.y = 20 * em_unit(this) / 10;
size.y -= title_mask_size.y;
}
@@ -236,7 +227,7 @@ void ImageGrid::UpdateLayout()
int cell_height = m_cell_size.GetHeight();
int ncol = (size.GetWidth() - cell_width + m_border_size.GetWidth()) / cell_width;
if (ncol <= 0) ncol = 1;
int total_height = (m_model->GetCount() + ncol - 1) / ncol * cell_height + cell_height - m_border_size.GetHeight();
int total_height = (m_file_sys->GetCount() + ncol - 1) / ncol * cell_height + cell_height - m_border_size.GetHeight();
int nrow = (total_height - size.GetHeight() + cell_height / 4 - 1) / (cell_height / 4);
m_row_offset = m_row_offset * m_col_count / ncol;
m_col_count = ncol;
@@ -245,7 +236,7 @@ void ImageGrid::UpdateLayout()
m_row_offset = m_row_count == 0 ? 0 : m_row_count - 1;
m_scroll_offset = 0;
// create mask
if (m_model->GetGroupMode() == FileGridGroup::All) {
if (m_file_sys->GetGroupMode() == PrinterFileSystem::G_NONE) {
title_mask_size.x = (m_col_count - 1) * m_cell_size.GetWidth() + m_border_size.GetWidth();
}
else {
@@ -261,7 +252,7 @@ void ImageGrid::UpdateLayout()
void Slic3r::GUI::ImageGrid::UpdateFocusRange()
{
if (!m_model) return;
if (!m_file_sys) return;
wxSize size = GetClientSize();
wxPoint off;
int index = firstItem(size, off);
@@ -270,21 +261,15 @@ void Slic3r::GUI::ImageGrid::UpdateFocusRange()
count += m_col_count;
off.y += m_cell_size.y;
}
m_model->SetFocusRange(index, count);
}
bool Slic3r::GUI::ImageGrid::hasSecondAction(const FileGridCard &card) const
{
return m_show_download || card.downloading ||
(m_model->GetFileType() == FileGridType::Model && card.printable);
m_file_sys->SetFocusRange(index, count);
}
std::pair<int, size_t> Slic3r::GUI::ImageGrid::HitTest(wxPoint const &pt)
{
if (!m_model)
if (!m_file_sys)
return {HIT_NONE, -1};
wxSize size = GetClientSize();
if (m_model->GetCount() == 0) {
if (m_file_sys->GetCount() == 0) {
if (wxRect({0, 0}, m_border_size).CenterIn(wxRect({0, 0}, size)).Contains(pt))
return {HIT_STATUS, 0};
return {HIT_NONE, -1};
@@ -302,19 +287,17 @@ std::pair<int, size_t> Slic3r::GUI::ImageGrid::HitTest(wxPoint const &pt)
index += m_col_count;
off.y -= m_cell_size.GetHeight();
}
if (index >= m_model->GetCount()) { return {HIT_NONE, -1}; }
if (index >= m_file_sys->GetCount()) { return {HIT_NONE, -1}; }
if (!m_content_rect.Contains(off)) { return {HIT_NONE, -1}; }
if (!m_selecting) {
wxRect hover_rect{0, m_content_rect.GetHeight() - m_buttons_background.GetHeight(), m_content_rect.GetWidth(), m_buttons_background.GetHeight()};
const FileGridCard &card = m_model->GetFile(index);
int btn = card.downloading && card.download_progress >= 0 ? 3 : 2;
if (m_model->GetFileType() == FileGridType::Model) {
if (m_show_download && card.printable)
auto & file = m_file_sys->GetFile(index);
int btn = file.IsDownload() && file.DownloadProgress() >= 0 ? 3 : 2;
if (m_file_sys->GetFileType() == PrinterFileSystem::F_MODEL) {
if (m_show_download)
btn = 3;
hover_rect.y -= m_content_rect.GetHeight() * 64 / 264;
}
if (!hasSecondAction(card))
btn = 1;
if (hover_rect.Contains(off.x, off.y)) {
return {HIT_ACTION, index * 4 + off.x * btn / hover_rect.GetWidth()};
} // Two buttons
@@ -332,12 +315,12 @@ void ImageGrid::mouseMoved(wxMouseEvent& event)
m_hit_item = hit.second;
if (hit.first == HIT_ITEM) {
SetToolTip({});
const FileGridCard &card = m_model->GetFile(hit.second);
if (auto title = card.title; !title.empty()) {
auto tip = wxString::Format(_L("File: %s\nTitle: %s\n"), from_u8(card.name), from_u8(title));
auto & file = m_file_sys->GetFile(hit.second);
if (auto title = file.Title(); !title.empty()) {
auto tip = wxString::Format(_L("File: %s\nTitle: %s\n"), from_u8(file.name), from_u8(title));
SetToolTip(tip);
} else {
SetToolTip(from_u8(card.name));
SetToolTip(from_u8(file.name));
}
} else
SetToolTip({});
@@ -389,9 +372,9 @@ void ImageGrid::mouseReleased(wxMouseEvent& event)
else if (m_hit_type == HIT_ACTION)
DoAction(m_hit_item / 4, m_hit_item & 3);
else if (m_hit_type == HIT_MODE)
SetGroupMode(static_cast<int>(2 - m_hit_item));
SetGroupMode(static_cast<PrinterFileSystem::GroupMode>(2 - m_hit_item));
else if (m_hit_type == HIT_STATUS)
m_model->Retry();
m_file_sys->Retry();
else
Refresh();
} else {
@@ -420,11 +403,20 @@ void ImageGrid::mouseWheelMoved(wxMouseEvent &event)
Refresh();
}
void Slic3r::GUI::ImageGrid::onModelChange(FileGridChange change)
void Slic3r::GUI::ImageGrid::changedEvent(wxCommandEvent& evt)
{
BOOST_LOG_TRIVIAL(debug) << "ImageGrid::onModelChange: " << (int) change;
if (change == FileGridChange::Files || change == FileGridChange::Mode)
evt.Skip();
BOOST_LOG_TRIVIAL(debug) << "ImageGrid::changedEvent: " << evt.GetEventType() << " index: " << evt.GetInt()
<< " name: " << evt.GetString().ToUTF8().data() << " extra: " << evt.GetExtraLong();
if (evt.GetEventType() == EVT_FILE_CHANGED) {
if (evt.GetInt() == -1)
m_file_sys->DownloadCheckFiles(wxGetApp().app_config->get("download_path"));
UpdateFileSystem();
}
else if (evt.GetEventType() == EVT_MODE_CHANGED)
UpdateFileSystem();
//else if (evt.GetEventType() == EVT_THUMBNAIL)
// RefreshRect(itemRect(evt.GetInt()), false);
else
Refresh();
}
@@ -439,7 +431,7 @@ void ImageGrid::paintEvent(wxPaintEvent& evt)
size_t Slic3r::GUI::ImageGrid::firstItem(wxSize const &size, wxPoint &off)
{
int size_y = size.y;
if (m_model->GetGroupMode() == FileGridGroup::All)
if (m_file_sys->GetGroupMode() == PrinterFileSystem::G_NONE)
size_y -= m_title_mask.GetHeight();
int offx = (size.x - (m_col_count - 1) * m_cell_size.GetWidth() - m_border_size.GetWidth()) / 2;
int offy = (m_row_offset + 1 < m_row_count || m_row_count == 0) ?
@@ -447,8 +439,8 @@ size_t Slic3r::GUI::ImageGrid::firstItem(wxSize const &size, wxPoint &off)
size_y - (size_y + m_border_size.GetHeight() - 1) / m_cell_size.GetHeight() * m_cell_size.GetHeight();
int index = (m_row_offset + 1 < m_row_count || m_row_count == 0) ?
m_row_offset / 4 * m_col_count :
((m_model->GetCount() + m_col_count - 1) / m_col_count - (size_y + m_border_size.GetHeight() - 1) / m_cell_size.GetHeight()) * m_col_count;
if (m_model->GetGroupMode() == FileGridGroup::All)
((m_file_sys->GetCount() + m_col_count - 1) / m_col_count - (size_y + m_border_size.GetHeight() - 1) / m_cell_size.GetHeight()) * m_col_count;
if (m_file_sys->GetGroupMode() == PrinterFileSystem::G_NONE)
offy += m_title_mask.GetHeight();
off = wxPoint{offx, offy};
return index;
@@ -542,7 +534,7 @@ void ImageGrid::render(wxDC& dc)
wxSize size = GetClientSize();
dc.SetPen(wxPen(GetBackgroundColour()));
dc.SetBrush(wxBrush(GetBackgroundColour()));
if (!m_model || m_model->GetCount() == 0) {
if (!m_file_sys || m_file_sys->GetCount() == 0) {
dc.DrawRectangle({ 0, 0, size.x, size.y });
if (!m_status_msg.IsEmpty()) {
auto si = m_status_icon.GetBmpSize();
@@ -570,7 +562,7 @@ void ImageGrid::render(wxDC& dc)
{
// Draw one line
wxPoint pt{off.x, off.y};
end = (index + m_col_count) < m_model->GetCount() ? index + m_col_count : m_model->GetCount();
end = (index + m_col_count) < m_file_sys->GetCount() ? index + m_col_count : m_file_sys->GetCount();
while (index < end) {
pt += m_content_rect.GetTopLeft();
// Draw content
@@ -579,7 +571,7 @@ void ImageGrid::render(wxDC& dc)
&ImageGrid::renderContent1,
&ImageGrid::renderContent2
};
(this->*contentRender[static_cast<int>(m_model->GetFileType())])(dc, pt, index, hit_image == index);
(this->*contentRender[m_file_sys->GetFileType()])(dc, pt, index, hit_image == index);
pt -= m_content_rect.GetTopLeft();
// Draw colume spacing at right
dc.DrawRectangle({pt.x + m_border_size.GetWidth(), pt.y, m_cell_size.GetWidth() - m_border_size.GetWidth(), m_border_size.GetHeight()});
@@ -596,13 +588,13 @@ void ImageGrid::render(wxDC& dc)
off.y += m_cell_size.GetHeight();
}
// Draw floating date range for non-group list
if (m_model->GetGroupMode() == FileGridGroup::All && m_model->GetCount() > 0) {
if (m_file_sys->GetGroupMode() == PrinterFileSystem::G_NONE && m_file_sys->GetCount() > 0) {
//dc.DrawBitmap(m_title_mask, {off.x, 0});
dc.DrawRectangle({off.x, 0}, m_title_mask.GetSize());
const FileGridCard & file1 = m_model->GetFile(start);
const FileGridCard & file2 = m_model->GetFile(end - 1);
auto date1 = wxDateTime((time_t) file1.time).Format(_L(TIME_FORMATS[static_cast<int>(m_model->GetGroupMode())]));
auto date2 = wxDateTime((time_t) file2.time).Format(_L(TIME_FORMATS[static_cast<int>(m_model->GetGroupMode())]));
auto & file1 = m_file_sys->GetFile(start);
auto & file2 = m_file_sys->GetFile(end - 1);
auto date1 = wxDateTime((time_t) file1.time).Format(_L(TIME_FORMATS[m_file_sys->GetGroupMode()]));
auto date2 = wxDateTime((time_t) file2.time).Format(_L(TIME_FORMATS[m_file_sys->GetGroupMode()]));
dc.SetFont(Label::Head_16);
dc.SetTextForeground(StateColor::darkModeColorFor("#262E30"));
dc.DrawText(date1 + " - " + date2, wxPoint{off.x, 2});
@@ -612,7 +604,7 @@ void ImageGrid::render(wxDC& dc)
dc.DrawRectangle({off.x, off.y, size.x - off.x * 2, size.y - off.y});
// Draw position bar
if (m_timer.IsRunning()) {
int total_height = (m_model->GetCount() + m_col_count - 1) / m_col_count * m_cell_size.GetHeight() + m_cell_size.GetHeight() - m_border_size.GetHeight();
int total_height = (m_file_sys->GetCount() + m_col_count - 1) / m_col_count * m_cell_size.GetHeight() + m_cell_size.GetHeight() - m_border_size.GetHeight();
if (total_height > size.y) {
int offset = (m_row_offset + 1 < m_row_count || m_row_count == 0) ? m_row_offset * (m_cell_size.GetHeight() / 4) : total_height - size.y;
wxRect rect = {size.x - 16, offset * size.y / total_height, 8,
@@ -626,35 +618,15 @@ void ImageGrid::render(wxDC& dc)
void Slic3r::GUI::ImageGrid::renderContent1(wxDC &dc, wxPoint const &pt, int index, bool hit)
{
bool selected = false;
const FileGridCard &card = m_model->GetFile(index, selected);
auto &file = m_file_sys->GetFile(index, selected);
// Draw thumbnail
if (card.thumbnail.IsOk()) {
if (m_model->preserve_thumbnail_aspect()) {
// Contain: fit the whole thumbnail and letterbox against the tile background.
float scale = std::min((float) m_content_rect.GetWidth() / card.thumbnail.GetWidth(),
(float) m_content_rect.GetHeight() / card.thumbnail.GetHeight());
int thumb_w = (int) (card.thumbnail.GetWidth() * scale);
int thumb_h = (int) (card.thumbnail.GetHeight() * scale);
int offx = (m_content_rect.GetWidth() - thumb_w) / 2;
int offy = (m_content_rect.GetHeight() - thumb_h) / 2;
auto brush = dc.GetBrush();
auto pen = dc.GetPen();
dc.SetBrush(StateColor::darkModeColorFor(0xEEEEEE));
dc.SetPen(StateColor::darkModeColorFor(0xEEEEEE));
dc.DrawRectangle(pt, m_content_rect.GetSize());
dc.SetUserScale(scale, scale);
dc.DrawBitmap(card.thumbnail, {(int) ((pt.x + offx) / scale), (int) ((pt.y + offy) / scale)});
dc.SetUserScale(1, 1);
dc.SetBrush(brush);
dc.SetPen(pen);
} else {
float hs = (float) m_content_rect.GetWidth() / card.thumbnail.GetWidth();
float vs = (float) m_content_rect.GetHeight() / card.thumbnail.GetHeight();
dc.SetUserScale(hs, vs);
dc.DrawBitmap(card.thumbnail, {(int) (pt.x / hs), (int) (pt.y / vs)});
dc.SetUserScale(1, 1);
}
if (m_model->GetGroupMode() != FileGridGroup::All) { dc.DrawBitmap(m_title_mask, pt); }
if (file.thumbnail.IsOk()) {
float hs = (float) m_content_rect.GetWidth() / file.thumbnail.GetWidth();
float vs = (float) m_content_rect.GetHeight() / file.thumbnail.GetHeight();
dc.SetUserScale(hs, vs);
dc.DrawBitmap(file.thumbnail, {(int) (pt.x / hs), (int) (pt.y / vs)});
dc.SetUserScale(1, 1);
if (m_file_sys->GetGroupMode() != PrinterFileSystem::G_NONE) { dc.DrawBitmap(m_title_mask, pt); }
}
bool show_download_state_always = true;
// Draw checked icon
@@ -662,14 +634,14 @@ void Slic3r::GUI::ImageGrid::renderContent1(wxDC &dc, wxPoint const &pt, int ind
dc.DrawBitmap(selected ? m_checked_icon.bmp() : m_unchecked_icon.bmp(), pt + wxPoint{10, 10});
// can't handle alpha
// dc.GradientFillLinear({pt.x, pt.y, m_border_size.GetWidth(), 60}, wxColour(0x6F, 0x6F, 0x6F, 0x99), wxColour(0x6F, 0x6F, 0x6F, 0), wxBOTTOM);
else if (m_model->GetGroupMode() == FileGridGroup::All) {
else if (m_file_sys->GetGroupMode() == PrinterFileSystem::G_NONE) {
wxString nonHoverText;
wxString secondAction = m_show_download ? _L("Download") : "";
wxString thirdAction;
int states = 0;
// Draw download progress
if (card.downloading) {
int progress = card.download_progress;
if (file.IsDownload()) {
int progress = file.DownloadProgress();
if (progress == -1) {
secondAction = _L("Cancel");
nonHoverText = _L("Download waiting...");
@@ -687,7 +659,7 @@ void Slic3r::GUI::ImageGrid::renderContent1(wxDC &dc, wxPoint const &pt, int ind
thirdAction = wxString::Format(L"%d%%...", progress);
}
}
if (m_model->GetFileType() == FileGridType::Model && card.printable) {
if (m_file_sys->GetFileType() == PrinterFileSystem::F_MODEL) {
if (secondAction != _L("Play"))
thirdAction = secondAction;
secondAction = _L_CONTEXT("Print", "Verb");
@@ -697,8 +669,7 @@ void Slic3r::GUI::ImageGrid::renderContent1(wxDC &dc, wxPoint const &pt, int ind
wxArrayString texts;
if (hit) {
texts.Add(_L("Delete"));
if (hasSecondAction(card))
texts.Add(secondAction);
texts.Add(secondAction);
if (!thirdAction.IsEmpty())
texts.Add(thirdAction);
renderButtons(dc, texts, rect, m_hit_type == HIT_ACTION ? m_hit_item & 3 : -1, states);
@@ -708,7 +679,7 @@ void Slic3r::GUI::ImageGrid::renderContent1(wxDC &dc, wxPoint const &pt, int ind
}
} else {
dc.SetTextForeground(*wxWHITE); // time text color
auto date = wxDateTime((time_t) card.time).Format(_L(TIME_FORMATS[static_cast<int>(m_model->GetGroupMode())]));
auto date = wxDateTime((time_t) file.time).Format(_L(TIME_FORMATS[m_file_sys->GetGroupMode()]));
dc.DrawText(date, pt + wxPoint{24, 16});
}
if (m_selecting && show_download_state_always)
@@ -717,7 +688,7 @@ void Slic3r::GUI::ImageGrid::renderContent1(wxDC &dc, wxPoint const &pt, int ind
void Slic3r::GUI::ImageGrid::renderContent2(wxDC &dc, wxPoint const &pt, int index, bool hit)
{
const FileGridCard &card = m_model->GetFile(index);
auto &file = m_file_sys->GetFile(index);
// Draw thumbnail & buttons
int h = m_content_rect.GetHeight() * 64 / 264;
m_content_rect.SetHeight(m_content_rect.GetHeight() - h);
@@ -740,14 +711,14 @@ void Slic3r::GUI::ImageGrid::renderContent2(wxDC &dc, wxPoint const &pt, int ind
auto em = em_unit(this);
wxRect rect{pt.x, pt.y + m_content_rect.GetHeight() - h, m_content_rect.GetWidth(), h / 2};
rect.Deflate(em, 0);
renderText2(dc, from_u8(card.name), rect);
renderText2(dc, from_u8(file.name), rect);
rect.Offset(0, h / 2);
rect.SetWidth(rect.GetWidth() / 2 - em);
dc.SetFont(Label::Body_13);
dc.SetTextForeground(StateColor::darkModeColorFor("#6B6B6B"));
renderIconText(dc, m_model_time_icon, card.time_text.empty() ? wxString("0m") : from_u8(card.time_text), rect);
renderIconText(dc, m_model_time_icon, file.Metadata("Time", "0m"), rect);
rect.Offset(m_content_rect.GetWidth() / 2, 0);
renderIconText(dc, m_model_weight_icon, card.weight_text.empty() ? wxString("0g") : from_u8(card.weight_text), rect);
renderIconText(dc, m_model_weight_icon, file.Metadata("Weight", "0g"), rect);
}
void Slic3r::GUI::ImageGrid::renderButtons(wxDC &dc, wxArrayString const &texts, wxRect const &rect2, size_t hit, int states)
+1 -8
View File
@@ -23,7 +23,6 @@
#include "Widgets/StateColor.hpp"
#include "wxExtensions.hpp"
#include "Printer/IFileGridModel.h"
class Button;
class Label;
@@ -45,8 +44,6 @@ public:
void SetFileSystem(boost::shared_ptr<PrinterFileSystem> file_sys);
void SetModel(boost::shared_ptr<IFileGridModel> model);
void SetStatus(ScalableBitmap const & icon, wxString const &msg);
boost::shared_ptr<PrinterFileSystem> GetFileSystem() { return m_file_sys; }
@@ -79,12 +76,9 @@ protected:
std::pair<int, size_t> HitTest(wxPoint const &pt);
// True when the hover bar shows a second action next to Delete.
bool hasSecondAction(const FileGridCard &card) const;
protected:
void onModelChange(FileGridChange change);
void changedEvent(wxCommandEvent& evt);
void paintEvent(wxPaintEvent& evt);
@@ -123,7 +117,6 @@ protected:
private:
boost::shared_ptr<PrinterFileSystem> m_file_sys;
boost::shared_ptr<IFileGridModel> m_model;
ScalableBitmap m_status_icon;
wxString m_status_msg;
+11 -25
View File
@@ -221,40 +221,26 @@ MediaFilePanel::MediaFilePanel(wxWindow * parent)
SetSelecting(false);
});
Bind(wxEVT_SHOW, &MediaFilePanel::on_show_hide, this);
// A Device sub-tab switch hides the page container (StoragePanel); a main tab
// switch hides MonitorPanel inside LazyPage. Neither event reaches the other,
// so watch every ancestor below the top-level frame.
for (wxWindow* w = GetParent(); w && !w->IsTopLevel(); w = w->GetParent()) {
w->Bind(wxEVT_SHOW, &MediaFilePanel::on_show_hide, this);
m_show_hosts.push_back(w);
}
auto onShowHide = [this](auto &e) {
e.Skip();
if (auto w = dynamic_cast<wxWindow *>(e.GetEventObject()); !w || w->IsBeingDeleted())
return;
CallAfter([this] {
auto fs = m_image_grid ? m_image_grid->GetFileSystem() : nullptr;
if (fs) IsShownOnScreen() ? fs->Start() : fs->Stop();
});
};
Bind(wxEVT_SHOW, onShowHide);
parent->GetParent()->Bind(wxEVT_SHOW, onShowHide);
m_lan_user = "bblp";
}
MediaFilePanel::~MediaFilePanel()
{
Unbind(wxEVT_SHOW, &MediaFilePanel::on_show_hide, this);
for (wxWindow* w : m_show_hosts)
if (w && !w->IsBeingDeleted())
w->Unbind(wxEVT_SHOW, &MediaFilePanel::on_show_hide, this);
m_show_hosts.clear();
UpdateByObj(nullptr);
}
void MediaFilePanel::on_show_hide(wxShowEvent& e)
{
e.Skip();
if (auto w = dynamic_cast<wxWindow*>(e.GetEventObject()); !w || w->IsBeingDeleted())
return;
CallAfter([this] {
auto fs = m_image_grid ? m_image_grid->GetFileSystem() : nullptr;
if (fs) IsShownOnScreen() ? fs->Start() : fs->Stop();
});
}
void MediaFilePanel::UpdateByObj(MachineObject* obj)
{
bool sdcard_state_changed = false;
-5
View File
@@ -11,7 +11,6 @@
#include <boost/smart_ptr/shared_ptr.hpp>
#include <cstddef>
#include <set>
#include <vector>
#include "GUI_Utils.hpp"
#include "wxExtensions.hpp"
@@ -54,8 +53,6 @@ public:
private:
void modeChanged(wxCommandEvent & e);
void on_show_hide(wxShowEvent & e);
void fetchUrl(boost::weak_ptr<PrinterFileSystem> fs);
void doAction(size_t index, int action);
@@ -102,8 +99,6 @@ private:
int m_last_mode = 0;
int m_last_type = 0;
std::set<int> m_last_errors;
std::vector<wxWindow*> m_show_hosts;
};

Some files were not shown because too many files have changed in this diff Show More