Table of Contents

Controller Page Web Implementation

This document describes the Quasar-based web implementation of the Controller Page in the HiNC-2025-webservice project.

Overview

The Controller Page is built on the Quasar CLI SPA that lives under HiNC-2025-webservice/wwwroot-src/.

Top-level structure:

  • Backend API controller: Controller/ControllerController.cs (extended; see Endpoints).
  • Frontend Vue 3 / TypeScript <script setup> components: wwwroot-src/src/pages/ControllerPage.vue + seven sibling components under wwwroot-src/src/components/controller/.
  • Typed API wrapper: wwwroot-src/src/api/controller.ts.
  • Shared infrastructure reused unchanged: ObjectManagementMenuButton, RenderingCanvas, RenderingCanvasToolBar, NumericInput, useCleanupHub, projectStore.

Backend Implementation

Endpoints

All endpoints live under Controller/ControllerController.cs (route prefix api/Controller). Endpoints introduced in the 2026-04-19 rebuild are marked new.

  • Initialize
    • POST /api/Controller/initialize - new. Registers the project's HardNcEnv under a fresh IndexService key so the ObjectManagementMenuButton can target it. Returns { ncEnvKey, cncBrand }.
  • Brand
    • GET/PUT /api/Controller/cnc-brand - read / write CncBrand.
    • GET/PUT /api/Controller/heidenhain-master-axis-char - new. Exposes HeidenhainMasterAxisChar as a single-character string (A, B, or C).
  • ISO coordinate table (string keys: G54, ..., G59.9)
    • GET /api/Controller/iso-coordinate-table
    • PUT /api/Controller/iso-coordinate-table/{index}
    • POST /api/Controller/iso-coordinate-table/{index}/set-to-program-zero
    • POST /api/Controller/iso-coordinate-table/{index}/set-to-machine-zero
    • PUT /api/Controller/iso-coordinate-selection - updates IsoCoordinateEntryDisplayee.
  • Heidenhain datum preset / shift
    • GET/PUT /api/Controller/heidenhain-datum-preset-table and /{index}
    • POST /api/Controller/heidenhain-datum-preset-table/{index}/reset - new. Resets the entry to zero (matches WPF DatumPresetTablePanel.SetDatumPosition_Click).
    • GET/PUT /api/Controller/heidenhain-datum-shift-table and /{index}
    • POST /api/Controller/heidenhain-datum-shift-table/{index}/reset - new.
  • Milling tool offset table (MillingToolOffsetTable)
    • GET /api/Controller/milling-tool-offset-table - returns Dictionary<int, MillingToolOffsetTableRow>.
    • PUT /api/Controller/milling-tool-offset-table - whole-table replacement (legacy batch API, still supported).
    • PUT /api/Controller/milling-tool-offset-table/{toolId:int} - new. Row-level PUT.
    • POST /api/Controller/milling-tool-offset-table - new. Adds a default-valued row and returns the new tool ID.
    • DELETE /api/Controller/milling-tool-offset-table/{toolId:int} - new.
    • POST /api/Controller/milling-tool-offset-table/{toolId:int}/rename - new. Body: int newToolId. Rejects duplicate keys.
    • POST /api/Controller/set-ideal-offset-from-toolhouse - recomputes the offset table from the current MachiningToolHouse.
    • GET/PUT /api/Controller/ideal-offset-dependent - IsIdealOffsetDependentOnToolHouse toggle.
  • Machine (strokes, speeds, rapid feed)
    • GET/PUT /api/Controller/rapid-feedrate
    • GET/PUT /api/Controller/tooling-time
    • GET/PUT /api/Controller/stroke-limit-xyz - double[6] in mm.
    • GET/PUT /api/Controller/stroke-limit-abc - double[6] in radians.
    • GET/PUT /api/Controller/max-rotary-speed-abc - double[3] in rad/s.
  • Config
  • Display
    • POST /api/Controller/initialize-display/{connectionId} - binds the shared MachiningProjectDisplayee to the rendering engine keyed by connectionId. The Controller page and Player page share this displayee, so Display Options ▾ toggles via the rendering-flags API propagate to both viewers.

Frontend Implementation

pages/ControllerPage.vue

  • Two-pane <q-splitter>. Left: management tabs. Right: rendering viewer.
  • Head line: ObjectManagementMenuButton + “Controller” title + status badge; binds to HardNcEnv via the initialize endpoint.
  • Tabs: a <q-tabs> row with seven tabs. Datum Preset and Datum Shift are gated behind isHeidenhain (cncBrand === CncBrand.Heidenhain), mirroring the WPF UpdateBrandSpecificTabs() behaviour.
  • Viewer: RenderingCanvasToolBar (View dropdown) + ControllerExtendedToolBar (Display Options dropdown) + RenderingCanvas.

src/api/controller.ts

Full-feature typed wrapper. Retains the CncBrand / getCncBrand exports used by PlayerExtendedToolBar.vue so the Player page stays compatible. Adds initializeController, row-level offset-table helpers, master-axis char read/write, and the two new datum reset helpers.

Per-tab components (src/components/controller/)

  • CoordinateTableTab.vue - string-keyed ISO coordinate editor (G54, ..., G59.9). P0 / M0 action buttons. Row-click → iso-coordinate-selection so the selected coordinate is highlighted in the viewer.
  • DatumPresetTab.vue - Heidenhain-only. Q339 int key. Reset-to-zero action. “Show on Display” icon toggles RenderingFlag.HeidenhainCoordinate.
  • DatumShiftTab.vue - Heidenhain-only. D int key. Same structure as DatumPresetTab.
  • OffsetTableTab.vue - MillingToolOffsetTable editor. When isIdealOffsetDependentOnToolHouse is on, tool IDs are read-only and a “Refresh from Tool House” button appears. When off, each tool ID is inline-editable via the shared NumericInput, and an Add button creates new rows.
  • MachineTab.vue - Rapid Feedrate (mm/min) + Tooling Time (sec) + Linear Stroke (mm) + Rotary Stroke (deg) + Max Rotary Speed (rpm). Client-side unit conversion (rad ↔ deg, rad/s ↔ rpm) in the onStrokeAbcChange / onMaxRotaryChange handlers. Uses NumericInput so the backend's ±Infinity default stroke limits display literally.
  • BrandTab.vue - <q-select> with Fanuc / Heidenhain / Mazak / Siemens / Syntec. When Heidenhain, exposes a Master-axis character <q-select> with options A / B / C. Emits @brand-changed to the parent.
  • ConfigTab.vue - “Enable Shortest Rotary Path” toggle + info banner.

ControllerExtendedToolBar.vue

Thin Display Options ▾ wrapper around the shared /api/rendering-flags endpoints, grouped into Solid / Coordinate / Display Aids (the same structure as PlayerExtendedToolBar.vue). The Heidenhain Coordinate row is visible only when the current brand is Heidenhain. Mirrors the win-desktop ControllerExtendedRenderingCanvasToolBar.xaml which is itself a RenderingFlagSubmenu wrapper.

Integration Points

  • Shared displayee with Player: initialize-display/{connectionId} attaches ProjectDisplayeeService.MachiningProjectDisplayee, so flag toggles on the Controller page's extended toolbar also update the Player viewer.
  • Project reload: the page is hosted under MainLayout.vue's <keep-alive :key="projectEpoch"> wrapper, so it remounts automatically on project open / reload (no explicit project-watch boilerplate in ControllerPage.vue).
  • Cleanup hub: initialize returns a fresh NcEnv key each time; the page registers it with useCleanupHub so it is dropped on unmount to prevent IndexService leaks.

Notable design decisions

  • Row-level tool-offset CRUD replaces the older “PUT whole table” pattern. Backed by the four endpoints PUT /{toolId} (update one row), POST (add a default-valued row), DELETE /{toolId} (remove one row), and POST /{toolId}/rename (change the tool ID of an existing row).
  • Infinity round-trip: Program.cs enables JsonNumberHandling.AllowNamedFloatingPointLiterals, and the shared NumericInput.vue already parses / formats Infinity, -Infinity, and NaN strings. Default StrokeLimitXyz_mm of [-Inf, +Inf, ...] is therefore displayed and editable without any special handling in MachineTab.vue.
  • IsoCoordinateTable string keys: the table is a Dictionary<string, Vec3d> with human-readable G-code keys ("G54", "G59.2"). The frontend consumes this directly - no IndexDisplay computed property is required. See iso-coordinate-table-string-key.md in the Second Brain.
  • Heidenhain-conditional tab visibility is driven by a single cncBrand: CncBrandValue | null state on the page. BrandTab emits @brand-changed; ObjectManagementMenuButton reloads trigger a full initialize refetch. Switching away from Heidenhain while one of those tabs is active falls back to Coordinate Table.

Key Differences from WPF Implementation

  1. Dependency-free SPA: served by the same ASP.NET Core host that provides the REST API; no embedded WPF or web-view bridge.
  2. Component architecture: Vue 3 <script setup> + Quasar <q-tabs> / <q-splitter> / <q-markup-table> / <q-select> instead of WPF UserControl + TabControl + DataGrid.
  3. Shared displayee: the Quasar implementation binds MachiningProjectDisplayee once (via initialize-display) and lets the rendering-flags API drive toggles, rather than each WPF tab owning per-tab ShowXxx bool bindings.
  4. Granular endpoints: both the legacy and Quasar implementations split NcEnv into per-property endpoints, but the Quasar implementation additionally introduces row-level CRUD for the tool offset table.
  5. Unit conversion: handled in the per-tab component (rad ↔ deg, rad/s ↔ rpm), same as the legacy draft.

Future Enhancements

  • Undo / redo coordination with HardNcEnv's change-tracking.
  • Batch updates (debounced “commit all deltas” for high-frequency sliders).
  • Keyboard shortcuts (Arrow keys for row navigation, Enter to commit, Esc to revert inline edits).
  • Additional per-row tooltips explaining each column.