Skip to content

The U1 printer panel: three cards, an ACE mode row and a toolhead gri… - #39

Draft
physicsG wants to merge 5 commits into
mainfrom
develop/add-multiace-support
Draft

The U1 printer panel: three cards, an ACE mode row and a toolhead gri…#39
physicsG wants to merge 5 commits into
mainfrom
develop/add-multiace-support

Conversation

@physicsG

Copy link
Copy Markdown
Owner

This pull request introduces comprehensive design documentation for the new U1 printer panel, ACE visual standards, and U1 plate template in the documentation directory. These documents lay out the intended user interface, visual language, and data handling for upcoming features, providing detailed references for future implementation. No code changes are included—these are design-only documents meant to guide development and ensure consistency across the user interface.

Printer Panel Design and Workflow

  • Added 15-printer-panel.md, detailing the planned redesign of the printer panel for the U1, including a unified sidebar for Prepare/Preview, improved topology and filament information, and a new sync workflow. The document specifies current defects, the new UI structure, ACE mode handling, assignment popovers, and outlines the implementation/shipping order.

ACE Visual Standards

  • Added 16-ace-visuals.md, establishing a standardized visual language for ACE units, including badge and glyph specifications, color schemes, and integration with existing AMS widgets. It covers visual treatments, moisture/temperature display, and how filament levels are determined and rendered.

Plate Template and Visual Consistency

  • Added 17-plate-template.md, defining the canonical silhouette and fill strategy for U1 build plate thumbnails. The document covers precise measurements, drawing conventions, photo usage for official plates, and SVG requirements for generics, ensuring visual consistency and correct identification across the UI.…d (The U1 printer panel: three cards, an ACE mode row and a toolhead grid #38)

The Printer section is one widget seen in both Prepare and Preview. On a U1 it drew four tabs named Nozzle 1..4 that each held the same Diameter combo — changing any one writes all four and switches the whole preset, because the machine refuses a mixed set — and it hid its sync behind an unlabelled 16px glyph in the title bar. Measured by running the app, not by reading it.

It now follows docs/ace-mmu/15-printer-panel.md and its mockup.

What is here

Commit
docs(ace-mmu) Corrects the plates paired with the wrong BedType enum

The two design commits underneath are the docs branch this stacks on, so the design and the code arrive together.

The panel — three cards at equal height (printer with its thumbnail over the preset combo, plate, and Sync info); an ACE mode row mirroring the printer's own three-way switch; a bordered box per toolhead wrapped 2×2 with an ACE row and a Diameter row. The plate card absorbs what was a full-width Bed type row, which is where the height for the head boxes comes from, and the whole card opens the list so the picture is the click target.

Heads are named Toolhead N, which is what the machine's own interface calls them; the sidebar was the last place saying Nozzle N. Every other printer keeps the names it had — an IDEX machine's Left / Right Nozzle tabs are untouched.

update_nozzle_settings builds those boxes instead of notebook pages, which leaves CustomNotebook with no callers; it goes, 269 lines and the stray #pragma once that warned on every build. The 135-line sync handler is lifted verbatim into Sidebar::sync_printer_info.

Configace_head_unit and ace_head_capacity are taken verbatim from feat/ace-mmu-slicing so that branch merges onto this rather than colliding. ace_mode is new: that branch reads the mode from the printer and never stores it, and a panel has to draw something with the printer switched off.

Description

Screenshots/Recordings/Graphs

Tests

#38)

The Printer section is one widget seen in both **Prepare** and **Preview**. On a U1 it drew four tabs named *Nozzle 1..4* that each held the same `Diameter` combo — changing any one writes all four and switches the whole preset, because the machine refuses a mixed set — and it hid its sync behind an unlabelled 16px glyph in the title bar. Measured by running the app, not by reading it.

It now follows `docs/ace-mmu/15-printer-panel.md` and its mockup.

## What is here

| Commit | |
|---|---|
| `docs(ace-mmu)` | Corrects the plates paired with the wrong `BedType` enum |
| `feat(config)` | `ace_mode` + per-head `ace_head_unit` / `ace_head_capacity` |
| `feat(sidebar)` | The panel rebuild and seven plate assets |

The two design commits underneath are the docs branch this stacks on, so the design and the code arrive together.

**The panel** — three cards at equal height (printer with its thumbnail over the preset combo, plate, and *Sync info*); an ACE mode row mirroring the printer's own three-way switch; a bordered box per toolhead wrapped 2×2 with an `ACE` row and a `Diameter` row. The plate card absorbs what was a full-width `Bed type` row, which is where the height for the head boxes comes from, and the whole card opens the list so the picture is the click target.

Heads are named **Toolhead N**, which is what the machine's own interface calls them; the sidebar was the last place saying *Nozzle N*. **Every other printer keeps the names it had** — an IDEX machine's *Left / Right Nozzle* tabs are untouched.

`update_nozzle_settings` builds those boxes instead of notebook pages, which leaves `CustomNotebook` with no callers; it goes, 269 lines and the stray `#pragma once` that warned on every build. The 135-line sync handler is lifted verbatim into `Sidebar::sync_printer_info`.

**Config** — `ace_head_unit` and `ace_head_capacity` are taken verbatim from `feat/ace-mmu-slicing` so that branch merges onto this rather than colliding. `ace_mode` is new: that branch reads the mode from the printer and never stores it, and a panel has to draw something with the printer switched off.
…eset on Linux (#41)

One process profile is on disk as

    0.10mm Color Mixing @Snapmaker U1 (0.4 Nozzle).json

while Snapmaker.json lists it, and the preset inside it names itself, with a lowercase "nozzle".
It is the only one of 198 profile filenames spelled that way; every other file, every manifest
entry and every compatible_printers string uses "nozzle".

Windows and macOS have case-insensitive filesystems and never notice. On Linux the file is not
found, load_vendor_configs_from_json fails on it, and one missing file of 570 takes the whole
vendor bundle down with it. No Snapmaker machine preset loads at all:

    load_from_json: parse .../0.10mm Color Mixing @Snapmaker U1 (0.4 nozzle).json got a parse_error
    load_vendor_configs_from_json, got error when parse process setting from ...
    can not find parent for config .../user/default/machine/<any user preset>.json!

The printer combo comes up empty, the sidebar draws NO IMAGE with a blank nozzle diameter, and
any user preset inheriting a Snapmaker machine is orphaned. A clean install on Linux cannot
select a printer.

The fix is the filename rather than the manifest: the manifest entry, the preset's own "name"
field and its compatible_printers all already agree on lowercase, so the file is the outlier.

The vendor version goes to 02.02.55.03 as well. Without it the corrected file only reaches new
installs - check_installed_vendor_profiles() reinstalls from resources only when the resource
bundle is newer than the one in the datadir, so anyone already carrying 02.02.55.02 would keep
the broken set.

Verified on Linux against a datadir in the failing state: zero parse errors, zero orphaned
parents, all 570 manifest entries resolving, and the printer selectable again.
Reconstructs the U1 **Device page** — the webview Orca shows for a connected printer —
as plain HTML/JS, alongside the shipped Flutter bundle. `PrinterWebView` has an
**Original / Rebuilt** switcher and the choice persists in `u1_reconstructed_ui`, so
nothing is replaced: both are there and either can be shown.

Cut from `develop/add-multiace-support`, 0 behind, so it merges clean.

## What is in it

| | |
|---|---|
| `resources/web/device_page/` | The reconstructed Device page: two destinations (Device control, Storage), five panels, one directory per panel |
| `resources/web/shared/` | Bridge client, protocol constants, state store, a simulated printer, the fault catalogue |
| `resources/web/print_processing/` | The print-processing popup, same treatment |
| `docs/u1-webui/` | The reverse engineering it was built from, and 19 extractors that regenerate every data file |
| `src/slic3r/` | The host side: answer every page request, survive a printer that is not there |

`js/registry.js` is the table of contents — destinations and their panels — and
everything inside `.content` is built from it, so `index.html` is 42 lines of shell. A
panel is handed its own commands and nothing else, which makes reaching for another
panel's command a `TypeError` rather than a quiet dependency.


## Where to start reading

[`docs/u1-webui/STATUS.md`](docs/u1-webui/STATUS.md) — what is proven against hardware,
what is not, and what to pick up next. It is deliberately explicit about the second: the
**Not verified** section names what was built against the simulator only, because that
proves the client and the simulation agree and not that the printer does.
Nothing loads `flutter_web?path=2` any more and the Original / Rebuilt
switcher is gone. Getting there was not a matter of deleting a button:
the shipped page was doing one job for Orca that nothing else does.

`sw_UpdateMachineFilamentInfo` reads like "set the filament on the
printer" and never leaves the host. It writes `machine_filaments` and
`m_connect_machine_info_list` — the only source the Prepare sidebar's
filament combos have, one writer, three clearers. The reconstruction
was not calling it, so retiring the shipped page silently deleted the
sidebar's filament sync. `core/orcasync.js` does it now, on every state
change, guarded by a comparison and re-armed on reconnect.

Two controls WERE calling it, for something else: `setFilament` and
Print Preferences sent a flat `print_task_config` patch, which fails the
host's first `if` and never reaches the printer. Neither awaits its own
request, so both did nothing, silently, on a real machine. What writes
the printer is `SET_PRINT_FILAMENT_CONFIG` and `SET_PRINT_PREFERENCES`,
recovered verbatim from the shipped bundle — including that the first is
single-quoted, the second is not, and that Auto Leveling goes out as
BED_LEVEL though the machine reports `auto_bed_leveling`. The print
popup had the same two calls. `nozzle_diameters` is not in
`print_task_config` at all; it comes from `machine.system_info`.

Three bugs that only running the app could show:

- A side effect must not ride on the repaint. The sync was inside
  render()'s requestAnimationFrame; WebKit fires none into a view that
  is not composited, which the Device tab is not at startup. It ran in
  every suite and never once in Orca.
- A push-only channel needs an initial value. sw_SubscribeUserLoginState
  registered a subscriber and replied with nothing, so it reported only
  changes — and the account is restored ~4 s before the Flutter bundle
  finishes parsing and subscribes. The Home tab showed signed-out over a
  live session that was in the config the whole time.
- Finishing the path lit up code that had never run. With
  `m_connect_machine_info_list` non-empty for the first time,
  `refresh_filament_sync_marks()` segfaulted at address 0:
  SyncMarkOverlay is a child of the filament combo, both removal sites
  Destroy() the combo without dropping the pointer, and SetSynced()
  reaches the virtual IsShown() through a freed vtable. The invariant
  was a comment on the member and nothing else. Same switch-on is why
  the sidebar's "Machine Filament" section had never been drawn: it is
  filtered on a nozzle diameter the page never used to send.

Also: `load_current_presets()` no longer runs on the stack of a webview
message handler, and `handle_web_message` catches rather than letting a
malformed command unwind through wxWidgets.

The Snapmaker login is untouched — sw_UserLogin still opens Orca's own
SMUserLogin on id.snapmaker.com, and no reconstructed page has a login
form. The rail can now ask for that dialog rather than offering one.

Verified in Orca against 811002511261022618B3. Suites: unit 208/208,
conformance 165/165 (7 new), coverage clean, WebKit 61/61 and 54/54,
drive/orca-sync.js 23/23 (new).
…n hardware (#44)

Rebuilds the print-processing popup — the dialog Orca opens between "Send to printer" and
the print starting — from the shipped Flutter bundle, the same way the Device page was
done, **and then drives it against a real U1 end to end**: upload, start, print, cancel.

## Why it needed rebuilding rather than finishing

Reading the host's own handlers turned up more than "unfinished":

- **The filament card read a shape the host has never returned.** It read
  `mapping.filaments[i]`; `sw_GetFileFilamentMapping` replies with **parallel arrays**.
  Only the popup's own mock invented an array of objects, so it drew four rows against
  the simulator and *"No filament requirements read from this file yet."* against Orca.
- **The send failed on its first command.** `sw_StartLocalPrint` rejects `{}`.
- **The file came through the wrong door.** `sw_GetPrintZip` serialises a
  `std::vector<char>`, so a 12 MB zip crosses the bridge as ~40 MB of JSON integers.
- **The progress bar was a `setTimeout`** counting to 100 whether or not anything uploaded.
- **Five fields arrived and went undrawn**, chiefly `thumbnails[]` — the rendered plate.

Written up with line numbers in
[`04-requirements.md`](docs/u1-webui/03-print-processing/04-requirements.md).

## What the shipped dialog actually is

The widget tree is all in `main.dart.js` and the constants resolve, so
[`original-dialog-mockup.html`](docs/u1-webui/03-print-processing/original-dialog-mockup.html)
is measurement rather than inference — every number cites its source, held by
`check_mockup.py` (73 checks).

**Three things neither shipped screenshot could show**, both having been captured with the
filament section empty:

- **Edit Filament is a Wrap of 80 × 100 cards**, a colour block over a toolhead picker.
- **There is a fifth section** — a nozzle-mismatch banner (`A.R5`).
- **A toolhead whose type _or_ nozzle does not match is passed `enabled: false` and cannot
  be picked at all.** That is behaviour; the drive script checks it by clicking a refused
  item and asserting nothing moved.

## The rebuild

Follows the Device page's contract — a registry, a folder per panel, its own commands
module each. Six primitives moved to `shared/` to be used by both surfaces: `dom`,
`render`, `pending`, the modal sheet, the trace pane, and `connection.js`. `cssColor` is
**not** reimplemented; it stays where the alpha-zero rule already lives.

**One mock for both surfaces.** The U1 was already shared; Orca's half of this dialog now
is too, on the same simulated printer. Not tidiness — this dialog matches the *file's*
filaments against the *machine's*, and a mock that invented both lists could never report
a mismatch.

## Driven against a real U1

`811002511261022618B3`, Orca closed, a person watching. The full account with the wire is
[`05-hardware-e2e.md`](docs/u1-webui/03-print-processing/05-hardware-e2e.md).

**The send contract, which Orca's source does not contain** — `sw_StartLocalPrint` passes
params straight through, so only the firmware could say:

- the upload is the page's and works cross-origin: `POST :7125/server/files/upload` →
  `HTTP 201` with the item it created
- **`path` carries no root.** `item.path` and `item.root` are separate fields;
  `gcodes/x.zip` → `File not found`, `x.zip` → `Print started`. The page reads the stored
  path back off the upload's own reply instead of constructing one.
- **the firmware unpacks the zip** — the start reply names the inner `.gcode`, and the
  archive does not survive.

**A bug the simulator could not find.** `initialAssignment` fell back to *identity* when
Orca had no `filament_extruder_map` — and outside Orca there never is one. On this machine
that put filament 3 on the empty head and filament 4 on PETG: **two toolheads the picker
itself refuses**, with nothing on screen saying so, because the warning mark means
"nothing chosen" and something had been. Unassigned is the default now, which is the
bundle's own answer.

**A cancel that reported failure while it worked** — and the reason was not what I first
said. Klipper runs G-code sequentially, so the cancel was queued behind the homing move.
Measured with `G4` blocking the queue, moving nothing:

| queue | `sw_MachinePrintCancel` |
|---|---|
| empty | 197 ms |
| behind `G4 P3000` | 3323 ms |
| behind `G4 P6000` | 6213 ms |

The round trip is the queue plus ~250 ms every time. `u1_bridge.py` had already learned
this (80 s, after a 31 s toolchange came back as "the printer refused the command"); the
page's own client was still at 15 s for everything. `sswcp.js` now waits on two clocks,
split by `PRINTER_BACKED`.

## Testing it, and a skill for next time

```bash
R=resources/web/shared/tests
python3 $R/run_webkit.py --size 714x750 --page 'web/print_processing/index.html?mock=1' \
    --drive $R/drive/print-dialog.js                      # the simulator
python3 $R/run_webkit.py --real --sn <SN> --size 714x750 --settle 25 \
    --gcode ~/models/plate.gcode --page web/print_processing/index.html \
    --drive $R/drive/print-dialog-machine.js              # a real U1, read-only
```

`u1_bridge.py` answers Orca's half out of the `.gcode` passed to `--gcode`, parsing the
metadata Orca's own slicer wrote, so the dialog sees a real plate rather than a fixture.
`sw_StartLocalPrint` is gated behind `--allow-print`: a suite that can start a print is a
suite that will.

**`.claude/skills/u1-hardware-test`** holds the procedure — preconditions, the ladder of
read-only rungs before anything that moves the machine, the rails on the one script that
starts a print, the cleanup (the firmware unpacks the zip, so the *unpacked* name is what
to delete), and how to read a slow reply.

## Three bugs found in existing code

- **`sw_GetFileStream` was misclassified as printer-backed, and the check agreed.** The
  conformance suite took each handler's span up to the next `sw_*` signature; anything not
  named `sw_*` in between landed in the earlier one's span. List and check came from the
  same derivation, so they agreed with each other and not with the C++.
- **`bridge-methods.json` had `sw_StartLocalPrint → method: null`** with an honest reason
  and a wrong conclusion — the method was three lines above the call, behind a named
  constant the extractor only looked for as a literal.
- **The mock returned a `file_url` on port zero**, so the send path was *unexercisable* in
  the simulator. Nothing had noticed because no check had ever pressed Send there; the
  first thing to try it was a script being smoke-tested before it ran against hardware.

## One deliberate departure from the shipped dialog

**Printer rows carry an address**, and the serial when that is what tells two rows apart.
Orca's config routinely holds a stale record beside a live one — this machine has two
identically-named entries on the same IP. Noted in both directions: the README says what
and why, and the specification mockup says the reconstruction differs here.

## Checks

| | |
|---|---|
| print dialog — simulator, `?path=4` / `?path=5` | 52/52, 5/5 |
| print dialog — real bridge, real plate, no printer | 21/21 |
| print dialog — **connected U1**, read-only | 15/15 |
| cancel route, idle machine | 4/4 |
| cancel latency under a blocked queue | 5/5 |
| **send → print → cancel, real machine** | 6/6 |
| specification mockup | 73/73 |
| Device page | 61/61 |
| conformance · unit_jsc · coverage | 165/165 · 228/228 · clean |

## Honest limits

- **No plate has been left to run.** Everything is cancelled within seconds.
- **That homing is what filled the queue is inference** from the timing; the `G4`
  measurement is direct, that observation is not.
- **The cloud path is untouched** — `sw_StartCloudPrint`, `server.files.pull`, S3.
- **Whether a started job honours the mapping and preferences** is unchecked; the macros
  are acknowledged, which is not the same thing.
- **The multi-plate Model Information layout is not built** — unreachable through this
  host, recovered and drawn in the mockup.
- **A file filament's nozzle is inferred**, the only inferred thing here.

## Also in this branch

The first commit predates the brief being clarified: three interactive **design options**
under `resources/web/print_processing/mockups/`. Kept deliberately — the reconstruction
was asked for *first*, and these are where a design conversation starts once it ships.
They are not wired into anything.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant