mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-15 11:51:33 +00:00
Merge branch 'main' into feat/generic-n-extruders
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
# Color picker
|
||||
|
||||
The color picker is a local HTML dialog hosted by `ColorPickerDialog`, a
|
||||
`WebViewHostDialog` subclass. Its header and implementation own the ordered
|
||||
selection contract and shared filament conversion. Bridge validation, favorites
|
||||
persistence, placement and gettext strings remain private to the implementation.
|
||||
Its colors use the existing `ColorRGBA` utility.
|
||||
It uses a native title bar and fixed window border, allowing dragging but no
|
||||
manual resizing or maximization. Native title-bar colors use the application's
|
||||
shared dialog dark-theme update on creation and theme changes.
|
||||
Like SpeedDial, the host disables browser zoom accelerators, restores native
|
||||
WebView focus on activation and readiness, and synchronizes layout and the macOS
|
||||
viewport before repainting the backend. Activation never dismisses this modal
|
||||
dialog; focus restoration preserves an active editor and its partial input.
|
||||
The initialized page reports
|
||||
its intrinsic content height; native code validates the current page identifier
|
||||
and bounded numeric height, converts CSS pixels to device-independent window
|
||||
size, and fits within the display's work area. Identical sizes are ignored. The
|
||||
page observes content rather than viewport height, so resizing does not create
|
||||
a feedback loop; oversized content remains scrollable.
|
||||
The panel snapshots the triggering color or More Colors button's screen rectangle.
|
||||
It aligns below the button, flips above when needed, and clamps to that button's
|
||||
display work area on initial display. Later content and DPI changes update only
|
||||
the client size, preserving the position chosen by dragging the title bar.
|
||||
wxGTK clears the dialog's default
|
||||
parent-centering constraint and reapplies its native position after showing,
|
||||
bypassing wxGTK's requested-position cache. Absolute top-level placement remains
|
||||
subject to the window manager; Wayland compositors control it. The official filament palette's
|
||||
existing placement is independent of this custom panel.
|
||||
A solid selection contains one color; a gradient contains exactly two ordered
|
||||
endpoints. The bridge represents these as `{"type":"solid|gradient",
|
||||
"colors":["#RRGGBBAA", ...]}`. RGB input is also accepted and becomes opaque.
|
||||
Native validation rejects malformed hex and incorrect cardinality.
|
||||
|
||||
The project filament color action and the official filament picker's More Colors
|
||||
action enable two-endpoint gradients and independent endpoint alpha. The preset
|
||||
combo box reads project colors in their stored vector order locally and applies
|
||||
changes through `sync_colour_config`,
|
||||
retaining the existing RGB/RGBA profile/project formats and dirty marking.
|
||||
Sidebar gradient and segmented multicolor swatches draw colors in the supplied
|
||||
order without sorting them.
|
||||
Opaque colors use `#RRGGBB`; nonopaque colors use `#RRGGBBAA`, including fully
|
||||
transparent alpha `00`. Initial selections, alpha-only edits and cancellation
|
||||
retain stored alpha. Ordered
|
||||
custom results bypass the official palette's `FilamentColor` set. Choosing an
|
||||
official swatch retains its existing palette behavior.
|
||||
The Linux sidebar's Change extruder color menu uses this same custom editor and
|
||||
the filament swatch anchor, including alpha, ordered gradients and configuration syncing.
|
||||
|
||||
Striped colors and gradients with more than two endpoints cannot be represented
|
||||
by this editor. A notice explains that the current selection is retained; the
|
||||
editor starts from the first color, and unchanged confirmation is a no-op.
|
||||
Cancellation is also a no-op. If the WebView backend cannot initialize, a local
|
||||
unavailable panel offers Cancel and Escape, without opening a system color picker.
|
||||
|
||||
Generic color option fields use an ordinary color button on every platform and
|
||||
open the same panel with gradients and alpha disabled. The panel is anchored to
|
||||
the field button. Empty or invalid field values remain undefined (the empty
|
||||
string), distinct from opaque black. Right-click resets to undefined. Cancel does
|
||||
not notify the field; confirmation and reset notify only when its RGB string
|
||||
changes. Programmatic assignments remain silent, and reading a field never
|
||||
writes legacy favorites.
|
||||
|
||||
AMS material settings retain their fixed/AMS swatches and first-level popup. Only
|
||||
its custom-color action opens the solid, opaque editor, anchored to that custom
|
||||
control. The transient popup releases its grab while the modal is open and returns
|
||||
to the same selection on cancellation. Confirmation uses the existing default-color
|
||||
and packed RGBA event path. Device permission/status checks remain at the first-level
|
||||
entry.
|
||||
|
||||
Texture import's color-mapping Add Material action opens a solid, opaque editor
|
||||
anchored to the mapping row. The existing filament limit is checked before opening.
|
||||
The first-level material popup closes as an action; its callback is copied before
|
||||
dismissal, so popup destruction does not invalidate the modal's completion path.
|
||||
Confirmation invokes the existing virtual-filament callback once; cancellation
|
||||
adds no material.
|
||||
|
||||
Native gettext strings are installed at document start. Static page labels and
|
||||
palette names use stable English keys; RAL codes, channel symbols, and numeric
|
||||
labels remain data. The palette color values do not depend on the UI language.
|
||||
The application locale also sets the document language. Numeric editor validation
|
||||
uses the application's gettext messages, rather than browser language defaults;
|
||||
missing translations fall back to the stable English keys.
|
||||
|
||||
The pure color/state regressions run with
|
||||
`node --test resources/web/dialog/ColorPickerDialog/colorpicker.test.js`.
|
||||
Optional real-browser regressions live beside them in
|
||||
`colorpicker.browser.test.js`. They use an existing Playwright installation
|
||||
(`ORCA_PLAYWRIGHT_MODULE` can name its module path) and an installed browser
|
||||
(`ORCA_BROWSER_EXECUTABLE` can name its executable), without adding a production
|
||||
dependency. They cover bridge initialization, capability combinations, invalid
|
||||
editor recovery, ordered endpoint alpha, favorites, gettext keys, and live theme
|
||||
changes. These browser checks complement native contract/persistence tests; they
|
||||
do not establish the full native modal confirmation and favorites journey.
|
||||
|
||||
Gradient and alpha capabilities are independent and default off. Unsupported
|
||||
gradients are rejected; disabled alpha normalizes the selected result to opaque.
|
||||
Favorites retain both capabilities independently of the current caller. The page
|
||||
stores alpha as a byte and only converts to percentage when displaying or editing
|
||||
opacity, so switching endpoints does not change alpha precision.
|
||||
|
||||
The page starts disabled. Each document supplies a page identifier in its ready
|
||||
message, receives native initialization, and acknowledges it before native code
|
||||
accepts confirmation or favorite updates. Repeated initialization does not reset
|
||||
edits, and messages from an obsolete document are ignored. Native WebView message
|
||||
handling is deferred and guarded by a lifetime token. Only a validated confirmation
|
||||
sets the optional result. Cancel, Escape and native window close leave it empty.
|
||||
|
||||
Favorites belong to application configuration, separately from the selected result,
|
||||
in the editor configuration returned by `AppConfig::config_path()` under `data_dir()`.
|
||||
The `color_picker` section has string `version` equal to `1` and a `favorites`
|
||||
string containing a JSON array of typed selections. Native helpers normalize and
|
||||
deduplicate this array in order and limit it to 24 entries. Updates replace the
|
||||
entire section and immediately call `AppConfig::save()`, so accepted favorite
|
||||
changes survive cancellation. Empty arrays are saved explicitly. Core `AppConfig`
|
||||
only stores the raw section; color validation belongs to the GUI boundary.
|
||||
|
||||
When the section is absent, valid solid colors from `custom_color_list` are imported
|
||||
once, without changing that legacy section. The legacy native picker saves decimal
|
||||
`r,g,b,a` byte strings; hexadecimal RGB and RGBA strings are also supported. Migration
|
||||
strictly validates all four decimal channels and preserves alpha. Gradient strings
|
||||
and malformed values are ignored. The new version and an empty array are
|
||||
saved even when there is nothing to import. A present section with an unknown or
|
||||
missing version is left untouched and disables favorite writes. Malformed version
|
||||
1 data is displayed as empty and remains on disk until the user explicitly saves
|
||||
a valid replacement. Other configuration sections and project/profile color
|
||||
formats are independent of this storage.
|
||||
|
||||
Interface colors consume the host's `data-orca-theme` and `--orca-*` contract through
|
||||
shared styles. The picker opts into scoped global control roles. Application theme
|
||||
changes update UI surfaces without reloading the page or changing color data.
|
||||
@@ -0,0 +1,66 @@
|
||||
# Focus mode (hiding inactive plates) — High Level Design
|
||||
|
||||
## Purpose and scope
|
||||
|
||||
"Focus mode" is an entry of the canvas menu in the bottom left corner of Prepare. Turned on (eye
|
||||
lit), the 3D view hides the inactive plates and shows the current plate alone, so a project with
|
||||
many plates can be worked on one plate at a time. Turned off, every plate is shown. It is a view
|
||||
setting: it changes nothing in the model, the slice or the project file, and it does not reach
|
||||
plate thumbnails. The choice is kept in the app config as `focus_mode`, off by default.
|
||||
|
||||
Preview needs no such option, as it already draws the current plate alone and has its own plate
|
||||
toolbar. The assembly view and the Design tab do not lay objects out on plates, so the entry is
|
||||
disabled there.
|
||||
|
||||
## What is shown
|
||||
|
||||
The current plate is drawn with its bed, label and icons; the other plates are not drawn at all. The
|
||||
world axes, which Prepare draws at the corner of the first plate, move to the corner of the current
|
||||
plate, where Preview draws them.
|
||||
|
||||
An object instance is shown when its footprint, the XY bounding box of the convex hulls of its
|
||||
volumes, overlaps the current plate, or when it is selected. The test is made per instance, so the
|
||||
parts of an object are shown or hidden together, and a wipe tower follows its plate the same way.
|
||||
It is geometric rather than the plate membership `PartPlateList` keeps, because an instance stays on
|
||||
the plate it was first placed on: an object of another plate that reaches over this one, or one too
|
||||
large for any plate, would otherwise be invisible while it takes room on the current plate. A
|
||||
selected instance stays visible so that what a selection or a gizmo acts on is never hidden, for
|
||||
instance an object picked in the object list on another plate.
|
||||
|
||||
## Hiding
|
||||
|
||||
A hidden object is neither drawn nor picked. `GLCanvas3D::_update_inactive_plates_visibility()`
|
||||
runs at the start of each frame of Prepare. It clears `GLVolume::is_active` on the volumes of the
|
||||
hidden instances and deactivates their raycasters, which every render pass, the hover and rectangle
|
||||
selection, the shadow and center of mass markers already respect. It also deactivates the raycasters
|
||||
of the other plates, whose icons would otherwise still take clicks where nothing is drawn. The state
|
||||
persists between frames, so the raycasts made while handling the mouse, such as the anchor of a pan,
|
||||
see the same scene as the frame.
|
||||
|
||||
Other features clear `is_active` too: gizmos hide the objects they do not edit and show them all
|
||||
again when they close. `GLVolume::on_hidden_plate` records which volumes this update hid, so it only
|
||||
shows again what it hid itself, and it waits for a gizmo that hides objects to close before doing
|
||||
so. A volume another feature made active again is hidden again on the next frame. While Focus mode
|
||||
is off and nothing is left hidden, the update returns at once.
|
||||
|
||||
## Switching plates
|
||||
|
||||
The other plates cannot be clicked while Focus mode hides them, so a compact counterpart of the Preview
|
||||
plate toolbar takes their place: a column of plate thumbnails in the top left corner, below the top
|
||||
toolbar, shown when the project has more than one plate and no gizmo is open. The thumbnails are the
|
||||
ones the Preview toolbar shows (`PartPlate::thumbnail_data`); the canvas rebuilds its textures when
|
||||
`Plater::plate_toolbar_image_version()` changes, a counter each consumer tracks on its own so that the
|
||||
Preview toolbar's dirty flag is left alone. That counter moves after almost every edit, so the
|
||||
thumbnail of the current plate, the only one edited while the others are hidden, is drawn again each
|
||||
time, and the other plates' only once an edit to them has invalidated theirs. The current plate has an
|
||||
accent border, a hovered one the gray border of the Preview toolbar, empty plates are dimmed and the
|
||||
plate number sits in the corner; the tooltip gives the plate name. A click selects the plate and
|
||||
clears the selection, as a click on a plate row of the object list does. The column scrolls when it
|
||||
does not fit above the 3D navigator or the canvas toolbar. Its scrollbar works as the Preview
|
||||
toolbar's, with which it shares its style: it shows only while the mouse is over the column or drags
|
||||
the bar, and the mouse wheel scrolls the column either way.
|
||||
|
||||
Whatever selects a plate while the others are hidden, the camera moves its target over the new plate
|
||||
and keeps its angle, distance and height (`PartPlateList::select_plate()`), so the plate comes into
|
||||
view where the previous one was. A zoom to all plates, as after loading a project or adding a plate,
|
||||
frames the current plate instead. Turning Focus mode on does the same, so the current plate fills the view.
|
||||
@@ -15,8 +15,9 @@ Outlines of centerlines that cross each other overlap at every crossing, which
|
||||
over-extrudes the wall intersections. The line-crossing patterns Grid,
|
||||
Triangles, Tri-hexagon and Cubic therefore build centerlines that never cross
|
||||
(`FillRectilinear::fill_surface_trapezoidal()`), and so do Adaptive Cubic and
|
||||
Support Cubic (`FillAdaptive`); the other patterns outline their usual
|
||||
centerlines.
|
||||
Support Cubic (`FillAdaptive`). 3D Honeycomb skips `multiline_fill()` and draws
|
||||
its N lines itself as parallel copies of each wave, which never cross
|
||||
(`Fill3DHoneycomb`). The other patterns outline their usual centerlines.
|
||||
|
||||
## Non-crossing centerlines
|
||||
|
||||
|
||||
+38
-22
@@ -82,9 +82,7 @@ there.
|
||||
strong point is placed, no later strong modifier and no weak modifier is
|
||||
processed for that perimeter.
|
||||
- **Weak:** every weak modifier applies. They are applied from the lowest
|
||||
priority to the highest, so the highest one overwrites overlapping zones. A
|
||||
Blocked modifier that fully contains a perimeter is the exception: it is
|
||||
skipped there (see [Full containment](#full-containment)).
|
||||
priority to the highest, so the highest one overwrites overlapping zones.
|
||||
|
||||
A strong modifier without a usable segment, even one whose fragments were all
|
||||
discarded, passes the turn to the next one.
|
||||
@@ -232,20 +230,23 @@ begin and end positions on the source contour.
|
||||
### Full containment
|
||||
|
||||
A modifier that covers the whole perimeter has no boundaries on it. The policy
|
||||
follows seam painting, where painting a whole perimeter green is a meaningful
|
||||
choice and forbidding the seam all round is not:
|
||||
follows seam painting, where a whole perimeter can be painted in either color:
|
||||
|
||||
- **Seam Enforced** types the whole perimeter, like a perimeter painted green all
|
||||
round, with subdivision applied as described under [Weak modifiers](#weak-modifiers).
|
||||
- **Seam Neutral** types the whole perimeter Neutral, like an unmarked perimeter,
|
||||
clearing painting and lower zones.
|
||||
- **Seam Blocked** is skipped for the perimeter, with the full-containment
|
||||
warning. The seam cannot avoid the whole perimeter, so the modifier does not
|
||||
override anything below it: lower zones and painting stay in effect.
|
||||
- **Seam Center, Left and Right** are skipped with the same warning: there is no
|
||||
intersection to place the point on.
|
||||
- **Seam Blocked** types the whole perimeter Blocked, like a perimeter painted
|
||||
red all round, overriding painting and lower zones. The seam cannot avoid such
|
||||
a perimeter: the seam placer compares Blocked candidates with each other like
|
||||
Neutral ones, so the seam is placed as on an unmarked perimeter. If the
|
||||
perimeter stays Blocked after all weak zones, it is reported in the user
|
||||
warning (see [Diagnostics and warnings](#diagnostics-and-warnings)), so the
|
||||
user can check the modifier.
|
||||
- **Seam Center, Left and Right** are skipped with the full-containment warning:
|
||||
there is no intersection to place the point on.
|
||||
|
||||
Enforced and Neutral take part in the usual priority order (see
|
||||
Enforced, Neutral and Blocked take part in the usual priority order (see
|
||||
[Weak modifiers](#weak-modifiers)).
|
||||
|
||||
The perimeter is fully contained when the united intervals cover every source
|
||||
@@ -311,9 +312,9 @@ into every perimeter that has a strong seam.
|
||||
`collect_weak_modifier_segments()` extracts the segments of every weak modifier
|
||||
before the polygon is modified, so all positions refer to the same contour. Each
|
||||
segment becomes a zone with a type and two boundaries, kept in application
|
||||
order, lowest priority first. Full containment of an Enforced or Neutral
|
||||
modifier becomes a whole-perimeter zone at its place in that order: it has no
|
||||
boundaries and takes part in no insertion or helper step below. The boundaries
|
||||
order, lowest priority first. Full containment of a weak modifier becomes a
|
||||
whole-perimeter zone at its place in that order: it has no boundaries and takes
|
||||
part in no insertion or helper step below. The boundaries
|
||||
carry their positions on the source contour; these remain as provenance after
|
||||
insertion and are not indices into the modified polygon.
|
||||
|
||||
@@ -384,9 +385,10 @@ G-code export issues it as one non-critical warning with the ID
|
||||
`SlicingPreciseSeamWarning`. It is a single line, "Precise Seam: <causes>. Seam
|
||||
placement may differ from expected.", because the export warnings dialog shows
|
||||
only the first line of each warning. Repeated warning events replace the
|
||||
notification instead of appending to it. Except for the "had no effect" cause,
|
||||
the causes name the modifier types involved, as the menu names them, in menu
|
||||
order and each type once, for example "(Seam Left, Seam Enforced)".
|
||||
notification instead of appending to it. Except for the "covers an entire
|
||||
perimeter" and "had no effect" causes, the causes name the modifier types
|
||||
involved, as the menu names them, in menu order and each type once, for example
|
||||
"(Seam Left, Seam Enforced)".
|
||||
The causes are:
|
||||
|
||||
- **failed to process some intersections (types):** at least one fragment was
|
||||
@@ -395,8 +397,18 @@ The causes are:
|
||||
a Seam Center, Left or Right modifier had more than one segment on a
|
||||
perimeter (see [Strong modifiers](#strong-modifiers)).
|
||||
- **a perimeter is fully inside a modifier, the modifier was not applied to it
|
||||
(types):** a Seam Center, Left, Right or Blocked modifier was skipped for a
|
||||
(types):** a Seam Center, Left or Right modifier was skipped for a
|
||||
perimeter (see [Full containment](#full-containment)).
|
||||
- **Seam Blocked modifier "<name>" of "<object>" covers an entire perimeter:**
|
||||
a Seam Blocked modifier covered a whole perimeter (see
|
||||
[Full containment](#full-containment)). Only perimeters whose candidates are
|
||||
all Blocked after all weak zones contribute to this warning, and only through
|
||||
the zone in effect there: `apply_weak_modifiers_to_perimeter()` checks the
|
||||
final types and flags the modifier of the highest whole-perimeter zone when it
|
||||
is Blocked. A lower whole zone is overwritten by it and is not reported. Only
|
||||
the first flagged modifier in print and volume order is named, followed by
|
||||
"(N in total)" when there are several. The type name reuses the menu's
|
||||
translation.
|
||||
- **modifier "<name>" of "<object>" had no effect on the seam (it might not reach
|
||||
the centerline of the printed perimeter):** a modifier was evaluated on at
|
||||
least one perimeter and never gave a segment, full containment or a discarded
|
||||
@@ -420,6 +432,8 @@ The log records the following diagnostic markers:
|
||||
- `[PreciseSeamNoEffect]` for every modifier of the "had no effect" cause, with
|
||||
the object and modifier names. Unlike the user warning, the log lists all of
|
||||
them.
|
||||
- `[PreciseSeamFullyBlocked]` for every modifier of the "covers an entire
|
||||
perimeter" cause, listed the same way.
|
||||
|
||||
Failures and recoveries are counted separately. The first 10 of each per
|
||||
`init()` call are logged in detail, in parallel processing order; if a limit is
|
||||
@@ -563,12 +577,14 @@ last modifier would.
|
||||
priorities, weak boundaries that coincide or share an edge, enforced
|
||||
subdivision, whole-perimeter weak zones with painting and priorities, weak
|
||||
zones over painting's oversampled candidates, the warning type masks, usage
|
||||
tracking for the "had no effect" warning, volume sorting of strong and weak
|
||||
groups, restoration of strong points after alignment, raft layer indexing and
|
||||
structured slices. End-to-end tests slice a real object with Precise Seam
|
||||
tracking for the "had no effect" and whole-perimeter Blocked warnings, volume
|
||||
sorting of strong and weak groups, restoration of strong points after
|
||||
alignment, raft layer indexing and structured slices. End-to-end tests slice
|
||||
a real object with Precise Seam
|
||||
volumes and check the outer wall starts in the exported G-code: every strong
|
||||
mode under several seam positions and with a raft, Enforced and Blocked zones,
|
||||
a modifier with a hole, and the user warning.
|
||||
a perimeter blocked all round under every seam position, a modifier with a
|
||||
hole, and the user warning.
|
||||
- [Seam placer tests](../../tests/fff_print/test_seam_placer.cpp) cover
|
||||
enforced-patch selection independent of the contour start, fully painted
|
||||
contours, duplicate removal, and `Print::apply()` synchronization through type
|
||||
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user