Table of Contents

Legacy Controller Page

The Legacy Controller page is the superseded controller face: the screen that edits HardNcEnv, kept reachable while it still carries settings nothing else in the application edits. It ships at the route /controller/:tab?, is reached from the app menu bar's Page → Legacy-Controller entry, and owns no Control-Tree id of its own. Controller settings for a project are edited on the General Setup page at /general-setup under the Control-Tree id equipment/controller — Controller Branch — and the two surfaces edit different models, so an edit on one is invisible to the other.

Important

The equipment/controller id named above belongs to the Controller Branch rather than to this page, and Anatomy makes no claim that it is stable across versions. The migration hop that keeps an older value resolving is described in Tree Ids and Routes.

The model this screen edits — what HardNcEnv holds, the three settings it owns alone, what it shares with the branch, and the engine files behind all of it — is Legacy Controller Settings.

The Tabs

The left pane is a tab strip over seven panels, addressable as the route's optional segment: coordinate-table, datum-preset, datum-shift, offset-table, machine, brand, config. A bare /controller canonicalises to coordinate-table. Two tab buttons are conditional — Datum Preset and Datum Shift render only while the brand reads Heidenhain — but their panels are always present in the template, so both segments stay valid URL targets under any brand. Switching the brand away from Heidenhain while one of those two is active moves the selection back to Coordinate Table.

Tab Edits
Coordinate Table IsoCoordinateTable, plus the P0 / M0 / Align P0 row actions
Datum Preset HeidenhainDatumPresetTable, keyed Q339
Datum Shift HeidenhainDatumShiftTable, keyed D
Offset Table MillingToolOffsetTable with row-level add, delete and key rename
Machine RapidFeedrate_mmdmin, ToolingTime, StrokeLimitXyz_mm, StrokeLimitAbc_rad, MaxRotarySpeedABC_radds
Brand CncBrand, and the master-axis character on Heidenhain
Config EnableShortestRotary

The brand select offers all five declared brands — Fanuc, Heidenhain, Mazak, Siemens and Syntec — labelled with the brand names verbatim rather than through the locale bundle, and warns above the select that "Brand-specific settings may be lost when the brand changes."

The Machine tab's axis rows are fixed, not chain-driven. It renders exactly X, Y, Z under Linear Axis Stroke (mm) and exactly A, B, C under Rotary Axis Stroke (deg) & Max Speed (rpm), because both row sets are literal in the template. Every write there sends the whole vector — all six linear bounds, all six rotary bounds converted to radians, or all three speeds converted to rad/s — rather than the one cell that changed. The branch's per-axis leaves take the opposite shape: one row per machine-chain axis, one axis per write, described in Per-Axis Tables.

Both stroke limits construct as an infinite box, and the service serializes named floating-point literals, so those bounds reach the browser as the tokens Infinity and -Infinity rather than as numbers. The shared numeric field prints what it is handed and parses both words back, so an unconfigured Machine tab opens showing those words rather than a blank and takes them typed in; the rotary rows carry them through unconverted, because the degree conversion guards on a finite value.

A stroke write does not survive its own re-read. The read and the write agree on the wire order: the read emits the six bounds interleaved per axis, [minX, maxX, minY, maxY, minZ, maxZ], and the tab posts the same six back. The write then hands that vector to the box constructor, which reads its arguments grouped — the whole minimum corner first, the whole maximum corner second. The vector is de-interleaved on the way in, so only the first and the last number land where they were sent and the four between them move to other rows; from the infinite default above, that is enough to leave the middle axis reading a Max below its Min after any single edit. The rotary stroke endpoints share the constructor and the behaviour; the three Max Speed values travel as a plain triple and are unaffected. The regrouped box is not inert — with the stroke check on and no runner-side stroke config resolving, the legacy play path validates every step against StrokeLimitXyz_mm and StrokeLimitAbc_rad as stored. The branch's per-axis leaves, writing one axis at a time, carry none of it.

How an Edit Commits

The tabs reuse the same numeric field the rest of the application uses, so the timing is that widget's: commit on blur or on Enter, never per keystroke, with the bounds and parse behaviour set out in Numeric Input. Clearing a cell parses to null and every legacy handler returns on null, so an emptied cell is not an edit — the one exception is the Offset Table's tool-number cell, which forbids an empty value and shows a parse error in place instead. Selects, toggles and row buttons commit on the click.

The optimistic write is the same capture-assign-await-restore shape the branch's panels use, and Editing Contract is where that shape, its row-scoped payloads and its rollback semantics are set out once. Four differences are this screen's own:

  • Three fields commit without a rollback. The Machine tab's Rapid Feedrate and Tooling Time handlers assign the new value and await the write without capturing the old one, so a refused write raises the toast and leaves the field showing the number the server rejected. The Brand tab's Master-axis character select is the third and the least recoverable: its handler captures nothing and restores nothing, and its two-way binding has already put the pick into the tab's own state before the handler runs, so a refused write leaves the rejected character on screen looking accepted. Every other value handler — the stroke and speed vectors, the coordinate and datum cells, the offset row and its key rename, the brand select itself, the shortest-rotary toggle (two-way bound as well, but reconstructing its old value from the new one) — restores the old value on failure. Row actions are outside the shape entirely: P0, M0, the datum reset, an add and a delete all write first and touch the local rows only once the request resolves.
  • The REST surface answers with status codes, not a success envelope. A missing table, an unknown index or an unexpected paste type is answered as not found, bad request or conflict. Most of those answers are bare English sentences; four carry a code — no project loaded, no NcEnv found, no NcEnv configuration and no fixture — which the client re-renders from the locale bundle under any language but English. The branch's surface instead answers a missing dependency inside a 200 envelope; nothing here does.
  • A refused load is silent on six of the seven tabs. Every write wrapper in this page's API module goes through the plain-JSON helper, which throws on any non-2xx and returns the parsed body otherwise, so a refused write always reaches a toast. Eight read wrappers deliberately bypass it and answer a benign default on any non-2xx: an empty list for the coordinate table, both datum tables and the offset table, unknown for the brand, A for the master-axis character, and off for the tool-house dependence and shortest-rotary flags. A refused load therefore renders as an empty or default tab, indistinguishable from one whose table really is empty, with no toast and no console line. Only the Machine tab's five reads throw, so it is the one tab on which a refused load shows.
  • The tool-house dependence toggle writes before it flips. Turning it on awaits the flag write, then flips the local toggle, then refreshes the offsets from the tool house and re-reads the table. A failure in the refresh therefore leaves the flag committed on both sides rather than reverted.

Every tab composes its failure toast the same way: negative, 3.5 seconds, the tab's own localized context followed by the thrown error's own text, with a console line naming the component. That text is built by the shared helper as HTTP <status>: <server message>, so the status code reaches the user in the toast — where the branch's envelope failures surface the server's sentence alone.

Each tab fetches once on mount and again when its has-a-project prop turns true; the tab panels are kept alive, so moving between tabs does not refetch. That prop is the indexed key rather than the project store's own flag: Initialize answers not found where the project carries no NcEnv, so a project can be open while every tab still reads as having none. Nothing pushes changes at a mounted tab, and no tab re-reads after a cell edit — the Offset Table's two tool-house actions are the one exception, because the server recomputes the table under them.

The four tables that render as data tables — the coordinate table, the two datum tables and the offset table — are the only places in the application that use that component. None of them declares a sortable column, and all four run unpaginated with every row shown.

Object Management and the Install Chain

The left pane's head line carries the shared object-management ⋮ menu, the title Controller, and a badge reading ready while a key is indexed and no project otherwise. The menu's entries are Load, Save As, Copy, Paste and XML Mode; Load Resource is absent because the page passes no resource directory. Load and Save As browse the server file system through the shared file-explorer dialog filtered to *.NcEnv / *.xml, rooted at the project directory once a project is open; Save As proposes the name NcEnv.xml; Paste is checked against the expected type Hi.Numerical.HardNcEnv, HiUniNc.

Load, Paste and an XML apply swap only the indexed object, so the page then installs it: it posts the indexed key to the install endpoint before re-running Initialize, because Initialize re-indexes from the project and would otherwise resurrect the object that was replaced. A failed install stops the chain with a toast. A successful one re-reads the brand, which is what re-gates the two Heidenhain tabs, refreshes the rendering flags, and raises an informational toast naming the installed type. The XML dialog's Apply raises the load event as well as its own, and the page listens only to the load event, so the chain runs once rather than twice.

Initialize mints a fresh index key on every call, and the page registers each one for cleanup and drops the key it replaced, so repeated installs do not accumulate entries.

The Viewer

The right pane is a rendering canvas with its own toolbar: the shared view toolbar, a Scene ▾ dropdown, and a badge reading rendering or disconnected. The canvas binds the shared Execution displayee rather than a viewer of its own, so what is toggled here is what the Execution page shows.

The Scene dropdown groups its checkboxes as Solid — Machine, Tool, Workpiece, Fixture — Coordinate — Program Zero, ISO Coordinate, Heidenhain Coordinate — and Display Aids — Dimension Bar, Color Scale Bar. The Heidenhain Coordinate row is listed only while the brand reads Heidenhain. Three tabs carry a Show on Display button of their own that flips the same shared flags: the coordinate tab flips ISO Coordinate, and the two datum tabs both flip Heidenhain Coordinate.

The Heidenhain marker is gated twice over, and the second gate is never satisfied. The displayee is added to the scene only while its flag is set and the model's brand is Heidenhain; it then returns without drawing until an active datum number or datum-shift argument has been assigned to it, and no code path in the web service assigns either. So the marker stays absent whatever the flag reads. Where it does draw, it resolves its offset through the legacy datum tables — the ones this page's two datum tabs edit — and not through the runner's.

Layout

  • Legacy Controller Page — /controller/:tab?, a two-pane splitter, left pane 55 % and draggable between 25 % and 75 %
    • Management Pane
      • Head Line
      • Tab Strip — Coordinate Table, Datum Preset, Datum Shift, Offset Table, Machine, Brand, Config; the two datum buttons rendered only under Heidenhain
      • Tab Panels — kept alive; one shown at a time
        • ISO Coordinate Table Panel
          • Toolbar — Undo Align, Redo Align, Show on Display
          • Table — columns Index, X, Y, Z, Actions, with a single-selection column
            • Row Actions — P0, M0, Align P0
        • Datum Preset Table Panel — Show on Display; columns Q339, X, Y, Z, Actions, the action being a reset-to-zero button
        • Datum Shift Table Panel — the same shape, keyed D
        • Tool Offset Table Panel
          • Toolbar — Set ideal offset dependent on tool house toggle, and Refresh from Tool House while it is on
          • Table — columns Tool #, Ideal Radius, Radial Wear, Ideal Height, Axial Wear, and a delete button; the tool number is editable and the delete button present only while the dependence is off
          • Add Button — below the table, shown only while the dependence is off
        • Machine Configuration Panel
          • General Card — Rapid Feedrate (mm/min) and Tooling Time (sec) numeric fields
          • Linear Axis Stroke (mm) Card — Axis / Min / Max over fixed rows X, Y, Z
          • Rotary Axis Stroke (deg) & Max Speed (rpm) Card — Axis / Min (deg) / Max (deg) / Max Speed (rpm) over fixed rows A, B, C
        • CNC Brand Panel
          • Selection Card — the brand select over the five brands, above the brand-change warning banner
          • Heidenhain Settings Card — Master-axis character select over A / B / C; shown only under Heidenhain
        • Configuration Panel
          • Options Card — Enable Shortest Rotary Path toggle above its explanatory banner
    • Viewer Pane
      • Viewer Toolbar
        • RenderingCanvas Tool Bar
        • Scene ▾ Dropdown — Solid, Coordinate and Display Aids groups
        • Connection Badge — rendering / disconnected
      • Rendering Canvas — bound to the shared Execution displayee

Source Code Path

See HiNC App Anatomy for git repository links.

Web Application

HiNC-2025-webservice (Quasar CLI SPA):

  • wwwroot-src/src/pages/ControllerPage.vue — the page shell: the splitter, the seven tab panels and the Heidenhain gate over two of the buttons, the install-then-initialize chain, the rendering-flag snapshot the per-tab toggles read, and the tab reset on leaving Heidenhain.
  • wwwroot-src/src/components/controller/CoordinateTableTab.vue — the coordinate table: the per-cell whole-row write, the row selection that names the marker, the P0 / M0 actions, and Align P0 with its two client-side history stacks and their cap.
  • wwwroot-src/src/components/controller/DatumPresetTab.vue and wwwroot-src/src/components/controller/DatumShiftTab.vue — the two Heidenhain tables: the Q339 and D key columns, the reset-to-zero action, and the selection that is a highlight only.
  • wwwroot-src/src/components/controller/OffsetTableTab.vue — the offset editor: row add, delete and key rename with its duplicate guard, and the two-step tool-house dependence toggle.
  • wwwroot-src/src/components/controller/MachineTab.vue — the fixed X/Y/Z and A/B/C row sets, the whole-vector writes, the degree and rpm conversions with their infinity guards, and the two handlers that commit without capturing a rollback value.
  • wwwroot-src/src/components/controller/BrandTab.vue — the five-brand select with its captured rollback, the change warning, and the Heidenhain master-axis card: its read normalisation, its two-way binding and its write with no rollback.
  • wwwroot-src/src/components/controller/ConfigTab.vue — the shortest-rotary toggle, its banner and its reconstructed rollback.
  • wwwroot-src/src/components/controller/ControllerExtendedToolBar.vue — the Scene dropdown: the three flag groups and the brand test that hides the Heidenhain row.
  • wwwroot-src/src/api/controller.ts — the typed wrappers over every endpoint above, the brand constants and their literal labels, the align / revert payloads, the documented stroke-vector order, and the eight read wrappers that answer a default instead of raising.
  • wwwroot-src/src/api/http.ts — the plain-JSON helper every write goes through, its coded-error keys, and the success-envelope helper this module does not use.
  • wwwroot-src/src/api/renderingFlags.ts — the flag indices the Scene dropdown and the per-tab toggles write.
  • wwwroot-src/src/components/widgets/NumericInput.vue — the numeric cell: commit on blur or Enter, the empty-to-null parse the handlers reject, the generic text path that prints the infinity tokens, and the parse branch that takes them back.
  • wwwroot-src/src/components/widgets/ObjectManagementMenuButton.vue — the ⋮ menu, its server file browser and extension filter, the paste type check, and the load event an XML apply also raises.
  • wwwroot-src/src/components/RenderingCanvas.vue and wwwroot-src/src/components/RenderingCanvasToolBar.vue — the viewer pane's canvas and its shared view toolbar.
  • wwwroot-src/src/composables/useRouteTabs.ts — the tab-to-URL sync, the seven valid segments and the default the bare route canonicalises to.
  • wwwroot-src/src/composables/useCleanupHub.ts — the registration that drops each superseded index key.
  • wwwroot-src/src/router/routes.ts — the /controller/:tab? route beside /general-setup.
  • wwwroot-src/src/components/AppMenuBar.vue — the Page dropdown carrying the Legacy-Controller entry.
  • wwwroot-src/src/stores/project.ts — the project flag the page watches in order to re-run Initialize; the indexed key that call returns is what the seven tabs and the status badge read.
  • wwwroot-src/src/layouts/MainLayout.vue — the keep-alive that holds the page across navigation and rebuilds it on a project epoch change.
  • wwwroot-src/src/i18n/en/controller.ts — every title, tab label, column header, banner and error context quoted above.
  • wwwroot-src/src/i18n/en/menu.ts — the Page and Legacy-Controller menu strings.
  • wwwroot-src/src/i18n/en/common.ts — the shared column, action and status strings the tabs reuse.
  • Controller/ControllerController.cs — the page's whole REST surface: the index-and-install pair, the per-property readers and writers, the interleaved stroke reads beside the grouped stroke writes, the four coded error payloads, the row-level offset CRUD, the align endpoint that snapshots the transformer either side of the write, the stateless revert, and the display binding that attaches the shared Execution displayee.
  • Common/ApiError.cs — the four coded payloads named above.
  • Program.cs — the named-floating-point-literal serializer the infinite bounds travel under.
  • Widget/ObjectManagementController.cs — the server half of the ⋮ menu, including the paste that rejects an object the expected-type string does not admit.
  • Disp/ExecutionDisplayee.cs — the displayee this page's canvas binds: the two coordinate flags, the extra brand test on the Heidenhain one, and the legacy tables both markers are built over.
  • Disp/IsoCoordinateEntryDisplayee.cs — the one marker class serving both faces, over whichever coordinate provider it is handed.
  • Disp/HeidenhainCoordinateEntryDisplayee.cs — the Heidenhain marker: the brand guard, the datum number and shift argument it draws nothing without, and the legacy tables it resolves through.
  • Common/ProjectDisplayeeService.cs — where that single shared displayee is created.

HiAPI Engine

  • HiUniNc/Numerical/HardNcEnv.cs — the model behind the tab table above: the coordinate, datum and offset tables, the two stroke boxes that construct infinite, and the rapid rate and tooling time the Machine tab writes.
  • HiGeom/Geom/Box3d.cs — the box the two stroke limits are, its infinite construction, and the six-argument constructor whose grouped argument order the interleaved stroke writes are handed to.
  • HiUniNc/Numerical/MillingToolOffsetTable.cs — the offset table the Offset Table tab edits: a dictionary keyed on the offset id, which is the key a row rename moves an entry to.
  • HiNc/MachiningProcs/LocalProjectService.cs — the per-step stroke check that falls back to the legacy boxes when no runner-side stroke config resolves, which is what makes a regrouped box observable.

See Also

  • Legacy Controller Settings — the model this screen edits, the three settings it owns alone, and the engine files behind them
  • Controller Branch — the SoftNcRunner-native controller branch that supersedes this screen, and where controller settings for a project are edited
  • General Setup Page — the page that hosts the branch above, and the rest of the equipment tree beside it
  • Legacy Controller (manual) — the end-user task: the settings this screen still owns alone, and which face to edit for everything else