Engine calls from the UI¶
Reference for every path a UI element takes to the native engine. NpsAbi is the sole C# P/Invoke surface for NPSEngine.dll (src/Nps.EngineHost/NpsAbi.cs:3, DllName = "NPSEngine" at NpsAbi.cs:43). Everything above it is one of two facades:
EngineSession(src/Nps.EngineHost/EngineSession.cs:67) — owns the engine handle.ViewportController(src/Nps.Shell/ViewportController.cs:117) — owns the G-code viewport handle.
SimulatorApp also uses MeshController (src/Nps.EngineHost/MeshController.cs:71) for mesh snapshots, and the two shell painters split by app: Slicer projects its toolpath through GcodeToolpathPainter, Simulator projects its background mesh through MeshCanvasPainter (its toolpath still paints direct — see the last section). Export names below are verified against the [DllImport] declarations in NpsAbi.cs.
flowchart TD
UI[Button / slider / scrubber / canvas drag] --> APP[MainWindow.axaml.cs handler]
APP --> ES[EngineSession]
APP --> VC[ViewportController]
APP --> MC[MeshController]
APP --> PT[GcodeToolpathPainter / MeshCanvasPainter]
ES --> ABI[NpsAbi P/Invoke]
VC --> ABI
MC --> ABI
PT --> ABI
ABI --> ENG[NPSEngine native]
HOW IT IS: SlicerApp never calls NpsAbi directly — rg "NpsAbi\." src/SlicerApp returns nothing; even its CLI preflight goes through EngineSession (src/SlicerApp/Program.cs:22,27,35,59). SimulatorApp has two deliberate exceptions, covered in the Simulator-only section below.
EngineSession → NpsAbi exports¶
| Facade method | NpsAbi export(s) | Citations |
|---|---|---|
EnsureAbiMatchesHost() |
GetAbiVersion (compared to ExpectedAbiVersion = 1, NpsAbi.cs:238) |
EngineSession.cs:104, NpsAbi.cs:241 |
CreateAndInitialize() / Initialize() |
CreateEngine, InitializeEngine |
EngineSession.cs:165,184,191, NpsAbi.cs:224,230 |
EngineVersion (property) |
GetEngineVersion |
EngineSession.cs:125, NpsAbi.cs:233 |
LoadModel(path) |
LoadModelFromFile; on failure GetLastErrorMessage; on success GetPlateCount, GetTriangleCountForPlate, GetVertexPositions (count only), GetPaletteColorCount, GetPaletteColor, GetModelUnit |
EngineSession.cs:202–238, NpsAbi.cs:252,265,295,307,310,319,322,298 |
GenerateDemoToolpath(options) — demo toolpath, not a production slice (EngineSession.cs:248–250) |
GenerateGcode (with packed NpsGcodeGenParams), GetLastGcodeMoveCount, GetLastGcodeMoves |
EngineSession.cs:253–303, NpsAbi.cs:333,366,369 |
GetLastGeneratedGcodeText() |
GetLastGeneratedGcode |
EngineSession.cs:138, NpsAbi.cs:336 |
Analyze(profile) |
AnalyzeGcode (with packed NpsMaterialProfile) |
EngineSession.cs:332–361, NpsAbi.cs:443 |
GetFindingsSummary() |
GetAnalysisFindingsSummaryCount, GetAnalysisFindingsSummary |
EngineSession.cs:376–388, NpsAbi.cs:455,458 |
GetFindingDetail(id) |
GetAnalysisFindingsDetail |
EngineSession.cs:407–411, NpsAbi.cs:461 |
GetWarpGrid(layer) |
GetWarpGridInfo, GetWarpGridRisk |
EngineSession.cs:421–433, NpsAbi.cs:480,483 |
GetOverlayValue(id, layer, move) |
GetOverlayValue |
EngineSession.cs:486–489, NpsAbi.cs:464 |
Dispose() |
DestroyEngine (via SafeEngineHandle) |
NpsAbi.cs:227 |
ViewportController → NpsAbi exports¶
| Facade method | NpsAbi export(s) | Citations |
|---|---|---|
CreateAndInitialize() / Initialize() |
CreateGcodeViewport |
ViewportController.cs:203,222, NpsAbi.cs:389 |
SetMoves(ptr, count) |
ViewportSetGcodeMoves |
ViewportController.cs:240–245, NpsAbi.cs:395 |
SetLayerVisibility(maxLayer) |
ViewportSetLayerVisibility |
ViewportController.cs:252–256, NpsAbi.cs:398 |
SetCamera(state) (also SetCameraPreset) |
ViewportSetCamera |
ViewportController.cs:262–266, NpsAbi.cs:401 |
SetSimulationState(simulating, index) |
ViewportSetSimulationState |
ViewportController.cs:489–493, NpsAbi.cs:420 |
ApplyOverlay(mode, values, vmin, vmax, invert, mean?) |
ViewportApplyOverlay |
ViewportController.cs:517–547, NpsAbi.cs:432 |
ApplyOverlay(session, mode, invert) — session overload |
per move: EngineSession.GetOverlayValue → GetOverlayValue; then ViewportApplyOverlay; warp-risk mode also calls ViewportSetWarpParams(true, 5.0f), any other mode ViewportSetWarpParams(false, 0) |
ViewportController.cs:564–639, NpsAbi.cs:464,432,424 |
SetWarpParams(active, scale) |
ViewportSetWarpParams |
ViewportController.cs:646–649, NpsAbi.cs:424 |
BuildBuffers() |
ViewportBuildBuffers |
ViewportController.cs:657–660, NpsAbi.cs:404 |
ComputeZoomFit(...) |
ProjectOrbitXY (per move, into scratch buffer) |
ViewportController.cs:432–456, NpsAbi.cs:500 |
Dispose() |
DestroyGcodeViewport (via SafeGcodeViewportHandle) |
NpsAbi.cs:392 |
HOW IT IS: the legacy split setters ViewportSetOverlayMode, ViewportSetOverlayValues, ViewportSetOverlayRange are still exported (NpsAbi.cs:422–428) but deprecated — the facade's doc comment calls them legacy in favor of the one-shot ViewportApplyOverlay (ViewportController.cs:495–501). No facade method calls them.
MeshController and the two painters¶
| Caller | NpsAbi export(s) | Citations |
|---|---|---|
MeshController.Reload() |
GetPlateCount, GetPaletteColorCount, GetPaletteColor, then per plate GetVertexPositions, GetTriangleIndices, GetTriangleColorIndices (all Marshal.Copy into managed PlateMesh arrays) |
MeshController.cs:131–190, NpsAbi.cs:295,319,322,310,313,316 |
GcodeToolpathPainter.RenderTo(...) |
ProjectOrbitXY (per segment), ViewportGetHeatmapColor (when an overlay is active and mode ≠ motion-type) |
GcodeToolpathPainter.cs:85,160–175, NpsAbi.cs:500,494 |
MeshCanvasPainter.RenderTo(...) |
ProjectOrbitXY (per triangle vertex, 3 calls) |
MeshCanvasPainter.cs:112,174–178, NpsAbi.cs:500 |
Both painters hoist scratch buffers out of the per-segment loop (GcodeToolpathPainter.cs:54, MeshCanvasPainter.cs:74). Neither owns engine state: the mesh painter renders the MeshController snapshot, the toolpath painter reads ViewportController.MovesSpan (GcodeToolpathPainter.cs:125), a span over the pointer last handed to ViewportSetGcodeMoves.
Simulator-only raw smoke calls (Program.cs)¶
HOW IT IS: SimulatorApp/Program.cs calls NpsAbi directly, before Avalonia starts, on a throwaway engine. The UI then creates a second engine via EngineSession.CreateAndInitialize() (SimulatorApp/MainWindow.axaml.cs:108); the self-check engine is disposed and does not seed the UI session.
| Order | Call | Line |
|---|---|---|
| 1 | GetAbiVersion() vs ExpectedAbiVersion; mismatch → throw |
Program.cs:22–28 |
| 2 | CreateEngine() wrapped in SafeEngineHandle |
Program.cs:31 |
| 3 | InitializeEngine(engine) |
Program.cs:33 |
| 4 | GetEngineVersion(engine) (log) |
Program.cs:34 |
| 5 | LoadModelFromFile(engine, benchy) |
Program.cs:43 |
| 6 (success) | GetPlateCount, GetTotalVertexCount, GetTotalTriangleCount, per plate GetTriangleCountForPlate + GetVertexPositions, GetPaletteColorCount + GetPaletteColor loop, GetModelUnit — console output only |
Program.cs:50–71 |
| 6 (failure) | GetLastErrorMessage |
Program.cs:75 |
| — | DestroyEngine (implicit, SafeEngineHandle dispose) |
NpsAbi.cs:227 |
The UI starts regardless of the self-check outcome (Program.cs:98–99). Slicer's preflight covers the same ground but stays behind the facade: EnsureAbiMatchesHost, CreateAndInitialize, LoadModel, GenerateDemoToolpath with NumLayers: 0 (src/SlicerApp/Program.cs:22,27,35,59).
HOW IT IS: Simulator's MainWindow.RenderViewport also calls NpsAbi directly at paint time — ProjectOrbitXY and ViewportGetHeatmapColor (SimulatorApp/MainWindow.axaml.cs:768,770,778) — because it has not migrated to GcodeToolpathPainter; the painter's comment notes Simulator "may pass _simMoveIndex + 1 until it also migrates" (GcodeToolpathPainter.cs:78–79). Slicer's render path already goes through the painter.
Quick index: element → call chain¶
| UI element | Chain to the engine |
|---|---|
| Load + Gen + Analyze button | EngineSession.LoadModel → LoadModelFromFile … → GenerateDemoToolpath → GenerateGcode, GetLastGcodeMoves → ViewportController.SetMoves/SetLayerVisibility/SetCamera/BuildBuffers → ViewportSetGcodeMoves, ViewportSetLayerVisibility, ViewportSetCamera, ViewportBuildBuffers → Analyze → AnalyzeGcode |
| Findings panel (populate / select row) | GetFindingsSummary → GetAnalysisFindingsSummaryCount + GetAnalysisFindingsSummary; GetFindingDetail → GetAnalysisFindingsDetail; selection also SetSimulationState → ViewportSetSimulationState |
| Overlay radio (thermal / warp-risk / …) | ViewportController.ApplyOverlay(session, mode) → per move GetOverlayValue → one ViewportApplyOverlay → warp-risk adds ViewportSetWarpParams |
| Layer slider / toolbar L+/L− | SetLayerVisibility → ViewportSetLayerVisibility, then ViewportBuildBuffers |
| Play / scrub / finding jump (Simulator) | SetSimulationState(true, idx) → ViewportSetSimulationState, then ViewportBuildBuffers |
| Canvas orbit / pan / zoom-fit | camera kept in CameraState; SetCamera → ViewportSetCamera; zoom-fit projects via ProjectOrbitXY |
| Every repaint | painter → ProjectOrbitXY per segment/vertex; overlay colors via ViewportGetHeatmapColor |
| Warp grid label (Slicer only) | GetWarpGrid(l) for layers 0–5 → GetWarpGridInfo + GetWarpGridRisk (SlicerApp/MainWindow.axaml.cs:334–336) |
Note
Overlay mode strings are the engine's wire ids, defined once as NpsAbi.OverlayModeId constants (NpsAbi.cs:88–95): motion-type, thermal, warp-risk, bond-strength, cooling-eff. motion-type is the "no overlay" state, not a true off switch.
For where these calls sit inside each app's interaction flow, see Slicer interactions and the Simulator overview; the sharing analysis lives under Sharing.