06 — User and developer flows¶
Developer-level end-to-end sequences: every hop named, L5 app → L4
shell → L3 EngineHost → L2 C ABI → L1 engine. For the click-level,
per-control view of the same gestures see
../../slicer/workflows.md and
../../simulator/workflows.md.
Abbreviations: SA = src/SlicerApp, SI = src/SimulatorApp,
SH = src/Nps.Shell, EH = src/Nps.EngineHost.
App boot¶
sequenceDiagram
participant OS
participant Program as Program.Main
participant App as Avalonia App
participant MW as MainWindow
participant ES as EngineSession
participant VP as ViewportController
participant ABI as "NpsAbi / NPSEngine"
OS->>Program: start process
Program->>ABI: ABI gate (version assert)
Program->>ABI: CLI preflight (session + fixture load)
Program->>App: BuildAvaloniaApp().StartWithClassicDesktopLifetime
App->>MW: new MainWindow
MW->>MW: InitializeShell (wire chrome + events)
MW->>ES: CreateAndInitialize()
ES->>ABI: CreateEngine + InitializeEngine
MW->>VP: CreateAndInitialize()
VP->>ABI: CreateGcodeViewport
MW->>MW: "Status(shell ready)"
| # | Step | SlicerApp | SimulatorApp |
|---|---|---|---|
| 1 | Program.Main |
SA/Program.cs:14 |
SI/Program.cs:15 |
| 2 | ABI gate (NPS_ABI_VERSION = 1) |
facade EngineSession.EnsureAbiMatchesHost() SA/Program.cs:22 → EH/EngineSession.cs:104 |
raw NpsAbi.GetAbiVersion() SI/Program.cs:22, throws :23-28 |
| 3 | CLI preflight session | EngineSession.CreateAndInitialize() SA/Program.cs:27, disposed before GUI |
raw new SafeEngineHandle(NpsAbi.CreateEngine(), true) SI/Program.cs:31 |
| 4 | Fixture probe | session.LoadModel(benchy) SA/Program.cs:35 + demo-gen :59-64 (NumLayers: 0) |
raw NpsAbi.LoadModelFromFile SI/Program.cs:43; failure caught, GUI still starts :88-96 |
| 5 | Avalonia boot | SA/Program.cs:80-81 (ShellTrace.InstallStderrSink() :90) |
SI/Program.cs:98-99 (sink :108) |
| 6 | MainWindow ctor → InitializeShell |
SA/MainWindow.axaml.cs:59-65; playback tab hidden (IsPlaybackTabVisible="False" axaml:20) |
SI/MainWindow.axaml.cs:77-83; playback timer 80 ms :105-106, MeshController created :109 |
| 7 | GUI engine + viewport | _session :93, _viewport :94 |
_session :108, _mesh :109, _viewport :110 |
The GUI session in step 7 is a second engine instance; the CLI
preflight session is disposed (→ DestroyEngine) before the window
opens.
Load → demo gen → analyze → viewport¶
Primary button path (OnLoadAnalyzeClick — SA/MainWindow.axaml.cs:155
/ SI/MainWindow.axaml.cs:205).
sequenceDiagram
participant U as User
participant MW as MainWindow
participant FL as FixtureLocator
participant ES as EngineSession
participant Eng as nps::Engine
participant VP as ViewportController
participant UI as Canvas/Status
U->>MW: click Load + Gen + Analyze
MW->>FL: FindPrimaryBenchy3Mf()
FL-->>MW: path or missing
alt fixture missing
MW->>UI: "ERROR: primary 3mf required"
else ok
MW->>ES: LoadModel(path)
ES->>Eng: "LoadModelFromFile (ZIP/OPC → ModelXML → MeshResolver)"
Eng-->>ES: plates, verts, tris, palette, unit
ES-->>MW: LoadResult
MW->>UI: Status mesh summary
MW->>ES: GenerateDemoToolpath(options)
Note over ES,Eng: "DEMO TOOLPATH — not production slice"
ES->>Eng: GenerateGcode
Eng-->>ES: engine-owned move array
MW->>VP: SetMoves(LastMovesPointer, count)
MW->>VP: SetLayerVisibility + SetCamera + BuildBuffers
MW->>ES: Analyze(profile)
ES->>Eng: AnalyzeGcode (6 analyzers)
MW->>MW: LoadFindings (16B summary rows)
MW->>UI: RenderViewport + Status(DEMO TOOLPATH…)
end
Ordered steps (code)¶
| # | Step | App call site | Facade / export |
|---|---|---|---|
| 1 | Resolve fixture | SA:162 / SI:212 |
FixtureLocator.FindPrimaryBenchy3Mf SH/FixtureLocator.cs:60 |
| 2 | Load 3MF | _session.LoadModel SA:169 / SI:219 |
EngineSession.LoadModel EH/EngineSession.cs:202 → NpsAbi.LoadModelFromFile EH/NpsAbi.cs:252 |
| 3 | Mesh snapshot (Sim only) | _mesh?.Reload() SI:226 (unconditional — engine clears mesh on every load attempt) |
MeshController.Reload EH/MeshController.cs:131 |
| 4 | Demo toolpath | SA:261 (SliceParams-driven) / SI:245-250 (hardcoded) |
EngineSession.GenerateDemoToolpath EH/EngineSession.cs:253 → NpsAbi.GenerateGcode EH/NpsAbi.cs:333 |
| 5 | Feed viewport | _viewport.SetMoves SA:276 / SI:265 |
ViewportController.SetMoves SH/ViewportController.cs:240 → ViewportSetGcodeMoves EH/NpsAbi.cs:395 (exact export name) |
| 6 | Layer/camera/buffers | SA:277-286 / SI:266-268 |
SetLayerVisibility :252, SetCamera :262, BuildBuffers :657 |
| 7 | Analyze | SA:289 / SI:272 — both pass MaterialProfileOptions("PLA", 210, 60) |
EngineSession.Analyze EH/EngineSession.cs:332 → NpsAbi.AnalyzeGcode EH/NpsAbi.cs:443 |
| 8 | Findings summary | LoadFindings() SA:366 / SI:316 |
GetFindingsSummary EH/EngineSession.cs:376 (16B rows; 300B detail only on selection) |
| 9 | Render | RenderViewport() SA:597 / SI:695; Sim paints mesh underlay first SI:732 |
painters read MovesSpan SH/ViewportController.cs:672 |
Golden-fixture expectation after step 2: 1 plate / 16562 verts / 33097 tris / 4 palette colors.
Parse path (ParseGcode / ParseBgcode)¶
No app UI path exists today. Neither app has an open/paste G-code
gesture and EngineSession exposes no Parse facade; every caller is a
test harness (SH/ViewportController.cs:235 notes the "future ParseGcode
path"). What matters architecturally:
| Fact | Where |
|---|---|
ParseGcode(engine, text, len) |
EH/NpsAbi.cs:377 |
ParseBgcode(engine, bytes, len) |
EH/NpsAbi.cs:380 |
| Split text buffers: generated ≠ parsed | GetLastGeneratedGcode EH/NpsAbi.cs:336 vs GetLastParsedGcode :345 |
| Move array is shared after either gen or parse | GetLastGcodeMoveCount :366 / GetLastGcodeMoves :369 |
Finding click → detail¶
sequenceDiagram
participant U as User
participant FP as FindingsPanel
participant MW as MainWindow
participant ES as EngineSession
participant Eng as Engine
U->>FP: select finding row
FP->>FP: UpdateDetail(item)
FP->>MW: FindingSelected(item)
MW->>FP: TryGetCachedItem(findingId)
alt cache miss
MW->>ES: GetFindingDetail(findingId)
ES->>Eng: GetAnalysisFindingsDetail
Eng-->>ES: 300B category/message/suggestion
end
MW->>MW: status + layer bump + highlight
| Step | SlicerApp | SimulatorApp |
|---|---|---|
| Panel hop | FindingsPanel.axaml.cs:328 → event :165 |
same |
| App handler | SA:421 |
SI:359 |
| Cache first | TryGetCachedItem SH/Controls/FindingsPanel.axaml.cs:123-133 — no second host call |
same |
| Miss fetch | FindingFormatter.RefreshDetail → EngineSession.GetFindingDetail EH/EngineSession.cs:407 |
_session.GetFindingDetail SI:384-391 |
| Highlight | SetSimulationState(true, MoveIndex) SA:469-474 — highlight only, full path still drawn |
_simMoveIndex seek SI:413-417, scrubber mirror pp.SimIndex SI:423-427 |
Preferred ABI path honored: 16-byte NpsAnalysisFindingSummary rows
first (EH/NpsAbi.cs:180), one 300-byte NpsAnalysisFinding detail per
selection (:161, :461).
Overlay application¶
sequenceDiagram
participant U as User
participant MW as MainWindow
participant ES as EngineSession
participant VP as ViewportController
U->>MW: choose overlay mode
MW->>VP: ApplyOverlay(session, mode)
loop each move index
VP->>ES: GetOverlayValue(mode, layer, i)
end
VP->>VP: "one-shot ViewportApplyOverlay (NpsAbi.cs:432)"
MW->>MW: RenderViewport (heatmap via ViewportGetHeatmapColor)
- Radio "None" maps to mode id
motion-type, not a no-op (SH/Controls/OverlayPanel.axaml:10Tag);motion-typerenders as the inactive/plain path (SH/ViewportController.cs:576). - Apps call the session overload
ViewportController.ApplyOverlay(session, mode)SH/ViewportController.cs:564; it fetches per-move values viaEngineSession.GetOverlayValue(:588→EH/EngineSession.cs:486) and finishes with the one-shot exportViewportApplyOverlayEH/NpsAbi.cs:432. warp-riskadditionally auto-enables deformationSetWarpParams(true, 5.0f)SH/ViewportController.cs:627-634; any other mode sets(false, 0).- After every Load+Gen+Analyze both apps force the thermal overlay
(
SA:305-312/SI:288-295).
Playback (Simulator only)¶
sequenceDiagram
participant U as User
participant MW as MainWindow
participant T as DispatcherTimer
participant VP as ViewportController
U->>MW: press P (play)
MW->>T: start (120 / speed ms)
loop tick
T->>MW: StepPlayback(+1)
MW->>VP: SetSimulationState(true, ++SimMoveIndex)
MW->>MW: RenderViewport progressive
opt hit finding within 2 moves
MW->>T: stop (auto-pause)
end
end
U->>MW: P again (pause)
| Step | Where |
|---|---|
| Key dispatch (P / Left / Right only) | ShellKeybindings.Handle SH/Controls/ShellKeybindings.cs:84-110 |
Toggle play, interval 120 / max(0.1, speed) ms |
TogglePlay SI:510-536 (:520) |
Advance + clamp + ViewportSetSimulationState |
StepPlayback SI:616-658 (:623-625; export EH/NpsAbi.cs:420) |
Auto-pause rule: \|moveIndex − simIndex\| < 2 AND _autoPause |
SI:645-656; moveIndex == -1 findings excluded |
| Scrub pause/resume | apps subscribe ScrubberPointerPressed/Released (SI:132-133), NOT ScrubberDragStarted/Completed (defined PlaybackPanel.axaml.cs:118/121, unused) |
In SlicerApp there is no playback tab (SA/MainWindow.axaml:20) and
P/Left/Right only emit Status("Playback unavailable in Slicer.")
(SA:142-144).
Gates¶
| Gate | Behavior | Where |
|---|---|---|
| ABI gate | NPS_ABI_VERSION mismatch → InvalidOperationException before any handle alloc |
EH/EngineSession.cs:104-113; SA/Program.cs:22, SI/Program.cs:22-28 |
| Fixture gate | missing 4-colors+Benchy+AMS+test-v2.3mf → status error, engine never called |
SA:163-167 / SI:213-217 |
| Session guard | null/invalid _session → silent return |
SA:157-160 / SI:207-210 |
| Model-loaded gate | Apply/Regenerate blocked until a load succeeded | SA:200-204 |
| No-moves guard | gen success but 0 moves → status, stop | SA:268-272 / SI:257-261 |
| Load failure | engine code + GetLastErrorMessage text → "Load failed: {code} {message}" |
EH/EngineSession.cs:214-216 → SA:170-173 / SI:228-232 |
Developer: build / test / run¶
cmake -B build -S src/Engine -DBUILD_TESTING=ON -DCMAKE_BUILD_TYPE=Debug
cmake --build build -j
ctest --test-dir build --output-on-failure
DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 $HOME/.dotnet/dotnet test
# apps (native lib on LD_LIBRARY_PATH / build dir)
./start-gui.sh # or project-specific launch
See also¶
shell-and-apps.mdengine-host.mdengine-modules.md- ../../slicer/workflows.md — click-level Slicer view
- ../../simulator/workflows.md — click-level Simulator view