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 underwwwroot-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 theObjectManagementMenuButtoncan 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, orC).
- ISO coordinate table (string keys:
G54, ...,G59.9)GET /api/Controller/iso-coordinate-tablePUT /api/Controller/iso-coordinate-table/{index}POST /api/Controller/iso-coordinate-table/{index}/set-to-program-zeroPOST /api/Controller/iso-coordinate-table/{index}/set-to-machine-zeroPUT /api/Controller/iso-coordinate-selection- updates IsoCoordinateEntryDisplayee.
- Heidenhain datum preset / shift
GET/PUT /api/Controller/heidenhain-datum-preset-tableand/{index}POST /api/Controller/heidenhain-datum-preset-table/{index}/reset- new. Resets the entry to zero (matches WPFDatumPresetTablePanel.SetDatumPosition_Click).GET/PUT /api/Controller/heidenhain-datum-shift-tableand/{index}POST /api/Controller/heidenhain-datum-shift-table/{index}/reset- new.
- Milling tool offset table (MillingToolOffsetTable)
GET /api/Controller/milling-tool-offset-table- returnsDictionary<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 currentMachiningToolHouse.GET/PUT /api/Controller/ideal-offset-dependent-IsIdealOffsetDependentOnToolHousetoggle.
- Machine (strokes, speeds, rapid feed)
GET/PUT /api/Controller/rapid-feedrateGET/PUT /api/Controller/tooling-timeGET/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
GET/PUT /api/Controller/shortest-rotary- EnableShortestRotary.
- Display
POST /api/Controller/initialize-display/{connectionId}- binds the shared MachiningProjectDisplayee to the rendering engine keyed byconnectionId. The Controller page and Player page share this displayee, soDisplay 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 toHardNcEnvvia theinitializeendpoint. - Tabs: a
<q-tabs>row with seven tabs. Datum Preset and Datum Shift are gated behindisHeidenhain(cncBrand === CncBrand.Heidenhain), mirroring the WPFUpdateBrandSpecificTabs()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-selectionso the selected coordinate is highlighted in the viewer.DatumPresetTab.vue- Heidenhain-only. Q339 int key. Reset-to-zero action. “Show on Display” icon togglesRenderingFlag.HeidenhainCoordinate.DatumShiftTab.vue- Heidenhain-only. D int key. Same structure as DatumPresetTab.OffsetTableTab.vue- MillingToolOffsetTable editor. WhenisIdealOffsetDependentOnToolHouseis on, tool IDs are read-only and a “Refresh from Tool House” button appears. When off, each tool ID is inline-editable via the sharedNumericInput, 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 theonStrokeAbcChange/onMaxRotaryChangehandlers. UsesNumericInputso the backend's±Infinitydefault 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-changedto 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}attachesProjectDisplayeeService.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 inControllerPage.vue). - Cleanup hub:
initializereturns a fresh NcEnv key each time; the page registers it withuseCleanupHubso 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), andPOST /{toolId}/rename(change the tool ID of an existing row). - Infinity round-trip:
Program.csenablesJsonNumberHandling.AllowNamedFloatingPointLiterals, and the sharedNumericInput.vuealready parses / formatsInfinity,-Infinity, andNaNstrings. DefaultStrokeLimitXyz_mmof[-Inf, +Inf, ...]is therefore displayed and editable without any special handling inMachineTab.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 - noIndexDisplaycomputed property is required. Seeiso-coordinate-table-string-key.mdin the Second Brain. - Heidenhain-conditional tab visibility is driven by a single
cncBrand: CncBrandValue | nullstate on the page.BrandTabemits@brand-changed;ObjectManagementMenuButtonreloads trigger a fullinitializerefetch. Switching away from Heidenhain while one of those tabs is active falls back to Coordinate Table.
Key Differences from WPF Implementation
- Dependency-free SPA: served by the same ASP.NET Core host that provides the REST API; no embedded WPF or web-view bridge.
- Component architecture: Vue 3
<script setup>+ Quasar<q-tabs>/<q-splitter>/<q-markup-table>/<q-select>instead of WPFUserControl+TabControl+DataGrid. - Shared displayee: the Quasar implementation binds
MachiningProjectDisplayeeonce (viainitialize-display) and lets the rendering-flags API drive toggles, rather than each WPF tab owning per-tabShowXxxbool bindings. - 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.
- 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,
Enterto commit,Escto revert inline edits). - Additional per-row tooltips explaining each column.