Compare commits

..
Author SHA1 Message Date
ExPikaPaka 4bc300933e Texture displacement: keep a tooltip on screen long enough to be seen
A tooltip appeared only on the next pixel of mouse movement after its wait
had finished. ImGui hides a window on the first frame it is submitted, to
measure it before showing it, and does so every time a tooltip reappears
because those windows are recycled (the Popup|Tooltip branch of Begin()).
So the frame a tooltip is decided on is never the frame it is seen on, and
one carrying no animation asked for nothing further - leaving it in its
hidden measuring frame until some other event happened to produce the next
one. Before the hover delay this never showed, because the tooltip was
submitted on every mouse-move frame and the movement supplied the second.

It now asks for frames until it has been drawn twice in a row.
2026-10-13 14:30:53 +02:00
ExPikaPaka 5cfd91a309 Texture displacement: no tooltip while a control is being used, and a wait
that finishes on its own

The hover delay only finished while the mouse was moving: a pointer resting
on a control produces no events, so nothing redrew the canvas and the wait
never reached its end - the tooltip appeared on the next pixel of movement
instead. It asked for its frames with schedule_extra_frame(), which only
arranges a timer whose event an idle event is assumed to follow, and
on_idle renders nothing unless the canvas was already dirty when it ran.
The wait and the animations now both use request_extra_frame(), which ends
on_idle with evt.RequestMore() and so obliges wx to deliver the next idle;
it is what 3DBed's fade uses. Asking for frames as fast as they come is
safe now that which frame is drawn comes from the clock rather than from
the frame count - a redraw that arrives early draws the same frame again.

Nothing explains a control while the control is being used, so no tooltip
appears while a slider is being dragged, a value typed into one, a combo
open or a layer card dragged. The pointer sits on the very thing being
edited, so a tooltip there covered the model exactly while the user was
watching it change. The full wait comes back afterwards rather than the
short one: having just let go of a slider is not a reason to be shown a
picture of it.

AnimatedTexture::texture_id() loses its time-to-next-frame out-parameter,
which nothing asks for any more.
2026-10-13 14:21:55 +02:00
ExPikaPaka db0e788d35 Texture displacement: tooltip timing on a real clock, and a hover delay
The animations crawled when left alone and raced while the mouse moved.
They were timed by ImGui::GetTime(), and nothing in OrcaSlicer ever sets
io.DeltaTime, so it keeps imgui's default of 1/60 per frame: imgui's clock
counts frames, not seconds. On a canvas that renders only when something
asks it to, that is not a clock. Playback now reads a steady_clock, so a
frame is due when it is due whatever the canvas is doing.

An animation also restarts when its tooltip opens again, not only when a
different one opens: a gap in the frames it was drawn on is what that
looks like from here.

Tooltips now wait for the pointer to rest on a control for 0.45 s. The
panel is dense and a tooltip lands on the model beside it, so with no wait
a sweep from the top of the panel down to Bake flashed a dozen large panels
over the scene. After one has just been up the wait drops to 0.1 s for 0.7
s, so reading several settings in a row stays quick - but not to zero, or
that same sweep would flash again once one tooltip had been seen. A pointer
resting on a control produces no events, so the wait asks for the frame it
will appear on itself.
2026-10-13 13:58:52 +02:00
ExPikaPaka d2ba88c93d Texture displacement: animations that play on their own, and art for the
rest of the panel

The animations advanced one frame per mouse movement and stood still
whenever the mouse did. schedule_extra_frame() only guarantees an idle
event at the moment the next frame is due; GLCanvas3D::on_idle() returns
without rendering anything unless something marked the canvas dirty by
then, and nothing had. set_dirty() on the gizmo is what on_idle reads,
through GLGizmoBase::update_items_state(), to find out there are frames
still to draw.

Thirty-odd more pictures, covering the paint tools, brush size, the
connected-area fill, the five views, the five projections, subdivision,
feature-adaptive refinement, remeshing, hard edges, slivers, geometry
smoothing, filament mixing and denoise.

The generator grew three more drawing languages beside the shaded relief,
each picked by what the setting is rather than by taste: a flat mesh of
triangles where the subject is the mesh or a tool, a section where the
subject is where the surface sits relative to where it was, and a flat ramp
of greys where the subject is which filament a spot ends up as. Lighting a
colour illustration or looking down on a vertical change would both add a
second thing moving between two pictures meant to differ in one.

Mapping is one ball with the checker laid on it five ways, which is the
whole of what choosing a projection does - except Unwrap, which is not a
projection onto the model and so is drawn as the flat layout it produces.
2026-10-13 13:53:06 +02:00
ExPikaPaka fc5ac6d2a5 Texture displacement: illustrate the settings that are a shape
Ten more tooltips get a picture: Depth, Tile size, Repeat/Mirror, Invert,
Smoothing, Edge fade and Blend as stills, Depth, Rotation and Midlevel as
animations. All drawn by scripts/gen_texture_displacement_tips.py from the
same arithmetic the feature uses, one motif throughout, so that whatever
differs between two of them is the setting and nothing else.

Midlevel is drawn in section instead of as a relief. Shading reads slopes,
so a height field moved up or down as a whole shades exactly as it did
before - and where the relief sits relative to the surface is the entire
meaning of that setting. From the side, against the bare surface, it is the
one thing visible.

A single picture is now capped at 10 em tall and centred in its column,
rather than drawn at the tooltip's full width, where a square one came out
as tall as the text beside it was long.
2026-10-13 13:33:05 +02:00
ExPikaPaka 64ddc1fbba Texture displacement: tooltips beside the panel, with illustrations
The panel is pinned to the right edge of the canvas, so a tooltip left to
follow the mouse covers the very control it explains. panel_tooltip()
places every tooltip in the gizmo to the left of the panel instead - or of
the texture picker, while a tooltip is coming from inside that popup - the
same placement the picker itself already uses.

A tooltip can now carry pictures under its text: stills, or GIFs that loop
while it is open. Several settings in this panel describe a shape rather
than a number, and a sentence about a shape is read twice and understood
once. Files live in resources/images/texture_displacement and are named by
a tooltip; one that is missing is skipped, so a tooltip without its art yet
is exactly the text tooltip it was before.

AnimatedTexture decodes a file once and uploads a GL texture per frame -
wxImage for stills, wxGIFDecoder for GIFs, whose frames are composited here
with the disposal method each one asks for. An animation restarts when its
tooltip opens and asks the canvas for a redraw timed to its next frame.

Tile and Rotation are wired up as the first two; scripts/
gen_texture_displacement_tips.py draws their relief from the same
arithmetic the feature uses. docs/HLSD/tooltip-illustrations.md covers the
rest.
2026-10-13 13:21:17 +02:00
149 changed files with 2246 additions and 9968 deletions
-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 -->
+131
View File
@@ -0,0 +1,131 @@
# Tooltip illustrations
The texture-displacement panel's tooltips can carry a picture under their text: a still image, or a
short animation that loops while the tooltip is open. Several settings in that panel describe a shape
rather than a number - what Rotation turns, what Tile repeats, what Invert turns inside out - and a
sentence about a shape is read twice and understood once.
## Where a tooltip is drawn
`GLGizmoTextureDisplacement::panel_tooltip()` replaces `ImGuiWrapper::tooltip()` throughout the panel.
It differs in two ways.
It places the tooltip beside the panel instead of under the mouse. The panel is pinned to the right
edge of the canvas, so a tooltip following the mouse covers the control it is explaining, and one
carrying a picture covers most of the panel with it. The tooltip goes to the left of
`m_tooltip_anchor_min`/`_max` - the panel's own screen rectangle, or the texture picker's while that
popup is up and the tooltip comes from inside it - and to the right only when there is no room on the
left. This is the placement the picker popup itself already uses.
ImGui normally keeps a tooltip glued to the mouse and ignores `SetNextWindowPos()`. Orca's imgui
branch honours a position set for a tooltip (see the `ImGuiWindowFlags_Tooltip` branch of `Begin()` in
`deps_src/imgui/imgui.cpp`) and still clamps the result inside the canvas, which is what keeps a tall
tooltip near the bottom edge on screen.
It also gives the tooltip a fixed width rather than one derived from its text, because a picture has
to be laid out against something, and a column of tooltips that are all one width is steadier to read.
## Where the pictures live
`resources/images/texture_displacement/`, as PNG, JPG, BMP or GIF. A tooltip names them by file:
```cpp
hover_tip(_u8L("Repeats the texture across the painted area. ..."),
{ { "tile_off.png", _L("Off") }, { "tile_on.png", _L("On") } });
```
Several in one tooltip are laid out side by side at equal widths, which is how a setting is shown as a
comparison - the same model at two values of it. The caption under each is translated; the file name
is not.
A file that is missing or will not decode is skipped, and a tooltip whose pictures are all missing is
exactly the text tooltip it was before. So the art for a tooltip can land after the tooltip does, and
a half-finished set never breaks the panel.
## When a tooltip appears
A tooltip waits for the pointer to rest on its control for 0.45 s. The panel is dense and the tooltip
lands on the model beside it, so with no wait a sweep from the top of the panel down to Bake flashes a
dozen large panels over the scene. Once one has just been up, the wait drops to 0.1 s for 0.7 s
afterwards, so reading several settings in a row stays quick - but not to zero, or that same sweep
would flash again the moment one tooltip had been seen.
No tooltip appears at all while a control is being used - a slider dragged, a value typed into one, a
combo open, a layer card being dragged. The pointer sits on the very thing being edited, so a tooltip
there covers the model exactly while the user is watching it change. The full wait comes back
afterwards rather than the short one: having just let go of a slider is not a reason to be shown a
picture of it.
A pointer resting on a control produces no events, and the canvas only renders when something asks it
to, so the wait has to ask for its own frames, and so does an animation once one is on screen. So does
a tooltip that has just appeared, animated or not: ImGui hides a window on the first frame it is
submitted while it measures it, and does so every time a tooltip reappears because those windows are
recycled, so the frame a tooltip is decided on is never the frame it is seen on. Both use
`GLCanvas3D::request_extra_frame()`, which ends `on_idle()` with `evt.RequestMore()` and so obliges wx
to deliver the next idle. `schedule_extra_frame()` is not enough on its own: it only arranges a timer
whose event an idle event is assumed to follow, and on_idle renders nothing unless the canvas was
already dirty when it ran. Asking for frames as fast as they come is safe here because which frame is
drawn comes from the clock, not from the frame count, so a redraw that arrives early simply draws the
same frame again.
## Decoding and playback
`slic3r/GUI/AnimatedTexture.hpp` decodes a file once and uploads one GL texture per frame. Stills go
through `wxImage`; GIFs go through `wxGIFDecoder`, whose raw palette indices are composited here frame
by frame, replaying the disposal method each frame asks for. That replay is what makes an animation
look continuous: a GIF frame is a sub-rectangle drawn over what came before it, so an animation that
only re-sends the pixels that changed draws as a flicker of disconnected patches without it.
A frame delay of 0 or 1 hundredths of a second is read as 100 ms, the convention browsers settle on,
so an animation authored against that plays at the speed it was authored at.
The animation restarts when its tooltip opens, rather than running off a free-running clock: these are
short loops of one setting going from one value to another, and joining one halfway through shows the
end of a change the user never saw the start of.
Which frame is drawn is measured on a real clock, not `ImGui::GetTime()`. Nothing in OrcaSlicer ever sets
`io.DeltaTime`, so it keeps imgui's default of 1/60 per frame and imgui's clock counts frames rather
than seconds. On a canvas that renders only when asked, that is not a clock at all: an animation driven
by it crawls while the canvas is idle and races while a moving mouse is rendering it flat out.
## Making one
`scripts/gen_texture_displacement_tips.py` draws the lot, in four languages, picked by what the
setting actually is:
- **A shaded relief**, for the settings that are about a pattern: Depth, Tile, Tile size, Rotation,
Repeat/Mirror, Smoothing, Edge fade, Invert, Blend and the Normal, Fast, Checker and Distortion
views. One motif - a ring inside a square frame - runs through all of them, because using one shape
everywhere is what lets two pictures be compared: whatever differs between them is the setting,
since nothing else is.
- **A flat mesh of triangles**, for the settings that are about the mesh or about a tool: the paint
tools, brush size, the connected-area fill, subdivision, feature-adaptive refinement, remeshing,
hard edges, slivers and the wireframe view. These carry the one accent colour in the set, meaning
"painted", because that is the only thing in such a picture that is not geometry.
- **A section**, for Midlevel and for smoothing the geometry. The reason generalises: shading reads
slopes, so a height field moved up or down as a whole shades identically to where it started, and
any setting whose meaning is *where the surface sits relative to where it was* is invisible from
above and has to be drawn from the side.
- **A flat ramp of greys**, for Mix filaments, Mixed colors and Denoise. Those are about which
filament a spot ends up as; lighting one would only add a second thing changing between two
pictures meant to differ in one.
The five Mapping pictures are one ball with a checker laid on it five ways, which is the whole of what
choosing a projection does - except Unwrap, which is not a projection onto the model at all and so is
drawn as the flat layout it produces.
Captures of the real tool are still the first choice for anything involving a real model or real
filament colour: Bake, the mixed-colour count on a photograph, Adjust placement, the frame window and
the UV editor's seam tool have no picture yet for that reason.
Either way:
- Square, around 256 px (192 for an animation, where every frame ships). The tooltip is about 20 em
wide, so that is roughly native size on a 100% display and downscales cleanly above it. A single
picture is capped at 10 em tall and centred, two side by side take half the width each.
- Neutral grey. These say what a shape does; colour in one reads as a third thing the setting does,
and has to work against both themes.
- Animations loop seamlessly and stay short - 16 frames at 100 ms, under about 150 KB. They sit in
`resources/`, which ships with every copy of the application. A sweep driven by a cosine over the
frame count loops with no step, since it starts and ends at the same value and the same rate.
- No text in the picture. Captions are translated; pixels are not.
+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 "
+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: 2026-07-26 11:14-0300\n"
"Last-Translator: Alexandre Folle de Menezes\n"
"Language-Team: Portuguese, Brazilian\n"
@@ -4132,15 +4132,6 @@ msgstr "Projetos Recentes"
msgid "Go to Plate"
msgstr "Ir para Placa"
msgid "Device"
msgstr "Dispositivo"
msgid "Device (Web)"
msgstr "Dispositivo (Web)"
msgid "Multi-device"
msgstr "Multi-dispositivo"
msgid "AMS Materials Setting"
msgstr "Configuração de Materiais AMS"
@@ -4191,9 +4182,6 @@ msgstr "Tem certeza de que deseja limpar as informações do filamento?"
msgid "You need to select the material type and color first."
msgstr "Você precisa selecionar o tipo e a cor do material primeiro."
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 insira um valor válido (K entre %.1f~%.1f)"
@@ -4202,12 +4190,6 @@ msgstr "Por favor insira um valor válido (K entre %.1f~%.1f)"
msgid "Please input a valid value (K in %.1f~%.1f, N in %.1f~%.1f)"
msgstr "Por favor insira um valor válido (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'"
@@ -6240,9 +6222,18 @@ msgstr "Preparar"
msgid "Preview"
msgstr "Pré-visualizar"
msgid "Device"
msgstr "Dispositivo"
msgid "Multi-device"
msgstr "Multi-dispositivo"
msgid "Project"
msgstr "Projeto"
msgid "Device (Web)"
msgstr "Dispositivo (Web)"
msgid "Yes"
msgstr "Sim"
@@ -6933,22 +6924,6 @@ msgstr "O arquivo .gcode.3mf não contém dados de G-code. Por favor, fatie com
msgid "File '%s' was lost! Please download it again."
msgstr "O arquivo '%s' foi perdido! Por favor, baixe-o novamente."
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"
@@ -10898,12 +10873,6 @@ msgstr "Cancelar Timelapse e Imprimir"
msgid "Clean Up"
msgstr "Limpar"
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 trabalho de impressão"
@@ -30390,10 +30359,6 @@ msgstr "Ativar Ferramentas de Desenvolvedor"
msgid "An error occurred loading %s"
msgstr "Ocorreu um erro ao carregar %s"
# AI Translated
msgid "/"
msgstr "/"
# AI Translated
#, boost-format
msgid "Load %1% to %2%"
@@ -32449,6 +32414,10 @@ msgstr ""
#~ msgid "An error occurred loading "
#~ msgstr "Ocorreu um erro ao carregar "
# AI Translated
#~ msgid "/"
#~ msgstr "/"
# AI Translated
#, c-format, boost-format
#~ msgid "Load %s to "
+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 "
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

+793
View File
@@ -0,0 +1,793 @@
#!/usr/bin/env python3
"""Draws the illustrations the texture-displacement tooltips show.
The panel's tooltips can carry a picture under their text (see GLGizmoTextureDisplacement's
panel_tooltip()). A good many of those settings - the ones that are a shape rather than a photograph -
are better drawn from the same arithmetic the feature uses than captured from the screen, which is
what this script does: it shades a height field exactly as a relief would catch the light, so
"what Rotation turns" and "what Tile repeats" are visible rather than described.
Settings that involve the model, the mesh or colour are not here: those want a capture of the real
tool. See docs/HLSD/tooltip-illustrations.md.
Output goes to resources/images/texture_displacement/. Run it after changing one of the patterns
below; nothing in the build depends on it.
python3 scripts/gen_texture_displacement_tips.py
Needs numpy and pillow.
"""
import os
import numpy as np
from PIL import Image, ImageDraw
SIZE = 256 # px, square - the panel draws these at whatever width the tooltip has
GIF_SIZE = 192 # animations are the same picture, smaller: every frame of one ships in resources/
RELIEF = 30.0 # how hard the shading reacts to a slope; purely cosmetic
OUT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "resources", "images", "texture_displacement")
# Neutral enough to sit in a tooltip in either theme: a dark ground, a mid grey for the flat surface
# and a cool near-white for the lit slopes.
SHADOW = np.array([0.13, 0.14, 0.15])
MIDTONE = np.array([0.40, 0.42, 0.43])
HIGHLIGHT = np.array([0.78, 0.84, 0.83])
LIGHT = np.array([-0.45, -0.62, 0.65])
LIGHT /= np.linalg.norm(LIGHT)
def grid(size=SIZE):
"""Unit coordinates, -0.5 .. 0.5, y down as in an image."""
a = (np.arange(size) + 0.5) / size - 0.5
return np.meshgrid(a, a)
def shade(height):
"""A height field in 0..1, lit as a relief: the picture the tooltip shows."""
# The slope has to mean the same thing at either size, so the gradient is taken per unit of the
# picture rather than per pixel - otherwise the smaller animations would come out twice as flat.
gy, gx = np.gradient(height * RELIEF * (height.shape[0] / float(SIZE)))
normal = np.dstack([-gx, -gy, np.ones_like(height)])
normal /= np.linalg.norm(normal, axis=2, keepdims=True)
lam = np.clip(normal @ LIGHT, 0.0, 1.0)
# Two ramps rather than one: the dark half separates the slopes, the lit half picks up the
# accent, and the flat ground lands in the middle of both.
low = SHADOW + (MIDTONE - SHADOW) * (lam / 0.5)[..., None]
high = MIDTONE + (HIGHLIGHT - MIDTONE) * ((lam - 0.5) / 0.5)[..., None]
rgb = np.where((lam < 0.5)[..., None], low, high)
# A touch of the height itself, so a tall feature still reads as tall where it is flat on top.
#
# Deliberately almost grey: these say what a shape does, and a relief catching the light is grey.
# A coloured one would read as a third thing the setting does, and the amount of colour would move
# with the slope - a pattern at 45 degrees would flash where the same pattern upright did not.
rgb = np.clip(rgb * (0.88 + 0.18 * height[..., None]), 0.0, 1.0)
return (rgb * 255.0 + 0.5).astype(np.uint8)
def vignette(rgb):
"""Rounds the picture off at its border, so it does not end in a hard rectangle."""
x, y = grid(rgb.shape[0])
edge = np.clip((0.5 - np.maximum(np.abs(x), np.abs(y))) / 0.045, 0.0, 1.0)
return (rgb * edge[..., None]).astype(np.uint8)
def save_png(name, rgb):
path = os.path.join(OUT_DIR, name)
Image.fromarray(vignette(rgb)).save(path, optimize=True)
print("wrote", os.path.normpath(path), os.path.getsize(path) // 1024, "KB")
def save_gif(name, frames, delay_ms):
path = os.path.join(OUT_DIR, name)
images = [Image.fromarray(vignette(f)).convert("P", palette=Image.ADAPTIVE, colors=24) for f in frames]
images[0].save(path, save_all=True, append_images=images[1:], duration=delay_ms, loop=0, optimize=True)
print("wrote", os.path.normpath(path), os.path.getsize(path) // 1024, "KB")
def smoothstep(edge0, edge1, x):
t = np.clip((x - edge0) / (edge1 - edge0), 0.0, 1.0)
return t * t * (3.0 - 2.0 * t)
def blur(a, radius):
"""Separable box blur, run twice - near enough a gaussian for a picture of a blur."""
for _ in range(2):
for axis in (0, 1):
padded = np.pad(a, [(radius, radius) if i == axis else (0, 0) for i in range(2)], mode="edge")
cumulative = np.cumsum(padded, axis=axis)
cumulative = np.concatenate([np.zeros_like(np.take(cumulative, [0], axis=axis)), cumulative], axis=axis)
n = a.shape[axis]
hi = np.take(cumulative, np.arange(n) + 2 * radius + 1, axis=axis)
lo = np.take(cumulative, np.arange(n), axis=axis)
a = (hi - lo) / (2.0 * radius + 1.0)
return a
# --- the patterns ------------------------------------------------------------------------------
#
# One motif runs through nearly all of them - a ring inside a square frame. Using the same shape for
# every setting is what lets the pictures be compared: whatever changes between two of them is the
# setting, because nothing else is different.
def _motif_cell(u, v):
"""The motif within one cell, u and v in -0.5 .. 0.5."""
r = np.hypot(u, v)
ring = np.clip(1.0 - np.abs(r - 0.26) / 0.085, 0.0, 1.0)
frame_d = np.maximum(np.abs(u), np.abs(v))
frame = np.clip(1.0 - np.abs(frame_d - 0.42) / 0.055, 0.0, 1.0)
return np.clip(np.maximum(ring, frame) ** 0.7, 0.0, 1.0)
def motif(repeats, size=SIZE):
"""The motif laid out `repeats` times across the picture."""
x, y = grid(size)
return _motif_cell((x * repeats) % 1.0 - 0.5, (y * repeats) % 1.0 - 0.5)
def decal(motif_scale=0.46):
"""One copy of the motif in the middle of a flat field - what Tile off leaves."""
x, y = grid()
u, v = x / motif_scale, y / motif_scale
inside = (np.maximum(np.abs(u), np.abs(v)) <= 0.5).astype(float)
return _motif_cell(u, v) * inside
def ridges(angle_deg, period=0.16, size=SIZE):
"""Parallel ridges at an angle: the simplest pattern whose direction is unmistakable."""
x, y = grid(size)
a = np.radians(angle_deg)
t = (x * np.cos(a) + y * np.sin(a)) / period
return 0.2 + 0.6 * (0.5 + 0.5 * np.sin(2.0 * np.pi * t))
def bosses(repeats, size=SIZE):
"""Rounded domes standing on a flat field - a shape with an unmistakable right way up."""
x, y = grid(size)
u = (x * repeats) % 1.0 - 0.5
v = (y * repeats) % 1.0 - 0.5
return smoothstep(0.36, 0.08, np.hypot(u, v))
def chevron_tiles(mirrored, repeats=3):
"""Cells of diagonal ridges, laid out repeated or mirrored.
Repeated, every cell starts the pattern over and the seam between two of them is a step; mirrored,
each cell is the reflection of its neighbour and the ridges meet across the seam as chevrons. That
seam is the whole of what the Repeat/Mirror pair does, and a motif symmetric in itself would hide
it entirely.
"""
def axis(t):
if not mirrored:
return (t * repeats) % 1.0
s = (t * repeats) % 2.0
return np.where(s < 1.0, s, 2.0 - s)
x, y = grid()
t = (axis(x + 0.5) + axis(y + 0.5)) * 1.5
return 0.22 + 0.56 * (0.5 + 0.5 * np.cos(2.0 * np.pi * t))
def painted_area(size=SIZE, radius=0.44):
"""A round painted area in the middle of bare surface, with a soft edge so it does not alias."""
x, y = grid(size)
return smoothstep(radius, radius - 1.5 / size, np.hypot(x, y))
def painted_patch(fade, size=SIZE):
"""The motif inside a painted area, either stopping at its edge or fading into it."""
x, y = grid(size)
r = np.hypot(x, y) / 0.44
amount = smoothstep(1.0, 0.55, r) if fade else 1.0
return motif(3, size) * painted_area(size) * amount
def profile_wave(size=SIZE, periods=2.5):
"""A one-dimensional height profile with clear highs, lows and a flat stretch between them."""
t = (np.arange(size) + 0.5) / size * periods * 2.0 * np.pi
h = 0.5 + 0.34 * np.cos(t) + 0.16 * np.cos(2.0 * t + 0.9)
return np.clip((h - h.min()) / (h.max() - h.min()), 0.0, 1.0)
def profile_view(height_1d, midlevel, amplitude=0.26, size=SIZE):
"""A side view of the surface: material below, air above, and the bare surface as a dashed line.
Shading cannot say how high something is - it reads slopes, and a height field moved up or down as
a whole has exactly the slopes it had before. Midlevel is entirely about where the texture sits
relative to the surface it is on, so it is drawn in section, where that is the one thing visible.
"""
base = 0.52 * size
surface = base - (height_1d - midlevel) * amplitude * size
rows = np.arange(size)[:, None]
covered = np.clip(rows - surface[None, :] + 0.5, 0.0, 1.0) # 1 where there is material
air = np.array([0.11, 0.12, 0.13])
material = np.array([0.44, 0.46, 0.47])
rgb = air * (1.0 - covered)[..., None] + material * covered[..., None]
# The lit top face, and the bare surface the texture is measured against.
edge = np.clip(1.0 - np.abs(rows - surface[None, :]) / 1.6, 0.0, 1.0)
rgb = rgb * (1.0 - edge[..., None]) + np.array([0.80, 0.86, 0.85]) * edge[..., None]
dash = ((np.arange(size) // 7) % 2 == 0)[None, :]
line = (np.abs(rows - base) < 0.9) & dash
rgb[line] = np.array([0.58, 0.62, 0.62])
return (np.clip(rgb, 0.0, 1.0) * 255.0 + 0.5).astype(np.uint8)
def dome(size=SIZE):
"""One broad swell filling the picture - the layer underneath, for the blend pictures."""
x, y = grid(size)
return smoothstep(0.52, 0.02, np.hypot(x, y))
# --- meshes, cursors and sections ---------------------------------------------------------------
#
# The second family of pictures. The settings above are about a pattern, and a shaded relief is what a
# pattern looks like; these are about a tool or about the mesh, where what matters is which triangles
# exist and which of them are painted. So they are drawn flat, the way the panel's own wireframe and
# paint overlays draw them, with one accent colour meaning "painted" and nothing else coloured.
SS = 3 # supersampling: PIL fills polygons without antialiasing, so draw big and shrink
INK = (0.62, 0.67, 0.67) # mesh edges
BARE = (0.28, 0.30, 0.31) # unpainted surface
PAINT = (0.20, 0.47, 0.45) # painted with the active layer, in the panel's own accent
CURSOR = (0.92, 0.95, 0.95)
MARGIN = 0.05 # keeps the mesh off the picture's border, where the vignette darkens it
def _px(p, size):
"""Unit coordinates to pixels, inside the margin."""
return ((MARGIN + p[0] * (1.0 - 2.0 * MARGIN)) * size, (MARGIN + p[1] * (1.0 - 2.0 * MARGIN)) * size)
def _rgb255(c):
return tuple(int(round(v * 255.0)) for v in c)
def raster(draw_fn, size=SIZE, background=BARE):
"""Runs draw_fn(draw, size_in_pixels) on a supersampled canvas and hands back the shrunk result.
`background` is a colour, or a picture already drawn by the relief half of this script - which is
how a mesh is shown sitting on the surface it refines.
"""
if isinstance(background, np.ndarray):
image = Image.fromarray(background).resize((size * SS, size * SS), Image.BILINEAR)
else:
image = Image.new("RGB", (size * SS, size * SS), _rgb255(background))
draw_fn(ImageDraw.Draw(image), size * SS)
return np.asarray(image.resize((size, size), Image.LANCZOS)).astype(np.uint8)
def tri_mesh(n, jitter=0.0, seed=1):
"""A square patch of triangles, optionally with its inner vertices nudged off the grid.
Jitter is not decoration: a perfectly regular grid reads as graph paper, and the point of these
pictures is a mesh over a surface.
"""
rng = np.random.default_rng(seed)
v = np.stack(np.meshgrid(np.linspace(0.0, 1.0, n + 1), np.linspace(0.0, 1.0, n + 1)), axis=-1)
if jitter:
v[1:n, 1:n] += (rng.random((n - 1, n - 1, 2)) - 0.5) * (jitter / n)
tris = []
for i in range(n):
for j in range(n):
tris.append((v[i, j], v[i, j + 1], v[i + 1, j + 1]))
tris.append((v[i, j], v[i + 1, j + 1], v[i + 1, j]))
return tris
def centroids(tris):
return np.array([np.mean(t, axis=0) for t in tris])
def neighbours(tris):
"""Which triangles share an edge with which, keyed on the rounded vertex positions."""
edges = {}
for i, t in enumerate(tris):
for a, b in ((0, 1), (1, 2), (2, 0)):
key = tuple(sorted((tuple(np.round(t[a], 6)), tuple(np.round(t[b], 6)))))
edges.setdefault(key, []).append(i)
out = [[] for _ in tris]
for shared in edges.values():
if len(shared) == 2:
out[shared[0]].append(shared[1])
out[shared[1]].append(shared[0])
return out
def draw_mesh(draw, size, tris, painted=None, edge=INK, edge_width=0.7):
fill_bare, fill_paint = _rgb255(BARE), _rgb255(PAINT)
for i, t in enumerate(tris):
draw.polygon([_px(p, size) for p in t],
fill=fill_paint if painted is not None and painted[i] else fill_bare,
outline=_rgb255(edge), width=max(1, int(edge_width * SS)))
def draw_brush(draw, size, centre, radius, filled=False):
"""The brush cursor: a ring, which is how the gizmo draws it on the model."""
c = _px(centre, size)
r = radius * (1.0 - 2.0 * MARGIN) * size
if filled:
draw.ellipse([c[0] - r, c[1] - r, c[0] + r, c[1] + r], fill=(255, 255, 255, 40))
draw.ellipse([c[0] - r, c[1] - r, c[0] + r, c[1] + r], outline=_rgb255(CURSOR), width=max(1, int(1.6 * SS)))
def draw_pointer(draw, size, at):
"""A small arrow, for the tools that are clicked rather than dragged."""
x, y = _px(at, size)
k = 0.055 * size
draw.polygon([(x, y), (x, y + 1.45 * k), (x + 0.38 * k, y + 1.05 * k),
(x + 0.62 * k, y + 1.55 * k), (x + 0.90 * k, y + 1.40 * k),
(x + 0.66 * k, y + 0.92 * k), (x + 1.05 * k, y + 0.84 * k)],
fill=_rgb255(CURSOR), outline=(30, 32, 34), width=max(1, int(0.9 * SS)))
def stroke_path(t):
"""The path a demonstrated brush stroke follows, t in 0..1."""
return np.array([0.14 + 0.72 * t, 0.46 + 0.22 * np.sin(2.0 * np.pi * t * 1.1)])
def brush_frames(size, erase, steps=14, radius=0.115, hold=3):
tris = tri_mesh(9, jitter=0.55)
cents = centroids(tris)
painted = np.ones(len(tris), bool) if erase else np.zeros(len(tris), bool)
frames = []
for k in range(steps):
centre = stroke_path(k / (steps - 1.0))
hit = np.linalg.norm(cents - centre, axis=1) < radius
painted = painted & ~hit if erase else painted | hit
state = painted.copy()
frames.append(raster(lambda d, s, st=state, c=centre: (draw_mesh(d, s, tris, st), draw_brush(d, s, c, radius)), size))
frames += [frames[-1]] * hold
return frames
def face_frames(size, steps=9, hold=3):
"""The Face tool: one triangle per click, under a pointer."""
tris = tri_mesh(7, jitter=0.5, seed=4)
cents = centroids(tris)
order = np.argsort(np.linalg.norm(cents - np.array([0.5, 0.5]), axis=1))[:steps]
painted = np.zeros(len(tris), bool)
frames = []
for i in order:
painted[i] = True
state = painted.copy()
at = cents[i]
frames.append(raster(lambda d, s, st=state, a=at: (draw_mesh(d, s, tris, st), draw_pointer(d, s, a)), size))
frames += [frames[-1]] * hold
return frames
def flood_frames(size, creases, steps=12, hold=3, seed=7):
"""The Connected area tool: a flood from one click, stopped by the creases it may not cross.
`creases` is a list of x positions; a triangle pair straddling one cannot pass the fill between
them, which is what an edge sharper than the angle threshold does on the model.
"""
tris = tri_mesh(9, jitter=0.5, seed=seed)
cents = centroids(tris)
adj = neighbours(tris)
blocked = lambda i, j: any((cents[i][0] - x) * (cents[j][0] - x) < 0.0 for x in creases)
seed_tri = int(np.argmin(np.linalg.norm(cents - np.array([0.22, 0.5]), axis=1)))
painted = np.zeros(len(tris), bool)
painted[seed_tri] = True
frontier = [seed_tri]
frames = []
for _ in range(steps):
state = painted.copy()
frames.append(raster(lambda d, s, st=state: (draw_mesh(d, s, tris, st),
draw_creases(d, s, creases),
draw_pointer(d, s, cents[seed_tri])), size))
nxt = []
for i in frontier:
for j in adj[i]:
if not painted[j] and not blocked(i, j):
painted[j] = True
nxt.append(j)
frontier = nxt
frames += [frames[-1]] * hold
return frames
def draw_creases(draw, size, creases):
"""A fold too sharp for the fill to cross: drawn dark with a bright crest, like a real one."""
for x in creases:
a, b = _px((x, -0.02), size), _px((x, 1.02), size)
draw.line([a, b], fill=_rgb255((0.08, 0.09, 0.10)), width=max(1, int(4.0 * SS)))
draw.line([a, b], fill=_rgb255((0.97, 0.99, 0.99)), width=max(1, int(1.4 * SS)))
def split4(tri):
a, b, c = tri
ab, bc, ca = (a + b) * 0.5, (b + c) * 0.5, (c + a) * 0.5
return [(a, ab, ca), (ab, b, bc), (ca, bc, c), (ab, bc, ca)]
def refine(tris, density, max_depth, threshold=0.45):
"""Splits a triangle into four wherever `density` at its middle asks for it, up to max_depth."""
def go(tri, depth):
if depth >= max_depth:
return [tri]
centre = np.mean(tri, axis=0)
if density(centre[0], centre[1]) < threshold:
return [tri]
return [out for part in split4(tri) for out in go(part, depth + 1)]
return [out for tri in tris for out in go(tri, 0)]
def sampler(field):
"""Looks a numpy field up at unit coordinates, for use as a density."""
n = field.shape[0]
return lambda x, y: float(field[int(np.clip(y * n, 0, n - 1)), int(np.clip(x * n, 0, n - 1))])
def feature_density(size=192):
"""High where the pattern bends, which is where feature-adaptive refinement spends triangles.
One large copy of the motif rather than a field of them: the point is that the triangles gather
somewhere and not elsewhere, and a pattern that bends everywhere has no elsewhere to show.
"""
m = motif(1, size)
gy, gx = np.gradient(m)
g = blur(np.hypot(gx, gy), 2)
return g / max(g.max(), 1e-6)
def irregular_mesh(seed=3):
"""The uneven mesh a model arrives with: coarse here, fine there, nothing of one size."""
rng = np.random.default_rng(seed)
tris = tri_mesh(4, jitter=0.9, seed=seed)
out = []
for t in tris:
out.extend(split4(t) if rng.random() < 0.45 else [t])
final = []
for t in out:
final.extend(split4(t) if rng.random() < 0.3 else [t])
return final
def snap_to_crease(tris, crease, distance=0.07):
"""Pulls every vertex near the crease onto it, so mesh edges run along it instead of across."""
(x0, y0), (x1, y1) = crease
d = np.array([x1 - x0, y1 - y0], float)
d /= np.linalg.norm(d)
def snap(p):
t = np.dot(p - np.array([x0, y0]), d)
foot = np.array([x0, y0]) + t * d
return foot if np.linalg.norm(p - foot) < distance else p
return [tuple(snap(np.asarray(v, float)) for v in t) for t in tris]
def sliver_mesh(seed=5):
"""A mesh carrying the thin triangles refinement inherits from a model's own triangulation."""
rng = np.random.default_rng(seed)
tris = tri_mesh(7, jitter=0.4, seed=seed)
out = []
for t in tris:
if rng.random() < 0.45:
a, b, c = (np.asarray(v, float) for v in t)
mid = (b + c) * 0.5
a = mid + (a - mid) * 0.08 # all but flattened onto the opposite edge
out.append((a, b, c))
else:
out.append(t)
return out
def draw_profile(draw, size, height_1d, baseline=0.62, amplitude=0.30):
"""A side view of a surface, as a filled region under its profile."""
n = len(height_1d)
top = [(i / (n - 1.0) * size, (baseline - (height_1d[i] - 0.5) * amplitude) * size) for i in range(n)]
draw.polygon(top + [(size, size * 1.1), (0, size * 1.1)], fill=_rgb255((0.44, 0.46, 0.47)))
draw.line(top, fill=_rgb255((0.82, 0.88, 0.87)), width=max(1, int(1.8 * SS)))
def bayer(size, n=4):
"""An ordered dither matrix tiled over the picture, for the mixing illustrations."""
m = np.array([[0]])
while m.shape[0] < n:
m = np.block([[4 * m, 4 * m + 2], [4 * m + 3, 4 * m + 1]])
m = (m + 0.5) / m.size
return np.tile(m, (size // m.shape[0] + 1, size // m.shape[1] + 1))[:size, :size]
def levels_image(levels, dither=False, speckle=0.0, size=SIZE, seed=11):
"""A smooth ramp reduced to a handful of greys - what a bake does to a photograph's colours.
Flat rather than lit: this family of settings is about which filament a pixel ends up as, and a
relief would only add a second thing changing between two pictures that are meant to differ in
one.
"""
x, y = grid(size)
ramp = np.clip(0.5 + 0.5 * np.cos(2.0 * np.pi * (x + 0.25 * y)), 0.0, 1.0)
noise = bayer(size) - 0.5 if dither else 0.0
q = np.round(np.clip(ramp + noise / max(levels - 1, 1), 0.0, 1.0) * (levels - 1)) / (levels - 1)
if speckle > 0.0:
rng = np.random.default_rng(seed)
hit = rng.random((size, size)) < speckle
q = np.where(hit, rng.integers(0, levels, (size, size)) / (levels - 1), q)
grey = 0.16 + 0.62 * q
return (np.dstack([grey, grey * 1.03, grey * 1.02]).clip(0.0, 1.0) * 255.0 + 0.5).astype(np.uint8)
def draw_mesh_lines(draw, size, tris, colour=(0.90, 0.94, 0.94), width=0.8):
"""Only the edges, for drawing a mesh over a picture of the surface it sits on."""
for t in tris:
pts = [_px(p, size) for p in t]
draw.line(pts + [pts[0]], fill=_rgb255(colour), width=max(1, int(width * SS)))
def draw_line(draw, size, segment, colour=(0.97, 0.99, 0.99)):
a, b = _px(segment[0], size), _px(segment[1], size)
draw.line([a, b], fill=_rgb255((0.08, 0.09, 0.10)), width=max(1, int(4.0 * SS)))
draw.line([a, b], fill=_rgb255(colour), width=max(1, int(1.4 * SS)))
def posterize(rgb, levels):
"""The shaded picture reduced to flat bands - what the fast preview's per-fragment quantizing
does to a surface that the real preview shades smoothly."""
v = rgb.astype(float) / 255.0
return (np.round(v * (levels - 1)) / (levels - 1) * 255.0 + 0.5).astype(np.uint8)
def with_checker(rgb, squares=10, strength=0.30, warp=False):
"""A checker laid over the surface, evenly or stretched.
Even squares are what the Checker view is for - a texture laid out without stretching. The warped
version is the same squares through a mapping that does stretch, which is what the Distortion view
is there to find: cells crowded in one place and pulled apart in another.
"""
size = rgb.shape[0]
x, y = grid(size)
u, v = (np.sign(x) * np.abs(2.0 * x) ** 2.4 * 0.5, y * (1.0 + 0.9 * np.abs(x))) if warp else (x, y)
cells = (np.floor((u + 0.5) * squares) + np.floor((v + 0.5) * squares)) % 2.0
return (rgb * (1.0 - strength * cells)[..., None]).astype(np.uint8)
def projected_sphere(kind, size=SIZE, squares=9):
"""A ball with a checker laid on it by one of the panel's projections.
One shape, five mappings: what differs between the pictures is only where the squares go, which is
the whole of what choosing a projection does. A sphere is the shape that shows it - every one of
these mappings is well behaved on a flat face and tells itself apart on a curved one.
"""
a = (np.arange(size) + 0.5) / size * 2.24 - 1.12
x, y = np.meshgrid(a, a)
inside = x * x + y * y <= 1.0
z = np.sqrt(np.clip(1.0 - x * x - y * y, 0.0, 1.0))
lam = np.clip(x * LIGHT[0] + y * LIGHT[1] + z * LIGHT[2], 0.0, 1.0)
rgb = SHADOW + (HIGHLIGHT - SHADOW) * (lam ** 0.85)[..., None]
around = np.arctan2(x, z) / (2.0 * np.pi) + 0.5
if kind == "sphere":
u, v = around, np.arccos(np.clip(-y, -1.0, 1.0)) / np.pi
elif kind == "cylinder":
u, v = around, (y + 1.0) * 0.5
elif kind == "view":
u, v = (x + 1.0) * 0.5, (y + 1.0) * 0.5
elif kind == "cube":
axis = np.argmax(np.abs(np.dstack([x, y, z])), axis=2)
u = np.where(axis == 0, z, x)
v = np.where(axis == 1, z, y)
u, v = (u + 1.0) * 0.5, (v + 1.0) * 0.5
else:
raise ValueError(kind)
cells = (np.floor(u * squares * 2.0) + np.floor(v * squares)) % 2.0
rgb = rgb * (1.0 - 0.34 * cells)[..., None]
rgb = np.where(inside[..., None], rgb, np.array([0.13, 0.14, 0.15]))
return (np.clip(rgb, 0.0, 1.0) * 255.0 + 0.5).astype(np.uint8)
def uv_islands(size=SIZE, squares=9, seed=12):
"""The flat layout an unwrap produces: the surface cut into islands and laid out side by side.
Not a projection onto the model like the other four, so not drawn on one - an unwrap is the model
carrying its own coordinates, and this is what those look like.
"""
rng = np.random.default_rng(seed)
x, y = grid(size)
cells = (np.floor((x + 0.5) * squares * 2.0) + np.floor((y + 0.5) * squares)) % 2.0
checker = (np.array([0.40, 0.43, 0.43]) + np.array([0.22, 0.24, 0.24]) * (1.0 - cells)[..., None])
def blob(centre, radius, lobes):
angles = np.linspace(0.0, 2.0 * np.pi, 26, endpoint=False)
wobble = 1.0 + 0.22 * np.sin(lobes * angles + rng.random() * 6.0)
return [(centre[0] + np.cos(t) * radius * w, centre[1] + np.sin(t) * radius * w)
for t, w in zip(angles, wobble)]
islands = [blob((0.31, 0.32), 0.21, 3), blob((0.72, 0.30), 0.17, 4),
blob((0.34, 0.74), 0.17, 4), blob((0.72, 0.71), 0.20, 3)]
mask = np.asarray(raster(lambda d, px: [d.polygon([(p[0] * px, p[1] * px) for p in island], fill=(255, 255, 255))
for island in islands],
size, background=(0.0, 0.0, 0.0))).astype(float)[..., 0] / 255.0
rgb = checker * mask[..., None] + np.array([0.13, 0.14, 0.15]) * (1.0 - mask)[..., None]
out = (np.clip(rgb, 0.0, 1.0) * 255.0 + 0.5).astype(np.uint8)
return raster(lambda d, px: [d.polygon([(p[0] * px, p[1] * px) for p in island], outline=_rgb255(INK),
width=max(1, int(1.1 * SS)))
for island in islands],
size, background=out)
def main():
os.makedirs(OUT_DIR, exist_ok=True)
# Tile: one copy against the pattern filling the painted area.
save_png("tile_off.png", shade(decal()))
save_png("tile_on.png", shade(motif(3)))
# Tile size: how wide one copy is.
save_png("tile_size_large.png", shade(motif(2)))
save_png("tile_size_small.png", shade(motif(5)))
# Repeat against Mirror, where the seam between two copies is the whole point.
save_png("tile_repeat.png", shade(chevron_tiles(mirrored=False)))
save_png("tile_mirror.png", shade(chevron_tiles(mirrored=True)))
# Invert: the negative of the image. Domes rather than the ring motif, which is close enough to
# its own negative that inverting it would look like nothing had happened.
save_png("invert_off.png", shade(bosses(3)))
save_png("invert_on.png", shade(1.0 - bosses(3)))
# Smoothing: a stepped image against the same one blurred before it is used. Domes again, where
# the steps show up as contour rings - what a photograph with too few grey levels does.
stepped = np.floor(bosses(3) * 5.0) / 4.0
save_png("smoothing_off.png", shade(np.clip(stepped, 0.0, 1.0)))
save_png("smoothing_on.png", shade(np.clip(blur(stepped, max(2, SIZE // 40)), 0.0, 1.0)))
# Edge fade: relief that stops at the edge of the painted area against relief that sinks into it.
save_png("edge_fade_off.png", shade(painted_patch(fade=False)))
save_png("edge_fade_on.png", shade(painted_patch(fade=True)))
# Blend: piling relief on top of the layer below against using it as a mask over it. Add keeps
# both shapes, Multiply keeps the motif only where the layer under it is high.
base = dome()
save_png("blend_add.png", shade(np.clip(0.55 * base + 0.42 * motif(3), 0.0, 1.0)))
save_png("blend_multiply.png", shade(base * motif(3)))
# Rotation: a half turn brings parallel ridges back onto themselves, so a single sweep loops with
# no jump and the file stays a fraction of the size a there-and-back one would be.
steps = 16
save_gif("rotation.gif", [shade(ridges(180.0 * i / steps, size=GIF_SIZE)) for i in range(steps)], delay_ms=100)
# Depth and Midlevel are both about where the relief sits relative to the surface it is on, so
# both are drawn as a painted area in the middle of bare surface, which is that reference. Without
# it neither would show: shading reads slopes, and a height field that moves up or down as a whole
# has exactly the same slopes it had before.
#
# A cosine over the frame count starts and ends at the same value with no step in between, so a
# single pass through it loops.
area = painted_area(GIF_SIZE)
pattern = motif(3, GIF_SIZE)
sweep = [0.5 - 0.5 * np.cos(2.0 * np.pi * i / steps) for i in range(steps)]
# Depth: the relief growing out of the surface and settling back flush with it.
frames = [shade(np.clip(0.5 + 0.45 * pattern * (0.08 + 0.92 * t) * area, 0.0, 1.0)) for t in sweep]
save_gif("depth.gif", frames, delay_ms=100)
# Midlevel: the grey that stays put, sweeping from "the whole texture pushes out" through
# "mid-grey stays where the surface is" to "the whole of it cuts in". Drawn in section - see
# profile_view() for why this one is not a relief like the rest.
wave = profile_wave(GIF_SIZE)
frames = [profile_view(wave, t, size=GIF_SIZE) for t in sweep]
save_gif("midlevel.gif", frames, delay_ms=100)
# The paint tools, each shown doing the one thing that tells it apart from the others.
save_gif("paint.gif", brush_frames(GIF_SIZE, erase=False), delay_ms=110)
save_gif("erase.gif", brush_frames(GIF_SIZE, erase=True), delay_ms=110)
save_gif("tool_face.gif", face_frames(GIF_SIZE), delay_ms=180)
save_gif("tool_smart_fill.gif", flood_frames(GIF_SIZE, creases=[0.63]), delay_ms=110)
# Brush size: the ring, and what it covers, over one size and back.
brush_tris = tri_mesh(9, jitter=0.55, seed=2)
brush_cents = centroids(brush_tris)
frames = []
for i in range(steps):
r = 0.07 + 0.16 * (0.5 - 0.5 * np.cos(2.0 * np.pi * i / steps))
under = np.linalg.norm(brush_cents - np.array([0.5, 0.5]), axis=1) < r
frames.append(raster(lambda d, sz, u=under, rr=r: (draw_mesh(d, sz, brush_tris, u),
draw_brush(d, sz, (0.5, 0.5), rr)), GIF_SIZE))
save_gif("cursor_size.gif", frames, delay_ms=90)
# Angle threshold: the same fill, let past one fold and then past both.
for name, creases in (("smart_fill_angle_low.png", [0.40, 0.70]), ("smart_fill_angle_high.png", [0.70])):
done = flood_frames(SIZE, creases=creases, steps=22, hold=0)[-1]
save_png(name, done)
# Whole model: every face painted, and every face cleared.
whole = tri_mesh(9, jitter=0.55, seed=2)
save_png("select_all.png", raster(lambda d, sz: draw_mesh(d, sz, whole, np.ones(len(whole), bool)), SIZE))
save_png("erase_all.png", raster(lambda d, sz: draw_mesh(d, sz, whole, np.zeros(len(whole), bool)), SIZE))
# The views the panel offers over the painted area.
relief = shade(motif(3))
save_png("view_fast.png", posterize(relief, 3))
# Checker and Distortion are about how the texture is laid out, not about the texture, so they go
# on a plain swell where the checker itself is what there is to look at.
plain = shade(0.25 + 0.5 * dome())
save_png("view_checker.png", with_checker(plain, squares=7, strength=0.40))
save_png("view_distortion.png", with_checker(plain, squares=7, strength=0.40, warp=True))
save_png("view_wireframe.png", raster(lambda d, sz: draw_mesh_lines(d, sz, tri_mesh(9, jitter=0.55, seed=2)),
SIZE, background=relief))
# The projections: one ball, the checker put on it five different ways.
for kind in ("cube", "cylinder", "sphere", "view"):
save_png("map_%s.png" % kind, projected_sphere(kind))
save_png("map_unwrap.png", uv_islands())
# Refinement: where the triangles go, and how many.
density_feature = sampler(feature_density())
coarse = tri_mesh(3)
save_png("adaptive_off.png", raster(lambda d, sz: draw_mesh(d, sz, refine(coarse, lambda x, y: 1.0, 2)), SIZE))
save_png("adaptive_on.png", raster(lambda d, sz: draw_mesh(d, sz, refine(coarse, density_feature, 3)), SIZE))
disc = sampler(painted_area(192, radius=0.34))
painted_mesh = refine(coarse, disc, 3)
save_png("painted_only_on.png",
raster(lambda d, sz: draw_mesh(d, sz, painted_mesh,
np.array([disc(*np.mean(t, axis=0)) > 0.5 for t in painted_mesh])), SIZE))
everywhere = refine(coarse, lambda x, y: 1.0, 3)
save_png("painted_only_off.png",
raster(lambda d, sz: draw_mesh(d, sz, everywhere,
np.array([disc(*np.mean(t, axis=0)) > 0.5 for t in everywhere])), SIZE))
# Subdivide: every triangle into four, a step at a time.
level = tri_mesh(2)
frames = []
for _ in range(4):
frames.append(raster(lambda d, sz, m=level: draw_mesh(d, sz, m), GIF_SIZE))
level = [out for t in level for out in split4(t)]
save_gif("subdivide.gif", frames, delay_ms=650)
# Remesh: the model's own uneven triangulation, rebuilt at one size.
save_png("remesh_before.png", raster(lambda d, sz: draw_mesh(d, sz, irregular_mesh()), SIZE))
save_png("remesh_after.png", raster(lambda d, sz: draw_mesh(d, sz, tri_mesh(8, jitter=0.35, seed=9)), SIZE))
# Hard edges: a fold the remesh runs across, against one it lays its edges along.
crease = ((0.06, 0.84), (0.94, 0.20))
across = tri_mesh(5, jitter=0.45, seed=6)
save_png("preserve_edges_off.png",
raster(lambda d, sz: (draw_mesh(d, sz, across), draw_line(d, sz, crease)), SIZE))
save_png("preserve_edges_on.png",
raster(lambda d, sz: (draw_mesh(d, sz, snap_to_crease(across, crease, 0.13)),
draw_line(d, sz, crease)), SIZE))
# Thin triangles, and the same mesh without them.
save_png("collapse_slivers_off.png", raster(lambda d, sz: draw_mesh(d, sz, sliver_mesh()), SIZE))
save_png("collapse_slivers_on.png", raster(lambda d, sz: draw_mesh(d, sz, tri_mesh(7, jitter=0.4, seed=5)), SIZE))
# Smoothing the geometry after the texture is in it: the steps relax out of the surface.
stepped_profile = np.floor(profile_wave(GIF_SIZE, periods=2.0) * 5.0) / 4.0
frames = []
for t in sweep:
h = stepped_profile.copy()
for _ in range(int(round(t * 6))):
h = blur(h[None, :], 2)[0]
frames.append(raster(lambda d, sz, hh=h: draw_profile(d, sz, hh), GIF_SIZE, background=(0.11, 0.12, 0.13)))
save_gif("geometry_smoothing.gif", frames, delay_ms=100)
# Mixing, how many mixes, and denoise - all about which filament a spot ends up as, so all flat.
save_png("mix_off.png", levels_image(4))
save_png("mix_on.png", levels_image(4, dither=True))
save_png("mixed_colors_few.png", levels_image(3, dither=True))
save_png("mixed_colors_many.png", levels_image(8, dither=True))
save_png("denoise_off.png", levels_image(5, dither=True, speckle=0.05))
save_png("denoise_on.png", levels_image(5, dither=True))
if __name__ == "__main__":
main()
-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>
+36 -111
View File
@@ -4102,11 +4102,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 +4112,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 +4132,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 +4170,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 +4182,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 +4257,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 +4264,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 +4279,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 +4290,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 +4729,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 +4753,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 +4766,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)
-3
View File
@@ -413,9 +413,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
+3 -14
View File
@@ -20,6 +20,8 @@ set(SLIC3R_GUI_SOURCES
GUI/Widgets/FilamentLoad.hpp
GUI/AboutDialog.cpp
GUI/AboutDialog.hpp
GUI/AnimatedTexture.cpp
GUI/AnimatedTexture.hpp
GUI/AmsMappingPopup.cpp
GUI/AmsMappingPopupUpdate.cpp
GUI/AmsMappingPopup.hpp
@@ -372,10 +374,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
@@ -467,11 +465,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 +778,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
@@ -929,7 +918,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
+240
View File
@@ -0,0 +1,240 @@
#include "AnimatedTexture.hpp"
#include <algorithm>
#include <cmath>
#include <cstddef>
#include <fstream>
#include <ios>
#include <iterator>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include <boost/filesystem/operations.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/log/trivial.hpp>
#include <boost/system/error_code.hpp>
#include <wx/animdecod.h>
#include <wx/gdicmn.h>
#include <wx/gifdecod.h>
#include <wx/image.h>
#include <wx/log.h>
#include <wx/mstream.h>
#include "slic3r/GUI/GLTexture.hpp"
namespace Slic3r::GUI {
namespace {
std::vector<unsigned char> read_file_bytes(const std::string &path)
{
std::ifstream ifs(path, std::ios::binary);
return std::vector<unsigned char>(std::istreambuf_iterator<char>(ifs), std::istreambuf_iterator<char>());
}
bool has_gif_signature(const std::vector<unsigned char> &bytes)
{
return bytes.size() > 6 && bytes[0] == 'G' && bytes[1] == 'I' && bytes[2] == 'F';
}
// wxImage keeps colour and alpha in two separate buffers, and a file with no alpha at all has none;
// OpenGL wants the four channels interleaved.
std::vector<unsigned char> image_to_rgba(const wxImage &image)
{
const size_t n = size_t(image.GetWidth()) * size_t(image.GetHeight());
const unsigned char *rgb = image.GetData();
const unsigned char *a = image.HasAlpha() ? image.GetAlpha() : nullptr;
std::vector<unsigned char> out(n * 4);
for (size_t i = 0; i < n; ++i) {
out[i * 4 + 0] = rgb[i * 3 + 0];
out[i * 4 + 1] = rgb[i * 3 + 1];
out[i * 4 + 2] = rgb[i * 3 + 2];
out[i * 4 + 3] = a != nullptr ? a[i] : 255;
}
return out;
}
// One decoded frame on its way to the GPU: the whole animation's area, already composited.
struct DecodedFrame
{
std::vector<unsigned char> rgba;
double delay; // seconds
};
// Every frame of a GIF, composited onto the animation's own canvas.
//
// A GIF frame is a sub-rectangle that is drawn over what came before it, and the one before says how
// to clean up after itself (leave it, erase it, put back what it covered). Without replaying that, an
// animation that only re-sends the pixels that changed - which is most of them - draws as a flicker of
// disconnected patches. wxGIFDecoder hands over the raw palette indices, which is what makes replaying
// it here straightforward: a transparent index means "keep what is underneath", nothing else does.
bool decode_gif(const std::vector<unsigned char> &bytes, int &width, int &height, std::vector<DecodedFrame> &out)
{
wxMemoryInputStream stream(bytes.data(), bytes.size());
wxGIFDecoder decoder;
// A truncated file still decodes everything up to the cut, which is worth showing.
const wxGIFErrorCode err = decoder.LoadGIF(stream);
if ((err != wxGIF_OK && err != wxGIF_TRUNCATED) || decoder.GetFrameCount() == 0)
return false;
const wxSize size = decoder.GetAnimationSize();
width = size.GetWidth();
height = size.GetHeight();
if (width <= 0 || height <= 0)
return false;
std::vector<unsigned char> canvas(size_t(width) * size_t(height) * 4, 0);
std::vector<unsigned char> saved;
for (unsigned int i = 0; i < decoder.GetFrameCount(); ++i) {
const wxSize frame_size = decoder.GetFrameSize(i);
const wxPoint frame_pos = decoder.GetFramePosition(i);
const wxAnimationDisposal disposal = decoder.GetDisposalMethod(i);
const unsigned char *indices = decoder.GetData(i);
const unsigned char *palette = decoder.GetPalette(i);
const int transparent = decoder.GetTransparentColourIndex(i);
if (disposal == wxANIM_TOPREVIOUS)
saved = canvas;
if (indices != nullptr && palette != nullptr)
for (int y = 0; y < frame_size.GetHeight(); ++y) {
const int cy = frame_pos.y + y;
if (cy < 0 || cy >= height)
continue;
for (int x = 0; x < frame_size.GetWidth(); ++x) {
const int cx = frame_pos.x + x;
if (cx < 0 || cx >= width)
continue;
const unsigned char index = indices[size_t(y) * size_t(frame_size.GetWidth()) + size_t(x)];
if (int(index) == transparent)
continue;
unsigned char *dst = &canvas[(size_t(cy) * size_t(width) + size_t(cx)) * 4];
dst[0] = palette[3 * size_t(index) + 0];
dst[1] = palette[3 * size_t(index) + 1];
dst[2] = palette[3 * size_t(index) + 2];
dst[3] = 255;
}
}
// Browsers treat 0 and 1 hundredths of a second as "as fast as the file can mean", and settle
// on 100 ms; an animation authored against that convention plays at the intended speed only if
// this does the same.
const long delay_ms = decoder.GetDelay(i);
out.push_back({ canvas, (delay_ms <= 10 ? 100.0 : double(delay_ms)) / 1000.0 });
if (disposal == wxANIM_TOBACKGROUND) {
for (int y = 0; y < frame_size.GetHeight(); ++y) {
const int cy = frame_pos.y + y;
if (cy < 0 || cy >= height)
continue;
const int x0 = std::max(0, frame_pos.x);
const int x1 = std::min(width, frame_pos.x + frame_size.GetWidth());
if (x1 > x0)
std::fill_n(&canvas[(size_t(cy) * size_t(width) + size_t(x0)) * 4], size_t(x1 - x0) * 4, 0);
}
} else if (disposal == wxANIM_TOPREVIOUS) {
canvas = saved;
}
}
return !out.empty();
}
} // namespace
bool AnimatedTexture::load_from_file(const std::string &path)
{
reset();
boost::system::error_code ec;
if (!boost::filesystem::is_regular_file(path, ec))
return false;
const std::vector<unsigned char> bytes = read_file_bytes(path);
if (bytes.empty())
return false;
std::vector<DecodedFrame> frames;
int width = 0, height = 0;
if (has_gif_signature(bytes)) {
if (!decode_gif(bytes, width, height, frames)) {
BOOST_LOG_TRIVIAL(error) << "AnimatedTexture: cannot decode GIF " << path;
return false;
}
} else {
wxLogNull no_log; // a format wxWidgets cannot read is handled here, not in a dialog
wxImage image;
wxMemoryInputStream stream(bytes.data(), bytes.size());
if (!image.LoadFile(stream, wxBITMAP_TYPE_ANY) || !image.IsOk()) {
BOOST_LOG_TRIVIAL(error) << "AnimatedTexture: cannot decode image " << path;
return false;
}
width = image.GetWidth();
height = image.GetHeight();
frames.push_back({ image_to_rgba(image), 0.0 });
}
m_width = width;
m_height = height;
m_frames.reserve(frames.size());
for (DecodedFrame &frame : frames) {
auto texture = std::make_unique<GLTexture>();
// Mipmapped: a hint is drawn at whatever width the panel has room for, which is usually
// smaller than the source, and an unfiltered downscale of a diagram is all aliasing.
if (!texture->load_from_raw_data(std::move(frame.rgba), (unsigned int) width, (unsigned int) height, false,
/* use_mipmaps */ true)) {
BOOST_LOG_TRIVIAL(error) << "AnimatedTexture: cannot upload " << path;
reset();
return false;
}
m_frames.push_back({ std::move(texture), m_duration, frame.delay });
m_duration += frame.delay;
}
return true;
}
void AnimatedTexture::reset()
{
m_frames.clear();
m_width = 0;
m_height = 0;
m_duration = 0.;
}
unsigned int AnimatedTexture::texture_id(double time) const
{
if (m_frames.empty())
return 0;
if (m_frames.size() == 1 || m_duration <= 0.)
return m_frames.front().texture->get_id();
const double t = time - m_duration * std::floor(time / m_duration);
// Frames are few; a linear walk beats keeping an index around and getting it stale.
size_t i = 0;
while (i + 1 < m_frames.size() && t >= m_frames[i].start + m_frames[i].delay)
++i;
return m_frames[i].texture->get_id();
}
const AnimatedTexture *AnimatedTextureCache::get(const std::string &path)
{
const auto it = m_items.find(path);
if (it != m_items.end())
return it->second.get();
auto texture = std::make_unique<AnimatedTexture>();
if (!texture->load_from_file(path))
texture.reset(); // remembered as a failure, so the file is not re-read every frame
const AnimatedTexture *result = texture.get();
m_items.emplace(path, std::move(texture));
return result;
}
} // namespace Slic3r::GUI
+75
View File
@@ -0,0 +1,75 @@
#ifndef slic3r_AnimatedTexture_hpp_
#define slic3r_AnimatedTexture_hpp_
#include <map>
#include <memory>
#include <string>
#include <vector>
#include "slic3r/GUI/GLTexture.hpp"
namespace Slic3r::GUI {
// An illustration for the UI to draw: one still image, or the frames of an animated GIF together with
// the delays the file asks for. Decoded on the CPU once and uploaded as one GL texture per frame.
//
// A texture per frame rather than an atlas: these are small hints a few hundred pixels across and a
// handful of frames long, so the extra textures cost little, while drawing a frame stays a plain
// ImGui::Image with no UV arithmetic and no dependence on the maximum texture size.
//
// load_from_file() uploads to the GPU, so it needs a current GL context - call it from rendering code.
class AnimatedTexture
{
public:
AnimatedTexture() = default;
AnimatedTexture(AnimatedTexture &&) = default;
AnimatedTexture &operator=(AnimatedTexture &&) = default;
AnimatedTexture(const AnimatedTexture &) = delete;
AnimatedTexture &operator=(const AnimatedTexture &) = delete;
// PNG, JPG and BMP through wxImage; GIF, animated or not, through wxGIFDecoder. False, and nothing
// loaded, if the file is missing or cannot be decoded.
bool load_from_file(const std::string &path);
void reset();
bool is_valid() const { return !m_frames.empty(); }
bool is_animated() const { return m_frames.size() > 1; }
int width() const { return m_width; }
int height() const { return m_height; }
// Length of one loop, in seconds; 0 for a still image.
double duration() const { return m_duration; }
// The texture to draw `time` seconds into the loop, which wraps. 0 when nothing is loaded.
unsigned int texture_id(double time) const;
private:
struct Frame
{
std::unique_ptr<GLTexture> texture;
double start{0.}; // seconds into the loop this frame appears at
double delay{0.}; // seconds it stays up
};
std::vector<Frame> m_frames;
int m_width{0};
int m_height{0};
double m_duration{0.};
};
// The illustrations a panel has shown at least once, keyed by absolute path, decoded and uploaded on
// first use and kept until clear() - which has to run while the GL context is still current.
class AnimatedTextureCache
{
public:
// Null when the file is missing or cannot be decoded. The failure is remembered too, so a tooltip
// whose illustration has not been drawn yet costs one lookup, not one decode attempt per frame.
const AnimatedTexture *get(const std::string &path);
void clear() { m_items.clear(); }
private:
std::map<std::string, std::unique_ptr<AnimatedTexture>> m_items; // null value: tried once, failed
};
} // namespace Slic3r::GUI
#endif // slic3r_AnimatedTexture_hpp_
+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 -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
+2 -16
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);
-3
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<->""

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