Resizable Bar Component
+Anatomy by Source Directory
-A Vue component that provides draggable dividers for resizing adjacent panels in web applications.
-Overview
-The ResizableBar component creates a draggable bar that allows users to resize panels by clicking and dragging. It supports both horizontal and vertical orientations.
-Key Features
+The rest of Anatomy is keyed on the shipped surface — a route, a Control-Tree branch, a reusable +control. This layer is keyed on the source tree instead, and it is the entry point for the reader who +knows which directory a change landed in but not which screen it shows up on.
+Ordered the way a request travels: the front end first, then the process that serves it.
+Directories
-
-
- Directional Support: Works in both horizontal (for width adjustment) and vertical (for height adjustment) orientations -
- Unit Flexibility: Supports pixel, percentage, and custom unit systems through converters -
- Visual Feedback: Changes appearance on hover and during drag operations -
- Constraint System: Enforces minimum and maximum size limits +
- Web Service SPA Source Tree — the Quasar front end under
+
wwwroot-src/src: howcomponents/is grouped two ways at once, what the Control-Tree registry +actually is, and why half the route table is redirects
+ - Web Service Backend Source Tree — the ASP.NET Core half: how to read +a filename, why folder nesting does not predict a route, and which declared hub is never mapped
Usage Pattern
-The component should be placed between two panels that need to be resizable. The resize events provide size information that parent components use to adjust panel dimensions.
-Unit Modes
--
-
- Pixel Mode (default): Direct pixel value manipulation -
- Percentage Mode: Automatic calculation relative to parent container -
- Custom Mode: User-defined unit converters for specialized requirements -
Integration Example
-See the player-panel.js implementation for a practical example of using ResizableBar to create adjustable layouts between rendering canvas and side panels.
-Web Application Source Code Path
+What This Layer Is For
+A page in the rest of Anatomy answers what is this thing on my screen made of. Both of these answer +the inverse: this directory changed — what does it show up as, and which page is now wrong. That +makes them the first stop after a refactor, and the reason each directory entry names the pages that +document it.
+They are directory indexes, not file listings. A file worth naming is named on the page that +documents the surface it implements; what lives here is the shape of the tree, the conventions that +hold across it, and the traps that survive a careful reading of any single file.
+What Is Not Indexed Here
+The HiAPI engine repositories are not given a directory document. Anatomy cites engine source +where a shipped surface depends on it, but the engine's own structure is covered by the generated +API Reference, which stays correct in a way a hand-written index could +not.
+Neither is the outgoing Windows desktop client. It takes no new feature work, so a hand-written +index of its tree drifts with every flagship change and is read by nobody; the same reasoning +retired the page-by-page map of its files.
+See Also
-
-
- common/resizable-bar -
- common/resizable-bar-example -
- player/player-panel +
- HiNC App Anatomy — the section index, and the surface-keyed way in +
- Web Service SPA Source Tree — the flagship front end +
- Web Service Backend Source Tree — the process that +serves it +
- Platform — the machinery under every screen, entered by mechanism rather +than by directory
See this page ~/app-anatomy/index.md for git repository.
Table of Contents
+ +Web Service Backend Source Tree
+ +The C# half of HiNC-2025-webservice is an ASP.NET Core process that serves the SPA, answers its
+REST calls, and pushes to it over SignalR. It is organised by the engine's domain vocabulary, not
+by web-framework artifact type: a controller, its hub, its DI service and its non-web scene objects
+sit together in one domain folder rather than in a Controllers / Hubs / Services split.
Folders below are ordered by how much of the shipped surface they answer for.
+Reading a Filename
+The role of a file is told by its suffix and base type, and the convention holds throughout:
+| Suffix | +What it is | +
|---|---|
*Controller.cs |
+a REST controller with a route attribute | +
*Hub.cs |
+a SignalR hub — but see the two traps below | +
*Service.cs |
+a singleton registered in Program.cs |
+
*Displayee.cs |
+a scene-composition object handed to the engine; no HTTP surface at all | +
*Dtos.cs, *Config.cs |
+plain wire or settings types | +
Two names deliberately break the reader's expectation. Controller/ControllerController.cs means
+CNC controller, not an MVC controller. And Mech/'s *DisplayController.cs files are ordinary
+REST controllers that attach a displayee to a rendering connection somebody else already owns — they
+are not hubs.
Important
+Folder nesting does not predict the route. Mech/Topo/ and Mech/MechBuilder/ expose routes
+that carry no api/mech/ prefix at all, and even inside Mech/ two conventions coexist: the newer
+ports use kebab-case paths under api/mech/, while the older keyed-object editors use the
+controller-name default. Read the route attribute, never the path.
Composition Root
+Program.cs is the only C# file at the repository root, and it is where the questions a reader
+usually has are actually answered: which services are singletons, which hubs are mapped, and what the
+middleware order is. Two facts about it are worth carrying:
-
+
- A declared hub is not a mapped hub.
Program.csmaps eight hub endpoints. Anything not in that +list is unreachable however complete its class looks — see the trap underExecution/below.
+ - The tree is not self-contained. The project service types
Program.csleans on hardest live in +the sibling HiNc engine repository, not here.
+
Documented in Program and Hosting.
+Execution
+Execution/ is the run cockpit's whole backend: playback control, the strip and cycle-line charts,
+the NC-program branch index, run-output queries, and the real-time push layer.
-
+
Execution/ExecutionController.cs— the playback surface.
+Execution/ExecutionChartsController.csandExecution/ClStripController.cs— the chart data. +Three separate classes share the case-insensitiveapi/executionprefix on purpose, one of them +routed there rather than under Mission with a comment saying why.
+Execution/SessionSinkHub.cs— four mapped hubs declared in one file, one per message sink, so +hub-per-file does not hold here. Each pairs with a broadcast service thatProgram.csresolves +eagerly, so it subscribes to its sink before the first client connects.
+Execution/ClStripHub.cs,Execution/ExecutionStatusHub.cs— the strip and status pushes.
+
Warning
+Execution/ExecutionCanvasHub.cs declares a hub that Program.cs never maps and that nothing in
+the repository references. The Execution canvas rides Disp/RenderingHub.cs like every other
+canvas. A reader looking for “the hub behind the Execution page” by name will find this file first
+and be wrong.
Documented in Execution Page, +Program Branch, +Selected-Step Info Panel, +Strip Charts and Cycle-Line Charts.
+Mech
+Mech/ is the largest domain folder: the REST surfaces for the machine tool, the tool house and
+cutter editing, fixtures and workpieces, spindle capability, background and coolant, and the three
+runner suits. It also holds the display controllers that bind a mechanism scene onto a rendering
+connection. Mech/CutterDtoBuilder.cs is the shared read-side DTO shape two controllers reuse, and
+Mech/NcSuitUsage.cs is what lets the Control Tree show the CSV and CL runner branches only when the
+loaded project actually plays them. No SignalR hub lives here.
-
+
Mech/MechBuilder/— the standalone mechanism-building session, held by a process-wide singleton +rather than per user. Documented in Mechanism Builder Page.
+Mech/Topo/— one thin controller per transformer kind, each editing an instance held in the +keyed object store. Documented in +Transformer Select Panel.
+Mech/SoftNcRunnerController.cs— the one endpoint family behind the Controller branch, and the +file whose snapshot decides which of that branch's nodes exist. Documented in +Controller Branch, with +Brand Matrix for the snapshot flags themselves.
+Mech/CsvRunnerController.csandMech/ClRunnerController.cs— the two resident non-brand runner +suits, reached from tree branches the Preference menu hides by default. Documented in +Preference Menu Dropdown until those branches have a page.
+
Documented in General Setup Page and +Tool House Page and the panels beneath them.
+Missions
+Missions/ is the mission command tree's backend. Two things here surprise readers:
-
+
- The folder is plural and the route is singular — and the SPA folder is singular too. +
Missions/NcOptOptionEndpoints.csis not a minimal-API endpoint file despite the name. It is a +second file of the same partial controller class, which is why its routes resolve under the mission +prefix.
+
Missions/MissionCommandCatalog.cs reflects over every session command carrying the catalog
+attribute once per process, replacing hand-maintained kind switches, and
+Missions/MissionCommandFields.cs does the same for annotated scalars — which is how a simple
+command gets an editor without a bespoke panel. Missions/ScriptCompletionService.cs and
+Missions/ScriptCompileCheckService.cs are the Roslyn pair behind the script editor; the compile
+check reuses the same options and globals type the script command uses at run time.
Documented in Mission Root Panel and the command panels under it.
+Disp
+Disp/ is the rendering layer. Disp/RenderingHub.cs is the single transport surface for every 3D
+canvas in the application — canvas initialization, pointer, key and touch input, resize, view
+presets, cache clearing and snapshots — and the SPA's canvas component defaults to it, so all pages
+share one hub rather than one hub per page. Disp/RenderingService.cs owns the per-connection
+engines the hub resolves against. Disp/StlPreviewController.cs is the only other REST surface here.
The *Displayee.cs files are scene-graph composition objects rather than web types, and each has a
+live instantiation site: the execution scene, the equipment-setup scene, the step-subtraction scene
+and the two coordinate-frame displayees the first two compose in.
Documented in Rendering Canvas on Web Service,
+RenderingCanvas Tool Bar and — for
+Disp/StlPreviewController.cs and the per-connection slot behind it —
+STL Preview Pane.
Environments
+Environments/ is session and environment scope: the project lifecycle, per-user preference
+persistence, the Execution page's division flags, and the shipped localized step-present catalog. It
+is also where the Log Viewer's data comes from — the log endpoints sit on the project controller
+beside status, new, load, save, reload, save-as and close.
Documented in Main Panel, +Session State, +Preference Menu Dropdown, +Internationalization, +Log Viewer Page and +Step Present Dialog.
+Common, Geom and Widget
+-
+
Common/— cross-cutting infrastructure with no single domain owner: the optional login gate, the +named-root file explorer, path guards, the daily file logger, and two pieces the whole application +rests on.Common/IndexService.csis the keyed object store behind the index a backend object, +then edit it by key pattern every geometry, transformer and widget controller uses. +Common/CleanupHub.csis not a messaging hub, and it is not what bounds that store either. Its +key registry is an ordinary instance property and SignalR builds a fresh hub instance for every +invocation, so the entryAddrecords is discarded with the instance that received it and the +disconnect handler always walks an empty registry. The bound comes from the browser instead: +wwwroot-src/src/composables/useCleanupHub.tsposts the index-remove endpoint on +Common/IndexController.csfor every key it holds when its host unmounts, and again whenever a +key it registered is replaced. Documented in +Dictionary Service Pattern, +WebAPI Hub Cleanup Pattern, +Login and Authentication and +Log Viewer Page.
+Geom/— one CRUD controller per geometry kind, on the same keyed-object pattern, and nothing +else: no hubs, no services. Documented in Geometry Panels.
+Widget/— the generic value and lifecycle controllers the reusable inputs post to: the 3D vector, +the 4×4 matrix, and the object-management surface that handles file operations, XML editing and +copy-paste for indexed objects. Documented in +Object Management Menu Button, +Vec3dControl Component and +Mat4dControl Component.
+
Controller and Demo
+Controller/ is the single-file surface behind the legacy controller page. It has a successor in
+Mech/SoftNcRunnerController.cs, whose own doc comment says so, but both are live — the legacy
+page has not been removed. Documented in Legacy Controller.
Demo/ is customer-facing sample code — a custom colour guide and a per-step optimization sweep —
+compiled into the assembly with no call site in the running application. Read it as an example of
+how to extend the engine, not as part of the shipped behaviour.
See Also
+-
+
- Anatomy by Source Directory — the other two source trees, and how this +layer is meant to be entered +
- Web Service SPA Source Tree — the front end this process +serves and answers +
Table of Contents
+ +Web Service SPA Source Tree
+ +HiNC-2025-webservice/wwwroot-src/src is the flagship front end: a Quasar CLI single-page
+application in Vue 3, TypeScript and Pinia, served by the same ASP.NET Core process that answers its
+REST calls. Every page in Anatomy outside the desktop map documents something in this tree.
Folders below follow the stock Quasar skeleton order, with the two places the skeleton breaks called +out where they occur.
+The One Thing to Know First
+components/ is grouped both ways at the same level, and that is deliberate rather than untidy:
-
+
- Page-scoped —
components/controller/,components/execution/and half ofcomponents/mech/+are chrome for exactly one route.
+ - Domain-scoped —
components/geom/,components/topo/,components/toolhouse/, +components/spindle/,components/workpiece/andcomponents/mission/are pulled in from +wherever the domain surfaces, most often the Control Tree.
+ - Primitives —
components/widgets/andcomponents/panels/have no domain at all and are the +most-imported folders in the application.
+
So a component's folder does not tell a reader who mounts it. components/mission/ has no Mission
+page — /mission redirects into the Execution page's tree — and components/workpiece/ holds a
+single dropdown while the real workpiece editors are Control-Tree panels.
The second break is history. The application has been re-architected repeatedly, and because
+Control-Tree node ids ride in ?tree= links they are a public surface: every regroup adds a
+migration hop rather than rewriting the last one. The residue is visible in wwwroot-src/src/router/routes.ts, where a large share of the table is
+redirects preserving URLs from earlier architectures — a route existing there does not mean a
+page exists for it.
Root and Boot
+-
+
wwwroot-src/src/App.vue— not the shell. It is a bare router view plus the once-per-load wiring +that subscribes the project store to the execution-status hub. The real shell is +wwwroot-src/src/layouts/MainLayout.vue. Documented in +Session State.
+wwwroot-src/src/boot/auth.ts,wwwroot-src/src/boot/i18n.ts, +wwwroot-src/src/boot/routine-toast.ts— Quasar boot files, run once before mount and in a +declared order. The auth boot file patches the global fetch and inspects every 401, which is why +no API module carries its own 401 handling — but the redirect it can raise is conditional: it +fires only while the auth store reports the login gate enabled, and not when the router is +already on the login route, so on a build with the gate off a 401 redirects nowhere. The toast +boot file patches the shared notify helper so every toast is mirrored into the footer history +without touching a call site. Documented in +Login and Authentication and +Internationalization.
+wwwroot-src/src/layouts/MainLayout.vue— the shell: menu bar, the routed page container, footer. +It also owns the mechanism every page depends on and no page implements: a project epoch, bumped +when the loaded project changes, is the keep-alive key, so a project change destroys and rebuilds +every cached page. Documented in Main Panel and +Session State.
+
The REST Edge
+wwwroot-src/src/api/ is one thin typed module per backend controller family — functions, DTO types
+and kind unions, no Vue code. This is the tightest correspondence between the two halves of the
+application: each module wraps one named controller almost one-to-one.
-
+
wwwroot-src/src/api/http.ts— the shared response layer. Its own header names the modules that +deliberately bypass it, so “every API module goes through it” would be wrong.
+wwwroot-src/src/api/index-service.ts— not a barrel file. It wraps the backend's keyed object +store, which is where thekeystring threaded through the whole application comes from. +Documented in Dictionary Service Pattern.
+
Components
+-
+
wwwroot-src/src/components/— the shared top level:wwwroot-src/src/components/AppMenuBar.vue, +wwwroot-src/src/components/AppFooter.vue,wwwroot-src/src/components/RenderingCanvas.vue, +wwwroot-src/src/components/FileExplorer.vueandwwwroot-src/src/components/StlPreviewPane.vue. +The canvas does not render locally: it opens a SignalR connection and paints server-rendered +frames, which is why several pages each own a canvas bound to a different backend scene. +Documented in +Rendering Canvas on Web Service, +File Explorer and +STL Preview Pane.
+wwwroot-src/src/components/controlTree/— the largest folder in the SPA by a factor of two, and +a registry-driven panel system rather than a folder of tree widgets. +wwwroot-src/src/components/controlTree/itemTypes.tsand its domain siblings map an item-type +string onto a panel component and a child-building function; +wwwroot-src/src/components/controlTree/useControlTreeHost.tsis the state machine that builds the +tree, gates a dirty selection switch, and syncs?tree=; the many +panel components are the editors the registry resolves. It serves three consumers, not one — +the Execution and General Setup pages each instantiate their own scoped host, while the Tool House +page reuses the identical panels and registry through a tab cascade and never touches the host. +Documented in Control Tree — the folder's own page — with +Execution Page, General Setup Page and +Tool House Page for the three consumers, and +Program Branch for the NC-program item types and panels that live in +this folder. The nineteenSoftNc*panels and their registry are a wave of their own, documented +under Controller Branch: which of them the tree mounts at all is decided +by Brand Matrix, and what they share is +Editing Contract.
+wwwroot-src/src/components/controlTree/toolhouse/— the Tool House branch's panels. Easy to +confuse withwwwroot-src/src/components/toolhouse/: these are the panels the registry mounts, +that folder holds +the content fragments those panels embed, and the dependency runs one way only.
+wwwroot-src/src/components/execution/— the run cockpit's own panels, plus itscharts/+sub-folder, the uPlot charting layer. Not purely page-local: +wwwroot-src/src/components/execution/ExecutionToolBar.vueis mounted by the Control Tree's +primary panel, and the spindle contours chart imports from the charts folder. Documented in +Execution Page, Strip Charts and +Cycle-Line Charts.
+wwwroot-src/src/components/geom/andwwwroot-src/src/components/topo/— structural twins: one +editor per kind, the samemodelKeyprop andchanged/erroremits, and a single kind → editor +map —wwwroot-src/src/components/geom/geometryEditors.tsand +wwwroot-src/src/components/topo/transformerEditors.ts— that is the source of truth for both the +switchboard and the Control Tree. A new kind must be registered there, +not merely dropped in the folder. “topo” means coordinate transformers, not mesh topology. +Documented in Geometry Panels and +Transformer Select Panel.
+wwwroot-src/src/components/widgets/— the reusable input library: numeric, vector and matrix +inputs, the file-path input and picker, the CodeMirror text editor, the display-options and +object-management menus. Documented in Widgets.
+wwwroot-src/src/components/panels/— pure layout machinery with no domain: the collapsible +expansion panel and the resizable stack whose registration contract produces the “rows collapse in +place” behaviour the tree pages describe. Documented in +Control Tree, whose dock rows are both expansion rows, and +Session State for the keep-mounted flag that decides whether a +collapse unmounts its content.
+wwwroot-src/src/components/mission/,.../toolhouse/,.../spindle/,.../preference/, +.../mech/,.../workpiece/and.../controller/— the domain and page folders named above. The +*Div.vuesuffix inside the Tool House folder is a convention carried over from the legacy Blazor +components: a Div is an embeddable content fragment with no panel chrome.
+
State, Routing and Text
+-
+
wwwroot-src/src/composables/— three different concerns in one folder: hub access, shared domain +state, and UI mechanics.wwwroot-src/src/composables/useSharedHub.tsis the reference-counted connection manager +behind every hub composable, so a hub opens only while something consumes it. +wwwroot-src/src/composables/useToolHouse.tsandwwwroot-src/src/composables/useSpindleCapability.tsare module-level +singletons, not per-component instances.wwwroot-src/src/composables/useViewPrefs.tsstores layout state in the +browser only — it is neither in the project file nor in the server's user config. Documented in +Session State.
+wwwroot-src/src/stores/— four Pinia stores.wwwroot-src/src/stores/index.tsis not a barrel; it is the +Quasar factory. Most shared state lives incomposables/instead. Documented in +Session State.
+wwwroot-src/src/router/—wwwroot-src/src/router/routes.tsis the table plus the legacy redirects, and +wwwroot-src/src/router/treeRoutes.tsis the load-bearing file its name understates: it holds the accumulated +chain of tree-id renames and the id → page resolver, plus the tab-name constants both the router +and the tab composable consume. A route'smeta.titleholds an i18n key, not a title. +Documented in Tree Ids and Routes.
+wwwroot-src/src/i18n/— three locales shipped together, each a list of namespace files. +English is the schema, not merely a locale: the two Chinese bundles are typed against it, so a +key present in English and missing there is a build error. Keys are split by UI region rather than +by source file, so there is no one-to-one mapping between an i18n file and a components folder. +Documented in Internationalization and +Translation Remarks.
+wwwroot-src/src/pages/— one file per route, and page size is a poor guide to importance: the +General Setup page is small because it delegates almost everything to the Control Tree dock and the +equipment canvas, while the File Explorer page is a thin wrapper around a large shared component.
+wwwroot-src/src/utils/,wwwroot-src/src/directives/,wwwroot-src/src/css/— small and mostly +presentational. Two severity scales exist and must not be conflated: +wwwroot-src/src/utils/messageSeverity.tsmaps the engine's diagnostic scale, while the footer's routine severity +is a separate, shorter toast scale.
+
See Also
+-
+
- Anatomy by Source Directory — the other two source trees, and how this +layer is meant to be entered +
- Web Service Backend Source Tree — the process that +serves this application and answers its REST and hub traffic +
DictionaryService and DictionaryHub Pattern
Overview
diff --git a/App/wwwroot/HiAPI-docsite/app-anatomy/widget/gui-file-path-assignment.html b/App/wwwroot/HiAPI-docsite/anatomy/conventions/gui-file-path-assignment.html similarity index 98% rename from App/wwwroot/HiAPI-docsite/app-anatomy/widget/gui-file-path-assignment.html rename to App/wwwroot/HiAPI-docsite/anatomy/conventions/gui-file-path-assignment.html index 17c89ae7..c8aa4ee2 100644 --- a/App/wwwroot/HiAPI-docsite/app-anatomy/widget/gui-file-path-assignment.html +++ b/App/wwwroot/HiAPI-docsite/anatomy/conventions/gui-file-path-assignment.html @@ -11,7 +11,7 @@ - + @@ -84,7 +84,7 @@ -GUI File Path Assignment
See the remarks of MakeXmlSource(string, string, bool) to know the design pattern of file path treatment.
diff --git a/App/wwwroot/HiAPI-docsite/app-anatomy/preference/graphic-cache-dropdown.html b/App/wwwroot/HiAPI-docsite/anatomy/conventions/index.html similarity index 50% rename from App/wwwroot/HiAPI-docsite/app-anatomy/preference/graphic-cache-dropdown.html rename to App/wwwroot/HiAPI-docsite/anatomy/conventions/index.html index f6cbdb7c..ed93f0a2 100644 --- a/App/wwwroot/HiAPI-docsite/app-anatomy/preference/graphic-cache-dropdown.html +++ b/App/wwwroot/HiAPI-docsite/anatomy/conventions/index.html @@ -2,16 +2,16 @@ -Graphic-Cache SubMenu
+Conventions
-In the WPF application the submenu locates on the Preference Menu Dropdown. The web application moved it onto the Player toolbar's Workpiece ▾ dropdown, since it acts on the workpiece meshed geometry's rendering cache.
The model UserService is from its parent component.
-Layout
+The rules and shared contracts every Anatomy page assumes. A page in this folder describes +something that is true across screens rather than something a reader can point at: how a message +reaches the user, how a control behaves when its model is wrong, how a file path is stored, how a +component releases what it indexed on the server.
+Ordered from the rules that bind every page in both applications, through the two shared UI +contracts, down to the service patterns and the canvas transport beneath them.
+Rules That Bind Every Page
+Message and Exception Handling
+HiNC uses three independent message categories: Diagnostic (IProgress<object>), UI
+Notification (MessageBoardUtil), and App Log (ILogger). See
+Message Management for the full design pattern.
For async exception handling, use CatchExceptions with a caller-provided handler:
await task.CatchExceptions(ex => progress?.Report(ex));
+
+The Bottom Message Bar displays UI-level notifications. The +Session Message Panel displays session diagnostic messages.
+Loose Manner
+The Loose Manner pattern handles rapidly-called synchronous actions where only the last call needs +to be effective.
+The LooseRunner class manages skippable rapid-calling synchronous actions. When an +action is called rapidly, only the last call is executed while previous calls are safely skipped. +The TryRun method is used to execute actions in this manner.
+The LooseRunner should be disposed when its owner is disposed to ensure proper +resource cleanup.
+Loose Couple
+If the model of a UI component is null or mismatched, apply a status badge instead of throwing an +exception, so the rest of the UI keeps working.
+Pages
-
-
- Graphic-Cache SubMenu
-
-
-
Graphic-Cache Lower Limit Input Text Field
-Graphic-Cache Upper Limit Input Text Field
-Graphic-Cache Input Text Field
-Graphic-Cache Slider
+- Translation Remarks — The terminology every translated UI label is held to, so the same concept keeps the same word across screens and languages +
- GUI File Path Assignment — How a control stores a chosen file: relative to the configuration directory when it sits inside it, absolute when it does not +
- Numeric Input/Output Utilities — Handling Infinity, -Infinity and NaN across the JavaScript / C# boundary, where plain JSON cannot carry them +
- DictionaryService and DictionaryHub Pattern — Connection-scoped indexing that lets a hierarchical component reference a backend object across hub connections +
- Webapi with Hub-Cleanup Assistance Pattern — Which component owns an index key and where it releases it, so a component never cleans a key it did not create +
- Rendering Canvas on the Web Service — The SignalR transport that puts a server-rendered 3D canvas on a browser page
-
Behavior
-Graphic-Cache Input Text Field and Graphic-Cache Slider bind the GraphicCacheMb. The limit text fields also bind to the properties of UserConfig.
Source Code Path
-See this page for git repository.
-WPF Application Source Code Path
+See Also
-
-
- MainWindow (be included in preference menu) -
Web Page Application Source Code Path
-HiNC-2025-webservice (Quasar CLI SPA):
--
-
wwwroot-src/src/components/preference/GraphicCacheMenu.vue— nested<q-menu>underWorkpiece → Graphic Cachewith Lower / Upper (NumericInput) + Current (NumericInput) +<q-slider>. Commits on blur / change.
-wwwroot-src/src/components/widgets/NumericInput.vue— sharedInfinity-friendly numeric field reused for the three limit inputs.
-wwwroot-src/src/components/player/PlayerExtendedToolBar.vue— hosts the Graphic Cache entry inside the Player toolbar'sWorkpiece ▾dropdown (next to Diff Visual Radius). The menu bar (wwwroot-src/src/components/AppMenuBar.vue) no longer carries it.
-wwwroot-src/src/api/preference.ts— typed wrapper overGET/POST /api/preference/graphic-cache.
-Environments/PreferenceController.cs— REST endpoints:GET /api/preference/graphic-cachereturns{ graphicCacheMb, lowerLimitMb, upperLimitMb };POSTwrites throughUserService.SaveUserConfig()→UserConfig.GraphicCacheMb→CubeTree.DispCacheMb.
+- Platform — the other cross-screen folder: the machinery these rules are applied on top of, including the host process that starts HiAPI +
- App Shell — the window frame these conventions are applied inside +
- Widgets — the reusable controls that implement two of them
Table of Contents
+ +Numeric Input/Output
+ +NaN, Infinity and -Infinity are ordinary values in a geometry model and ordinary things for a
+user to type, but JSON has no encoding for any of them. This page describes how the web application
+carries them across the boundary in both directions.
The Server Half
+One line of configuration does it. Program.cs sets
options.JsonSerializerOptions.NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals;
+
+so the ASP.NET Core serializer reads and writes the three values as the JSON string literals
+"NaN", "Infinity" and "-Infinity" instead of failing. It sits on the AddControllers JSON
+options, so every controller inherits it and no DTO, converter or endpoint has to opt in. Without it
+a double.NaN anywhere in a response throws during serialization, which is why the setting is
+load-bearing rather than a convenience.
The reach stops at the controllers. Hub payloads are serialized by SignalR's own protocol options,
+and nothing configures them: AddSignalR() is registered bare and the service contains no
+AddJsonProtocol call, so the named literals are a controller-JSON contract rather than a
+service-wide one.
The Client Half
+There is no shared numeric module. Each input widget formats and parses the special values itself,
+and the three that ship do not agree on all of it. The widgets are also not the only readers of this
+boundary: an API module that has to put a non-finite number on the wire converts it in place, as
+wwwroot-src/src/api/mission.ts does for the mission command fields. That conversion is not
+symmetric — it writes all three literals but recognises only the two infinity spellings on the way
+back — so a NaN returned by those endpoints resolves to the caller's supplied default instead.
| + | NumericInput.vue |
+Vec3Input.vue |
+Mat4Input.vue |
+
|---|---|---|---|
NaN displays as |
+empty field | +NaN |
+0 |
+
Infinity / -Infinity display as |
+literal text | +literal text | +literal text | +
| An empty field parses to | +null, under the default allowEmpty |
+0 |
+0 |
+
| Unparseable text on blur | +stays in the box under an error message | +reverts to the last valid value | +reverts to the last valid value | +
All three accept the same spellings on the way in: infinity, -infinity and nan
+case-insensitively. The ∞ and -∞ glyphs are where they part: the single-value field and the
+three-axis editor take them, the matrix grid does not. All three commit on blur or Enter rather than
+per keystroke, but only the vector and matrix editors compare the parsed value against the model
+before emitting. The single-value field carries no such guard and emits on every accepted commit, so
+committing with Enter and then leaving the field writes the same value twice.
The consequence worth knowing: NaN does not survive a round trip through the matrix editor.
+Mat4Input.vue renders it as 0, so re-committing a cell that held NaN writes a real zero.
NumericInput.vue is the input the rest of the app reaches for: no other component in the client is
+embedded by anywhere near as many editors, so a new numeric field should embed it rather than repeat
+the parse. Its own props, bounds and validation messages are documented at
+Numeric Input.
Source Code Path
+See HiNC App Anatomy for git repository links.
+Web Page Application Source Code Path
+-
+
wwwroot-src/src/components/widgets/NumericInput.vue— the shared single-value numeric field: localformatValue/parseValue, blur-and-Enter commit, and the rejected text left standing under an error message when a commit fails.
+wwwroot-src/src/components/widgets/Vec3Input.vue— the three-axis editor; carries its own copy of the same format and parse pair, plus a(x, y, z)text form.
+wwwroot-src/src/components/widgets/Mat4Input.vue— the sixteen-cell matrix grid; the same pair again without the glyph spellings, and with theNaN-to-zero behaviour above.
+wwwroot-src/src/api/mission.ts— a non-widget reader of the same boundary:numericToApiStringwrites all three literals for the mission command fields,parseMaybeInfiniteNumberreads only the two infinity spellings back.
+Program.cs— theAllowNamedFloatingPointLiteralssetting on the controller JSON options that lets the three values cross as JSON at all, and the bareAddSignalR()registration that does not share it.
+
See Also
+-
+
- Vec3dControl Component — the three-axis editor, one of the three inputs that carries its own format and parse +
- Mat4dControl Component — the sixteen-cell grid, the one input that renders NaN as zero +
- Numeric Input — the single-value field, and the widget that carries this contract to the most callers +
Table of Contents
+ +Rendering Canvas on Web Service Application
+ +Overview
+The web application's 3D canvas is rendered on the server and streamed to the browser over a
+SignalR hub connection at /renderingHub. Disp/RenderingHub.cs serves that hub, Program.cs
+maps it, and the singleton in Disp/RenderingService.cs owns one native display engine per hub
+connection. The browser paints the frames that arrive and forwards the user's input back.
Core Component
+-
+
- Location:
wwwroot-src/src/components/RenderingCanvas.vue
+ - Purpose: owns one hub connection, forwards input to the server engine, and paints the frames +the server streams back +
Its hubUrl prop defaults to /renderingHub, and it exposes connect, disconnect, the eight
+view commands and clearCache — the surface through which
+wwwroot-src/src/components/RenderingCanvasToolBar.vue drives the canvas instance it is bound to.
Connection Management
+SignalR Hub Connection
+Each RenderingCanvas instance opens its own hub connection and therefore owns one connection ID;
+the server keeps one display engine per connection ID, so several canvases can be live at the same
+time. On initialization the hub sends the connection ID back to the caller as CanvasInitialized,
+and the component re-emits it as serverInitialized. It is re-emitted after every reconnect,
+because the server disposes the engine on disconnect and a reconnect gets a new ID — the parent
+page answers by registering its content against that new ID.
The connection ID is the index for every canvas operation. Disp/RenderingService.cs keys the
+engine dictionary, the last-input time, the last lossless-frame hash and the negotiated frame
+format by it, and Disp/RenderingHub.cs keys its own sketch-view cache the same way.
Engine Lifetime
+The engine's life is the connection's: GetOrCreateEngine creates it when the canvas initializes,
+and RemoveEngine — called from OnDisconnectedAsync — disposes it and raises EngineRemoved.
+An HTTP endpoint therefore resolves an engine through the non-creating GetEngine; creating one
+for an ID that has already disconnected would orphan an engine no disconnect can ever clean up.
+Disp/StlPreviewService.cs subscribes to EngineRemoved to release the per-connection native
+topology the engine itself does not own.
Frame Encoding
+The frame encoding is negotiated at initialization. InitializeCanvasV2(width, height, formats)
+with jpeg in the accepted list selects SkiaSharp JPEG frames delivered on ImageUpdateV2 at an
+adaptive quality — interactive frames compress harder and a still frame refines once — and adding
+png lets that refine be encoded lossless. A browser without createImageBitmap, or a server that
+does not carry the V2 method, falls back to InitializeCanvas and the gzip-RGBA frames delivered
+on ImageUpdate.
Connection ID Naming Convention
+One value carries two names on the frontend:
+-
+
renderingConnectionId— the parameter name used throughout the API wrappers under +wwwroot-src/src/api, and the route-parameter name on most display controllers
+renderingConnId— how the Execution page holds it (wwwroot-src/src/pages/ExecutionPage.vue), +filled from the canvas's@server-initializedand passed to the typed wrappers in +wwwroot-src/src/api/execution.ts, whose parameter isrenderingConnectionId
+
Data Flow Architecture
+Frontend Responsibilities
+The component owns the connection and its reconnect schedule, forwards pointer, wheel, key, touch, +resize and visibility events to the hub, and paints each frame as it arrives. A canvas the layout +has hidden keeps its connection open and tells the engine to pause rendering instead, so a hidden +canvas costs nothing while a simulation runs.
+Backend Integration
+Rendering and frame encoding happen on the server; a controller decides only what the engine shows, +by resolving the engine from the connection ID and assigning its displayee. That is why any feature +can put its own content on a canvas without touching the transport.
+Example: Execution Controller
+-
+
- File:
Execution/ExecutionController.cs
+ - Method:
InitializeExecution(POST /api/Execution/initialize/{connectionId})
+ - Purpose: resolves the engine with
RenderingService.GetEngine(connectionId)and assigns the +Execution displayee to it
+
Six further controllers bind content the same way — the STL preview, the Tool House, the General +Setup equipment, the machine tool, the Mech Builder general mechanism and the Controller page — +each with its own route and its own displayee, all reaching the engine through the same connection +ID.
+Key Points
+-
+
- Every canvas data-stream operation is indexed by the connection ID. +
- Rendering and frame encoding happen on the server; the frontend owns the connection, the input +events and the painting of received frames. +
- The engine is created and destroyed with the hub connection, so anything else that holds
+per-connection resources releases them from
EngineRemoved.
+
Source Code Path
+See HiNC App Anatomy for git repository links.
+Web Service Source Code Path
+-
+
Disp/RenderingHub.cs— the hub: creates the per-connection engine, encodes and pumps frames, +handles mouse, wheel, key, touch, resize and visibility, and disposes the engine on disconnect.
+Disp/RenderingService.cs— the singleton holding the per-connection maps, the playback render +throttle and theEngineRemovedevent.
+Disp/StlPreviewService.cs— theEngineRemovedsubscriber that frees the File Explorer +preview's native topology.
+Program.cs— registers SignalR and maps the hub at/renderingHub.
+Execution/ExecutionController.cs— the worked example above:InitializeExecutionassigns the +Execution displayee to the engine named by the connection ID.
+Disp/StlPreviewController.cs— binds an STL preview to a canvas (api/stl-preview).
+Mech/ToolHouseDisplayController.cs— the Tool House canvas binding +(api/mech/tool-house-display).
+Mech/EquipmentSetupDisplayController.cs— the General Setup canvas binding +(api/mech/equipment-setup-display).
+Mech/MachineToolDisplayController.cs— the machine-tool canvas binding +(api/mech/machine-tool/display).
+Mech/MechBuilder/GeneralMechanismDisplayController.cs— the Mech Builder canvas binding +(api/general-mechanism/display).
+Controller/ControllerController.cs— the Controller page's canvas binding +(initialize-display/{connectionId}).
+
Web Page Application Source Code Path
+-
+
wwwroot-src/src/components/RenderingCanvas.vue— the client half: one hub connection per +instance, encoding negotiation, frame painting, input forwarding and the reconnect loop.
+wwwroot-src/src/components/RenderingCanvasToolBar.vue— the view-control bar bound to one +canvas instance through its exposed methods.
+wwwroot-src/src/pages/ExecutionPage.vue— mounts the main canvas, stores the ID as +renderingConnIdand initializes the Execution content from@server-initialized.
+wwwroot-src/src/api/execution.ts— the typed wrappers whoserenderingConnectionIdparameter +addresses that canvas.
+wwwroot-src/src/components/execution/StepVolumePanel.vue— a second canvas on the Execution +page, with its own connection and its own engine.
+wwwroot-src/src/components/mech/EquipmentSetupPanel.vue— the General Setup page's canvas +column.
+wwwroot-src/src/components/toolhouse/ToolHouseSetupPanel.vue— the Tool House page's canvas +column.
+wwwroot-src/src/components/StlPreviewPane.vue— the File Explorer STL preview canvas.
+
See Also
+-
+
- RenderingCanvas Tool Bar — the
Viewmenu that drives the hub, and theScenemenu beside it
+ - Program and Hosting — the host that maps this hub and registers the services behind it +
- STL Preview Pane — the one caller that opens a rendering connection outside a project +
Table of Contents
+ +Translation Remarks
+ +Terminology is a contract: one concept keeps one word on every screen and in every locale. Three
+locales ship together — en, zh-Hant and zh-Hans — and the English bundle is the schema the
+Chinese bundles are typed against, so a key missing from a Chinese locale is a build error. The
+readings below are the ones the zh-Hant bundle ships.
Tool House and Mechanism Terms
+-
+
Anchor 錨點
+
+Tool 刀具
+
+Cutter 刀具本體
+-
+
- Insert-End Cutter 刀片式刀具本體 +
+Holder 刀把
+-
+
- Cylindroid Holder 柱狀刀把 +
- Freeform Holder 任意形刀把 +
+Shank 刀柄
+
+Fixture 夾具
+
+Milling Cutter 銑刀
+
+Freeform Remover 任意移除工具
+
+Flute 刀刃
+-
+
- Flute Profile 刃包絡形 +
- Flute Contours 刃雕
+
-
+
- Baseline Contour 基準刃雕 +
- Side Contour 側向刃雕 +
- Bottom Contour 底部刃雕 +
+
+Upper Beam 夾持柱
+
+Integral Mode 刀頭形式
+-
+
- Solid End 一體式 +
- Insert End 刀片式 +
+Preset 預設
+-
+
- Datum Preset 基準點預設 +
+General Config 一般組態 — the mission tree's label for the
+presettingcommand kind
+Meshed Geometry 網格幾何
+
+Machining Resolution 加工解析度
+
+
Words That Carry More Than One Sense
+Contour is the sharpest case: in the cutter namespace it is 刃雕 (Flute Contours 刃雕, Baseline
+Contour 基準刃雕), and in the spindle-capability namespace it is 等值線 (Power Contours 功率等值線,
+Torque Contours 扭矩等值線). Each sense keeps its own entry rather than one compromise word.
Fluting is a cutter's whole set of flute contours — one shared baseline or one per flute — and
+translates as 刃雕構型: Uniform Fluting 對稱刃雕構型, Free Fluting 自由刃雕構型.
Program is the UI-layer word and does translate: 程式 in zh-Hant, 程序 in zh-Hans. Control in
+the frontend names the Control Tree UI, 控制樹, and never takes 程式.
Never Translate
+HiNC · hincproj · SoftNc · MachiningStep · brand names (Fanuc, Heidenhain, Mazak, Siemens,
+Syntec) · NC codes (G54, M128, CYCLE800, TRAORI, RTCP, TCPM) · file extensions and
+MIME-ish strings (STL, *.hincproj) · units (mm, rpm, mm/min, N, Nm, kW, KB, MB,
+GB) · the keyboard shortcuts embedded in tooltip sentences · route names, enum keys,
+localStorage keys and series identifiers — anything a machine compares. Language self-names
+(English, 简体中文, 繁體中文) stay in their own language in every locale. The full list lives with
+the bundles, in the i18n folder's README.
Terms Carried by the Desktop Client
+Three milling-cutter surfaces exist only on the WPF client, and their terms belong to the same +contract, so none of the words is reused elsewhere:
+-
+
- Insert-Cutter 刀片 — a tab on
Mech/ToolHouse/MillingCutterPanel.xaml
+ - Flute-Inner-Beam 刃中芯 — the tab served by
Mech/ToolHouse/FluteInnerBeamPanel.xaml
+ - Cutter Integral Mode 刀頭形式 — a labelled tab there as well; the web application carries the
+same choice as the
integralModedatum, which decides whether the Tool House material section +offers a separate shank material, and shows no label of its own
+
Source Code Path
+See HiNC App Anatomy for git repository links.
+-
+
wwwroot-src/src/i18n/en/toolhouse.tsandwwwroot-src/src/i18n/zh-Hant/toolhouse.ts— the Tool +House control-tree node names and editor labels: Cutter, Holder, Flute Profile, Flute Contours, +Upper Beam, and the cutter and holder types.
+wwwroot-src/src/i18n/en/mech.tsandwwwroot-src/src/i18n/zh-Hant/mech.ts— the General Setup +rendering flags and the anchor labels.
+wwwroot-src/src/i18n/en/tree.tsandwwwroot-src/src/i18n/zh-Hant/tree.ts— the shared +control-tree node names and the mission command kinds.
+wwwroot-src/src/i18n/zh-Hant/mission.ts— the PreSetting command fields, including Machining +Resolution and the meshed-geometry file picker.
+wwwroot-src/src/i18n/zh-Hant/controller.tsandwwwroot-src/src/i18n/zh-Hant/softNc.ts— the +Datum Preset tab, its table, and the runner's datum-preset row.
+wwwroot-src/src/i18n/zh-Hant/execution.ts— the Execution rendering flags, where Meshed +Geometry and Fixture appear together.
+wwwroot-src/src/i18n/index.ts—createI18n(),applyLocale()andSUPPORTED_LOCALES, the +only place a locale switch happens.
+wwwroot-src/src/i18n/schema.ts— the English bundle as the schema that types the Chinese ones.
+wwwroot-src/src/i18n/glossary.yaml— generated data, never imported by the application: the +harvested legacy corpus with an adjudicated head term per concept, used to settle a reading +before it is written into a bundle.
+
See Also
+-
+
- Language Selection SubMenu — the sub-menu that switches the locale these terms are written for +
- Internationalization — the bundle layout these terms are written into, and the lint that holds them to it +
Webapi with hub-cleapup assistence pattern
+Webapi with Hub-Cleanup Assistance Pattern
any of the index key should be registerForCleanup. i.e. any of indexXxx should follow the registerForCleanup.
clean the indexed key which indexed by the host component in beforeUnmount. And so that the component doesn't clean the key that doesn't create by the component itself.
-Although cleanupHub clean them for sure, the code demonstrates example of pure web-api cleanup (So that it can be a complete web-api workflow).
+The hub is not a second, redundant path that would make this web-api cleanup a mere demonstration.
+Common/CleanupHub.cs keeps its key registry in an ordinary instance property, and SignalR builds a
+fresh hub instance for every invocation, so the entry Add records is discarded with the instance
+that received it and OnDisconnectedAsync always walks an empty registry. The index-remove calls
+wwwroot-src/src/composables/useCleanupHub.ts makes — on unmount and on every key replacement — are
+the only ones that actually free a key, which is why registering a key without releasing it there
+leaks it for the life of the process.
Notice
- before current key modified, the previous key should be called this.cleanupKey. diff --git a/App/wwwroot/HiAPI-docsite/app-anatomy/player/cycle-line-charts.html b/App/wwwroot/HiAPI-docsite/anatomy/execution/cycle-line-charts.html similarity index 50% rename from App/wwwroot/HiAPI-docsite/app-anatomy/player/cycle-line-charts.html rename to App/wwwroot/HiAPI-docsite/anatomy/execution/cycle-line-charts.html index ab43502c..1bfc17f8 100644 --- a/App/wwwroot/HiAPI-docsite/app-anatomy/player/cycle-line-charts.html +++ b/App/wwwroot/HiAPI-docsite/anatomy/execution/cycle-line-charts.html @@ -11,7 +11,7 @@ - + @@ -84,94 +84,100 @@ -
- Header Row
- Title label. -
- Flag picker (Force chart only) —
<q-btn-dropdown>betweenForceToWorkpieceOnProgramCoordinate(default) andForceToToolOnToolRunningCoordinate.
+ - Flag picker (Sim Cutting Force chart only) — a
<q-btn-dropdown>betweenForceToWorkpieceOnProgramCoordinate(default) andForceToToolOnToolRunningCoordinate.
+ - Mode picker (
enableLocuscharts only — the two spindle-moment charts) —Line/Dartboard.
+ - Value-boundary dropdown —
Auto, orFixedwith a ± bound that pins the y range symmetrically so two steps can be compared without the axis moving. Its label reads the bound when one is set.
+ - Reload.
- - Body —
UplotChart.vuerendering three series (X / Y / Z channels) over a shared time axists.
- - Empty-state overlay — when
hasData=false(no selection, no shots), a placeholder is shown; the underlying series still carry a shape-preservingts=[0,360],xs/ys/zs=[NaN,NaN]payload so the canvas does not jump.
+ - Body —
UplotChart.vuerendering three series (X / Y / Z channels) over the cycle parameterts, orXyLocusChart.vuein dartboard mode, which plots the same samples as a locus in the plane. The line-mode legend width is shared by every line-mode cycle chart, as the strip charts share theirs; the locus view has no legend divider and is unaffected.
+ - Empty-state overlay —
No step selectedwith nothing picked,Not readybefore the payload arrives,No data for stepwhen the step carries none. The underlying series still carry a shape-preservingts=[0,360],xs/ys/zs=[NaN,NaN]payload so the canvas does not jump. The two sensor charts readNo data for stepfor every step of a project with noTimeMappingshot data, which is the normal state of a project that has not been measured. - Step-driven refetch.
BaseCycleLineChartaccepts afetcher: () => Promise<CycleLineResponse>prop. OnSelectedStepInfoHub.StepChangedthe fetcher is re-invoked and the chart re-renders.
- - Server-resolved step. Cycle-line endpoints resolve “currently-selected step” from
LocalProjectService.ClStrip.GetSelectedPos()rather than acceptingstepIndexas a query parameter. This keeps the contract simple and aligns with the event-driven model used by the rest of the player plumbing.
- - Toolbar slot. The optional
#toolbarslot lets callers embed per-chart controls (e.g. the Force chart's flag picker) without subclassing.
+ - Step-driven refetch.
BaseCycleLineChartaccepts afetcher: () => Promise<CycleLineResponse>prop. The step-selection push arrives on/clStripHubasStepSelected; the fetcher is re-invoked and the chart re-renders.
+ - Server-resolved step. The cycle-line endpoints resolve “currently-selected step” from
LocalProjectService.ClStrip.GetSelectedPos()rather than acceptingstepIndexas a query parameter. This keeps the contract simple and matches the event-driven model used by the rest of the execution plumbing.
+ - Toolbar slot. The optional
#toolbarslot lets callers embed per-chart controls (such as the force chart's flag picker) without subclassing.
+ - Shared cursor mark, in two groups. Clicking a point publishes that sample's cycle parameter — not its index — to a module-scoped mark shared by the group:
simfor the two simulated charts (spindle angle),sensorfor the two measured ones (seconds). Each chart draws the mark as a vertical line in line mode and a highlighted point in dartboard mode, and the mark survives both a mode switch and a change of selected step. The split is deliberate: a click on a simulated chart must not move a sensor-side cursor, because the two axes are not the same quantity.
+ - The
simmark reaches outside the chart family. The CWE panel of the Execution Page's Step Info column watches it and pushes the angle to its own displayee, so the flute overlay in the engagement view rotates to the phase clicked on a force or moment chart. The angle persists on that displayee across steps — the server re-applies it with each step's spin direction — so it is pushed only on (re)connect and on a new click. - Not implemented. -
wwwroot-src/src/components/player/charts/BaseCycleLineChart.vue— shared cycle-line skeleton.
-wwwroot-src/src/components/player/charts/UplotChart.vue— uplot wrapper (shared with strip charts).
-wwwroot-src/src/components/player/charts/ForceCycleLineChart.vue— force cycle-line chart with flag picker.
-wwwroot-src/src/components/player/charts/SimSpindleMomentCycleLineChart.vue— simulated spindle moment.
-wwwroot-src/src/components/player/charts/SensorSpindleMomentCycleLineChart.vue— sensor-measured spindle moment.
-wwwroot-src/src/components/player/charts/DynamometerForceCycleLineChart.vue— dynamometer force.
-Players/PlayerChartsController.cs— cycle-line endpoints: +wwwroot-src/src/components/execution/charts/BaseCycleLineChart.vue— shared cycle-line skeleton.
+wwwroot-src/src/components/execution/charts/UplotChart.vue— uplot wrapper (shared with the strip charts).
+wwwroot-src/src/components/execution/charts/XyLocusChart.vue— the dartboard (locus) renderer used in place of the uplot chart in that mode.
+wwwroot-src/src/composables/useCycleSyncMark.ts— the two module-scoped cursor marks and their group keys.
+wwwroot-src/src/components/execution/StepVolumePanel.vue— the CWE panel that follows thesimmark to rotate its flute overlay.
+wwwroot-src/src/components/execution/charts/ForceCycleLineChart.vue— force cycle-line chart with the flag picker.
+wwwroot-src/src/components/execution/charts/SimSpindleMomentCycleLineChart.vue— simulated spindle moment.
+wwwroot-src/src/components/execution/charts/SensorSpindleMomentCycleLineChart.vue— sensor-measured spindle moment.
+wwwroot-src/src/components/execution/charts/DynamometerForceCycleLineChart.vue— dynamometer force.
+Execution/ExecutionChartsController.cs— cycle-line endpoints:-
-
GET /api/player/cycle-line/force?flag=ForceToWorkpieceOnProgramCoordinate|ForceToToolOnToolRunningCoordinate
-GET /api/player/cycle-line/sim-spindle-moment
-GET /api/player/cycle-line/sensor-spindle-moment
-GET /api/player/cycle-line/dynamometer-force
+GET /api/execution/cycle-line/force?flag=ForceToWorkpieceOnProgramCoordinate|ForceToToolOnToolRunningCoordinate
+GET /api/execution/cycle-line/sim-spindle-moment
+GET /api/execution/cycle-line/sensor-spindle-moment
+GET /api/execution/cycle-line/dynamometer-force
- True twin overlay (two series on one canvas) for Sim + Sensor Spindle Moment. Currently rendered as two sibling cards sharing the same chart grid, which keeps the uplot instances independent. Revisit if operators ask for overlap. -
- Strip Charts — windowed mission-timeline charts that share the same
uplotengine and drive step selection.
- - Player Panel — top-level layout that hosts the charts. +
- Execution Page — the page whose Step Info column hosts these charts +
- Strip Charts — windowed mission-timeline charts that share the same
uplotengine and drive step selection
+ - Inspecting a Step — the task these charts serve, with the two scales and the shared mark read as a procedure
Execution Extended RenderingCanvas Tool Bar+-
+
+Tool Path— shows or hides the CL strip.
+
+Path Points— the CL-strip dot markers. Editable only while ClStrip is set in the displayee's rendering-flag bit array.
+
+Scene ▾— RenderingFlag-based checkboxes over that same bit array, in three groups:-
+
- Solid — Machine, Tool, Workpiece, Fixture +
- Coordinate — Program Zero, ISO Coordinate, Heidenhain Coordinate +
- Display Aids — Dimension Bar, Color Scale Bar +
The ClStrip flag is not offered here; the
+Tool Pathbutton owns it. Heidenhain Coordinate appears only when MachiningProject.NcEnv.CncBrand is Heidenhain. The menu is a shared component because other screens use it too — see the Legacy Controller Page.
+
+Meshed Geom ▾— the workpiece's rendering cache and geometry-diff settings: the Graphic-Cache SubMenu and Diff Visual Radius, with aDiffbadge when a difference is present.
+
+wwwroot-src/src/components/execution/ExecutionExtendedToolBar.vue— the tool bar itself.
+wwwroot-src/src/components/widgets/DisplayOptionsMenu.vue— the sharedScene ▾menu.
+wwwroot-src/src/components/preference/GraphicCacheMenu.vue— the Graphic Cache entry underMeshed Geom ▾.
+Execution/ExecutionController.cs—GET /api/Execution/cl-strip-dotsandPOST /api/Execution/update-cl-strip-dots.
+Common/RenderingFlagsController.cs— the rendering-flag reads and writes behind theScene ▾checkboxes.
+- Execution Page — the page whose canvas this tool bar decorates +
- RenderingCanvas Tool Bar — the generic view controls beside it +
- Strip Charts — where Fit View now lives +
- Watching the Run — the task these controls serve, and what the canvas draws by default +
Execution Tool Bar+-
+
Status Text Field
+Start Button(F5; Resume once the run is paused)
+Pause Button(F6)
+Run-One-Line Button(F7 — one NC line)
+Run-One-Step Button(F8 — one machining step)
+Stop Button
+Reset Button
+
+- Both the webservice and the win-desktop application watch LocalProjectService events to track PacePlayer status changes. +
- In the webservice,
ExecutionStatusServicesubscribes to those events and broadcasts status changes over SignalR throughExecutionStatusHub.
+ - The win-desktop application subscribes to the same LocalProjectService events directly. +
- The frequently used buttons carry hotkeys: Start / Resume, Pause, Run One Line and Run One Step. The app's tool-tips are the only place those key bindings are written down. +
- The background color of the
Status Text Fieldfollows the status: +-
+
- Warning style — Running +
- Secondary style — Paused, No Project +
- Success style — Finished, Ready +
+ wwwroot-src/src/components/execution/ExecutionToolBar.vue— the buttons. All state and handlers come from the shareduseExecutionTransportcomposable, so the component is pure markup.
+Execution/ExecutionController.cs—POST /api/Execution/start | pause | resume | run-line | run-step | stop | reset, andGET /api/Execution/status.
+Execution/ExecutionStatusHub.cs+Execution/ExecutionStatusService.cs— the status broadcast.
+- Execution Page — the page these controls drive +
- Starting and Stepping — the task these controls serve, with the enable rules read as a procedure +
- Graphic-Cache SubMenu
+
-
+
Lowernumeric field (unit MB)
+Uppernumeric field (unit MB)
+Currentnumeric field (unit MB)
+- Slider +
+ - There is no apply step and no Save button: each field posts its own value the moment it is committed — on blur, or on Enter — and the whole panel is loaded once when it is mounted. +
- The
Currentfield is bounded by the two limit fields, so a value belowLoweror aboveUpperis refused with an inline message and never sent. The server clamps a submitted value into the limits regardless, and the panel re-applies the effective state that comes back, so the server's answer is always what ends up on screen.
+ - The slider is integer-valued and reflects
Currentin MB. Its range tracksLower/Upper, clamped defensively so the minimum never exceeds the maximum. Releasing the slider commits the value.
+ - A failed write shows the message inline under the fields, raises a notification, and re-reads from the server so the panel never drifts from stored state. +
- A write lands on the live user configuration, so it takes effect at once —
GraphicCacheMbis a pass-through ontoCubeTree.DispCacheMb. All three values are part of the user-config XML, so they reach disk with the next save of that config.
+ wwwroot-src/src/components/preference/GraphicCacheMenu.vue— the panel itself: Lower / Upper / Current numeric fields plus the slider. Loads on mount, commits each edit to the REST endpoint, re-applies the server's clamped response, and reports failures inline plus a notification.
+wwwroot-src/src/components/execution/ExecutionExtendedToolBar.vue— where the entry hangs: theMeshed Geom ▾dropdown on the Execution page canvas panel's expansion header, whoseGraphic Cacherow opens this panel in a nested menu.
+wwwroot-src/src/components/workpiece/WorkpieceDiffRadiusMenu.vue— the siblingDiff Visual Radiusrow in the same dropdown.
+wwwroot-src/src/components/widgets/NumericInput.vue— the shared numeric field used for all three values.
+wwwroot-src/src/api/preference.ts—getGraphicCache/setGraphicCacheoverGET/POST /api/preference/graphic-cache, typed as{ lowerLimit, upperLimit, value }.
+Environments/PreferenceController.cs—GetGraphicCacheSettingsreturns{ success, lowerLimit, upperLimit, value };UpdateGraphicCacheSettingstakes the same three as nullable fields, clampsvalueinto the limits, writes the liveUserConfig, and returns the effective state.
+Environments/UserConfig.cs—GraphicCacheLowerLimitMb(default 10) andGraphicCacheUpperLimitMb(default 1200) are plain stored values;GraphicCacheMbis a pass-through whose getter and setter areCubeTree.DispCacheMb. All three round-trip through the config XML.
+- Preference Menu Dropdown — the dropdown that hosts this entry in the WPF client +
- LocalProjectService — the project data service the whole page reads. +
- MachiningProject — reached through the displayee bound to the canvas. +
UserService— per-user configuration, including the stored panel layout.
+- Left dock — the Control Tree over the primary editor panel for the selected tree node. The Execution Tool Bar — the transport buttons — sits atop that panel and is shown for the Execution root and every node under it. +
- Main — the RenderingCanvas over the Session Message Panel, stacked as collapsible panels with a draggable divider between them. The canvas panel's header adopts the RenderingCanvas Tool Bar and the Execution Extended RenderingCanvas Tool Bar. +
- Strip Charts — the strip-chart group bar over the three Strip Charts: Availability Chart, Surface Roughness Chart and Color Index Time Chart. +
- Step Info — Sentence Syntax as its own region (an NC sentence need not map to any machining step, so it is not a member of the step-based panels), then the Step Info group bar, then Step Properties, CWE (cutter–workpiece engagement) and the four Cycle-Line Charts. +
- The browser component opens a SignalR connection to
/renderingHuband streams frames. It holds no displayee of its own.
+ POST /api/Execution/initialize/{connectionId}resolves that connection's engine on the server, bindsProjectDisplayeeService.ExecutionDisplayeeto the engine's DispEngine.Displayee unless one is already bound there, and snaps the camera to the home view through SetViewToHomeView().
+- The displayee receives project data from LocalProjectService. +
- The connection is per rendering session, so a collapsed canvas keeps its connection and only stops drawing; the engine is paused while the panel is collapsed or the page is off-screen. +
wwwroot-src/src/pages/ExecutionPage.vue— the routed page at/execution: the four columns, their pixel / ratio splitters, the panel-expansion stacks, and theexecution-scoped Control Tree host it provides.
+wwwroot-src/src/router/routes.ts— the/→executionredirect, theexecutionroute, and the/missionredirect to/execution?tree=execution/mission.
+wwwroot-src/src/router/treeRoutes.ts—routeForTreeId, which lands a?tree=id on the page that owns that branch.
+wwwroot-src/src/components/controlTree/useControlTreeHost.ts— the Control Tree state machine:buildExecutionRoot()builds theexecutionroot holdingexecution/missionaboveexecution/program, syncs?tree=, and re-reads the Program branch whenever the broadcast execution status changes.
+wwwroot-src/src/components/controlTree/ControlTreeDock.vue— the left dock: the Control Tree row over the primary editor row.
+wwwroot-src/src/components/controlTree/ControlTreePanel.vue— the tree pane; carries the live execution-status badge on the Execution tree item.
+wwwroot-src/src/components/controlTree/PrimarySlavePanel.vue— the primary editor pane for the selected node; mounts the Execution Tool Bar on top whenever the selection isexecutionor a descendant.
+wwwroot-src/src/components/controlTree/ExecutionRootPanel.vue— the editor panel of the Execution root node.
+wwwroot-src/src/components/controlTree/missionItemTypes.ts— the branch's ItemType registry and the per-command enable ticks.
+wwwroot-src/src/components/controlTree/MissionRootPanel.vue— the entry-list editor of a list command, serving the branch root and every nested list.
+wwwroot-src/src/components/controlTree/MissionCommandSlavePanel.vue— the per-command editor host.
+wwwroot-src/src/components/controlTree/MissionSectionPanel.vue— one setting section of a command.
+- Program Branch — the branch's ItemType registry, its root, file and conversion panels, and the read-only endpoints behind them. +
wwwroot-src/src/components/RenderingCanvas.vue— the browser canvas and its/renderingHubconnection.
+wwwroot-src/src/components/execution/ExecutionToolBar.vue— the transport controls.
+wwwroot-src/src/components/execution/ExecutionExtendedToolBar.vue— the view and rendering-flag controls adopted onto the canvas panel's header.
+wwwroot-src/src/components/execution/SelectedStepInfoPanel.vue— Step Properties.
+wwwroot-src/src/components/execution/StepVolumePanel.vue— the CWE panel.
+wwwroot-src/src/components/execution/SyntaxPiecePanel.vue— Sentence Syntax.
+wwwroot-src/src/components/execution/SessionMessagePanel.vue— the message panel and its four sink tabs.
+wwwroot-src/src/components/panels/PanelExpansion.vueandwwwroot-src/src/components/panels/ResizablePanelStack.vue— the collapse-in-place panels and their draggable heights.
+wwwroot-src/src/layouts/MainLayout.vue— the shell whose keep-alive wrapper, keyed on the project epoch, holds this page mounted across navigation.
+- Strip Charts — three
uplot-backed strip charts driven byClStrip.
+ - Cycle-Line Charts — four
uplot-backed per-step charts.
+ wwwroot-src/src/components/execution/charts/UplotChart.vue— thin uplot wrapper withResizeObserverand reactivedata/series/bandsbindings.
+wwwroot-src/src/components/execution/charts/BaseStripChart.vue— strip-chart skeleton: min / max banded series, header aspect picker, wheel / drag / click pointer handlers.
+wwwroot-src/src/components/execution/charts/BaseCycleLineChart.vue— cycle-line skeleton that takes afetcherprop and re-fetches when the step selection changes.
+Execution/ExecutionController.cs—POST /api/Execution/start | pause | resume | run-line | run-step | stop | reset;GET status,status/{connectionId},project-status,selected-step-infoandcl-strip-dots; the canvas actionsinitialize/{connectionId}(binds the displayee and callsSetViewToHomeView) andfit-view/{connectionId}; and thestep-volume/{connectionId}family behind the CWE panel.
+Execution/ExecutionStatusHub.cs+Execution/ExecutionStatusService.cs—/executionStatusHub, the run-state broadcast that drives the transport and the tree badge.
+Execution/SessionSinkHub.cs+Execution/SessionSinkBroadcastService.cs— one hub per message sink (/shellMessageHub,/ncDiagnosticHub,/stepDiagnosticHub,/ncManipulationDiagnosticHub), so a client subscribes to exactly the sink it wants.
+Execution/SelectedStepInfoService.cs— the selected-step payload. It has no hub of its own: the “selection changed” push rides/clStripHub.
+Execution/ClStripController.cs+Execution/ClStripHub.cs+Execution/ClStripBroadcastService.cs— CL-strip range state and the zoom / pan / select / enter broadcasts.
+Execution/ExecutionChartsController.cs— the strip-chart and cycle-line-chart data endpoints under/api/execution.
+Disp/RenderingHub.cs+Disp/RenderingService.cs— the shared/renderingHubevery page's RenderingCanvas connects to, keyed per rendering connection id.
+Disp/ExecutionDisplayee.cs— the server-side displayee bound to the connection's engine.
+Common/ProjectDisplayeeService.cs— owns theExecutionDisplayeeinstance handed to the engine on initialize.
+- Create the layout with a RenderingCanvas. +
- Set up the canvas behavior. +
- Add the Execution Tool Bar. +
- Add the Execution Extended RenderingCanvas Tool Bar with the CL-strip, fit-view and rendering-items behaviors. +
- Reach the page from the navigation menu on Main Panel. +
- Make it the landing page, with its tool bars. +
- Build the Session Message Panel and the Selected-Step Info Panel, and put the button that opens the Step Present Dialog on the latter's title bar. +
- Execution Tool Bar — The transport controls on the primary panel header, and the status they read +
- Execution Extended RenderingCanvas Tool Bar — The run-specific canvas controls: the CL strip, fit view and the Scene menu +
- Selected-Step Info Panel — The Step Properties panel in the Step Info column +
- Strip Charts — The whole-program charts in the Strip Charts column +
- Cycle-Line Charts — The per-step charts in the Step Info column +
- Graphic-Cache Menu — The meshed-geometry cache limits, edited from the canvas header +
- Step Present Dialog — The step-property presentation settings, opened from the Step Properties panel +
- Mission — The command-list branch this page hosts, one page per command type +
- Program Branch — The read-only inspection twin of Mission: one node per NC source file the run read, its passes and its execution marks +
- Execution Tool Bar — the transport buttons and their status feed +
- Execution Extended RenderingCanvas Tool Bar — CL strip, fit view and the rendering-items menu +
- Selected-Step Info Panel — the Step Properties panel in the Step Info column +
- Strip Charts — the whole-program charts in the Strip Charts column +
- Cycle-Line Charts — the per-step charts in the Step Info column +
- Session Message Panel — the run log under the canvas +
- RenderingCanvas Tool Bar — the view controls the canvas header adopts +
- Main Panel — the shell that routes to this page +
- Mission — the command-list branch this page hosts, one page per command type +
- General Setup Page — the other Control-Tree page, and the equipment this run consumes +
- Program Branch — the read-only inspection twin of Mission this page hosts below it +
- Control Tree — the engine behind this page's tree, shared with General Setup +
- Tree Ids and Routes — the
?tree=surface this page's selection rides on
+ - Primary: ListCommand — Title +and CommandEntryList. +
- Supporting:
+
-
+
- EnablingWrapper — one entry: +Command plus +IsEnabled. +
- ITitleCommand —
ListCommandimplements it, and +GetCommandTitle composes the label a row shows.
+ - PlayerCommand — the mission's command, a list. +
- CommandCatalogAttribute and CommandCategory
+— what makes
Listaddable, and the group it is offered under.
+
+ - List Entry Panel
+
-
+
- Control Bar
+
-
+
- Up / Down / Duplicate / Delete — labelled buttons for the operations that rewrite the +parent list. Delete asks for confirmation in a dialog naming the command. +
- At the bar's left, a caption while the command is disabled: skipped during play, still +editable. +
+ - Title (optional) Input +
- Embedded Entry-List Editor
+
-
+
- This list's own entries, scoped to the node's path. +
+
+ - Control Bar
+
- Into a list — drag a row onto the middle band of a list row; that row's outer quarters still +reorder around it. +
- Out of a list — drag onto the drop-out zone of a nested list's editor, which is visible only +while a row inside that editor is being dragged. The entry lands right after the list command +itself in the owning list. +
- A new command lands at the end of the list it was added to, root or nested. Nothing is pinned; +the order is entirely the user's, changed with the row's up / down buttons or by dragging. +
- Duplicate is a deep copy. The entry is cloned through the same XML round-trip the project file +uses, so a nested list copies with its whole sub-tree, and the clone lands right after the source. +
- Two columns: a 400px entry column, a
GridSplitter, and the selected command's content column, +both columns floored at 300px.
+ - One toolbar over the list — Add, Remove, Move Up, Move Down — acting on the list selection. There +is no duplicate. +
- Add is a plain button that pops a fixed six-item context menu built in its click handler: +“Pre-Setting Command”, “NC Opt Option Command”, “NC File Command”, “NC Code Command”, +“Script Command” and “Post Execution Command”. +
- The list box is extended-select. Remove takes every selected entry, prompting with a count when +more than one is selected, and a drag can carry a whole selection; Move Up and Move Down stay +disabled unless exactly one entry is selected. +
- Each entry box carries a labelled Enable checkbox at its left, the command's title in bold, and a +pin icon with a “Pin at beginning” / “Pin at end” label — shown on the first entry when it is a +General Config command, and on the last when it is a Post-Execution command. +
- Insertion works around the ends: a new entry lands before a trailing Post-Execution when that is +the list's only one, and a General Config lands at the top unless the list already starts with +one. A drag that would move a pinned entry — or drop another entry onto it — is refused, the guard +testing that the entry is the only command of its kind and sits at that end. The Move Up / Move +Down buttons are not covered by that guard. +
- The content panel is greyed and made read-only while the selected entry's Enable box is clear. +
- Files dropped from the file explorer onto the entry list create one enabled NC File command per +file, taking a project-relative path when the file sits under the project directory. +
- The entry label falls back to the raw class name for a command that composes no title of its
+own —
GeomDiffCommand,OptimizeToFilesCommand,WriteStepFilesCommandand +WriteShotFilesCommand.
+ wwwroot-src/src/components/controlTree/MissionCommandSlavePanel.vue— a list entry's panel: the +control bar, the optional Title input with its debounce / flush / cancel discipline, the embedded +entry-list editor of a list entry, and the bespoke-or-generic editor choice for every other kind.
+wwwroot-src/src/components/controlTree/MissionRootPanel.vue— the entry-list editor embedded +here and serving the branch root: the rows and their actions, the three drag landings, and the +drop-out zone shown only during a drag inside a nested editor.
+wwwroot-src/src/components/controlTree/missionItemTypes.ts— the child builders that recurse +into a list's own entries, the per-kind editor override map, the kind and category icons, and the +per-build stamp that forces the panel remount.
+wwwroot-src/src/components/controlTree/useControlTreeHost.ts— which nodes show a checkbox, the +dim that flows down a disabled command's sub-tree, the enable write, and the in-place label +refresh after a title edit or a language switch.
+wwwroot-src/src/api/mission.ts— the typed wrapper over the Mission API and the path convention +(rootor empty = the root list,0.2= a nested entry).
+wwwroot-src/src/components/mission/GenericCommandPanel.vue— the editor a catalog kind gets when +it ships no bespoke panel, built from the command's declared scalar fields.
+wwwroot-src/src/components/mission/NcFileCommandPanel.vue— the multi-pick file dialog: the +first pick lands on the command, each further pick becomes another Program File command right +after it.
+wwwroot-src/src/pages/ExecutionPage.vue— the page-level splitter between the left dock and the +central area.
+wwwroot-src/src/i18n/en/tree.ts— thetree.mission.*strings: the kind names, the drop-out +zone, the run-order hint, the disabled hint and the operation labels.
+Missions/MissionController.cs— the entry lifecycle:GET list-command/entries(recursive), +POST list-command/entriesandPOST list-command/entries/{path}(append at the tail of the root +or of a nested list),DELETE list-command/entries/{path}, +POST list-command/entries/{path}/move,PUT list-command/reorder, +POST list-command/entries/{path}/duplicate(the XML-round-trip deep clone), +POST list-command/entries/{path}/reparent(drag-into-list and drop-out, with the self/descendant +guard),PUT commands/{path}/listcommandfor the title, andPUT commands/{path}/enabledfor an +entry's enable flag.GetCommandTitlecomposes every entry label the tree shows.
+Missions/MissionCommandCatalog.cs— reflects the[CommandCatalog]commands into the addable +set and constructs the picked kind.
+HiNc/SessionCommands/ListCommand.cs— the container: the optional title, the entry list a run +walks top-down, the whitespace-only-title-is-unset XML rule, the Flow catalog attribute, and the +List/List [title]composition.
+HiNc/SessionCommands/EnablingWrapper.cs— the enable flag plus the wrapped command that together +make one entry.
+HiNc/SessionCommands/ITitleCommand.cs— the interface a command implements to compose its own +title from the caller's vocabulary.
+HiNc/SessionCommands/CommandCatalogAttribute.cs—CommandCategoryand the attribute (category, +order, kind key, aliases) that makes the addable set backend-owned.
+HiNc/SessionCommands/PreSettingCommand.cs— the General Config bundle: no catalog attribute, and +a loaded project expands a stored one into the split setting commands.
+HiNc/MachiningProcs/MachiningProject.cs— declares the mission's command and keeps it a list.
+- Mission Root Panel — the entry-list editor this command embeds, documented once +
- Building a Mission — the task this command serves: grouping a mission and moving entries between groups +
- Mission — the rest of the command panels +
- NcCodeCommand Panel
+
-
+
- Title Field
+
-
+
- The model is Title. +
- Labelled "Title (optional)". A newly added command arrives with the model's default name,
+
NC Code, already in the field; renaming it renames the program in the run log and in the +mission row.
+
+ - NC Code Editor Area
+
-
+
- The model is NcText. +
- A plain monospace text area: on the web an 18-row field with a 360 px floor, on WPF a Consolas +text box in an NC Code group box that takes the panel's remaining height. +
- No line-number gutter and no syntax highlighting on either client. The rich editor in this +folder is the Script command's — that command ships +CodeMirror with a C# grammar, this one ships a text area. +
+ - Stats and Actions Row (web)
+
-
+
- Line count and character count, both recomputed as the text changes. +
- Trim Blank Lines Button — trims every line and drops the ones left empty, then saves at once. +
- Clear Button — confirms in a dialog before emptying the NC text. +
- Both buttons stay disabled while the code is empty. +
+
+ - Title Field
+
wwwroot-src/src/components/mission/NcCodeCommandPanel.vue— this panel: the Title input, the +18-row monospace text area, the 400 ms debounced saves, the live line and character stats, Trim +Blank Lines, and Clear behind a confirm dialog.
+wwwroot-src/src/components/controlTree/missionItemTypes.ts— maps thenccodekind to this +panel, and states the rule it belongs to: a single-purpose kind embeds its whole editor on the +command node.
+wwwroot-src/src/api/mission.ts—loadNcCodeandsetNcCode, and the command shape the panel +edits (NC code plus title).
+wwwroot-src/src/i18n/en/mission.ts— the panel's wording: the NC Code label, the character +count, Trim Blank Lines, and the line count it shares with the other panels.
+wwwroot-src/src/i18n/en/dialog.ts— the Clear confirmation's title and message.
+Missions/MissionController.cs— the single patch PUT for this command, whose NC-code and title +fields are each applied when the body carries them, and the command-snapshot arm that returns both +back to the panel.
+HiNc/SessionCommands/NcCodeCommand.cs— the model:NcText,Titledefaulting to the command's +own display name, the Program-category catalog registration, the XML round-trip that puts the NC +text into the project file,Runhanding text and title to the session shell, and the row-label +rule that shows the bare name while the title is unset or still the default.
+HiNc/MachiningProcs/SessionShell.cs—RunNctakes the NC text plus an alternative file name, +and that name is the title this command supplies for the log.
+HiNc/MachiningProcs/MachiningProject.cs— nests the command list into the project XML, which is +how this command's NC text persists with the project.
+- NcFileCommand Panel — the other Program command: a path to a file instead of text stored in the project +
- Script Command Panel — the rich editor this folder does have, and why this command ships a plain text area instead +
- Playing a Program — the task these two commands serve +
- Mission — the rest of the command panels +
- NcFileCommand Panel
+
-
+
- Head Line
+
-
+
- Program File Path Field
+
-
+
- The model is NcFile. +
- Its hint names both accepted forms: an absolute path on the server, or a path relative to +the project folder. +
- Every keystroke saves the path. Leaving the field or pressing Enter additionally refreshes +the file-info banner. +
- The web field flexes to fill the row beside the Browse button at any panel width; the WPF +field is a fixed 200 px column carrying its own label. +
+ - Browse Button
+
-
+
- Opens the shared file-explorer dialog — see Browsing for a Program. +
+
+ - Program File Path Field
+
- Play As Select
+
-
+
- The model is NcKind. +
- Four choices: Auto (by extension), Brand NC, CL (CLSF) and CSV. The hint restates what Auto +detects, so the runner can be pinned when the extension would route the file elsewhere. +
- This control is the web client's. The WPF panel ships no kind control, so a project edited +there keeps the kind it already holds — Auto until something else sets it. +
+ - File Info Banner
+
-
+
- Appears once the path field is non-empty. Every fact in it is read on the server. +
- Found: a green banner stating the file size, its modified stamp and its line count, with a +Preview button in the banner's action slot. +
- Missing: an orange banner saying the file is not found on the server, and no Preview button. +
+ - Preview Dialog
+
-
+
- Read-only. The server returns the file's first 100 lines and the dialog shows them in a +monospace block that scrolls within 60% of the viewport height, inside a card capped at 80%. +An empty file previews as "(empty)". +
+
+ - Head Line
+
- NC Files —
.nc,.anc,.tap,.eia,.mpf,.spf,.cnc,.ptp,.h. The open set: +brand controller extensions, of which these are the common ones rather than all of them.
+ - CL Files —
.cl,.cls,.clsf.
+ - CSV Files —
.csv.
+ - All Files — an empty extension list, which is the empty filter the Load Pattern asks every +browser to preserve. It is the backstop for a brand extension the NC group does not name. +
- Web — the panel does not edit the file. It assigns the path, picks the runner, reports what +the server knows about the file, and previews its first 100 lines read-only. Nothing in the +browser writes NC bytes back; the two file endpoints this panel calls are both reads. +
- WPF — the panel opens the file. A non-text file, or one the panel cannot find, disables the +editor and states why in the Head Message Place above it. A text file within 20000 lines opens for +editing, with its line count in the group-box header; a longer one shows its first 20000 lines +read-only and warns in the Head Message Place. Two seconds after the last keystroke the editor +writes the file back, and reports the save in that same message place. The editor is AvalonEdit +with line numbers where that assembly resolves, and a plain Consolas text box otherwise. +
wwwroot-src/src/components/mission/NcFileCommandPanel.vue— this panel: the path field and +Browse, the “Play As” kind select, the file-info banner and its Preview button, the read-only +preview dialog, the four filter groups, and the multi-pick fan-out.
+wwwroot-src/src/components/widgets/FileExplorerDialog.vue— the shared server-side browser +Browse opens: the pickable / multi / filters / initial-root / allowed-roots props, and the pick +event whose emitted strings pair a root name with a relative path — that relative half is what the +command stores.
+wwwroot-src/src/components/widgets/fileFilter.ts— the filter shape the four groups are built +from; an empty extension list means all files.
+wwwroot-src/src/components/controlTree/missionItemTypes.ts— maps thencfilekind to this +panel. A kind absent from that map is served by the generic field editor instead.
+wwwroot-src/src/api/mission.ts—loadNcFile,setNcFilePath,setNcFileNcKind, +getNcFileInfoandpreviewNcFile, plus the kind union and the file-info shape.
+wwwroot-src/src/i18n/en/mission.ts— the panel's wording: the path hint, the Play As hint, the +four filter labels, the banner's found and not-found lines, and the preview titles.
+Missions/MissionController.cs— four endpoints serve this command: a PUT for the path, a PUT for +the NC kind, and the two server-side reads behind the banner and the preview (both POSTs, both +resolving a relative path against the project folder). The command-snapshot builder emits the file +path and the NC kind for this kind of command.
+HiNc/SessionCommands/NcFileCommand.cs— the model:NcFile,NcKinddefaulting to Auto, the +“Program File” display name and its Program-category catalog registration, the XML round-trip that +writes the path verbatim, theProgram File [path]row label, andRunhanding path and kind to +the session shell.
+HiMech/MachiningProcs/NcKind.cs— the kind enum (Auto / BrandNc / Cl / Csv) and the +detect-by-path helper, the authority for what Auto does.
+HiNc/MachiningProcs/SessionShell.cs—RunNcFilepasses the stored path together with the +project's base directory to the local project service.
+- NcCodeCommand Panel — the other Program command: NC text stored in the project instead of a path to a file +
- Playing a Program — the task these two commands serve +
- Mission — the rest of the command panels +
- The NC Optimization Config node itself carries the four enable checkboxes, below the +move / duplicate / delete control bar every command node shows. +
- The node grows five section children — Distances, Feedrate, Motion Dynamics, Force & Safety, +Compensation — and each mounts the same component scoped to its own section. +
NC Optimization Config Node
+-
+
- Enable Optimization CheckBox
+
-
+
- The model is EnableOpt. +
+ - Enable Feedrate Optimization CheckBox
+
-
+
- The model is EnableOptFeedrate. +
+ - Enable Depth Splition CheckBox
+
-
+
- The model is EnableDepthSplition. +
- The desktop client labels the same switch “Enable Depth Splitting”. +
+ - Enable Interpolation CheckBox
+
-
+
- The model is EnableInterpolation. +
+
+- Enable Optimization CheckBox
+
Distances Section
+-
+
- Extended Pre Distance Numeric Field (mm)
+
-
+
- The model is ExtendedPreDistance_mm. +
+ - Extended Post Distance Numeric Field (mm)
+
-
+
- The model is ExtendedPostDistance_mm. +
+
+- Extended Pre Distance Numeric Field (mm)
+
Feedrate Section
+-
+
- Min Feedrate Numeric Field (mm/min)
+
-
+
- The model is MinFeedrate_mmdmin. +
+ - Max Feedrate Numeric Field (mm/min)
+
-
+
- The model is MaxFeedrate_mmdmin. +
+ - Rapid Feed Numeric Field (mm/min)
+
-
+
- The model is RapidFeed_mmdmin. +
+ - Min Feed Per Tooth Numeric Field (mm)
+
-
+
- The model is MinFeedPerTooth_mm. +
+ - Max Feed Per Tooth Numeric Field (mm)
+
-
+
- The model is MaxFeedPerTooth_mm. +
+ - Feedrate Assignment Ratio Numeric Field
+
-
+
- The model is FeedrateAssignmentRatio. +
+
+- Min Feedrate Numeric Field (mm/min)
+
Motion Dynamics Section
+-
+
- Max Acceleration Numeric Field (mm/s²)
+
-
+
- The model is MaxAcceleration_mmds2. +
+ - Max Jerk Numeric Field (mm/s³)
+
-
+
- The model is MaxJerk_mmds3. +
+
+- Max Acceleration Numeric Field (mm/s²)
+
Force & Safety Section
+-
+
- Preferred Force Numeric Field (N)
+
-
+
- The model is PreferedForce_N. +
- The one field hinted "Accepts Infinity.", and the one field with no lower bound. +
+ - Yielding Safety Factor Numeric Field
+
-
+
- The model is YieldingSafetyFactor. +
+ - Thermal Yield Safety Factor Numeric Field
+
-
+
- The model is ThermalYieldSafetyFactor. +
+ - Spindle Torque Safety Factor Numeric Field
+
-
+
- The model is MaxSpindleTorqueSafetyFactor. +
+ - Spindle Power Safety Factor Numeric Field
+
-
+
- The model is MaxSpindlePowerSafetyFactor. +
+
+- Preferred Force Numeric Field (N)
+
Compensation Section
+-
+
- Enable Forward Compensation CheckBox
+
-
+
- The model is EnableForwardCompensation. +
+ - Enable Side Compensation CheckBox
+
-
+
- The model is EnableSideCompensation. +
+ - Enable Depth Compensation CheckBox
+
-
+
- The model is EnableDepthCompensation. +
+
+- Enable Forward Compensation CheckBox
+
wwwroot-src/src/components/mission/NcOptOptionCommandPanel.vue— this panel. Asectionprop +picks the one group it renders, a setter map routes each field to its API call, and a failed write +re-reads the command and notifies.
+wwwroot-src/src/components/controlTree/missionItemTypes.ts— registers thencoptoptionkind's +bespoke editor, declares its five section children, gives it thetuneicon and the +NC Optimization Configdisplay name, and leaves it out of the section-enable readers and writers +so its section nodes carry no checkbox.
+wwwroot-src/src/components/controlTree/MissionCommandSlavePanel.vue— the control bar above the +command node's checkboxes.
+wwwroot-src/src/components/controlTree/MissionSectionPanel.vue— mounts this panel once per +section node, scoped to that node's section.
+wwwroot-src/src/api/mission.ts— the option shape the panel edits, the reader that takes the +ncOptOptionsnapshot and parsesInfinity, and one setter per property over +commands/{path}/ncoptoption/{endpoint}.
+wwwroot-src/src/components/widgets/NumericInput.vue— the field every non-boolean option uses: +it commits on blur or Enter, spells infinity asInfinity, and enforces the minimum the panel +passes.
+wwwroot-src/src/i18n/en/mission.ts— the option labels and the “Accepts Infinity.” hint.
+wwwroot-src/src/i18n/en/tree.ts— the kind name and the five section names the tree shows.
+Missions/NcOptOptionEndpoints.cs— a partial of the sameMissionController: one PUT per +editable property, two of them string-bodied soInfinityround-trips. Each creates the option +object when the command has none, and answers a command-type mismatch when the path holds another +kind.
+Missions/MissionController.cs— builds the command snapshot the panel reads, re-keying the two +spindle safety factors onto their wire names, and owns the entry lifecycle — add, delete, move, +duplicate, reparent — that puts this command in the list.
+HiNc/SessionCommands/NcOptOptionCommand.cs— the mission entry: theNC Optimization Config+display name, its Optimization catalog registration, the single option property, the fixed command +title, the XML round-trip that nests the options in the project file, and theRunthat assigns +them onto the session shell.
+HiMech/NcOpt/NcOptOption.cs— the option model: the engine-side names +MaxSpindleTorqueSafetyFactorandMaxSpindlePowerSafetyFactor, the compensation booleans as bit +accessors over one mask, and the mm/min feedrates as conversions over mm/s storage.
+- PostExecutionCommand Panel — where the optimized programs are written out, under the settings this command puts in force +
- The Other Commands — the task this command serves +
- Mission — the rest of the command panels +
- Step Files Output Section
+
-
+
- The node's checkbox is EnableWriteStepFiles. +
- Step File Template Field
+
-
+
- The model is StepFileTemplate. +
- Default value:
Output/[NcName].step.csv
+
+ - Writes the step-series data of what the session has played. +
+ - Shot Files Output Section
+
-
+
- The node's checkbox is EnableWriteShotFiles. +
- Shot File Template Field
+
-
+
- The model is ShotFileTemplate. +
- Default value:
Output/[NcName].shot.csv
+
+ - Shot File Time Resolution Number Field (ms)
+
-
+
- The model is ShotFileTimeResolution_ms. +
- Default value: 1 +
- This sampling period — not the machining resolution — sets the accuracy ceiling of the +time-series data, and a fine period produces a large file: a six-cut program writes about +13 MB at 1 ms and about 128 MB at 0.1 ms. +
+
+ - Optimization Output Section
+
-
+
- The node's checkbox is EnableOptimizeToFiles. +
- Optimization File Template Field
+
-
+
- The model is OptimizationFileTemplate. +
- Default value:
Output/Opt-[NcName]
+
+ - The SoftNc pipeline runs when the SoftNc runner is on and the session holds played syntax +layers; otherwise the HardNc path runs. Both clear every cutter's optimization-limit cache +first. +
+ - CL → NC Writeback Section
+
-
+
- The node's checkbox is EnableConvertClToNcFiles. +
- NC Output Template Field
+
-
+
- The model is ClToNcFileTemplate. +
- Default value:
Output/[NcName].nc
+ - Its hint states the substitution:
[NcName]is replaced by the source file name, extension +kept.
+
+ - A caption below the field explains the synthesis: the MSYS frame becomes a
G68.2tilted +working plane, tool posture becomesG43.4RTCP with rotary words, and motions become +G00/G01/G02/G03. The CL must have been played on an XYZABC machine chain first. The +converted files appear as→ file.ncnodes on the Execution Program branch, with source ⇄ +output line jumps.
+
+ - Geometry Difference Detection Section
+
-
+
- The node's checkbox is EnableGeomDiff. +
- Detect Radius Number Field (mm)
+
-
+
- The model is GeomDiffDetectRadius_mm. +
- Default value: 1 +
+ - Compares the workpiece geometry at that radius. With no workpiece in the session it reports +“No Workpiece exist” and does nothing. +
+ wwwroot-src/src/components/mission/PostExecutionCommandPanel.vue— the command node's caption +and one headerless block per section: step-files, shot-files, optimization, cl-nc-writeback and +geom-diff. No checkboxes live here.
+wwwroot-src/src/components/controlTree/missionItemTypes.ts— declares the five sections with +their labels, marks Shot Files and Optimization physics-gated with an explicit note that CL → NC +is not, filters the gated sections out of the tree, and maps each section id to its enable-flag +reader and writer.
+wwwroot-src/src/components/controlTree/MissionSectionPanel.vue— renders one section headerless, +the breadcrumb already naming it, and passes no enable state down because the flags decide only +what runs.
+wwwroot-src/src/components/controlTree/useControlTreeHost.ts— the ticks: a command node ticks +its own enable flag, a section node with an enable flag ticks that, and every other node hides the +box.
+wwwroot-src/src/api/mission.ts—loadPostExecutionand the elevenpostexecution/*PUTs.
+wwwroot-src/src/i18n/en/mission.ts— the field labels, the[NcName]hint, the command node's +overview caption and the writeback paragraph.
+wwwroot-src/src/i18n/en/tree.ts— the five section node labels.
+wwwroot-src/src/stores/appState.ts— the physics preference and licence flags the tree builder +reads.
+Environments/PreferenceController.cs— serves the physics preference already combined with the +advanced-physics licence, the same condition the WPF panel evaluates.
+Missions/MissionController.cs— thecommands/{path}/postexecution/*endpoints: the five enable +flags, the four templates, the shot-file time resolution and the geom-diff detect radius.
+HiNc/SessionCommands/PostExecutionCommand.cs— the model, its display name and Output-category +catalog registration, every default above, the run order, and the warning raised for a project +still carrying a meshed-geometry output.
+HiNc/SessionCommands/ListCommand.cs— the in-order run that skips disabled entries, which is +what makes this command's placement decide what it covers.
+HiNc/MachiningProcs/SessionShell.cs— what each output actually calls: the shot-file writer and +its sampling-period guidance, the step-file writer, the optimization routing between the SoftNc +and HardNc paths, the CL → NC writeback synthesis, and the workpiece difference.
+HiNc/SessionCommands/GeomDiffCommand.cs,HiNc/SessionCommands/WriteStepFilesCommand.cs, +HiNc/SessionCommands/WriteShotFilesCommand.csand +HiNc/SessionCommands/OptimizeToFilesCommand.cs— the standalone commands behind the same four +outputs: loadable from a project file, absent from the catalog.
+HiNc/SessionCommands/RecordMeshedGeomCommand.csand +HiNc/SessionCommands/ExportMeshedGeomToStlCommand.cs— the two catalogued Output commands that +carry the meshed-geometry snapshot.
+HiNc/SessionCommands/CommandCatalogAttribute.cs— the attribute that separates a loadable +command from an addable one.
+- PreSettingCommand Panel — the other half of the positional pair: those settings apply forward, these outputs cover everything up to here +
- NC Optimization Option Panel — the settings the Optimization Output section writes its result under +
- The Other Commands — the task this command serves +
- Mission — the rest of the command panels +
- Machining Resolution — MachiningResolutionCommand, 0.125 mm by default,
+reading
Machining Resolution [0.125 mm]in the list.
+ - Machining Motion Resolution — MachiningMotionResolutionCommand, Feed Per +Cycle by default. +
- Collision Detection — CollisionDetectionCommand, on by default. +
- Pause on Failure — PauseOnFailureCommand, off by default. +
- Physics — PhysicsCommand, on by default, its field declared +physics-licence gated. +
- Command Node
+
-
+
- Machining Resolution Number Field
+
-
+
- The model is MachiningResolution_mm. +
- Unit mm, floored at 0, default 0.125. A free numeric field — the web offers no option list. +
+ - Motion Resolution
+
-
+
- The model is MachiningMotionResolution. +
- A caption over a select with three choices: Feed Per Cycle, Feed Per Tooth and Fixed. +
- Fixed adds a row of two number fields inline, both floored at 0: Linear Resolution (mm) and +Rotary Resolution (deg), the two values of +FixedMachiningMotionResolution. +
- Switching the type to Fixed carries the outgoing resolution's current linear and rotary values +into the new fixed pair rather than resetting them. +
+ - Enable Collision Detection CheckBox
+
-
+
- The model is EnableCollisionDetection. +
- Default value: true +
+ - Enable Pause On Failure CheckBox
+
-
+
- The model is EnablePauseOnFailure. +
- Default value: false +
+ - Enable Physics CheckBox
+
-
+
- The model is EnablePhysics. +
- Default value: true +
- Disabled unless the advanced-physics licence is held. The engine declares the same gate on the +split Physics command's field, so both editors refuse the same edit for the same reason. +
+
+ - Machining Resolution Number Field
+
- Meshed Geometry Section Node
+
-
+
- The node's own tree checkbox is +EnableReadMeshedGeom; the panel below it holds the +file reference only. The flag decides what runs, not what can be edited, so the field stays +editable whether or not the box is ticked — a read can be prepared before it is switched on. +
- Meshed Geometry File Field
+
-
+
- The model is MeshedGeomFile. +
- Hinted as a
.wctor.stlpath relative to the project folder.
+
+ - Browse Button
+
-
+
- Opens the shared server-side file explorer, filtered to
.wct/.stlwith an All Files +fallback. It opens on the project directory and allows no other root, so a pick always yields +a project-relative path.
+
+ - Opens the shared server-side file explorer, filtered to
+ - Meshed Geometry Settings — an Enable Read Meshed Geometry checkbox over a Geometry File text
+box and a Browse button; the grid beneath the checkbox is disabled until it is ticked. Browse is
+an
OpenFileDialogfiltered to*.wct;*.stl, storing a pick under the project directory as a +relative path and one outside it as an absolute path.
+ - Machining Settings — the Machining Resolution control is an editable ComboBox pre-filled with
+fifteen powers of two, from 0.0009765625 up to 16 in doublings, and a typed value outside that
+list is accepted. Beside the Motion Resolution ComboBox a content presenter holds the fixed
+fields, built at runtime and labelled Linear Resolution (mm) and Angle Resolution (deg) — Angle
+Resolution being this client's wording for the same
RotaryResolution_degthe web labels Rotary +Resolution. The three checkboxes sit loose in the same grid, and the Enable Physics one carries no +licence gate.
+ wwwroot-src/src/components/mission/PreSettingCommandPanel.vue— this editor in both of its +modes: the command node's machining settings, and themeshed-geometrysection's file field with +its Browse button.
+wwwroot-src/src/components/controlTree/missionItemTypes.ts— maps thepresettingkind to this +panel, declares its one Meshed Geometry section child, and turnsEnableReadMeshedGeominto that +node's tree checkbox on read and on write.
+wwwroot-src/src/components/controlTree/useControlTreeHost.ts— which nodes show a checkbox: a +command node ticks its own enable flag, a section node with an enable flag ticks that, and every +other node hides the box. A disabled command dims its whole subtree.
+wwwroot-src/src/components/controlTree/MissionSectionPanel.vue— renders one section of a +command's editor headerless, the breadcrumb already naming it.
+wwwroot-src/src/components/widgets/FileExplorerDialog.vue— the server-side browser Browse +opens, with the project-directory root and the filter list this panel passes it.
+wwwroot-src/src/components/mission/GenericCommandPanel.vue— the editor the four +bespoke-panel-less Setup commands get, built from their declared scalar fields.
+wwwroot-src/src/api/mission.ts—loadPreSettingand the ninepresetting/*PUTs behind it.
+wwwroot-src/src/i18n/en/mission.ts— the panel's wording, including the Rotary Resolution label +and the meshed-geometry hint and filter names.
+wwwroot-src/src/stores/appState.ts— the physics-licence flag the Enable Physics checkbox reads.
+Missions/MissionCommandCatalog.cs— the addable set the Add Command dialog is built from: the +reflected[CommandCatalog]entries, pluspresettingas a read-only kind key that creation +never looks at.
+Missions/MissionController.cs— thecommands/{path}/presetting/*endpoints: +enable-read-meshed-geom, meshed-geom-file, machining-resolution-mm, +machining-motion-resolution-type, fixed-linear-resolution-mm, fixed-rotary-resolution-deg, +enable-collision-detection, enable-pause-on-failure and enable-physics. The type switch is where +the current linear and rotary values are carried into a new fixed resolution.
+HiNc/SessionCommands/PreSettingCommand.cs— the model, its defaults, the orderRunapplies +them onto the session shell, and the expansion into the split Setup commands.
+HiNc/SessionCommands/CommandCatalogAttribute.cs— the attribute the catalog reflects, and the +five categories. A command without it stays loadable from project files but is not offered for +creation.
+HiNc/SessionCommands/CommandFieldAttribute.cs— the scalar-field declaration the generic editor +renders, with its label, unit and physics-licence gate.
+HiNc/SessionCommands/ListCommand.cs— the in-order run that skips disabled entries, and the XML +read path that expands a stored bundle in place.
+HiNc/MachiningProcs/MachiningProject.cs— the same expansion for a bundle stored as the +project's bare root command.
+HiNc/SessionCommands/MachiningResolutionCommand.cs, +HiNc/SessionCommands/MachiningMotionResolutionCommand.cs, +HiNc/SessionCommands/CollisionDetectionCommand.cs, +HiNc/SessionCommands/PauseOnFailureCommand.csandHiNc/SessionCommands/PhysicsCommand.cs— the +five Setup commands the web catalog offers, their defaults and their list titles. The detection, +pause and physics settings each carry the “from this command on” wording.
+HiNc/MachiningProcs/LocalProjectService.cs— the session's machining resolution and the seeding +from the workpiece's initial resolution on project load or assignment.
+- PostExecutionCommand Panel — the other half of the positional pair: these settings apply forward, those outputs cover everything up to there +
- The Other Commands — the five Setup commands this bundle expands into, as a task +
- Mission — the rest of the command panels +
- Head Line
-
-
-
- Object Management Menu Button
-
-
-
- file extension is MillingTool -
- the pointed Editor Panel is Stick Tool Management Panel +
- Mission Root Panel — The branch root: the command list, and how a mission is assembled +
- List Command Panel — The container command; a nested one becomes a sub-tree of the mission +
- PreSetting Command Panel — What is applied to the machine state before the run reaches a program +
- NC Optimization Option Panel — The optimizer's options as a command in the list, so a run can change them mid-mission +
- NcFile Command Panel — Playing a program file from disk +
- NcCode Command Panel — Playing NC text held in the project rather than in a file +
- Script Command Panel — Driving the session from C# script rather than from NC +
- PostExecution Command Panel — What runs once the program above it has finished
- - Title Label -
- - Object Management Menu Button
-
- Stick Tool Management Panel
+
The pages above do not cover every command kind a mission can hold. Three more ship an editor of +their own — Machining Motion Resolution, Record Meshed Geom and Export Meshed Geom — and the rest +fall back to a generic field editor built from the scalars the command declares, so a kind becomes +addable and editable before anyone writes a panel for it. The set on offer is whatever the server's +command catalog returns, which is why this folder cannot be a closed list.
+See Also
-
-
- Cutter Tab
-
-
-
- Cutter Panel -
- - Holder Tab
-
-
-
- Holder Panel -
- - Clamping Tab
-
-
-
- Exposed-Cutter-Height TextField -
- Preserved-Distance-Between-Flute-and-Spindle-Nose TextField -
- - Intelligent Holder Tab -Visible if EnablePhysics is true. -
- Info Tab
-
-
-
- Abstract Note TextField (readonly) -
- Note TextField (editable) -
-
- - Cutter Tab
-
- Build the Stick Tool Panel Layout framework. Since the framework helps to check of the child componenet. -
- Build accessory part of the framework.
-
-
-
- Object Management Menu Button -
- Info Tab -
- Clamping Tab -
- - Build Holder Panel and the related holder type panel. -
- Build Cutter Panel and the related cutter type panel. -
- Mech/ToolHouse/StickToolPanel -
- wwwroot/mech/stick-tool-panel.js -
- Controller/Mech/MechController.cs +
- Execution Page — the run cockpit this branch belongs to
- Primary: PlayerCommand — the mission's command,
+always a
ListCommand.
+ - Supporting:
+
-
+
- MachiningProject +
- ListCommand — the root command and every nested one. +CommandEntryList is the row list, and a run walks it +top-down. +
- EnablingWrapper — one entry: the command plus +IsEnabled. +
- CommandCatalogAttribute and CommandCategory +— what Add Command offers, and the group it is offered under. +
+ - Mission Root Panel
+
-
+
- Head Line
+
-
+
- Add Command Button
+
-
+
- Opens the Add Command dialog. Disabled while no project is open. +
+ - Commands Caption +
- Command Count Badge
+
-
+
- Outlined, and grey rather than primary while the list is empty. +
+
+ - Add Command Button
+
- Separator +
- Empty State
+
-
+
- “No project loaded” while nothing is open; otherwise “No commands yet”, naming Add Command. +
+ - (Each) Command Row
+
-
+
- Drag Handle Icon
+
-
+
- The whole row is draggable; the handle is the affordance for it. +
+ - Command Label
+
-
+
- The command's title as the engine composes it: the kind's localized display name, with a
+title the user typed appended as
Name [title].
+
+ - The command's title as the engine composes it: the kind's localized display name, with a
+title the user typed appended as
- Move Up Button / Move Down Button
+
-
+
- Disabled at the ends of the list. +
+ - Duplicate Button +
- Delete Button +
- A disabled command's row is dimmed, and its buttons stay live. +
- Clicking the row anywhere but on a button selects that command's tree node. +
+ - Drag Handle Icon
+
- Drop-Out Zone
+
-
+
- Present in a nested list editor only, and shown only while a row is being dragged. It sits +below the rows, so the rows do not shift under the pointer at drag start. +
+ - Run-Order Hint
+
-
+
- Commands run top-down, and the checkbox on each command's tree item enables or disables it. +
+
+ - Head Line
+
- Setup — Machining Resolution, Machining Motion Resolution, Collision Detection, Pause on +Failure, Physics +
- Program — Program File, NC Code, Script +
- Optimization — NC Optimization Config +
- Output — Post-Execution, Record Meshed Geometry, Export Meshed Geometry (STL) +
- Flow — List +
- On another row — reorder within this list. A plain row splits at its midline into before and +after, and the whole new order is sent as one ordered path list. +
- On the middle band of a
listrow — move the entry inside that list. That row's outer quarters +still reorder around it.
+ - On the drop-out zone of a nested list editor — move the entry out, landing right after the list +command itself in the owning list. +
- Nine kinds have a bespoke editor: General Config, Machining Motion Resolution, +Program File, NC Code, +Script, NC Optimization Config, +Post-Execution, Record Meshed Geometry and Export Meshed +Geometry. +
listis edited inline by the control-bar panel: the optional title over the embedded +list editor.
+- The four remaining Setup kinds — Machining Resolution, Collision Detection, Pause on Failure and
+Physics — are served by the generic field editor, which renders the command's reflected
+
[CommandField]scalars with server-localized labels. That is what the app does instead of +shipping one panel per kind: a catalog kind needs frontend code only when it wants a richer +editor.
+ wwwroot-src/src/components/controlTree/MissionRootPanel.vue— this panel: Add Command, the entry +rows and their four actions, the three drag landings, and the delete confirmation. It serves the +Mission root and, embedded under a command's control bar, every nestedlistnode.
+wwwroot-src/src/components/controlTree/AddCommandDialog.vue— the search-first catalog picker.
+wwwroot-src/src/components/controlTree/MissionCommandSlavePanel.vue— one entry's panel: the +move / duplicate / delete control bar over the kind's editor, or over the title input and embedded +list editor of alistentry.
+wwwroot-src/src/components/controlTree/MissionSectionPanel.vue— the panel of a command's +section child, rendering that one card.
+wwwroot-src/src/components/controlTree/missionItemTypes.ts— the Mission wave of the Control +Tree: the item types, the child builders that carry the recursion, the per-kind editor map, the +section definitions and their enable flags, and the kind and category icons.
+wwwroot-src/src/components/controlTree/useControlTreeHost.ts— builds the Execution root with +the Mission branch node above the Program branch, and drives the command and section checkboxes.
+wwwroot-src/src/api/mission.ts— typed wrapper over/api/Mission/*:listCommandEntries, +addEntry,removeEntry,moveEntry,duplicateEntry,reparentEntry,reorderEntries, +setListTitle,getCommandCatalog,loadCommandFields/setCommandField, and the per-kind +readers and writers.
+wwwroot-src/src/components/mission/GenericCommandPanel.vue— the fallback editor for a catalog +kind with no bespoke panel: the command's reflected[CommandField]scalars.
+- The bespoke editors:
+
-
+
wwwroot-src/src/components/mission/PreSettingCommandPanel.vue
+wwwroot-src/src/components/mission/MachiningMotionResolutionCommandPanel.vue
+wwwroot-src/src/components/mission/NcFileCommandPanel.vue
+wwwroot-src/src/components/mission/NcCodeCommandPanel.vue
+wwwroot-src/src/components/mission/ScriptCommandPanel.vue
+wwwroot-src/src/components/mission/NcOptOptionCommandPanel.vue
+wwwroot-src/src/components/mission/PostExecutionCommandPanel.vue
+wwwroot-src/src/components/mission/RecordMeshedGeomCommandPanel.vue
+wwwroot-src/src/components/mission/ExportMeshedGeomCommandPanel.vue
+
+ wwwroot-src/src/i18n/en/tree.ts— thetree.mission.*strings this panel renders: Add Command, +the Commands caption, the no-commands-yet state, the drop-out zone, the run-order hint, the kind +display names, the section names and the operation labels.
+wwwroot-src/src/router/routes.ts— resolves/missiononto/execution?tree=execution/mission.
+Missions/MissionController.cs— the entry lifecycle (GET list-command/entries, +POST list-command/entriesandPOST list-command/entries/{path}to add at the root or inside a +nested list,DELETE list-command/entries/{path},POST list-command/entries/{path}/move, +PUT list-command/reorder,POST list-command/entries/{path}/duplicateand +POST list-command/entries/{path}/reparent), theGET command-catalogthe Add Command dialog +reads, and the per-command endpoints including the genericcommands/{path}/fields[/{key}]pair. +reparentis what backs both drag-into-a-list and drop-out-to-the-parent, and it is the endpoint +that rejects moving a list into itself or its own descendants.
+Missions/MissionCommandCatalog.cs— reflects every[CommandCatalog]command into the addable +set served to Add Command, and creates the picked kind.
+Missions/MissionCommandFields.cs— the reflection layer behind the generic field endpoints: it +describes and updates a command's[CommandField]scalars.
+Missions/NcOptOptionEndpoints.cs— the NC Optimization Config per-property PUT endpoints.
+HiNc/SessionCommands/CommandCatalogAttribute.cs—CommandCategoryand the[CommandCatalog]+attribute (category, order, kind key, aliases), plus the class-name-minus-Commandderivation of +the kind key. This is what makes the addable set backend-owned.
+HiNc/SessionCommands/ListCommand.cs— the container command: the entry list a run walks +top-down, skipping disabled entries.
+HiNc/MachiningProcs/MachiningProject.cs— declares the mission's command as a list and keeps it +one when a project is read.
+- Program Branch — the read-only inspection twin of this list: what a +run actually read, one node per NC source file +
- List Command Panel — the same editor one level down, and what moving an entry in or out of a nested list costs +
- Building a Mission — the task this panel serves, as a procedure +
- Head Line
+
-
+
- Script Title Text Field
+
-
+
- The model is ScriptTitle. +
- Labelled “Title (optional)” on the web, “Script Title” on WPF. +
+ - Autosave Indicator (web)
+
-
+
- Shares the title row. See Saving. +
+
+ - Script Title Text Field
+
- Script Editor Area
+
-
+
- The model is ScriptText. +
- Fills the rest of the panel. +
+ - Unsaved Changes, raised when the Control Tree tries to move the selection off a command whose +save is still pending: Save & switch flushes and then allows the switch, Discard drops the pending +write, Cancel keeps the selection where it is. +
- Script changed elsewhere, raised when the server rejects the save because the hash no longer +matches — another tab, or a project reload, changed the command underneath. The rejection carries +the server's current text, title and hash, so the panel can offer Discard & reload, Force +overwrite, or Cancel without a second round trip. +
wwwroot-src/src/components/mission/ScriptCommandPanel.vue— this panel: the title row sharing +its line with the autosave indicator, the editor below it, the 500 ms autosave, the two +three-button prompts, the path snapshot taken at mount, and the change notice that relabels the +tree node.
+wwwroot-src/src/components/widgets/TextEditor.vue— the CodeMirror 6 wrapper: language and +read-only swapped through compartments, CRLF normalised on the way in so an unedited file cannot +dirty the autosave, an opt-in completion source that overrides the language's own, and a widened +monospace popup so full signatures fit before the dim detail column truncates.
+wwwroot-src/src/components/widgets/missionScriptLanguage.ts— themission-scriptmode: the +keyword set, the string and character rules, numbers, comments, call-site tagging, and its own +highlight style.
+wwwroot-src/src/components/mission/csharpCompletionSource.ts— the completion source: fires on +an identifier prefix or an explicit request, sends the whole document with the cursor offset, +drops stale results, and applies every candidate as a snippet.
+wwwroot-src/src/api/scriptCompletion.ts— the typed wrapper over the completion endpoint and the +kind union that maps straight onto CodeMirror's completion types.
+wwwroot-src/src/api/mission.ts— the script command shape (text, title, content hash), its +loader, the save that carries the expected hash, and the conflict error that carries the server's +current copy.
+wwwroot-src/src/composables/useAutoSave.ts— the debounce, the state machine and the conflict +recovery behind the panel's saving, saved and error states.
+wwwroot-src/src/components/widgets/AutoSaveIndicator.vue— the status pill beside the title.
+wwwroot-src/src/components/controlTree/useControlTreeHost.ts— funnels every selection change +through the active panel's switch gate, which is how the Unsaved Changes prompt gets its say.
+wwwroot-src/src/components/controlTree/missionItemTypes.ts— maps thescriptkind to this +panel and gives it no section children, so the whole editor lives on the command node.
+wwwroot-src/src/i18n/en/mission.ts— the panel's wording: both prompts, their buttons, and the +load and save error contexts.
+Missions/ScriptCompletionController.cs—POST /api/script/completions: a thin wrapper that +returns the items, 499 on cancellation and 500 with the message on failure.
+Missions/ScriptCompletionService.cs— the singleton holding one workspace, built from the same +script options the session evaluates with, and the snippet insert text it builds for methods.
+Missions/ScriptCompileCheckService.cs— compiles a script without running it, and walks the +mission for every enabled one; it backs the two compile-check endpoints and the pre-start gate.
+Missions/MissionController.cs— the single script PUT with its hash-based concurrency, the +content hash stamped into the command snapshot, and the two compile-check endpoints.
+Execution/ExecutionController.cs— the start gate that compile-checks the enabled scripts and +refuses to run while one of them has an error.
+HiNc/SessionCommands/ScriptCommand.cs— the model: the title and text, the Program-category +catalog registration, the XML round-trip that puts the script into the project file, the +evaluation against the session shell with a compile failure reported into the run's message +stream, the returned actions yielded into the run, and the label rule that brackets the title +after the command name.
+- NcCodeCommand Panel — the other command that stores its text in the project, and the one that ships no editor chrome +
- The Other Commands — the task this command serves, beside the settings and output kinds +
- Mission — the rest of the command panels +
- Mission's nodes edit the project. Program's item types declare a panel and a child builder and +nothing else — no enable checkbox (the tree hides the tick on every node that is not a Mission +command or an enable-bearing Mission section), no control bar, no add, delete, move or duplicate. +
- Mission's shape is authored. Program's shape is a consequence: a subprogram file appears on it +only because a run followed a call into it. +
- Mission belongs to the project. Program's contents belong to the session — the index behind it is +fed piece by piece as the run executes, and it is dropped when the session ends or the project +changes. +
- the call term with its P word —
M98 P8,M198 P8— when the piece carries a Fanuc +subprogram-call record. Only that record is read, so a pass opened by a Siemens or Heidenhain +call, or by a Fanuc macro call, falls through to the two cases below;
+ re-entrywhen this session has already seen a pass over the same path;
+topotherwise.
+- Page initialization. Building the Execution root builds the branch, and only when a project is +loaded; without one it stays empty. +
- Any change in the broadcast execution status. The status the hub reports is one of
+
NoProject,Ready,Running,PausedandFinished, and the host re-reads the branch +whenever that value changes. It deliberately does not take the structural-change path used +elsewhere in the tree, because that path force-expands the node it rebuilds and a run would then +keep re-opening a branch the reader had closed. The panels pick up part of the change from +the replaced nodes: the path caption, the pass selector, the inline wording and the executed-line +count are all derived from the node, but the file text is not — it is fetched once per page on +mount and on scroll, with no watcher on the path, and the panel is not remounted because its key +is built from the node id, which is positional and therefore unchanged. A rebuild that puts a +different file at the same index leaves the previous file's text on screen under the new node's +caption and marks.
+ - The refresh button on the root panel. This one does take the structural-change path: it +re-reads the file tree from the current session and expands the branch. +
- Program Node
+
-
+
- Intro Caption +
- Status Row
+
-
+
- Run-State Badge — reads
run datawhen any of the branch root's direct file children holds at +least one invocation andnot run yetotherwise; like the count beside it, it looks one level +deep and never at nested subprogram nodes. It is computed from the nodes, not from the +file-tree response's own run-data flag.
+ - File Count Caption — the branch root's direct file children only; nested subprogram nodes are +not counted. +
- Converted File Count Caption — shown only when the session holds conversions. +
- Refresh Button +
+ - Run-State Badge — reads
- Not-Run Hint — shown while the badge reads
not run yet.
+ - Conversions Hint — shown only when the session holds conversions. +
+ - Program File Node
+
-
+
- Path Caption — the path as the run stamped it; an inline node appends an inline-NC-code tag. +
- Pass Select +
- Follow Toggle +
- Not-Run Hint — shown while the node has no invocations. +
- Missing-Source Caption — the disk-file wording or the inline-text wording. +
- Line Viewer
+
-
+
- Line Number +
- Line Text +
- Step Badge +
- Converted-Line Badge +
+ - Count Footer — line count, plus the selected pass's executed-line count. +
+ - Converted File Node
+
-
+
- Path Caption +
- Converted-From Line — the source file's name, linked to its node when a root file node matched. +
- Line Viewer
+
-
+
- Line Number +
- Line Text +
- Source-Line Badge +
+ - Count Footer — line count and converted-piece count. +
+ wwwroot-src/src/components/controlTree/programItemTypes.ts— the branch's three item types, the +node ids, the labels, the conversion stamping, and the two-request child builder.
+wwwroot-src/src/components/controlTree/ProgramRootPanel.vue— the branch root: intro, run-state +badge, file and conversion counts, hints and the refresh button.
+wwwroot-src/src/components/controlTree/ProgramFilePanel.vue— one source file: the pass +selector, the Follow toggle, the paged line viewer, the execution marks, the step and +converted-line badges, and the cursor and hover wiring.
+wwwroot-src/src/components/controlTree/ProgramConversionFilePanel.vue— one written file: the +paged line viewer and the source-line links back into the file panel.
+wwwroot-src/src/components/controlTree/useControlTreeHost.ts— declares the branch root under +the Execution root, re-reads it on every execution-status change without expanding it, and hides +the tick on every node that is not a Mission command or an enable-bearing Mission section.
+wwwroot-src/src/components/controlTree/itemTypes.ts— the node shape carrying the program +bookkeeping, the merged item-type registry, and the eager subtree builder.
+wwwroot-src/src/components/controlTree/ControlTreePanel.vue— the tree pane that renders the +branch's nodes and binds the tree's strict tick strategy.
+wwwroot-src/src/components/controlTree/PrimarySlavePanel.vue— mounts the selected node's panel, +forwards its selection request to the host, and pins the transport bar above every Execution-scope +node.
+wwwroot-src/src/api/ncProgram.ts— the typed client for the file tree, the paged lines, the line +marks, the syntax piece, the sentence-to-step mapping and both directions of conversion links.
+wwwroot-src/src/api/clStrip.ts— the step-select and step-enter posts a line click and a line +hover make.
+wwwroot-src/src/composables/useConversionJump.ts— the single parked-jump slot the two viewers +hand a target through.
+wwwroot-src/src/composables/useSentenceCursor.ts— the shared source position, written by a line +click and by the resolved step selection.
+wwwroot-src/src/composables/useClStripHub.ts— the throttled run-advance counter that +invalidates the visible marks.
+wwwroot-src/src/composables/useExecutionStatusHub.ts— the status the branch rebuild watches and +the running cursor the Follow toggle scrolls to.
+wwwroot-src/src/components/execution/SyntaxPiecePanel.vue— the Step Info column's Sentence +Syntax panel, the other reader of the shared cursor.
+wwwroot-src/src/i18n/en/tree.ts— the branch's labels, hints, badges and pluralised counts.
+Execution/NcProgramController.cs— the read-only endpoints: the file tree, the paged file lines +with their inline-command fallback and whole-file cache, the line marks, the syntax piece, the +sentence and step lookups, the conversion list and both directions of conversion links.
+Execution/NcProgramRegistryService.cs— the session-scoped index: the event feed, the invocation +records with their trigger and caller resolution, the file-tree snapshot with its mission +placeholders, the per-line marks, and the clear on reset or project change.
+HiNc/MachiningProcs/LocalProjectService.cs— the app-lifetime bridge that forwards each executed +piece and each built machining step, and the reset that ends the session.
+HiMech/MachiningProcs/MachiningSession.cs— the retained conversions and the CL-to-NC writeback +run that refills them.
+HiMech/NcParsers/NcWriteback/NcConversion.cs— one conversion: the destination piece stream, its +written path and the source-to-destination map the cross-links walk.
+HiMech/NcParsers/Syntaxs/SyntaxPiece.cs— the executed unit the index is keyed on, and the +source line stamped on its sentence.
+HiNc/SessionCommands/NcFileCommand.cs— the Program File command whose path seeds a placeholder.
+HiNc/SessionCommands/NcCodeCommand.cs— the NC Code command whose title is the pseudo-path of an +inline node and whose text is that node's only source.
+- Mission Root Panel — the editable twin: the command list this branch reports on, and +the only place the NC file list is changed +
- Execution Page — the page that hosts both branches, and the transport, canvas and charts +the branch's selections drive +
- Play/SelectedStepInfoPanel +
wwwroot-src/src/components/execution/SelectedStepInfoPanel.vue— the panel.
+wwwroot-src/src/components/execution/StepInfoGroupBar.vue— the group bar above it, shared by the step-based panels of the column.
+Execution/ExecutionController.cs—GET /api/Execution/selected-step-info.
+Execution/SelectedStepInfoService.cs— the payload.
+Execution/ClStripBroadcastService.cs— theStepSelectedbroadcast that tells the panel to re-pull.- wwwroot/player/selected-step-info-panel.js (Vue component) -
- wwwroot/player/selected-step-info-panel.css (Styles) -
- Players/PlayerController.cs (REST API - GetSelectedStepInfo endpoint) -
- Players/SelectedStepInfoService.cs (Business logic) -
- Players/SelectedStepInfoHub.cs (SignalR Hub for real-time updates) +
- Execution Page — the page whose Step Info column hosts this panel +
- Step Present Preference Page — chooses which properties are listed +
- Inspecting a Step — the task this panel serves, and the rest of the column around it
- Header
+
-
+
- Title, then the counts — n displayed / n available +
- The save-state line (Saving… / Saved / error), which stands in for a Save button +
Resetand close
+
+ - Splitter
+
-
+
- Candidate Keys Panel (left)
+
-
+
- One accordion per category, in the order the server sends them
+
-
+
- A tri-state checkbox on the category header +
- One checkbox per key, with the key's label and unit +
+ Add Selected
+
+ - One accordion per category, in the order the server sends them
+
- Displayed Keys Panel (right)
+
-
+
- One row per displayed key, in
UserConfig.DisplayedStepPresentKeyListorder, drag-reorderable +-
+
- Per-row up / down / remove +
+ Clear
+
+ - One row per displayed key, in
+ - Candidate Keys Panel (left)
+
fileCmdFlagTimeSystem— “File / Command / Flag / Time / System” (StepIndex,FileNo,LineNo,FilePath,LineText,FlagsText,EndTimecode,StepDuration).
+toolFeedrateSpindle— “Tool / Feedrate / Spindle Speed” (ToolId,SpindleSpeed_rpm,Feedrate_mmdmin,CuttingSpeed_mmds, …).
+coordinateMove— “Coordinate / Move” (Cl,MoveOnProgramCoordinate,MovingLength_mm, and everyMC.key).
+cuttingGeometryChip— “Cutting Geometry / Chip / Bias / Roughness” (CuttingDepth_mm,ChipThickness_um,Mrr_mm3ds, …).
+mechanicsPowerEnergy— “Mechanics / Power / Energy” (MaxAbsForce_N,SpindleOutputPower_W,AvgAbsTorque_Nm, …).
+temperatureWear— “Temperature / Wear” (ChipTemperature_C,AccumulatedFlankWearWidth_um, …).
+custom— “Custom”, the catch-all for every key the mapping does not place, including the keys registered at run time throughUserService.AdditionalStepPresentAccess.
+- Selected-Step Info Panel — the panel whose property list this page configures +
- Preference Menu Dropdown — the dropdown that hosts this entry in the WPF client +
- Inspecting a Step — the task this dialog serves, as a procedure +
wwwroot-src/src/components/preference/StepPresentPreferenceDialog.vue— the dialog itself: a Candidate Keys / Displayed Keys splitter, category accordions keyed on the stable category code, the per-category tri-state checkbox,Add Selected, drag-reorder plus up / down / remove andClear, the header counts,Resetand close, and the auto-save state. Refetches on every open and on a locale change.
+wwwroot-src/src/components/execution/SelectedStepInfoPanel.vue— what opens it: thetuneicon button teleported into the panel's title bar (the hosting expansion header, or the panel's own bar when standalone). Also the list the dialog configures, with theStepIndexrow filtered out.
+wwwroot-src/src/components/execution/StepInfoGroupBar.vue— the Step Info column's group bar, which carries the step-index badge instead.
+wwwroot-src/src/pages/ExecutionPage.vue— the only surface the dialog is reachable from.
+wwwroot-src/src/api/preference.ts—getStepPresentKeys(language-pinned) /setStepPresentKeys/resetStepPresentKeys, and theStepPresentKeyInfo/StepPresentCategory/StepPresentKeysSnapshottypes.
+Environments/PreferenceController.cs—GET/POST/DELETE /api/preference/step-present-keys, the seven-entry category table, theResolveStepPresentCategorymapping, and persistence ofDisplayedStepPresentKeyListthroughSaveUserConfig().
+Environments/PresentCatalogService.cs— server-side localization ofnameandshortNamefrom the shipped step-present catalog, with the live PresentAttribute data as the English base and fallback.
+Environments/UserService.cs—StepPresentAccessDictionaryandCandidateStepPresentKeyList, the candidate-key model.
+Environments/UserConfig.cs—DisplayedStepPresentKeyList, the ordered displayed-key model.
+- Group Bar
+
-
+
- Range chip —
[{dispBegin}..{absDispEnd}] / {count}, withShowing steps {begin}..{end} of {count} totalas its tool-tip.
+ - Cursor x readout — the hovered x value, shared by all three charts and reserving a fixed width so hovering never reflows the bar. It is the one place the hovered value is printed; the charts themselves carry no per-chart cursor row. +
- X-axis mode —
IndexByTime(default) /IndexByStep, applied to every strip chart at once.
+ - Fit View — fits the 3D canvas to the tool path / home position. Disabled without a rendering connection id, and the only control here that acts on something other than the charts. +
- Stick disp-end to live end — keeps the window pinned to the running end of the mission. The state is
dispEnd === -1, so a pan or a wheel turns it off by writing a concrete end, and Reset re-arms it.
+ - Reset display range — returns the window to the whole mission. +
- Reload all strip charts. +
+ - Range chip —
- Each chart panel
+
-
+
- Header — title label; the aspect picker on the Color Index chart, a dropdown listing every
StepPropertyAccessDictionarykey with aGetQuantityFunc, sorted byPresentAttribute.Nameand filtered by a type-to-search field; the Y-axis range editor (Fit / Lock / Symmetric with a ± bound); and, on the Color Index chart only, aColorsdropdown editing the colour guide's floor / ceiling / tone function. That editor drives the 3D workpiece colouring and the colour-scale bar, not the chart's own line.
+ - Body —
UplotChart.vuerendering one min/max band pair per item (two stroked series of the same colour plus a filled band at 15% opacity), with uPlot's own legend panel in a splitter pane beside the plot. The legend carries the swatches, labels and live values; its width is shared by all three charts, so dragging one divider moves them together.
+ - Empty-state overlay — in order: “No project loaded”; “Pick a property to inspect” (Color Index with no key chosen); “No mission data” when the fetch returned none; “No physics data” when every item's min / max arrays are all NaN, which is what Availability looks like without a physics licence. +
+ - Header — title label; the aspect picker on the Color Index chart, a dropdown listing every
- Windowed fetch. Only the
ClStrip.GetDispBegin() .. AbsDispEndwindow is fetched, at a bucket count matching the chart's pixel width. The server-sideClStrip.GetMinMaxList(threshold, itemCount)handles down-sampling.
+ - Pointer interactions — every one of them writes the shared
ClStrip, so a gesture on one chart moves all three: +-
+
wheelover the plot area →POST /api/execution/cl-strip/wheelwithscale = Math.pow(1.05, deltaY * 1/166)+xPositionPercentage.
+- Right / middle-button drag →
POST /api/execution/cl-strip/panwith accumulatedxOffsetPercentage. Frame-batched viarequestAnimationFrame. Pen drags also pan without a button modifier.
+ - Left-button drag → a rubber-band x-range zoom: a translucent band tracks the drag over the plot area and the release POSTs the swept fractions to
/api/execution/cl-strip/zoom-range. A drag under 6 px, or under half a percent of the plot width, is discarded so the trailing click selects a step instead — which is what makes click-to-select survive an unsteady hand. The gesture is mouse-only.
+ - Left click →
POST /api/execution/cl-strip/select-stepwith the nearest bucket's original step index (viachart.valToPosreverse lookup). A click that ended a pan or a zoom drag is ignored.
+ - Touch → one finger pans, a second finger converts the pan into an x-axis pinch-zoom; the trailing synthetic click is swallowed so panning past a step never selects it. +
- Mouse move (debounced 80 ms) →
POST /api/execution/cl-strip/enter-stepfor hover, which is also what fills the group bar's cursor readout.
+
+ - Live refresh.
useClStripHubsubscribes toDispRangeChanged(re-fetch when anyone else zooms / pans) andUpdated(coalesced re-broadcast while a mission is running). A rising edge on the execution status hub'shasProjectalso triggers a refetch.
+ - Client-side debounce.
BaseStripChart.load()is debounced 50 ms on the client so a burst ofUpdatedevents during a fast-running mission coalesces into one fetch.
+ wwwroot-src/src/components/execution/charts/StripChartGroupBar.vue— the group bar over the three charts.
+wwwroot-src/src/components/execution/charts/BaseStripChart.vue— shared strip-chart skeleton.
+wwwroot-src/src/components/execution/charts/UplotChart.vue— uplot wrapper.
+wwwroot-src/src/components/execution/charts/PluralStripAvailabilityChart.vue— availability chart.
+wwwroot-src/src/components/execution/charts/PluralStripRoughnessChart.vue— surface-roughness chart.
+wwwroot-src/src/components/execution/charts/StripIndividualChart.vue— aspect-picker individual chart.
+wwwroot-src/src/api/clStrip.ts— typed REST wrappers for the range calls.
+wwwroot-src/src/composables/useClStripHub.ts— singleton hub wrapper (the same ref-counting pattern as the other execution hubs).
+Execution/ExecutionChartsController.cs— strip-chart data: +-
+
GET /api/execution/strip-chart?aspect=Availability|SurfaceRoughness|Individual&xValueCategory=IndexByTime|IndexByStep&widthHint=…&dispBegin=…&dispEnd=…&inspectingKey=…→{ xs, indexes, items:[{ key, label, unit, min, max }], isXTicksRangeMode, dispBegin, absDispEnd, count }. The Color Index chart's picked key rides theinspectingKeyquery parameter of that same call.
+GET /api/execution/strip-chart-item-configandGET /api/execution/color-guide— the per-item display config and the colour legend.
+
+Execution/ClStripController.cs— range manipulation at/api/execution/cl-strip/*(the server-side counterparts of the pointer handlers above): +-
+
GET range— snapshot{ dispBegin, absDispEnd, count, selectedIndex, enteredIndex }.
+POST disp-rangebody{ dispBegin, dispEnd }, andPOST zoom-range.
+POST wheelbody{ scale, xPositionPercentage, xValueCategory }.
+POST panbody{ xOffsetPercentage, xValueCategory }. LegacyfeelingRatio=2.
+POST select-stepbody{ stepIndex }→ClStrip.SetSelectedPos(...).
+POST enter-stepbody{ stepIndex }(nullable) →ClStrip.SetEnteredPos(...).
+
+Execution/ClStripHub.cs+Execution/ClStripBroadcastService.cs— SignalR at/clStripHub. BroadcastsDispRangeChanged(snapshot),StepSelected({ stepIndex }),StepEntered({ stepIndex }),Updated(snapshot). A single-flightInterlockedmutex coalesces re-broadcasts; the client debounce handles the rest of the rate-limiting.
+- Execution Page — the page whose Strip Charts column hosts these charts +
- Cycle-Line Charts — per-selected-step charts that share the same
uplotengine
+ - Execution Extended RenderingCanvas Tool Bar — the tool-path display this column's Fit View acts on +
- Watching the Run — the task this column serves, with the pointer contract read as a procedure +
- BackgroundTemperature_C (Background +leaf) +
- the whole CoolantHeatCondition (Coolant leaf) — file-first, see below +
- Background leaf —
equipment/background, item typeThermalCondition+-
+
- Background Temperature NumberField (°C) — +BackgroundTemperature_C. +
+ - Coolant leaf —
equipment/coolant, the same item type and the same panel (file pick on top, +properties below, as on the workpiece material leaf) +-
+
- “Not attached” badge — visible until the first coolant-related save (see Lazy +CoolantHeatCondition creation below). +
- File row —
FilePathInput(.CoolantHeatCondition, resource subfolder +CoolantHeatCondition): a Select dropdown button whose menu holds Browse…, +Browse Resource… and — only while a file is set — Clear, beside a read-only path +input; it loads a saved condition by named root. A Save As… button follows it, prompting +for a project-relative file to export the current condition to. Tracked in +CoolantHeatConditionFile. The three +standard cooling types ship as ready-made files inHiNc-Resource+(Resource/CoolantHeatCondition/StandardForcedAir.default.CoolantHeatCondition, +StandardOilBasedCoolant.default.CoolantHeatConditionand +StandardWaterSolubleCoolant.default.CoolantHeatCondition), so Browse Resource… starts +populated like the workpiece-material picker — picking a shipped file is the cooling-type +selection.
+ - Name / Note — read-only mirror of the loaded condition. +
- Property fields (always visible, under Name / Note):
+
-
+
- Coolant Temperature (°C) —
CoolantTemperature_C.
+ - Flood Convection Coefficient (W/(m²·K)) —
CoolantConvectionCoefficient_Wdm2K,min: 0.
+ - Mist / Flood Ratio —
MistFloodConvectionRatio,min: 0.
+ - Off (Air) Convection Coefficient (W/(m²·K)) —
OffConvectionCoefficient_Wdm2K,min: 0.
+
+ - Coolant Temperature (°C) —
+ - Both leaves, with no project open: every control is disabled and a “no project — open one first”
+caption is shown. The panel fetches the snapshot itself on mount and gates on its
hasEquipment+flag, so the tree host holds no state for either leaf.
+ - File-based conditions (WorkpieceMaterial pattern). Picking a
.CoolantHeatConditionfile +installs it as the whole condition (XFactory.GenByFile); Save As… exports the current +values and renames the condition after the file (IPreferredFileName). While +CoolantHeatConditionFile is set, the +project save externalizes the condition as +<CoolantHeatCondition><XmlSource>relPath</XmlSource></CoolantHeatCondition>and regenerates the +side-file (a resource pick is copied into the project on save); with no file the bare inline shape +is kept byte-compatible. Coefficient edits tune this project's copy. The static presets on +CoolantHeatCondition (StandardPresets/ApplyPreset/ +MatchStandardPreset) are the API-side source the shipped files are authored from.
+ - The file row's Clear entry does nothing here.
FilePathInputreports a clear by emitting +update:modelValue(null), and this panel binds the widget's value one-way and listens only for +picked— so the entry is rendered, is clickable while a file is set, and drops nothing. Clearing +the tracked file is therefore not reachable from this leaf at all; the reference changes only by +loading another file or by a Save As.
+ - The
.defaultmarker never survives a user save. The shipped resource files carry a +.defaultownership marker in their names. The Save As… prompt strips it from its prefill, and +the controller strips it again from the name it stamps on the condition, so a user save cannot +mint a file that masquerades as a shipped default.
+ - The “not attached” badge is unreachable, and the lazy creation behind it never fires.
+SetupEquipment initializes
+CoolantHeatCondition at its declaration
+and nothing assigns null to it — the project-XML read replaces the instance only when the element
+is present — so the controller's
hasCoolant, which is a null test on that property, is true +whenever a project is open. The badge is therefore rendered only in the no-equipment case the +panel already covers with its own caption, and the null branch each scalar PUT carries is +defensive rather than reached. What a fresh project actually carries is the class defaults, which +are the water-soluble preset's values.
+ - Celsius is the wire format. CoolantHeatCondition stores Kelvin internally
+but exposes
_Caccessors that handle the conversion, and the DTO carries Celsius.
+ - Finite values only. Every field handler drops a null or non-finite entry before any request is
+made, so
Infinitynever reaches these endpoints even though the numeric widget can parse it. +Background and coolant temperatures accept negatives (e.g. -40 °C for cryogenic coolant); the +three convection fields are clamped tomin: 0.
+ - The run sees it at once. The background-temperature write, all four condition scalars and the +file load each end in the project service's ForwardSetupEnvironmentToExecution, which stamps the +background temperature and the condition reference onto the runtime equipment face — including +the lazily created condition, which otherwise would exist only on the authored side. +
wwwroot-src/src/components/controlTree/ThermalConditionPanel.vue— the one panel both leaves +share, branching on the node's last role-path segment: Background renders the single temperature +field, Coolant the not-attached badge, the file row, the read-only Name / Note and the four +property fields.
+wwwroot-src/src/components/controlTree/useControlTreeHost.ts— declares the two +ThermalConditionleaves (equipment/background,equipment/coolant) under the General Setup +group, with no host-level init state.
+wwwroot-src/src/components/controlTree/itemTypes.ts— registers theThermalConditionitem type +against that panel: no child tree, no geometry-cache chain.
+wwwroot-src/src/components/widgets/FilePathInput.vue— the file row widget: Browse… / +Browse Resource… (opening at theCoolantHeatConditionresource subfolder) / Clear.
+wwwroot-src/src/components/widgets/NumericInput.vue— the numeric field used by every value on +both leaves, and the source of themin: 0clamp on the three convection fields.
+wwwroot-src/src/api/backgroundCoolant.ts— typed client for/api/mech/background-coolant/*: +the flat snapshot, the load / save-as pair, and the five scalar PUTs.
+wwwroot-src/src/router/routes.ts— carries theequipment/background-coolantredirect, which +lands ongeneral-setup?tree=equipment/background.
+Mech/BackgroundCoolantController.cs— REST surface at/api/mech/background-coolantover the +authored equipment face: +-
+
GET /— flat snapshot{ hasEquipment, backgroundTemperature_C, hasCoolant, coolantTemperature_C, coolantConvectionCoefficient_Wdm2K, mistFloodConvectionRatio, offConvectionCoefficient_Wdm2K, coolantName, coolantNote, coolantHeatConditionFile }.
+POST /load,POST /save-as—{ rootName, relFile }: install a.CoolantHeatConditionfile +/ export the current condition. Both resolve the path under the named root and refuse anything +that escapes it, and both record the result in +CoolantHeatConditionFile as a +project-relative path when it lands under the project. Save-as appends the extension when it is +missing and renames the condition after the file (IPreferredFileName) with the.default+marker stripped.
+PUT /background-temperature-c— set +BackgroundTemperature_C.
+PUT /coolant-temperature-c,PUT /coolant-convection-coefficient-wdm2k, +PUT /mist-flood-convection-ratio,PUT /off-convection-coefficient-wdm2k— the four condition +scalars, all through one helper that lazy-creates the condition when absent, writes it, and +forwards to the runtime face.
+
+HiMech/Physics/CoolantHeatCondition.cs— the model: Kelvin storage with_Caccessors, the +flood / mist-ratio / off convection coefficients, Name and Note withPreferredFileName, and the +StandardPresets/ApplyPreset/MatchStandardPresetstatics behind the shipped resource +files.
+HiMech/Machining/MachiningEquipmentUtils/SetupEquipment.cs— the authored equipment face that +owns the background temperature, the condition and its file reference, and externalizes the +condition to the side-file when that reference is set.
+- Spindle Capability — sibling equipment editor, editing the same authored equipment face. +
- Coolant (manual) — the end-user task: picking a cooling type in the application. +
- Coolant Model — what the values on this panel mean, which one a running program applies, and how the condition is stored. +
hasProject— a null check on the loaded project rather than a dependency probe. It is the only +field the early-return snapshot sets when no runner resolves. The panels gate on the runner flag +instead, and the shared state watches the project store directly.
+hasCutterComp— would report a cutter-comp startup type on the Fanuc and Syntec tables.
+hasToolAxisDirection— would report a tool-axis-direction parameter on the Heidenhain table.
+hasIterationGuards— would report the iteration guards.
+nativeIdPrefix—#on Fanuc and Mazak,Pron Syntec,MDon Siemens,MPon Heidenhain.
+- The subprogram-folder flag is annotated as a Fanuc-family trait. All five presets carry the
+folder config: the Siemens entry is the
L-call lookup root and the Heidenhain entry the +CALL PGMlookup root, each with its own comment saying so. The node grows on every brand.
+ - The iteration-guard flag is annotated as the Fanuc GOTO / WHILE-DO guards. The probe also matches +the Siemens jump guard, and matches neither +HeidenhainGotoIterationDependency — which the +Heidenhain preset does carry — nor the Fanuc WHILE-DO guard. The flag would therefore misreport +Heidenhain even if something read it. +
- The flags are computed in the web service. The snapshot builder in
+
Mech/SoftNcRunnerController.csholds one probe per flag. Changing a probe's type, or +reordering the first-of-type lookups it depends on, moves a whole row of this table.
+ - What each brand carries is written in the engine. The five brand presets in
+
HiMech/NcParsers/SoftNcRunner.csare literal dependency lists. Adding or removing one entry +changes a brand column here, with no compile error and no failing test to mark it.
+ - Control Tree — the General Setup root
+
-
+
- Controller Node Row — the branch root; its editor carries the Object-Management ⋮ button,
+the brand badge and the runner-file caption, or the hourglass no-runner block
+
-
+
- Machine / Controller Group Stem — intro line plus a clickable list of its children
+
-
+
- Controller Brand Node Row +
- Machine Limits (Stroke) Node Row +
- Rapid Feedrates Node Row +
- Home / G28 Reference Node Row +
- Tool-Change Position Node Row +
- Controller Parameters Node Row +
- M-Code Declarations Node Row — gated +
- Canned Cycle (Peck) Node Row — gated +
- Block Skip / Delete Node Row — gated; absent on Heidenhain +
- Subprogram Folders Node Row — gated +
- Indexing Position Tables Node Row — gated; Siemens only +
- Parameters (Native) Node Row — gated +
+ - Program Data Group Stem — intro line plus a clickable list of its children
+
-
+
- Work Coordinates (G54…) Node Row +
- Tool Offsets Node Row — reads Tool Offsets (ISO G43 H) where the Siemens
$TC_DP+table resolves
+ - Tool Offsets ($TC_DP) Node Row — gated; Siemens only +
- Tool Names Node Row — gated; Siemens only +
- Datum Presets (Q339) Node Row — gated; Heidenhain only +
- Datum Shifts (D) Node Row — gated; Heidenhain only +
- Frames (Siemens) Node Row — gated; Siemens only +
- Retained Common Variables Node Row — gated; absent on Siemens and Heidenhain +
- R Parameters Node Row — gated; Siemens only +
+
+ - Machine / Controller Group Stem — intro line plus a clickable list of its children
+
+ - Controller Node Row — the branch root; its editor carries the Object-Management ⋮ button,
+the brand badge and the runner-file caption, or the hourglass no-runner block
+
wwwroot-src/src/components/controlTree/softNcItemTypes.ts— the branch builder: it mints every +node id in the table above, holds each leaf's gate, carries the tool-offsets relabel, and returns +an empty child list both when no runner resolves and when the snapshot request throws.
+wwwroot-src/src/components/controlTree/useControlTreeHost.ts— declares the +equipment/controllerroot that prefixes every id in the table, and rebuilds the branch when a +panel reports a structural change scoped to it.
+wwwroot-src/src/api/softNcRunner.ts— the snapshot type, its empty value and its parser; the +five fields with no reader are declared, defaulted and parsed here.
+wwwroot-src/src/composables/useSoftNcRunner.ts— the shared snapshot the leaf panels gate on, +the project watch that empties it, and the brand switch and Object-Management install that re-read +it.
+wwwroot-src/src/components/controlTree/SoftNcRunnerRootPanel.vue— the branch root's editor and +the no-runner block the branch collapses to.
+wwwroot-src/src/components/controlTree/SoftNcEmptyState.vue— the shared no-runner body every +leaf panel falls back to.
+wwwroot-src/src/components/controlTree/SoftNcWorkCoordinatesPanel.vue— the one consumer of the +snapshot's work-coordinate kind.
+wwwroot-src/src/components/controlTree/SoftNcControllerParamsPanel.vue— gates the cutter-comp, +tool-axis and iteration-guard controls on its own read rather than on the snapshot flags.
+wwwroot-src/src/components/controlTree/SoftNcNativeParamsPanel.vue— renders the native id +prefix from its own read.
+wwwroot-src/src/i18n/en/softNc.ts— the node label strings the tree renders, the two group +intros, and the empty-state bodies.
+Mech/SoftNcRunnerController.cs— builds the snapshot: one probe per flag over the +proxy-resolved dependency list, the early-return form used when no runner resolves, and the brand +switch that swaps the whole runner and sweeps the orphaned per-case tables.
+HiMech/NcParsers/SoftNcRunner.cs— the five brand presets as literal dependency lists, and the +proxy resolution every flag probe reads through.
+HiMech/NcParsers/ControllerPresetWriter.cs— the brand token list and the factory that turns one +token into a preset runner, for the shipped preset files.
+HiMech/NcParsers/Dependencys/ControllerParameterTableBase.cs— the machine-config interfaces +every brand table inherits, which is why the machine plane's ungated leaves have data on all five +brands.
+HiMech/NcParsers/Dependencys/CncBrandDependency.cs— the brand marker the snapshot reports, and +the five brand tokens.
+HiMech/NcParsers/Dependencys/Generic/GenericBlockSkipConfig.cs— the sole implementer of the +block-skip interface, and therefore the whole reason one node is missing on one brand.
+HiMech/NcParsers/Dependencys/Generic/FallbackConfig.cs— the peck-retraction provider that makes +the canned-cycle gate true on the two brands whose own table has none.
+HiMech/NcParsers/Dependencys/Generic/SubProgramFolderConfig.cs— the folder config every preset +carries, against the snapshot comment that calls it a Fanuc-family trait.
+HiMech/NcParsers/Dependencys/Fanuc/FanucParameterTable.cs, +HiMech/NcParsers/Dependencys/Syntec/SyntecParameterTable.cs, +HiMech/NcParsers/Dependencys/Siemens/SiemensMachineDataTable.cs, +HiMech/NcParsers/Dependencys/Heidenhain/HeidenhainParameterTable.cs— the four brand tables and +the extra interfaces that decide the canned-cycle, indexing and work-coordinate answers.
+HiMech/NcParsers/Dependencys/Fanuc/FanucParameterTableProxy.cs— the per-case resolution the +Fanuc and Mazak presets share, which is why those two survive each other's brand switch.
+- Controller Branch — the branch this table indexes, its two planes and the runner behind them +
- Editing Contract — the rules every leaf panel in this table obeys when it reads, commits and rolls back +
- Machine and Controller Plane — the machine plane, whose leaves make up most of this table +
- Program Data Plane — the program-data plane, whose per-case leaves are the rest of this table's rows +
- Controller Brand — the control that changes which rows of this table exist, and what it resets on the way +
- Per-Axis Tables — the four ungated per-axis leaves: stroke limits, rapid rates, home reference and the tool-change position +
- Interface Parameters — the interface parameter form and the canned-cycle leaf, two of the rows above +
- M-Code Declarations — the M-code declaration leaf, gated by the native-table flag +
- Program Reading — the block-skip and subprogram leaves, and the one gate that switches a node off for exactly one brand +
- Indexing Position Tables — the one row that appears on a single brand only +
- Native Parameters — the native parameter form behind the id prefix this page reports as unread +
- Hidden Controller Branches — the other kind of withheld node: the two equipment branches a device preference hides, which a link can reveal where none of these can +
- The write is row-scoped even though the handler is per cell. Editing one cell of a work +coordinate, a datum row, a frame or a tool-offset row sends the whole row or the whole XYZ triad +as it stands after the edit. An M-code edit likewise sends all four declaration fields, which is +why the wire format is a whole-declaration replace rather than a patch. +
- The rollback restores what the handler captured, which is usually narrower than what it sent. +A work-coordinate, datum, frame or tool-offset handler captures the one cell it edited and puts +back only that, though the request carried the whole row. Three handlers capture the whole set +they send and restore it intact — the M-code declaration row, the tool-change Stays put pair, +and the two subprogram folders. Either way, a failure the server had partly applied leaves the two +sides disagreeing; the panel's recovery is the toast and a later remount, not a re-read. +
- A project-to-project load does not refresh it. The has-a-project flag is derived from the +project path being non-empty, and loading another project assigns the new path directly, so the +flag never leaves true and the watch never fires. The page itself is destroyed and rebuilt, because +the layout keys its keep-alive on a project epoch — but the rebuilt panels call the same +now-idempotent installer, which does nothing. Meanwhile the branch builder makes its own, +independent request for the same snapshot, so the tree shape can be built from the new project +while every leaf gates on the old one. +
- Nothing pushes changes at a mounted panel. No panel holds a watcher, a poll or a status-hub +subscription over its table. A write made by another browser tab, by an NC run, or by the server's +own sweep after a brand switch is invisible until the panel is remounted by moving the selection +away and back. +
- A panel's own edits do not update the snapshot or the branch. Deleting the last row of a table +leaves both the cached snapshot and the tree branch as they were until one of the three refresh +events happens. +
- General Setup Control Tree — the left dock of
/general-setup+-
+
- Editor Row — the panel of whichever controller leaf is selected, one at a time
+
-
+
- No-Runner Body — "No NC runner — load a project first."; shown first, from the shared +snapshot +
- Absent-Table Body — one line naming the table this leaf edits, from the panel's own read +
- Editor Body
+
-
+
- Description Caption — the leaf's own one-line explanation, above the table +
- Toolbar — where the add button carries no fields: Add on Tool Offsets, Add Tool on
+the Siemens
$TC_DPtable, beside that panel's own toggles
+ - Table — dense, flat, bordered, no sort and no pagination
+
-
+
- Header Row — the leaf's own columns, over the shared labels Axis, Id, Value, +Unit and Actions +
- Data Row — a bold plain-text key cell, then one editable cell per column, then the row's +action buttons +
+ - Add Row Footer — the new row's fields, then Add / Set or Declare +
- Show all Toggle — Work Coordinates and Frames only, revealing the all-zero extended tail +
- Draft Bar — Add Position, Revert and Save Table with an unsaved badge on +Indexing Position Tables; Revert with Apply brand on Controller Brand +
+
+ - Toast — negative, three seconds, the panel's context followed by the server's own message +
+ - Editor Row — the panel of whichever controller leaf is selected, one at a time
+
wwwroot-src/src/composables/useSoftNcRunner.ts— the shared snapshot every panel gates on: the +module-scope installed flag, the project-presence watch, the three events that re-read it, the +reload no panel calls, and the global notification the brand panel's failures take.
+wwwroot-src/src/api/softNcRunner.ts— the branch's typed REST wrappers: the snapshot parser, one +reader and one setter family per table, and the whole-row payloads the per-cell handlers send.
+wwwroot-src/src/api/http.ts— the two failure shapes a panel cannot tell apart, and the coded +error rendering that a message without a code falls through.
+wwwroot-src/src/components/widgets/NumericInput.vue— the numeric field: commit on blur or +Enter, no per-keystroke emit, no equality guard, and the Enter listener that keeps firing where a +read-only field's blur is suppressed.
+wwwroot-src/src/components/controlTree/SoftNcEmptyState.vue— the shared no-runner line, and the +comment that counts the copies its extraction removed rather than its call sites.
+wwwroot-src/src/components/controlTree/softNcItemTypes.ts— the branch builder: the node ids the +two multi-leaf panels discriminate on, and the empty node key that reduces the remount key to the +id.
+wwwroot-src/src/components/controlTree/PrimarySlavePanel.vue— the editor row: the remount key, +the one-panel-at-a-time mounting, and the events it wires to the host.
+wwwroot-src/src/components/controlTree/useControlTreeHost.ts— the before-switch gate no panel in +this branch registers, and the rebuild that a reported structural change triggers.
+wwwroot-src/src/components/controlTree/SoftNcAxisTablePanel.vue— the three per-axis leaves, and +the second empty layer keyed on the row count instead of the presence flag.
+wwwroot-src/src/components/controlTree/SoftNcBrandPanel.vue— the staged select, the revert, the +confirmation and the structural change it reports.
+wwwroot-src/src/components/controlTree/SoftNcRunnerRootPanel.vue— the panel with its own empty +block and its own toast shape.
+wwwroot-src/src/components/controlTree/SoftNcIndexingPositionsPanel.vue— the draft table: the +unsaved badge, the client-side mirror of the endpoint's rules, the save that keeps a failed draft, +and the display-only assignment column.
+wwwroot-src/src/components/controlTree/SoftNcSubprogramPanel.vue— the two text fields committed +as a pair against a saved snapshot.
+wwwroot-src/src/components/controlTree/SoftNcMCodesPanel.vue— the change-event text cell, the +whole-declaration write, and the only add input that submits on Enter.
+wwwroot-src/src/components/controlTree/SoftNcToolOffsetsPanel.vue— the read-only ideal columns +that still commit, the renameable key with its duplicate guard, and the two-call dependence toggle.
+wwwroot-src/src/components/controlTree/SoftNcWorkCoordinatesPanel.vue— the row click that writes +to the display surface, and the row-scoped triad write.
+wwwroot-src/src/components/controlTree/SoftNcRParametersPanel.vueand +wwwroot-src/src/components/controlTree/SoftNcRetainedVariablesPanel.vue— the two panels that +commit a null to vacate an entry.
+wwwroot-src/src/components/controlTree/SoftNcSiemensToolOffsetsPanel.vueand +wwwroot-src/src/components/controlTree/SoftNcNativeParamsPanel.vue— the other two confirmed +deletions, and the two add flows that part company: one appends the minted row, the other re-reads +the whole table.
+wwwroot-src/src/components/controlTree/SoftNcToolNamesPanel.vue, +wwwroot-src/src/components/controlTree/SoftNcToolChangePanel.vue, +wwwroot-src/src/components/controlTree/SoftNcDatumTablePanel.vueand +wwwroot-src/src/components/controlTree/SoftNcControllerParamsPanel.vue— the conforming shape: +one read on mount, per-control commit, capture-assign-await-restore, one toast. The tool-change +Stays put box is the one handler among them that captures and restores a pair of fields.
+wwwroot-src/src/api/equipmentSetup.ts— the canvas marker id the work-coordinate row click +writes, outside this branch's own surface and through the plain-JSON helper.
+wwwroot-src/src/stores/project.ts— the has-a-project flag the shared snapshot watches, and the +direct path assignment that keeps it true across a project-to-project load.
+wwwroot-src/src/layouts/MainLayout.vue— the project epoch that rebuilds the page without +re-running the idempotent installer.
+wwwroot-src/src/i18n/en/softNc.ts— every empty line, column header, hint and error context this +branch renders, and the convention that an error key stores the bare context.
+wwwroot-src/src/i18n/en/common.ts— the shared column and action labels the tables reuse.
+Mech/SoftNcRunnerController.cs— the REST surface: the dependency lookup that answers a missing +table inside a success envelope, the reads that report presence independently of their rows, the +rapid rate defaulted to zero where its config is absent, the two runner-replacing writes that +answer real status codes instead, and the datum routes' defensive rejection of an unknown table +segment.
+Common/ApiError.cs— the coded payloads, and why only the no-project answer can be re-rendered in +the app locale.
+- Controller Branch — the branch these rules hold across, its two planes and the runner behind them +
- Machine and Controller Plane — the plane whose seven leaf pages this contract lets stay short +
- Program Data Plane — the other plane these rules hold across, whose six leaf pages inherit them the same way +
- Brand Matrix — which leaf exists on which brand, and the snapshot flags the first empty layer reads +
- Numeric Input — the numeric field's own contract: what parses, what the bounds do, and why Enter commits twice +
- Controller Brand — the staged, confirmed, whole-runner write that departs furthest from these rules +
- Per-Axis Tables — the three leaves whose empty layer keys on the row count, and the tool-change position beside them +
- Interface Parameters — the interface parameter form and the canned-cycle field, both plain per-control commits +
- M-Code Declarations — the whole-declaration write and the change-event text cell +
- Program Reading — the block-skip boxes and the paired folder inputs +
- Indexing Position Tables — the draft-then-save table, the one panel that keeps a failed edit +
- Native Parameters — the confirmed deletions and the add-or-set footer that keeps what was typed +
- NcRunnerSuit — the switchable suit the branch edits: one runner plus the +per-workpiece dependency list the runner's proxy placeholders resolve against. A suit constructs +on the Fanuc preset, and a project file that carried no runner element keeps that. +
- SoftNcRunner — the NC pipeline itself, and the object the branch root's +Object-Management menu loads, pastes and saves. +
- PipelineNcDependencyList — what a runner file carries. +
- PerCaseNcDependencyList — what stays on the project. +
- CncBrandDependency — the brand marker the root panel's badge +reads. Its Brand is a plain string with five +declared tokens, so the marker can be edited independently of the dependency list it sits in. +
- SoftNcRunner — the facade setter every install and +every brand switch assigns through. It re-binds the proxies to the suit, stamps the machining +chain's axis codes onto the axis config, and resets the per-session runner state. +
- The Object Management menu (
⋮), whose entries are Load, Save As, Copy, Paste and XML Mode. +Load and Save As browse the server file system through the shared file-explorer dialog, filtered to +*.Controller,*.SoftNcRunnerand*.xml; Save As proposes the nameNcRunner.Controller. +Paste checks the pasted object against the expected typeHi.NcParsers.SoftNcRunner, HiMech. +Load, Paste and an XML apply all install the swapped object onto the project and then regrow the +branch. The button is disabled while no runner key is indexed, which is the state with no project +open; with a project but no runner a blank placeholder is indexed so the menu stays reachable as a +Load target.
+ - A brand badge, shown only while a runner resolves. It reads the brand marker's string, or +Unknown brand when the marker is empty. +
- A runner-file caption. It reads the side-file path recorded on the suit, or Embedded in +project when the runner is inlined in the project file, or No NC runner when none resolves. +The web service reads that recorded path and never writes it, so a Save As from this menu writes a +file without changing what the caption names — and after an Object-Management install or a brand +switch the caption still names the file the project was loaded with rather than the runner now in +place. +
- The root panel, after an Object-Management Load, Paste or XML apply. It installs the indexed +object onto the project, refreshes the shared snapshot and the runner key, and then emits — whether +or not the install itself succeeded, so the branch always regrows against a fresh snapshot. +
- Controller Brand, after a brand switch, and only when the switch reported success. +
- General Setup Control Tree — the left dock of
/general-setup+-
+
- Controller Node Row —
equipment/controller, built after Workpiece and ahead of the two +optional runner-suit leaves +-
+
- Machine / Controller Group Row —
equipment/controller/machine+-
+
- Controller Brand, Machine Limits (Stroke), Rapid Feedrates, +Home / G28 Reference, Tool-Change Position, Controller Parameters — grown for +every runner +
- M-Code Declarations, Canned Cycle (Peck), Block Skip / Delete, +Subprogram Folders, Indexing Position Tables, Parameters (Native) — each grown +only where the snapshot reports its backing entry. Across the shipped presets only Block +Skip / Delete and Indexing Position Tables ever go missing; the other four are gated in code +and unconditional in practice +
+ - Program Data Group Row —
equipment/controller/program-data+-
+
- Work Coordinates (G54…) and Tool Offsets — grown for every runner. The second reads
+Tool Offsets (ISO G43 H) while the Siemens
$TC_DPtable resolves, so the two offset +ledgers do not read as one; its node id is unchanged by that relabel
+ - Tool Offsets ($TC_DP), Tool Names, Datum Presets (Q339), Datum Shifts (D), +Frames (Siemens), Retained Common Variables, R Parameters — each grown only where +the snapshot reports its backing table +
+ - Work Coordinates (G54…) and Tool Offsets — grown for every runner. The second reads
+Tool Offsets (ISO G43 H) while the Siemens
+ - Machine / Controller Group Row —
+ - Controller Node Row —
- Editor Row — the panel of whichever node is selected
+
-
+
- Controller Root Panel
+
-
+
- Object Management Menu Button (
⋮) — Load, Save As, Copy, Paste, XML Mode
+ - Brand Badge — the brand marker, or Unknown brand +
- Runner File Caption — the recorded side file, Embedded in project, or No NC runner +
- No-Runner Block — hourglass icon, No NC controller runner on this project., and the +attach-a-file hint; shown instead of the two items below +
- Presets Hint +
- No-Axes Warning — shown only while the snapshot carries no axis +
+ - Object Management Menu Button (
- Machine / Controller and Program Data Group Panels — the stem's intro line over a +clickable list of its children, each row selecting that node +
+ - Controller Root Panel
+
wwwroot-src/src/components/controlTree/SoftNcRunnerRootPanel.vue— the branch root's editor: the +Object-Management button and its install-then-regrow handler, the brand badge, the runner-file +caption, the no-runner block, the presets hint and the no-axes warning.
+wwwroot-src/src/components/controlTree/softNcItemTypes.ts— the wave's item-type registry and the +branch builder: the two plane stems, the leaves every runner grows, the snapshot flag each further +leaf is grown behind, and the Siemens relabel of the Tool Offsets leaf.
+wwwroot-src/src/composables/useSoftNcRunner.ts— the module-singleton snapshot and runner key, +the idempotent install of the project-presence watch, and the install and brand-switch calls the +two emitting panels use.
+wwwroot-src/src/api/softNcRunner.ts— the snapshot shape the tree and the panels parse, and the +typed wrappers over the controller's REST surface.
+wwwroot-src/src/components/controlTree/SoftNcEmptyState.vue— the shared “no runner” line the +leaf panels render; the root panel renders its own block instead.
+wwwroot-src/src/components/controlTree/SoftNcBrandPanel.vue— the other panel that regrows the +branch: the staged brand select, its confirmation, and the scope it emits.
+wwwroot-src/src/components/controlTree/useControlTreeHost.ts— builds theequipment/controller+root inside the equipment group, and turns a reported structure change into a branch rebuild.
+wwwroot-src/src/components/controlTree/GroupInfoPanel.vue— the editor both plane stems use.
+wwwroot-src/src/components/controlTree/itemTypes.ts— the base item-type registry the wave's own +registry is spread into, and the mapping of theGroupitem type to that shared editor.
+wwwroot-src/src/components/widgets/ObjectManagementMenuButton.vue— the⋮menu: the server file +browser and its extension filter, the expected type it sends with a paste, and the events the root +panel listens to.
+wwwroot-src/src/pages/GeneralSetupPage.vue— the route that creates theequipment-scoped tree +host this branch is built in.
+wwwroot-src/src/router/routes.ts— the/general-setuproute and the still-shipping +/controller/:tab?route beside it.
+wwwroot-src/src/components/AppMenuBar.vue— the Page menu entry that reaches the legacy route.
+wwwroot-src/src/i18n/en/softNc.ts— every label, group intro, hint and empty string this branch +renders.
+wwwroot-src/src/i18n/en/tree.ts— the Controller root label.
+Mech/SoftNcRunnerController.cs— the branch's REST surface: the snapshot the tree shape is built +from, the index-and-install pair behind Object Management, the brand switch with its carry and +sweep, and the per-group readers and writers the leaves use.
+Widget/ObjectManagementController.cs— the server half of the⋮menu: the paste that rejects +an object the expected-type string does not admit.
+HiMech/NcParsers/NcRunnerSuit.cs— the suit: the runner, its optional side-file path, the +per-case list, the nested serialization of both, and the proxy wiring that materialises a per-case +table into a project holding none of that type yet.
+HiMech/NcParsers/SoftNcRunner.cs— the pipeline and the five brand presets whose entries decide +which leaves a brand grows, plus the chain configuration that stamps axis codes onto the axis +config.
+HiMech/NcParsers/Dependencys/CncBrandDependency.cs— the brand marker behind the badge and the +five declared tokens.
+HiMech/NcParsers/Dependencys/ControllerParameterTableBase.cs— the base whose interface list is +why the per-axis leaves exist on every brand, and the axis-type rows the axis set is read from.
+HiMech/NcParsers/Dependencys/Fanuc/FanucParameterTableProxy.cs— the get-or-create proxy pattern +the parameter tables use: a serialized seed on the runner, the live table on the project.
+HiNc/MachiningProcs/LocalProjectService.cs— the facade setter: the hoist of pre-proxy baked +tables, the proxy re-binding, the chain-driven axis stamp and the session reset.
+HiNc/MachiningProcs/MachiningProject.cs— where the suit hangs off the project and the load path +that leaves a project with the default preset when its file names no runner.
+- Controller Brand Matrix — Which leaf exists on which brand, +the snapshot flag behind each one, and what a brand switch keeps, resets or destroys +
- Controller Editing Contract — The rules every leaf panel +in the branch follows — when an edit commits, what a failed write does, how a table is rendered — +and the panels that depart from them +
- Machine / Controller Plane — The plane that carries the machine +and controller settings: the brand switch, the per-axis tables, the parameter forms, the M-code +declarations, program reading and the indexing tables +
- Program Data Plane — The plane that carries the per-case +tables: the work coordinates, the two tool-offset ledgers, the Heidenhain datum rows, the Siemens +frames and the two variable tables +
- Brand Matrix — which leaf exists on which brand, and the snapshot flag +behind each one +
- Editing Contract — the commit, rollback and error rules every leaf panel in +this branch follows +
- Machine and Controller Plane — the plane whose leaves carry the machine and controller +settings, one page per editor +
- Program Data Plane — the plane whose leaves carry the per-case tables, and the +ownership rules that keep them on the project +
- General Setup Page — the page that hosts this branch, and the equipment items beside it +
- Control Tree — the engine that builds, rebuilds and selects this branch +
- Legacy Controller Page — the superseded HardNcEnv route this branch replaces, still shipped
+at
/controller
+ - Apply brand is enabled only while the staged value is non-empty and differs from the brand +the snapshot reports. It carries a spinner and both buttons disable while a request is in flight. +
- Revert drops the staging by re-reading the snapshot's brand, and is enabled under the same +condition. +
- Unticked — "Replace the {brand} runner with the {next} preset? Machine settings reset to the +preset's defaults; the old brand's program-data tables are removed." +
- Ticked — "Replace the {brand} runner with the {next} preset? Machine settings reset to the +preset's defaults; the old brand's program-data tables are removed after their work-coordinate XYZ +is carried over." +
- Capture. The outgoing work-coordinate provider — the first +IIsoCoordinateConfig in the runner's proxy-resolved list — is +asked for every coordinate id it currently holds a value for, and those id/offset pairs are held +aside. With the carry checkbox unticked the captured list is empty. +
- Swap. The preset is assigned through +SoftNcRunner, the single rewiring entry point. That +setter first hoists any brand parameter table baked directly into the outgoing runner's pipeline +list into the project's per-case list, so the incoming proxy can claim it; assigns the runner; +re-wires every proxy, the kinematics solver and the session script dictionaries; resets the +per-session runner state so the next run re-parses from scratch and clears the NC diagnostics; and +finally drops any hoisted table the incoming runner claimed through no proxy. Proxy re-wiring is +also where the new brand's own per-case tables are materialised: each get-or-create proxy installs +a deep clone of its seed only where the project holds no table of that type. +
- Carry. The held offsets are written into the new provider, and only for ids that provider +also exposes. The step is skipped entirely when the new provider is the same object as the old +one, which is exactly the Fanuc-to-Mazak case. +
- Sweep. Every per-case table the new runner resolves through no proxy is removed from the +project's per-case list, so the previous brand's tables do not linger unread. +
- ToolingMcConfig — the tool-change position, back to its +three-axis default, where X and Y are left unset and Z returns to zero. +
- GenericBlockSkipConfig — the block-skip layers, back to +layer 1 alone. The Heidenhain preset carries no block-skip config at all, so the node itself is +gone after a switch to that brand. +
- SubProgramFolderConfig — the subprogram lookup folders,
+back to an internal folder of
NCand no external folder.
+ - The macro iteration guards, back to the target preset's own guard set. +
- The selection does not move. Controller Brand exists on every brand, and the editor row's +remount key is composed from the node id and the node key, both unchanged — so the panel is not +remounted. The select is re-seeded by its watch rather than by a fresh mount, which is why the +staged value clears after a success and stays put after a failure that left the brand alone. +
- A failure that reached the server after the swap leaves the tree describing the previous shape. +The regrow is skipped on failure, but the snapshot re-read is not; the select and the root's brand +badge then show whatever brand the server now reports, over a branch built before the attempt. The +staging clears with the brand, so Apply brand is disabled and the branch cannot be regrown from +this node. Moving the selection does not regrow it either — only a structural change reported on +that branch, a whole-tree rebuild, or the page rebuild a project change forces will bring the two +back into agreement. +
- A failed snapshot re-read empties the branch's panels. That read resets the shared snapshot to +its empty value and raises its own toast, “Load controller settings” followed by the server's +message; every leaf panel then renders the shared no-runner line even where the tree still lists +the leaves. +
- General Setup Control Tree — the left dock of
/general-setup+-
+
- Controller Node Row
+
-
+
- Machine / Controller Group Row
+
-
+
- Controller Brand Node Row —
equipment/controller/machine/brand
+
+ - Controller Brand Node Row —
+ - Machine / Controller Group Row
+
+ - Controller Node Row
+
- Editor Row — Controller Brand Panel
+
-
+
- Shared Empty Line — "No NC runner — load a project first.", in place of everything below it +while the shared snapshot reports no runner +
- Controller brand Select — dense, outlined; five options in list order, Fanuc, +Siemens, Heidenhain, Syntec, Mazak; no clear button and no search field +
- Carry work-coordinate XYZ (G54…) into the new brand's table CheckBox — dense, ticked when the +panel mounts +
- Warning Banner — orange, dense, rounded; present only while a different brand is staged +
- Button Row
+
-
+
- Apply brand Button — primary, disabled unless a different brand is staged, spinner while +the request is in flight +
- Revert Button — flat, same enablement +
+
+ - Confirm Dialog — title Switch controller brand, one of the two message variants, Cancel and +OK +
wwwroot-src/src/components/controlTree/SoftNcBrandPanel.vue— this panel: the staged brand and +its watch, the carry checkbox and its default, the dirty rule behind both buttons, the confirm +dialog and its two message variants, and the rebuild scope derived by stripping the leaf's own id +suffix.
+wwwroot-src/src/components/controlTree/SoftNcEmptyState.vue— the shared no-runner line this +panel renders in place of its body.
+wwwroot-src/src/composables/useSoftNcRunner.ts— the shared snapshot the select is seeded from, +the brand-switch call with its success and failure toasts, and the snapshot and key re-read that +runs whichever way the call ends.
+wwwroot-src/src/api/softNcRunner.ts— the five-brand option constant, the snapshot shape, and the +typed brand-switch request carrying the target and the carry flag.
+wwwroot-src/src/api/http.ts— why a refusal and a part-way failure arrive as the same kind of +error, and why only a coded payload is re-rendered in the app locale.
+wwwroot-src/src/components/controlTree/softNcItemTypes.ts— this node's id, label and label key, +and the branch builder that regrows the whole child set from a fresh snapshot after the switch.
+wwwroot-src/src/components/controlTree/useControlTreeHost.ts— the branch root the scope resolves +to, and the rebuild that expands it and re-resolves the selection.
+wwwroot-src/src/components/controlTree/SoftNcRunnerRootPanel.vue— the brand badge and runner-file +caption that change with the snapshot, and the Object-Management install that replaces the runner +by the other route.
+wwwroot-src/src/i18n/en/softNc.ts— the tree label, the select label, the checkbox caption, the +banner sentence, the button captions and the two toast contexts.
+wwwroot-src/src/i18n/en/dialog.ts— the dialog title, the two message variants and the current +fallback used when the snapshot carries no brand marker.
+Mech/SoftNcRunnerController.cs— the brand-switch action: its refusals, the four ordered steps, +the sweep of per-case tables the new runner references through no proxy, and the runner-install +action that shares that sweep.
+Common/ApiError.cs— the coded no-project payload, the one refusal here a non-English locale can +re-render.
+HiMech/NcParsers/SoftNcRunner.cs— the five brand presets as literal dependency lists, each +property returning a fresh instance, together with the segmenter, initializers and syntax list each +brand brings; and the proxy resolution every read on this branch goes through.
+HiMech/NcParsers/NcRunnerSuit.cs— the suit that holds the runner beside the project's own +per-case dependency list, and the proxy wiring the swap triggers.
+HiMech/NcParsers/ControllerPresetWriter.cs— the brand token list and preset factory that mirror +the REST action's switch, and the controller-file extension a saved runner takes.
+HiMech/NcParsers/Dependencys/CncBrandDependency.cs— the brand marker itself: five tokens and +one free-form string property, with no behaviour of its own; its readers live elsewhere.
+HiMech/NcOpt/SoftNcOptProc.cs— where NC optimization reads the marker back out of the session's +effective dependency list, for the writeback grammar.
+HiMech/NcOpt/NcOptPieceClassifier.cs— the re-interpolation guards, none of them keyed on the +brand; the arc guard keys on the centre's provenance instead.
+HiMech/NcParsers/Keywords/Generic/ArcCenterSource.cs— the two centre-provenance stamps: the one +the arc guard refuses, and the one that tells the writeback a split arc's fragments share theCC+line unchanged. An arc whose own block states its centre carries neither.
+HiMech/NcParsers/NcWriteback/NcPatchWriter.cs— the writeback grammar the marker selects: the +variable prefix, the comment spans, the Heidenhain keyword set, the pre-feed word patterns that +place an insertedF, and the trailing-comment shape the embedded source note takes.
+HiMech/NcParsers/PostLogicSyntaxs/RadiusCompensationSyntax.cs— the negative-radius validation +warning raised only where the marker reads Heidenhain, in a syntax all five presets carry.
+HiMech/NcParsers/Dependencys/IIsoCoordinateConfig.cs— the work-coordinate provider contract the +carry reads and writes through, including the id enumeration that decides what crosses.
+HiMech/NcParsers/Dependencys/IsoCoordinateAddressMap.cs— the G54–G59 and G54.1 P1–P48 id set the +Fanuc-family and Syntec tables expose, and the seeding that makes every one of them present.
+HiMech/NcParsers/Dependencys/Siemens/SiemensFrameTable.cs— the frame table's own allocation, +G54–G57 plus the extended G505–G599 series, which is what a carry into Siemens accepts.
+HiMech/NcParsers/Dependencys/Heidenhain/HeidenhainDatumTable.cs— the datum table's preset rows +and the six of them aliased onto G54–G59.
+HiMech/NcParsers/Dependencys/Fanuc/FanucParameterTableProxy.cs— the get-or-create proxy shape +the brand tables share: a serialized seed on the runner, a deep clone installed only where the +project holds no table of that type, and a take thereafter.
+HiMech/NcParsers/Dependencys/Generic/ToolOffsetTableProxy.cs— the proxy every preset carries, +and therefore the reason the generic tool-offset table survives every switch.
+HiMech/NcParsers/Dependencys/Generic/ToolingMcConfig.cs— the tool-change position's preset +default, one of the runner-owned values every apply resets.
+HiMech/NcParsers/Dependencys/Generic/GenericBlockSkipConfig.cs— the block-skip layers' preset +default, and the config the Heidenhain preset does not carry.
+HiMech/NcParsers/Dependencys/Generic/SubProgramFolderConfig.cs— the subprogram folders' preset +defaults.
+HiNc/MachiningProcs/LocalProjectService.cs— the facade setter the swap assigns through: the +hoist of pre-proxy baked tables, the proxy and kinematics re-wiring, the per-session runner state +reset, and the drop of hoisted tables no proxy claimed.
+- Machine and Controller Plane — the plane this leaf opens, and the storage column that says +which of its other leaves this control resets +
- Brand Matrix — which nodes each brand grows, so what the branch looks like +after the switch is a lookup rather than a surprise +
- Editing Contract — the commit and error rules the rest of the branch +follows, and which this panel departs from on purpose +
- The per-axis rows, the M-code declarations and the native parameter rows are stored on the +project, because they are all rows of the brand parameter table. Installing a same-brand +runner file leaves them where they are, apart from the rotary reference and rapid rows that any +re-bind of the suit re-stamps. +
- Saving a runner file captures the proxy's seed, not the table the panels have been editing. A
+
.Controllerwritten after the axis tables were tuned carries the preset seed those tables were +cloned from.
+ - General Setup Control Tree — the left dock of
/general-setup+-
+
- Controller Node Row
+
-
+
- Machine / Controller Group Row —
equipment/controller/machine+-
+
- Controller Brand Node Row +
- Machine Limits (Stroke) Node Row +
- Rapid Feedrates Node Row +
- Home / G28 Reference Node Row +
- Tool-Change Position Node Row +
- Controller Parameters Node Row +
- M-Code Declarations Node Row — grown while the snapshot reports a brand parameter table +
- Canned Cycle (Peck) Node Row — grown while it reports a peck-clearance provider +
- Block Skip / Delete Node Row — grown while it reports a block-skip config +
- Subprogram Folders Node Row — grown while it reports a subprogram-folder config +
- Indexing Position Tables Node Row — grown while the brand table is the Siemens +machine-data table +
- Parameters (Native) Node Row — grown on the same flag as M-Code Declarations +
+ - Program Data Group Row — the plane beside this one +
+ - Machine / Controller Group Row —
+ - Controller Node Row
+
- Editor Row — the panel of whichever node is selected
+
-
+
- Machine / Controller Group Panel
+
-
+
- Intro Caption — the stem's introduction line +
- Child List — one bordered, separated row per leaf above, each showing the leaf's label in the +theme's primary colour with a right chevron; a click selects that leaf +
+
+ - Machine / Controller Group Panel
+
wwwroot-src/src/components/controlTree/softNcItemTypes.ts— the branch builder: the machine +stem's id, label key and intro key, the leaves it pushes unconditionally, and the snapshot flag +each further leaf is pushed behind, in the order the table above lists them.
+wwwroot-src/src/components/controlTree/GroupInfoPanel.vue— the stem's editor: the intro caption +and the clickable child list that selects a leaf.
+wwwroot-src/src/components/controlTree/useControlTreeHost.ts— builds theequipment/controller+root this stem hangs from, inside the equipment group.
+wwwroot-src/src/api/softNcRunner.ts— the snapshot shape the builder reads its flags from, and +the typed wrappers over each leaf's reader and writers.
+wwwroot-src/src/composables/useSoftNcRunner.ts— the shared snapshot the leaf panels gate on, and +the install and brand-switch calls that regrow the branch.
+wwwroot-src/src/i18n/en/softNc.ts— the stem's introduction line and every leaf label in the +table above.
+Mech/SoftNcRunnerController.cs— the plane's REST surface: the presence flags, the per-group +readers and writers, the runner install, and the brand switch with its carry and its sweep of +per-case tables the new runner references through no proxy.
+HiMech/NcParsers/NcRunnerSuit.cs— the suit: the runner, its optional side-file path, the +per-case list, the flat serialization that inlines the per-case half and file-references the +runner half, and the proxy wiring.
+HiMech/NcParsers/SoftNcRunner.cs— the pipeline list this plane's dependencies sit in, the five +brand presets that decide which of them a brand carries, the proxy resolution every read goes +through, and the chain configuration that stamps axis codes onto the table.
+HiMech/NcParsers/ControllerPresetWriter.cs— the canonical controller-file extension and the +writer that renders one brand preset as a standalone runner file.
+HiMech/NcParsers/Dependencys/INcDependencyProxy.cs— the maker-and-taker contract: what a proxy +may serialize, and why its resolved data is never written into the runner.
+HiMech/NcParsers/Dependencys/Fanuc/FanucParameterTableProxy.cs— the get-or-create proxy behind +the Fanuc-family table the Fanuc and Mazak presets share, and the shape the other three brand +tables each repeat in a proxy class of their own: a serialized seed on the runner, a deep clone +installed into the project only when the project has no table of that type, and a take thereafter.
+HiMech/NcParsers/Dependencys/ControllerParameterTableBase.cs— the role accessors the +domain-grouped leaves call, the per-brand parameter numbers they map onto, and the raw +dictionaries the native leaf edits.
+HiMech/NcParsers/Dependencys/Fanuc/FanucParameterTable.cs, +HiMech/NcParsers/Dependencys/Syntec/SyntecParameterTable.cs, +HiMech/NcParsers/Dependencys/Siemens/SiemensMachineDataTable.cs, +HiMech/NcParsers/Dependencys/Heidenhain/HeidenhainParameterTable.cs— the four brand tables and +their default seeds: the axis rows, the rapid rates, the peck clearance and its unit, and the only +pre-declared M-codes on the plane.
+HiMech/NcParsers/Dependencys/Generic/ToolingMcConfig.cs, +HiMech/NcParsers/Dependencys/Generic/GenericBlockSkipConfig.cs, +HiMech/NcParsers/Dependencys/Generic/SubProgramFolderConfig.cs, +HiMech/NcParsers/Dependencys/Generic/FallbackConfig.cs— the plain runner-owned dependencies +behind the tool-change, block-skip, subprogram and fallback-clearance leaves, and the values each +one serializes with the runner.
+HiMech/NcParsers/Dependencys/Fanuc/FanucGotoIterationDependency.cs— a macro guard, the +runner-owned half of the Controller Parameters leaf.
+HiMech/NcParsers/Dependencys/CncBrandDependency.cs— the brand marker the first leaf edits.
+HiNc/MachiningProcs/LocalProjectService.cs— the facade setter every install and brand switch +assigns through: the hoist of pre-proxy baked tables, the drop of the ones no proxy claims, the +proxy re-binding and the chain-driven axis stamp.
+HiGeom/Common/XmlUtils/XmlUtil.cs— the file-reference helper that decides whether the runner is +written to its side file or inlined.
+- Controller Brand — The brand select and its staged apply, +and what a brand change carries, resets and destroys +
- Per-Axis Tables — The four leaves whose rows follow the +machine chain: stroke limits, rapid rates, the G28 reference and the tool-change position +
- Interface Parameters — The domain-grouped parameter form and +the peck-clearance leaf beside it, including the fields whose storage half differs +
- M-Code Declarations — What a declared machine M-code consumes, the +tool-change trigger mode, and the codes an undeclared M word still warns about +
- Program Reading — The two leaves that decide how a program +is read rather than how the machine moves: block-skip layers and the subprogram lookup folders +
- Indexing Positions — The coded-position tables and the +per-axis assignment that decides which axis reads which +
- Native Parameters — The raw parameter form: brand +numbering, raw stored units, and the same table every other leaf on this plane edits +
- Controller Branch — the branch this plane is half of, its runner root and the program-data +plane beside it +
- Program Data Plane — that plane itself, whose leaves are project-owned throughout +where this one's ownership splits between the runner file and the project +
- Brand Matrix — which brand satisfies each gate named above, and what each +flag actually probes +
- Editing Contract — the commit, rollback and rendering rules every leaf on +this plane inherits, and the panels that depart from them +
- Controller Brand — the control that replaces the runner, and therefore +everything in the runner-owned column here +
- Per-Axis Tables — the per-axis leaves whose rows come from the machine chain +and whose values are stored on the project +
- Interface Parameters — the interface parameter form and the peck-clearance leaf, the +two entries that straddle the storage boundary +
- M-Code Declarations — the declaration map on the same table the per-axis leaves edit +
- Program Reading — the block-skip and subprogram leaves, the two settings +that change how a program is read rather than how the machine moves +
- Indexing Position Tables — the leaf that grows only where the brand table is the +Siemens machine-data table +
- Native Parameters — the raw form of the table behind most of this plane +
- They are global, not per-axis. Each is one ordered list shared by every axis assigned to it, so +an edit to Table 1 changes every axis whose assignment reads 1. +
- Row order is the position number, and numbering is 1-based. Row 1 is indexing position 1. The +used-length machine data beside each table — MD10900 and MD10920 — is not stored: the list count +is the used length, which is why the panel has no length field. +
- Values are in the axis' own native units — degrees on a rotary axis, millimetres on a linear +one — and the panel prints no unit suffix on the cells, because the unit follows whichever axes +read the table. +
- Values must be strictly ascending, and where a modular rotary axis reads the table they must
+also satisfy
0 ≤ position < 360.
+ - At most 60 entries per table, the MD10910 / MD10930 array size. Finer station counts are what +the equidistant definition below exists for. +
- Edited positions are stored on the project, serialized with the per-case table as one XML +element per entry carrying its table number and its value. +
- Installing another Siemens runner, or re-applying the Siemens brand, leaves them alone: the +incoming proxy re-binds to the project's table instead of cloning its seed over it. +
- Saving a controller file captures the seed, not the edited table. A
.Controllerwritten after +the tables were filled in carries the preset seed they were cloned from.
+ - Cells bind straight into the draft. Each numeric cell is bound with a two-way model and carries +no commit handler, so a cell's commit — which the widget runs on blur and on Enter, never on a +keystroke — lands in the draft row and goes no further. The badge, the validation line and the two +buttons therefore follow those commits rather than the typing. The field itself still refuses text +it cannot parse, with an untranslated message under the box; it carries no minimum and no maximum +here, so every rule below is the draft validator's — see +Numeric Input. +
- Add and delete are draft operations too. Add Position appends an empty row and is disabled at +60; the row's delete button removes it from the draft with no confirmation and no request. +
- An orange unsaved badge appears beside a table's title while the draft differs from what was +last saved, compared row count first and then value by value. +
- Save Table sends the whole list for that one table and is disabled while the draft is clean or +while validation fails. Revert restores the draft from the last saved values. +
- The validator mirrors the endpoint's own rules and reports the first violation as a line under
+the table: the per-table maximum, a fill in position … (or delete its row) line naming an empty
+or non-finite cell, a strictly-ascending check naming both offending positions with their values,
+and the
0 ≤ position < 360range when a modular rotary axis reads that table.
+ - A failed save does not roll the draft back. The draft stays dirty and the badge stays up, so +the work is not lost — the opposite of every other panel in the branch. +
- Saving an empty table clears it. The endpoint accepts an empty list, and the axes assigned to +that table then stop being indexing axes. +
- General Setup Control Tree — the left dock of
/general-setup+-
+
- Controller Node Row
+
-
+
- Machine / Controller Group Row
+
-
+
- Indexing Position Tables Node Row —
equipment/controller/machine/indexing-positions, +between Subprogram Folders and Parameters (Native)
+
+ - Indexing Position Tables Node Row —
+ - Machine / Controller Group Row
+
+ - Controller Node Row
+
- Editor Row — the panel of the selected node
+
-
+
- Indexing Position Tables Panel — one column, gutter-spaced
+
-
+
- Description Caption — "Global indexing position tables for coded-position programming
+(
CAC/CIC/CDC/CACP/CACN). Row order is position-number order — row 1 is indexing position 1. +Values are in the axis' native units (degrees for rotary, mm for linear) and must be strictly +ascending. Which axis reads which table is MD30500, edited in Parameters (Native); an axis +whose table is empty is not an indexing axis (its coded-position words stay unresolved)."
+ - Assignment Table — dense, flat, bordered; display-only
+
-
+
- Header Row — Axis, Indexing assignment (MD30500) +
- Data Row — the axis name in bold, with (wraps 0–360°) beside it on a modular rotary axis, +then the assignment's label +
- No-Assignment Caption — replaces the table when no axis carries an assignment +
+ - Position Table Block — one per table, twice
+
-
+
- Title Row — Table 1 (MD10910) or Table 2 (MD10930), then either used by and the +names of the axes assigned to it or not used by any axis, then an orange unsaved badge +while the draft is dirty +
- Table — dense, flat, bordered
+
-
+
- Header Row — Position #, Value, and an unlabelled action column +
- Data Row — the 1-based position number in bold, the numeric cell, and a delete button that +removes the row from the draft +
+ - Validation Line — the first rule the draft breaks, in the negative colour +
- Button Row — Add Position (disabled at 60 rows), then Revert and Save Table, both +disabled while the draft is clean and Save Table also while validation fails +
+ - No-Table Body — replaces everything above: "No Siemens machine-data table on the active +runner." +
- Shared Empty State — replaces the whole body while the snapshot reports no runner: "No NC +runner — load a project first." +
+ - Description Caption — "Global indexing position tables for coded-position programming
+(
- Toast — negative, three seconds, the panel's context followed by the server's own message +
+ - Indexing Position Tables Panel — one column, gutter-spaced
+
wwwroot-src/src/components/controlTree/SoftNcIndexingPositionsPanel.vue— this panel: the draft +model over the two tables, the dirty comparison behind the badge, the validator that mirrors the +endpoint's rules, the display-only assignment table with its four labels and its wrap note, and the +save that marks the draft clean without re-reading.
+wwwroot-src/src/components/controlTree/softNcItemTypes.ts— the node's id, label and label key, +and the snapshot flag it is pushed behind, between the subprogram and native leaves.
+wwwroot-src/src/api/softNcRunner.ts— the reader that parses the two tables and the axis rows, +the whole-table replace and its documented refusals, and the snapshot flag the builder gates on.
+wwwroot-src/src/composables/useSoftNcRunner.ts— the shared runner snapshot the first empty layer +reads.
+wwwroot-src/src/components/widgets/NumericInput.vue— the cell: the two-way binding this panel +uses in place of a commit handler, the blur-and-Enter moment that binding emits on, the +empty-to-null parse the validator then reports, and the untranslated parse message.
+wwwroot-src/src/components/controlTree/SoftNcEmptyState.vue— the shared no-runner line.
+wwwroot-src/src/components/controlTree/SoftNcNativeParamsPanel.vue— the sibling panel that owns +the assignment: the integer per-axis section and the footer that adds a row by id and axis.
+wwwroot-src/src/i18n/en/softNc.ts— the node label, the description caption, the four assignment +labels, the wrap note, the table titles, the used-by captions, the four validation messages and the +absent-table line quoted above.
+wwwroot-src/src/i18n/en/common.ts— the shared Axis and Value column headers.
+Mech/SoftNcRunnerController.cs— the REST surface: the read that pairs the two tables with the +per-axis assignments and reports presence from the Siemens table alone, the whole-table replace +with its capacity, finite, ascending and one-revolution checks, the integer per-axis parameter +write the assignment is edited through, and the brand switch whose sweep removes the table on +leaving Siemens.
+HiMech/NcParsers/Dependencys/IIndexingPositionConfig.cs— the contract: 1-based position +numbering, the native-unit and frame rules, the position count, the cyclic test, the number-to- +coordinate lookup and the anchor search, and the rule that an axis whose table is empty is not a +usable indexing axis.
+HiMech/NcParsers/Dependencys/Siemens/SiemensMachineDataTable.cs— the two position lists, their +XML round-trip and the statement that the lookups do not validate the Siemens constraints, the +MD30500 assignment dictionary, the equidistant spacing resolution over MD30501–MD30503, the five +contract implementations, and the default seed that declares none of them.
+HiMech/NcParsers/Dependencys/Siemens/SiemensMachineDataTableProxy.cs— the get-or-create proxy: +the seed serialized on the runner, the deep clone installed only into a project that has no table, +and the deliberate refusal to implement the machine-config interfaces itself.
+HiMech/NcParsers/Dependencys/IMachineAxisConfig.cs— the axis contract, and the modular-rotary +default that makes every rotary and spindle axis subject to the one-revolution rule.
+HiMech/NcParsers/SoftNcRunner.cs— the Siemens preset that carries the machine-data proxy, and +the proxy resolution every read on this leaf goes through.
+HiMech/NcParsers/Syntaxs/SiemensSyntaxUtil.cs— the syntax list that wires the coded-position +producer and its two write-stage consumers into the Siemens pipeline.
+HiMech/NcParsers/EvaluationSyntaxs/Siemens/SiemensAcIcSyntax.cs— the producer: it unwraps the +five coded verbs only on usable indexing axes and stamps the per-word override, leaving the word +untouched elsewhere.
+HiMech/NcParsers/LogicSyntaxs/CodedPositionUtil.cs— the shared resolution: position number to +coordinate, the hold-on-failure semantics, and the diagnostics for an invalid number or a table +that has gone missing between stages.
+HiMech/NcParsers/LogicSyntaxs/McAbcSyntax.cs, +HiMech/NcParsers/LogicSyntaxs/IncrementalResolveSyntax.cs— the two write-stage consumers, rotary +words and linear words.
+HiMech/NcParsers/LogicSyntaxs/McAbcCyclicPathSyntax.cs— the tail pass that turns the resolved +directional approach into a path, and the one other syntax pass that reads the modular-rotary flag +directly.
+- Machine and Controller Plane — the plane this leaf sits on, and which half of the project each +of its neighbours is stored in +
- Brand Matrix — the gate behind this node, why it is the machine plane's +only single-brand row, and what every other flag probes +
- Editing Contract — the per-cell commit rule the rest of the branch follows +and this panel deliberately breaks +
- Native Parameters — where the MD30500 assignment and the equidistant +machine data this leaf only displays are actually edited +
- Max spindle speed is non-null for each of the four brand parameter-table types, so it is
+present on all five brands. A
ControllerParameterTableBasesubclass outside those four answers +null, and the field disappears whilepresentstays true.
+ - Cutter compensation type is non-null only for the Fanuc and the Syntec table. +
- Tool-axis direction is non-null only for the Heidenhain table. +
- Each guard is the Fanuc dependency's value if one resolves, otherwise the Siemens dependency's, +otherwise null. +
hasIterationGuardsprobes the Fanuc and the Siemens jump-guard types only. The Heidenhain preset +carries a jump guard of its own type, so the flag reports false for a brand that has one. That +matches what the panel shows on the shipped presets — the reader probes the same two types, so the +Heidenhain guard has no field either — while under-describing the runner.
+- Its doc comment names the Fanuc GOTO / WHILE-DO guards alone while the code also matches the +Siemens jump guard. The code is what ships. +
- Peck retraction clearance drives the two cycle expansions above. +
- Tool-axis direction is read where a Heidenhain
PLANE SPATIAL … COORD ROTblock is resolved: +it decides which spatial angle may be non-zero for the rotation to count as purely about the tool +axis, which is the only case where COORD ROT suppresses rotary positioning and rotates the +coordinate system instead.
+ - The macro guards are soft caps with runtime counters. Above the cap the consuming block emits +an iteration-limit warning and stops firing the jump or the loop back-edge, so a runaway macro ends +as a diagnostic instead of an unbounded run. Each is keyed per source file and per target — per N +target, per label or per loop — and the counters reset on the session-init edge while the cap does +not. +
- Max spindle speed and cutter compensation type are stored, edited and serialized, and no +syntax or semantic in the shipped pipeline reads either. Cutter radius compensation itself is +resolved, by the G41 / G42 / G40 pass over the motion sections; what that pass does not consult is +the A / B / C startup-and-cancel vector style this field records. +
- The numeric fields commit on blur or on Enter, never per keystroke; their full contract is +Numeric Input. The two selects commit on the pick. +
- Each write is one field. Every control has its own endpoint and sends only its own value; no +edit here resends a neighbouring field. +
- A value the handler refuses is left on screen. Clearing a field parses to null and the handler
+returns before the request; a guard field additionally refuses anything that is not a whole number,
+and every numeric handler here refuses a non-finite one — the field accepts the words
Infinity+andNaNas text and turns them into numbers, and the handlers are what stop them. In each case +the box keeps what was typed — blank, where the field was cleared — while the stored value stands, +because nothing re-writes the box until the panel is remounted by selecting another node and coming +back.
+ - A bound violation never reaches a handler at all. The numeric field itself refuses a value +below its minimum and shows an untranslated Must be ≥ … under the box. Each handler carries a +lower-bound test of its own — 1 on a guard, 0 on the peck clearance — but the field's own minimum +is the same number and stops every finite value first, so those tests decide nothing. +
- General Setup Control Tree — the left dock of
/general-setup+-
+
- Controller Node Row
+
-
+
- Machine / Controller Group Row
+
-
+
- Controller Parameters Node Row —
equipment/controller/machine/parameters
+ - Canned Cycle (Peck) Node Row —
equipment/controller/machine/canned-cycle
+
+ - Controller Parameters Node Row —
+ - Machine / Controller Group Row
+
+ - Controller Node Row
+
- Editor Row — the panel of whichever node is selected
+
-
+
- Controller Parameters Panel — one column, gutter-spaced
+
-
+
- Max spindle speed Numeric Field — dense, outlined, suffixed
rpm, minimum 0
+ - Cutter compensation startup / cancel type Select — dense, outlined; options Type A, +Type B, Type C +
- Tool-axis direction Select — dense, outlined; options Z, Y, X +
- Guard Group — rendered only when at least one guard value resolves
+
-
+
- Separator +
- Macro loop guards (advanced) Caption +
- Jump-Guard Numeric Field — minimum 1; labelled GOTO — max jumps per N target or +GOTOF/GOTOB — max jumps per label +
- Loop-Guard Numeric Field — minimum 1; labelled WHILE/DO — max iterations per loop or +WHILE / FOR / REPEAT / LOOP — max iterations per loop +
+ - No-Table Block — replaces every field above: +"No controller parameter table on the active runner." +
+ - Max spindle speed Numeric Field — dense, outlined, suffixed
- Canned Cycle (Peck) Panel — one column, gutter-spaced
+
-
+
- Peck retraction clearance (G83) Numeric Field — dense, outlined, suffixed
mm, minimum 0
+ - Source Caption — one of the three sentences in the table above, or empty where a fourth +implementation answered +
- No-Config Block — replaces both: +"No canned-cycle config on the active runner." +
+ - Peck retraction clearance (G83) Numeric Field — dense, outlined, suffixed
- Shared Empty State — replaces either panel's whole body while the snapshot reports no runner: +"No NC runner — load a project first." +
+ - Controller Parameters Panel — one column, gutter-spaced
+
wwwroot-src/src/components/controlTree/SoftNcControllerParamsPanel.vue— the five-field panel: +the per-field!= nullconditions, the guard group's combined condition, the brand-marker choice +between the two guard label pairs, the translated compensation-type options beside the literal +tool-axis letters, and the three commit handlers with their null, non-finite and integer refusals +beside the lower-bound tests the field's own minimum shadows.
+wwwroot-src/src/components/controlTree/SoftNcCannedCyclePanel.vue— the peck panel: the single +field, the presence layer, and the caption chosen from the read's source token.
+wwwroot-src/src/components/controlTree/SoftNcEmptyState.vue— the shared no-runner line both +panels open with.
+wwwroot-src/src/components/widgets/NumericInput.vue— the numeric field: the unit rendered as a +suffix, commit on blur or Enter, the empty-text-to-null parse both panels reject, and the +untranslated bound message that stops a value before it is emitted.
+wwwroot-src/src/components/controlTree/softNcItemTypes.ts— the two node ids and their labels: +Controller Parameters among the unconditional machine leaves, Canned Cycle (Peck) behind the +peck-provider flag.
+wwwroot-src/src/api/softNcRunner.ts— the two readers and their setters, the five nullable +parameter keys, the peck source token, and the three snapshot flags that describe these fields and +are declared, defaulted and parsed without a consumer.
+wwwroot-src/src/composables/useSoftNcRunner.ts— the shared runner snapshot the first empty layer +reads.
+wwwroot-src/src/i18n/en/softNc.ts— the two node labels, the five field labels, the two guard +label pairs, the compensation-type options, the macro-guard caption, the three peck source +sentences and the two empty-state lines quoted above.
+Mech/SoftNcRunnerController.cs— the REST surface: the parameter read's type switches and its +table-only presence flag, one write per field, the guard writes that fall back from the Fanuc +dependency to the Siemens one, the peck read with its source token, and the peck write that +converts to microns for the Syntec table.
+HiMech/NcParsers/Dependencys/ControllerParameterTableBase.cs— the base every brand table +extends, and the raw system-parameter dictionary the first three fields are rows of.
+HiMech/NcParsers/Dependencys/Fanuc/FanucParameterTable.cs— the Fanuc and Mazak table: the +max-spindle, peck-clearance and cutter-comp parameter numbers, the compensation-type enum with its +three vector styles, the millimetre peck accessor, and the default seed.
+HiMech/NcParsers/Dependencys/Syntec/SyntecParameterTable.cs— the Syntec table: the same three +parameter numbers, the micron peck row and the accessor that converts it, and the default seed.
+HiMech/NcParsers/Dependencys/Siemens/SiemensMachineDataTable.cs— the Siemens table: the +max-spindle machine datum, and the absence of any compensation-type or peck member.
+HiMech/NcParsers/Dependencys/Heidenhain/HeidenhainParameterTable.cs— the Heidenhain table: the +max-spindle and tool-axis-direction machine parameters and their default seed.
+HiMech/NcParsers/Dependencys/Fanuc/FanucParameterTableProxy.cs— the proxy shape all four brand +tables share: the seed clone that fires only where the project holds no table of that type, and +the pure take afterwards.
+HiMech/NcParsers/Dependencys/ICannedCycleConfig.cs— the peck-clearance contract and its three +implementations.
+HiMech/NcParsers/Dependencys/Generic/FallbackConfig.cs— the runner-owned clearance the Siemens +and Heidenhain presets carry, and its millimetre default.
+HiMech/NcParsers/Dependencys/Fanuc/FanucGotoIterationDependency.cs, +HiMech/NcParsers/Dependencys/Fanuc/FanucWhileDoIterationDependency.cs, +HiMech/NcParsers/Dependencys/Siemens/SiemensGotoIterationDependency.cs, +HiMech/NcParsers/Dependencys/Siemens/SiemensLoopIterationDependency.cs— the four guards the two +advanced fields write: their caps, their per-file counters and the session reset that clears the +counters alone.
+HiMech/NcParsers/Dependencys/Heidenhain/HeidenhainGotoIterationDependency.cs— the fifth guard, +the one the reader does not probe and no field edits.
+HiMech/NcParsers/SoftNcRunner.cs— the five brand presets: which parameter-table proxy, which +guards and whether a fallback clearance each one carries.
+HiMech/NcParsers/LogicSyntaxs/CannedCycleSyntaxUtil.cs— the shared ISO cycle section: the fixed +eight-key parameter set none of whose keys is a retraction clearance, and the modal merge that +consumes them.
+HiMech/NcParsers/LogicSyntaxs/PeckDrillingCycleSyntax.cs— the G83 expansion, which reads the +clearance from the dependency rather than from the block.
+HiMech/NcParsers/LogicSyntaxs/HighSpeedPeckCycleSyntax.cs— the G73 expansion that spends the +same value as the chip-break retract distance.
+HiMech/NcParsers/Syntaxs/FanucSyntaxUtil.cs, +HiMech/NcParsers/Syntaxs/MazakSyntaxUtil.cs, +HiMech/NcParsers/Syntaxs/SyntecSyntaxUtil.cs, +HiMech/NcParsers/Syntaxs/SiemensSyntaxUtil.cs, +HiMech/NcParsers/Syntaxs/HeidenhainSyntaxUtil.cs— the five brand syntax lists, and which of +them register both peck expansions against the one that registers the G83 expansion alone.
+HiMech/NcParsers/LogicSyntaxs/Siemens/SiemensModalCycleSyntax.cs— the CYCLE83 mapping onto the +shared cycle section, and the arguments it drops.
+HiMech/NcParsers/LogicSyntaxs/Heidenhain/HeidenhainCannedCycleSyntax.cs— the klartext cycle +mapping onto the same section.
+HiMech/NcParsers/LogicSyntaxs/Heidenhain/HeidenhainPlaneTiltSyntax.cs— the one consumer of the +tool-axis direction: the pure-tool-axis test that decides whether COORD ROT suppresses rotary +positioning.
+HiMech/NcParsers/PostLogicSyntaxs/RadiusCompensationSyntax.cs— the cutter radius compensation +that is resolved, and that does not consult the stored startup-and-cancel type.
+HiGeom/Common/Collections/DictionaryUtil.cs— the get-or-create the table accessors read through, +and therefore why a read re-creates a deleted parameter row.
+- Machine and Controller Plane — the plane these two leaves sit on, and which half of the project +each of their values is stored in +
- Brand Matrix — why one of the two leaves is gated and the other is not, and +which brand resolves which parameter table +
- Editing Contract — the fetch, commit, rollback and empty-layer rules these +panels share with the rest of the branch +
- Native Parameters — the same rows by number, in their raw stored units, +including the peck clearance this page shows converted +
- The expansion sits at the declared flag's own position, and a repeat of the same declared code
+later in the block is dropped. Textual order therefore keeps deciding last-wins conflicts exactly
+as it did before any declaration existed — a composite code followed by a raw
M05still ends +with the spindle stopped.
+ - A canonical flag that also appears raw and undeclared in the same block is not emitted twice. +The raw occurrence keeps its position, so the expansion cannot reorder what the block already said. +
- Undeclared codes are untouched. One that no other syntax consumes then reaches
+UnconsumedCheckSyntax, which reports it as
+
Parsing--Unconsumed— the warning this leaf exists to answer.
+ - General Setup Control Tree — the left dock of
/general-setup+-
+
- Controller Node Row
+
-
+
- Machine / Controller Group Row
+
-
+
- M-Code Declarations Node Row —
equipment/controller/machine/m-codes
+
+ - M-Code Declarations Node Row —
+ - Machine / Controller Group Row
+
+ - Controller Node Row
+
- Editor Row — M-Code Declarations Panel
+
-
+
- No-Runner Line — "No NC runner — load a project first.", the branch's shared empty state, shown +while the snapshot reports no runner +
- No-Table Line — "No controller parameter table on the active runner.", shown when the read +itself reports no table +
- T word performs the tool change itself (turret / lathe) Toggle +
- Hint Caption — "Declares machine-specific M-codes: a tool-change trigger other than
M6, +composite OEM codes (e.g.M13= spindle CW + flood coolant), or known-but-unsimulated codes +(note only, one info message per occurrence). A declaration with nothing set consumes its code +silently. Undeclared codes keep the unconsumed warning."
+ - Declaration Table — dense, flat and bordered, one row per declared code
+
-
+
- Header Row — Code, Tool change, Spindle, Coolant, Not-simulated note, and +an unlabelled action column +
- Code Cell — bold plain text, not editable +
- Tool change CheckBox +
- Spindle Select — a dash, CW (M03), CCW (M04), STOP (M05) +
- Coolant Select — a dash, Mist (M07), Flood (M08), Off (M09) +
- Not-simulated note Field — placeholder e.g. chip conveyor forward +
- Delete Button — a trash icon, no confirmation +
+ - Add Row Footer
+
-
+
- M-code Field — placeholder
M106; Enter submits
+ - Declare Button +
+ - M-code Field — placeholder
+ wwwroot-src/src/components/controlTree/SoftNcMCodesPanel.vue— this panel: the two empty layers, +the trigger toggle, the two option lists with their dash entry, the whole-declaration write behind +every cell, the change-event note cell, the Enter-submitting add footer and the +confirmation-free delete.
+wwwroot-src/src/components/controlTree/SoftNcEmptyState.vue— the shared no-runner line the panel +opens with.
+wwwroot-src/src/components/controlTree/softNcItemTypes.ts— the node id, its label and label key, +the snapshot flag it is grown behind, and the item type that binds it to the panel.
+wwwroot-src/src/api/softNcRunner.ts— the declaration row and snapshot shapes, the reader that +coerces an empty string to a null field, the whole-declaration writer, the delete, and the trigger +setter.
+wwwroot-src/src/composables/useSoftNcRunner.ts— the shared runner snapshot the first empty layer +reads.
+wwwroot-src/src/i18n/en/softNc.ts— the node label, the toggle label, the hint, the column +headers, the two option label sets, the note placeholder, the empty-code toast and the two empty +strings quoted above.
+Mech/SoftNcRunnerController.cs— the REST surface: the read with its presence flag and its +case-insensitive text ordering, the declare endpoint's direction and coolant validation, the +delete, the trigger write, and the shared helper that answers a missing dependency with an +unsuccessful body rather than an error status.
+HiMech/NcParsers/Dependencys/IMCodeDeclarationConfig.cs— the declaration contract: the map, the +lookup that returns false for an undeclared code, and the declare and remove mutators whose stored +effects are copied rather than aliased.
+HiMech/NcParsers/Dependencys/MCodeEffects.cs— the four effects, the empty and spindle-only +predicates that partition the two consumption paths, and the coolant-mode normalizer.
+HiMech/NcParsers/Dependencys/IToolChangeTriggerConfig.cs— the trigger contract and the +magazine-versus-turret distinction it encodes.
+HiMech/NcParsers/Dependencys/ISpindleControlConfig.cs— the narrower spindle face over the same +storage, and the resolver the spindle-only rows take.
+HiMech/NcParsers/Dependencys/ControllerParameterTableBase.cs— the storage every brand inherits: +the case-insensitive map, the overridable effective view, the virtual trigger property, and the +XML round trip that writes a spindle-only row as the legacy element.
+HiMech/NcParsers/Dependencys/Siemens/SiemensMachineDataTable.cs— the two machine-data rows that +change this leaf's meaning: the tool-change mode bound to the trigger property, the tool-change +M function overlaid onto the effective view with its zero-padding rule, and the six auxiliary +codes the default table pre-declares.
+HiMech/NcParsers/LogicSyntaxs/MCodeExpansionSyntax.cs— the rewrite: in-place expansion, the +duplicate and raw-twin guards, the spindle-only skip, and the two diagnostics a declaration can +raise.
+HiMech/NcParsers/LogicSyntaxs/ToolChangeSyntax.cs— the consumer of the expanded tool change and +of the trigger mode, and the term it records for each.
+HiMech/NcParsers/LogicSyntaxs/SpindleSpeedSyntax.cs— the consumer that tries the machine map +before the ISO defaults, which is what lets a spindle-only row remap a canonical code.
+HiMech/NcParsers/LogicSyntaxs/CoolantSyntax.cs— the consumer of the three expanded coolant +flags.
+HiMech/NcParsers/LogicSyntaxs/Heidenhain/HeidenhainMFunctionSyntax.cs— the brand meanings a +declaration can pre-empt on that preset.
+HiMech/NcParsers/InspectionSyntaxs/UnconsumedCheckSyntax.cs— the warning an undeclared code +keeps, and the one a declaration removes.
+HiMech/NcParsers/Syntaxs/FanucSyntaxUtil.cs, +HiMech/NcParsers/Syntaxs/SiemensSyntaxUtil.cs, +HiMech/NcParsers/Syntaxs/SyntecSyntaxUtil.cs, +HiMech/NcParsers/Syntaxs/MazakSyntaxUtil.cs, +HiMech/NcParsers/Syntaxs/HeidenhainSyntaxUtil.cs— where the expansion sits in each brand's +syntax list, and why the Heidenhain list anchors it earlier than the rest.
+HiMech/NcParsers/Keywords/Generic/IsoKeywords.cs— the canonical flag spellings the expansion +emits,M06among them.
+HiMech/NcParsers/Keywords/ICoolantDef.cs— the three coolant constants the picker's values map +to.
+HiGeom/Numerical/SpindleDirection.cs— the direction enum behind the spindle column.
+- Machine and Controller Plane — the plane this leaf sits on, and which half of a project the +declaration map is written into +
- Brand Matrix — the flag that grows this node, and why it is never false on +a shipped brand preset +
- Editing Contract — the fetch, commit, rollback and toast rules this panel +shares with the branch, and the two places it is recorded as departing from them +
- On Siemens and Heidenhain the peck clearance has no row here at all. Those two presets supply +it through a generic fallback config, a plain runner entry rather than a parameter table, so the +interface leaf shows a field whose backing has no place in this form. +
- On the Fanuc-family and Syntec tables the work-coordinate offsets are rows of this table. The
+G54–G59 triads start at
#5221and the G54.1 P1–P48 triads at#7001, both on a stride of 20, all +in the System parameters section. That mixing is why the machine plane's parameter table is stored +on the project rather than on the runner — see +Machine and Controller Plane. On the Fanuc-family table those two +address ranges are also what an NC program's#-variable read resolves against, so those rows are +live in both directions.
+ - The Axis field appears only while the section is not System, is free text, and is trimmed and +checked for emptiness only. It is not validated against the machine's axes: any name is accepted +and becomes a new column. +
- A successful add re-reads the whole form. The footer's own fields are not cleared, and Enter +does not submit — see Editing Contract for how the +branch's add footers differ from one another. +
- The two client-side refusals are the id, which must be a non-negative whole number, and, for the +two per-axis sections, a non-empty axis name. Each raises a negative toast and sends nothing. +
- General Setup Control Tree — the left dock of
/general-setup+-
+
- Controller Node Row
+
-
+
- Machine / Controller Group Row
+
-
+
- Parameters (Native) Node Row —
equipment/controller/machine/native, the last leaf of the +plane, after Indexing Position Tables
+
+ - Parameters (Native) Node Row —
+ - Machine / Controller Group Row
+
+ - Controller Node Row
+
- Editor Row — the panel of the selected node
+
-
+
- Parameters (Native) Panel — one column, gutter-spaced
+
-
+
- Hint Caption — "Raw stored values in native units — no unit conversion is applied here. The +interface-form nodes edit the same backing table." +
- System parameters Section Heading +
- System Table — dense, flat, bordered
+
-
+
- Header Row — Id, Value, and an unlabelled action column +
- Data Row — the prefixed id in bold over its usage caption (absent on a free extra), a numeric +cell, and a delete button +
+ - Axis parameters Section Heading +
- Axis Table — dense, flat, bordered
+
-
+
- Header Row — Id, then one column per axis name, then an unlabelled action column +
- Data Row — the prefixed id in bold over its usage caption, one numeric cell per axis column +(blank where the row has no entry for that axis), and a delete button that removes the whole +id +
+ - Axis parameters (integer) Section Heading +
- Integer Axis Table — the same shape as the Axis Table, over the integer dictionary +
- Separator +
- Add Row — one line, bottom-aligned
+
-
+
- Section Select — System / Axis (double) / Axis (integer) +
- Parameter id Numeric Field — minimum 0 +
- Axis Text Field — present only while the section is not System +
- Value (raw) Numeric Field +
- Add / Set Button — primary +
+ - No-Table Body — replaces everything above: "No controller parameter table on the active +runner." +
- Shared Empty State — replaces the whole body while the snapshot reports no runner: "No NC +runner — load a project first." +
+ - Remove Dialog — Remove parameter over Remove
<prefixed id>from the<section>section?, +with a cancel
+ - Toast — negative, three seconds, the panel's context followed by the server's own message +
+ - Parameters (Native) Panel — one column, gutter-spaced
+
wwwroot-src/src/components/controlTree/SoftNcNativeParamsPanel.vue— this panel: the three tables +over the three dictionaries, the prefixed id column with its usage caption, the axis-column union, +the three commit handlers with the integer test that drops a fractional value, the +confirm-then-remove flow, and the add footer with its truncation and its two refusals.
+wwwroot-src/src/components/controlTree/softNcItemTypes.ts— the node's id, label and label key, +and the snapshot flag it is pushed behind, last on the machine plane.
+wwwroot-src/src/api/softNcRunner.ts— the reader that parses the prefix and the three row +families, the three per-cell writers and the three per-id removals, and the snapshot field carrying +a prefix this panel does not use.
+wwwroot-src/src/composables/useSoftNcRunner.ts— the shared snapshot the first empty layer and the +axis-column union read.
+wwwroot-src/src/components/widgets/NumericInput.vue— the cell: commit on blur or Enter, the +parsed value written back into the box, and the absence of any integer rule.
+wwwroot-src/src/components/controlTree/SoftNcEmptyState.vue— the shared no-runner line.
+wwwroot-src/src/components/controlTree/SoftNcIndexingPositionsPanel.vue— the sibling that +displays the MD30500 assignment this leaf writes, and points here for it.
+wwwroot-src/src/api/http.ts— the shared fetch helper that turns both a non-2xx status and a +success: falsebody into one thrown error.
+wwwroot-src/src/i18n/en/softNc.ts— the node label, the raw-units hint, the three section +headings, the three add-footer section options, the two footer refusals and the five error +contexts — one read and four writes, the two per-axis savers sharing a context.
+wwwroot-src/src/i18n/en/common.ts— the shared Id, Value, Axis and Add / Set +strings.
+wwwroot-src/src/i18n/en/dialog.ts— the remove dialog's title, its message, and the three storage +kind words it interpolates.
+Mech/SoftNcRunnerController.cs— the REST surface: the read that orders each dictionary by id and +attaches the brand's usage description to every row, the prefix computed from the resolved table's +type, the three get-or-create writers and the three whole-row removals, the shared dependency +wrapper that answers a missing table inside a success envelope, and the neighbouring readers this +form's rows also feed — the three per-axis reads that take the axis-type row's key set bare, the +tool-change read that falls back to its own configuration's keys when that set is empty, and the +M-code read bound to the stored declaration map rather than the overlaid view.
+HiMech/NcParsers/Dependencys/ControllerParameterTableBase.cs— the three dictionaries this leaf +is, the get-or-create bucket accessors, the role interfaces that let the domain-grouped leaves read +the same rows, the axis-name set taken from the axis-type row, the three usage-description methods, +the rotary-axis helper that writes type, reference position and rapid rate in one call, and the XML +round-trip that persists every row including free extras.
+HiMech/NcParsers/Dependencys/Fanuc/FanucParameterTable.cs— the#vocabulary: the well-known +numbers, the get-or-create defaults behind the modelled accessors, the macro-variable lookup over +the work-offset address ranges, and the seed a fresh Fanuc or Mazak runner opens on.
+HiMech/NcParsers/Dependencys/Syntec/SyntecParameterTable.cs— thePrvocabulary, and the peck +clearance stored in microns with the conversion kept in its accessor.
+HiMech/NcParsers/Dependencys/Siemens/SiemensMachineDataTable.cs— theMDvocabulary: the +indexing assignment, the three equidistant numbers this leaf alone edits and the spacing +resolution that reads them, the tool-change mode, the tool-change M function with the read-time +declaration overlay that never enters the stored map, the fixed-point position, and the seed that +declares one system row.
+HiMech/NcParsers/Dependencys/Heidenhain/HeidenhainParameterTable.cs— theMPvocabulary and its +two-row seed.
+HiMech/NcParsers/Dependencys/IsoCoordinateAddressMap.cs— the work-offset address scheme shared by +the Fanuc and Syntec tables, the per-address description that captions those rows, and the seeding +of every triad to zero.
+HiMech/NcParsers/Dependencys/IMachineAxisConfig.cs— the axis contract, and the axis-type +enumeration a cell of the integer section stores.
+HiMech/NcParsers/Dependencys/Generic/ToolingMcConfig.cs— the tool-change pose, stored outside +the three dictionaries, whose own X / Y / Z keys are what Tool-Change Position falls back to when +the axis-type row is gone.
+HiMech/NcParsers/SoftNcRunner.cs— the five brand presets and which parameter-table proxy each +carries, the Fanuc proxy inside the Mazak preset among them, the tool-change configuration every +preset seeds, and the chain configuration that is the only other writer of the axis-type row and, +on a rotary chain axis, of that axis' reference position and rapid rate.
+- Machine and Controller Plane — the plane this leaf sits on, and why the table it exposes is +stored on the project rather than on the runner file +
- Brand Matrix — the flag that grows this node, why it never reports false on +a shipped preset, and why Mazak resolves the Fanuc table +
- Editing Contract — the commit, rollback, confirm and toast rules this panel +shares with the branch, and the add-footer details it differs on +
- Interface Parameters — the same values in domain vocabulary, including the peck +clearance this leaf shows in the brand's own unit +
- Indexing Position Tables — the leaf that displays the MD30500 assignment and the +equidistant definition that are written here +
- Neither panel here has an add-row or delete-row control. Both render a fixed header over one
+
<tr>per axis and nothing else.
+ - The four per-axis routes behind them write one axis' value and nothing more: the caller names an +axis, and the endpoint writes into the stroke-limit, rapid-rate, home or tool-change store under +that name. None of them touches the axis-type row, so none of them changes which rows the next +read returns. +
- Parameters (Native) edits the axis-type row itself, and
+is the one place on the branch that widens the axis set: its Add / Set footer takes a
+free-text axis name, so the section Axis (integer), the brand's axis-type number, a name and a
+value of
0linear,1rotary or2spindle adds an axis, which then appears on all four +leaves. Its grid cannot — the per-axis columns are the union of the axis names already present, so +a cell can be written only for an axis that exists — and its delete button removes a whole +parameter row, every axis cell of one number at once, rather than one axis.
+ - The axis-table panel discards
presententirely. Its typed reader parses the flag; the panel +assigns only the rows. Its second empty layer keys on the row count instead: with no axes it shows +"No machine axes yet — these rows are driven by the Machine Tool chain (MechBuilder). Attach a +machine tool to edit per-axis values.", and with axes it renders the table whatever the flag said.
+ - The tool-change panel reads
presentand, when it is false, shows "No tool-change config on +the active runner."
+ - The writes are per cell, not per row. Editing + Limit sends that side alone, and the +endpoint leaves a side it was not sent unchanged. The branch's row-shaped editors do not agree on +this: a tool-offset or work-coordinate edit resends the whole row, while +Parameters (Native) writes one axis cell at a time as +these four do. +
- Clearing a cell writes nothing. An emptied field parses to null and the handler returns before +the request, so the box is left blank on screen while the stored number stands, and the number +returns when the panel is remounted by selecting another node and coming back. There is +consequently no way to unset a stroke limit or a reference position from these panels; the only +surface that removes one is the native leaf's whole-row delete. +
- No cell is bounded. None of the four tables passes a minimum or a maximum to its fields, so a +negative limit or a negative rapid rate is accepted and stored. The mechanism time is the one +bounded field on the four leaves, at zero or above. +
- General Setup Control Tree — the left dock of
/general-setup+-
+
- Controller Node Row
+
-
+
- Machine / Controller Group Row
+
-
+
- Machine Limits (Stroke) Node Row —
equipment/controller/machine/limits
+ - Rapid Feedrates Node Row —
equipment/controller/machine/rapid
+ - Home / G28 Reference Node Row —
equipment/controller/machine/home
+ - Tool-Change Position Node Row —
equipment/controller/machine/tool-change
+
+ - Machine Limits (Stroke) Node Row —
+ - Machine / Controller Group Row
+
+ - Controller Node Row
+
- Editor Row — the panel of whichever node is selected
+
-
+
- Machine Limits (Stroke) / Rapid Feedrates / Home / G28 Reference Panel — one
+component, its role taken from the selected id's last segment
+
-
+
- Description Caption — "Machine travel limits per axis (empty = no limit set). Motions beyond a +limit raise a stroke-limit validation error.", "G00 rapid traverse rate per axis." or +"G28 first reference (home) machine coordinate per axis. Axes without a value are seeded to 0 +when a machine tool attaches." +
- Axis Table — dense, flat, bordered
+
-
+
- Header Row — Axis, then + Limit and − Limit, or Rapid Rate, or +Home Position, then Unit +
- Axis Row, one per axis
+
-
+
- Axis Name Label — bold, not editable +
- Value Numeric Field, one per value column +
- Unit Label —
mm/deg, ormm/min/deg/minon Rapid Feedrates
+
+
+ - Footer Caption — "Axis rows follow the Machine Tool chain; they cannot be added here." +
- No-Axes Block — replaces the description, the table and the footer caption when the axis set is +empty: a precision-manufacturing icon beside "No machine axes yet — these rows are driven by +the Machine Tool chain (MechBuilder). Attach a machine tool to edit per-axis values." +
+ - Tool-Change Position Panel
+
-
+
- Tool-change mechanism time Numeric Field — suffixed
s, minimum 0
+ - Axis Table — dense, flat, bordered
+
-
+
- Header Row — Axis, Stays put, Position, Unit +
- Axis Row, one per axis
+
-
+
- Axis Name Label — bold, not editable +
- Stays put CheckBox +
- Position Numeric Field — disabled while Stays put is ticked +
- Unit Label —
mm/deg
+
+
+ - Footer Caption — “Stays put” leaves the axis where it is during a tool change. +
- No-Config Block — replaces the field, the table and the caption: +"No tool-change config on the active runner." +
+ - Tool-change mechanism time Numeric Field — suffixed
- Shared Empty State — replaces either panel's whole body while the snapshot reports no runner: +"No NC runner — load a project first." +
+ - Machine Limits (Stroke) / Rapid Feedrates / Home / G28 Reference Panel — one
+component, its role taken from the selected id's last segment
+
wwwroot-src/src/components/controlTree/SoftNcAxisTablePanel.vue— the panel behind the first +three leaves: the role taken from the node id's last segment, the value columns and description +per role, the unit strings computed from the row's rotary flag, the row-count empty layer that +discards the read's presence flag, and the per-cell commit that returns on a cleared field.
+wwwroot-src/src/components/controlTree/SoftNcToolChangePanel.vue— the tool-change panel: the +mechanism-time field, the Stays put checkbox that clears the position and sends 0 on the way +back, the disabled position cell, and the presence-flag empty layer.
+wwwroot-src/src/components/controlTree/SoftNcEmptyState.vue— the shared no-runner line both +panels open with.
+wwwroot-src/src/components/widgets/NumericInput.vue— the numeric cell: commit on blur or Enter, +and the empty-text-to-null parse the panels reject.
+wwwroot-src/src/components/controlTree/softNcItemTypes.ts— the four node ids, their labels and +label keys, and the two item types that bind them to the two panels.
+wwwroot-src/src/api/softNcRunner.ts— the four readers and their setters: the row shapes, the +presence flag both panels receive, the per-side stroke-limit payload, and the stay-or-position +tool-change payload.
+wwwroot-src/src/composables/useSoftNcRunner.ts— the shared runner snapshot the first empty layer +reads.
+wwwroot-src/src/i18n/en/softNc.ts— the four node labels, the three descriptions, the column +headers, the two footer captions and the two empty-state lines quoted above.
+wwwroot-src/src/i18n/en/common.ts— the shared Axis and Unit column headers.
+wwwroot-src/src/components/controlTree/SoftNcNativeParamsPanel.vue— the neighbouring leaf that +edits the same parameter rows by number: the per-cell axis writes, the grid columns unioned from +the existing axis names, the delete that drops a whole row, and the Add / Set footer whose +free-text axis field is what widens the axis-type row.
+Mech/SoftNcRunnerController.cs— the REST surface: the chain-driven axis helper every reader +builds its rows from, the four readers with their independently computed presence flags, the four +per-axis writers that name an axis but never declare one, the native per-axis-integer route that +does, the tool-change fallback to the tool-change configuration's own axis keys, the NaN sentinel +write, and the rotary lookup the rapid write re-derives server-side.
+HiMech/NcParsers/Dependencys/ControllerParameterTableBase.cs— the one object behind three of the +four leaves: the axis set as the keys of the axis-type row, the reference-position, rapid-rate and +stroke-limit accessors over the per-axis buckets, the fixed rapid defaults, and the rotary +configuration helper the chain walk calls, which writes the axis type, the reference position and +the rapid rate together with no guard on the two values.
+HiMech/NcParsers/Dependencys/IMachineAxisConfig.cs— the axis contract and the axis-type enum +whose rotary and spindle members both make a row rotary.
+HiMech/NcParsers/Dependencys/IStrokeLimitConfig.cs— the limit accessors, and the check itself +with its fixed X/Y/Z and A/B/C vocabulary and its validation report.
+HiMech/NcParsers/Dependencys/IRapidFeedrateConfig.cs— the linear and rotary rate accessors.
+HiMech/NcParsers/Dependencys/IHomeMcConfig.cs— the reference-position accessors.
+HiMech/NcParsers/Dependencys/IToolingMcConfig.cs— the tool-change contract: the NaN sentinel and +the mechanism time.
+HiMech/NcParsers/Dependencys/Generic/ToolingMcConfig.cs— the sole implementation: the per-axis +map, the preset default of X and Y staying and Z at 0, and the serialization that omits a zero +mechanism time.
+HiMech/NcParsers/Dependencys/Fanuc/FanucParameterTable.cs, +HiMech/NcParsers/Dependencys/Syntec/SyntecParameterTable.cs, +HiMech/NcParsers/Dependencys/Siemens/SiemensMachineDataTable.cs, +HiMech/NcParsers/Dependencys/Heidenhain/HeidenhainParameterTable.cs— the parameter numbers in +the table above and the three-axis default each brand opens on.
+HiMech/NcParsers/Dependencys/Fanuc/FanucParameterTableProxy.cs, +HiMech/NcParsers/Dependencys/Syntec/SyntecParameterTableProxy.cs, +HiMech/NcParsers/Dependencys/Siemens/SiemensMachineDataTableProxy.cs, +HiMech/NcParsers/Dependencys/Heidenhain/HeidenhainParameterTableProxy.cs— the placeholders the +brand presets actually carry: each clones its three-axis seed into the project's own dependency +list when that list holds none, and resolves to the project's table when it does.
+HiMech/NcParsers/SoftNcRunner.cs— the chain walk that stamps axis codes, hands a rotary axis to +the unconditional rotary helper on a brand table, and fills a missing reference position; and the +five brand presets that decide which parameter table and which tool-change configuration a brand +carries.
+HiNc/MachiningProcs/LocalProjectService.cs— where the chain walk is triggered from, why the +generic axis configuration is cleared first and a brand table is not, and the per-step stroke-limit +check that reads the limits through the proxy-resolved list.
+HiMech/MachiningProcs/MachiningSession.cs— the play loop that runs that check once per step and +pauses on failure.
+HiMech/NcParsers/Semantics/LinearMotionUtil.cs— rapid-traverse timing: per-axis rates, the +slowest axis, and the same six axis names the stroke check uses.
+HiMech/NcParsers/Initializers/HomeMcInitializer.cs— the reference positions written for every +declared axis at the first block.
+HiMech/NcParsers/LogicSyntaxs/ReferenceReturnSyntax.cs— the G28 return: the three linear names +it resolves a final position for, the three rotary letters it tests against the declared axes, and +the validation error a rotary letter the machine has not declared raises.
+HiMech/NcParsers/LogicSyntaxs/ToolChangeMotionSyntax.cs— the overlay: X, Y and Z plus the +declared rotary axes, of which only the ones carrying a non-NaN position move.
+HiMech/NcParsers/Semantics/ToolChangeSemantic.cs— the tool-change step that carries the +mechanism time as its duration.
+- Machine and Controller Plane — the plane these four leaves sit on, and which half of the +project each of their values is stored in +
- Brand Matrix — why all four are ungated, and which table each brand +resolves them through +
- Editing Contract — the fetch, commit, rollback and empty-layer rules these +panels share with the rest of the branch, and where they are recorded as departing from them +
- Layer on — the remaining text is moved into
+Body and the unparsed text is cleared. Every downstream
+parsing syntax then sees nothing, so the block emits no NC act, and the run records
+
BlockSkip--Skippedat message severity for that block.
+ - Layer off, or no config at all — the body stays null and the rest of the block parses exactly +as an unprefixed one would. A runner with no block-skip config therefore simulates the whole +program, which is the safe reading of an unknown machine setting. +
- Fanuc, Mazak, Syntec — an unresolved
M98/M198raisesSubProgramCall--FileNotFoundat +error severity, quoting the folder that was searched, and the call is consumed.
+ - Siemens, Heidenhain — an unresolved call raises
SiemensCall--Skippedor +HeidenhainCall--Skippedat warning severity and is consumed with no motion effect. That is +deliberate: the common unresolved callee on those controls is an OEM or measuring cycle whose +definition file ships with the machine and never travels with the program, so a hard error would +fire on ordinary, correct programs.
+ - General Setup Control Tree — the left dock of
/general-setup+-
+
- Controller Node Row
+
-
+
- Machine / Controller Group Row
+
-
+
- Block Skip / Delete Node Row —
equipment/controller/machine/block-skip, grown while +the snapshot reports a block-skip config
+ - Subprogram Folders Node Row —
equipment/controller/machine/subprograms, grown while it +reports a subprogram-folder config
+
+ - Block Skip / Delete Node Row —
+ - Machine / Controller Group Row
+
+ - Controller Node Row
+
- Editor Row — Block Skip / Delete Panel
+
-
+
- No-Runner Line — "No NC runner — load a project first.", the branch's shared empty state +
- No-Config Line — "No block-skip config on the active runner." +
- Description Caption — "Enabled layers skip their
/n-prefixed blocks (/alone = layer 1)."
+ - Layer CheckBox Column — nine dense boxes in one column
+
-
+
- Layer 1 (bare /) CheckBox +
- Layer 2 … Layer 9 CheckBoxes +
+
+ - Editor Row — Subprogram Folders Panel
+
-
+
- No-Runner Line — the same shared empty state +
- No-Config Line — "No subprogram-folder config on the active runner." +
- Internal folder (M98) Field — outlined and dense, hinted “Relative to the host NC file's +folder; empty = that folder itself” — a hint the resolver contradicts, the anchor being the +project root +
- External folder (M198) Field — outlined and dense, hinted “Fanuc external-storage calls; +empty = fall back to the internal folder” +
+ wwwroot-src/src/components/controlTree/SoftNcBlockSkipPanel.vue— the block-skip panel: the two +empty layers, the caption with its two code slots, the nine boxes and their layer-one label, and +the whole-set write with its rollback.
+wwwroot-src/src/components/controlTree/SoftNcSubprogramPanel.vue— the folder panel: the two +outlined fields, the blur and Enter bindings on both, the saved-snapshot guard that skips a +redundant write, and the paired rollback.
+wwwroot-src/src/components/controlTree/SoftNcEmptyState.vue— the shared no-runner line both +panels open with.
+wwwroot-src/src/components/controlTree/softNcItemTypes.ts— the two node ids, their labels and +label keys, the snapshot flag each is pushed behind, and the item types that bind them to their +panels.
+wwwroot-src/src/api/softNcRunner.ts— the two readers and their coercions, the layer-array +writer, the paired folder writer, and the snapshot fields the gates read.
+wwwroot-src/src/api/http.ts— the envelope reader that turns an unsuccessful body into the error +a panel's toast quotes.
+wwwroot-src/src/composables/useSoftNcRunner.ts— the shared snapshot both first empty layers +read, and its once-per-session install.
+wwwroot-src/src/i18n/en/softNc.ts— the two node labels, the block-skip caption and layer +labels, the four folder strings, the two empty-state lines and the error contexts.
+Mech/SoftNcRunnerController.cs— the REST surface: the two presence probes on the snapshot, the +block-skip read that enumerates the nine layers and the write that sets each one, the folder read +that substitutes an empty string for a null and the write that substitutes a null for a blank, the +brand switch whose remark names both of these among the runner-owned values it resets, and the +shared helper that answers a missing dependency with an unsuccessful body rather than an error +status.
+HiMech/NcParsers/Dependencys/IBlockSkipConfig.cs— the block-skip contract: the per-layer query +and mutator, and the stated safe default when the dependency is absent.
+HiMech/NcParsers/Dependencys/Generic/GenericBlockSkipConfig.cs— the only implementer: the +layer-one default, the comma-separated stored form, the range filter repeated in its reader and +its setter, and the blank-equals-absent rule behind the cleared-set round trip.
+HiMech/NcParsers/ParsingSyntaxs/BlockSkipSyntax.cs— the prefix parser: the layer regular +expression, the always-recorded section, the body move that silences a block, and the message the +skip reports.
+HiMech/NcParsers/Keywords/BlockSkip.cs— the recorded section: symbol, layer, and the body that +is null exactly when the skip did not take effect.
+HiMech/NcParsers/Dependencys/Generic/SubProgramFolderConfig.cs— the two folders, theNC+default applied by the property itself, the null fallbacks, and the writer that omits an element +for a null.
+HiMech/NcParsers/EvaluationSyntaxs/SubProgramCallSyntax.cs— the Fanuc-family inliner: the +internal-versus-external choice, the repetition loop, the file-not-found error, and the absence of +any call-depth comparison.
+HiMech/NcParsers/EvaluationSyntaxs/MacroFileResolver.cs— the shared resolver: the Fanuc file +name chain, the absolute-versus-relative anchoring, and the directory-exists precondition.
+HiMech/NcParsers/EvaluationSyntaxs/Fanuc/FanucMacroCallSyntax.cs, +HiMech/NcParsers/EvaluationSyntaxs/Fanuc/FanucModalMacroSyntax.cs— the other two Fanuc readers +of the internal folder: theG65one-shot macro call, and theG66modal that resolves the same +file again on every motion block it fires on.
+HiMech/NcParsers/EvaluationSyntaxs/Siemens/SiemensSubProgramCallSyntax.cs— the name-call +inliner: its own file-pattern chain, its own depth constant and property, and the safe-skip that +answers an unresolved OEM cycle.
+HiMech/NcParsers/EvaluationSyntaxs/Heidenhain/HeidenhainSubProgramCallSyntax.cs— the klartext +call inliner: theCALL PGMlookup through the same internal folder, the second depth constant, +and the separate repeat ceiling.
+HiMech/NcParsers/Keywords/CallStack.cs, +HiMech/NcParsers/EvaluationSyntaxs/SubProgramReturnSyntax.cs— the frame each inliner stamps on +an inlined block, and theM99return that pops it.
+HiMech/NcParsers/NcDiagnosticProgress.cs— the category and severity behind each diagnostic id +named above.
+HiMech/NcParsers/Dependencys/SystemWired/ProjectFolderDependency.cs— the base directory a +relative folder is anchored against, and the host-wired provider behind it.
+HiMech/MachiningProcs/MachiningSession.cs— where that provider is wired to the project root +before a play.
+HiMech/NcParsers/SoftNcRunner.cs— the five brand presets, and which of them carries each of +these two dependencies.
+HiMech/NcParsers/Syntaxs/FanucSyntaxUtil.cs, +HiMech/NcParsers/Syntaxs/SiemensSyntaxUtil.cs, +HiMech/NcParsers/Syntaxs/SyntecSyntaxUtil.cs, +HiMech/NcParsers/Syntaxs/MazakSyntaxUtil.cs, +HiMech/NcParsers/Syntaxs/HeidenhainSyntaxUtil.cs— the four lists that carry the block-skip +parser and the one that does not, and which call syntax each preset runs.
+- Machine and Controller Plane — the plane these two leaves sit on, and the storage split that +decides which of its values a brand apply resets and which it leaves alone +
- Brand Matrix — the two flags that grow these nodes, what each one probes, +and the brand sets they produce +
- Editing Contract — the commit, rollback and empty-state rules these panels +share with the branch, and the paired-field write recorded as one of its exceptions +
- Datum Presets (Q339) is the preset store.
CYCL DEF 247 DATUM SETTINGwithQ339=Nselects +row N, and its translation becomes the block's active coordinate offset. The same parser claims +the DIN/ISO spellingG247 Q339=+Nand stamps the identical cycle record, so both dialects reach +one store.
+ - Datum Shifts (D) is the shift store.
CYCL DEF 7with a#Nrow index reads row N, and the +translation composes on top of the active preset rather than replacing it — the two land in +separate entries of the block's transform chain.CYCL DEF 7written with direct X / Y / Z values +instead of a#index reads no table row at all.
+ - The row-number cell is bold plain text and cannot be edited. Its header is the role's literal —
+
Q339orD.
+ - The three value headers read
X (mm),Y (mm)andZ (mm). Those three and the index literal +beside them are hard-coded rather than translated, so they read the same under any locale, while +the description caption, both empty bodies and the action button's tooltip are translated.
+ - The action column has no header text and holds one button per row. +
- Datum Presets (Q339) aliases. The ISO face maps
G54throughG59onto preset rows 1 through +6, in order, on both the read and the write, and enumerates no id outside that series — one for +each of those six rows the table holds, which is all six unless a stored project table omits one of +them. Editing theQ339row 3 cells changes what Work Coordinates shows forG56, and an edit +made onG56changes row 3 — one instance, one cell, two faces. The row-zeroing button reaches the +same cells.
+ - Datum Shifts (D) does not alias. Nothing on the ISO face touches the shift store: the id +enumeration reads the preset dictionary only, and both the offset getter and the offset setter +resolve to preset rows. The shift table has exactly one editing surface, the leaf on this page. +
- Preset rows 7 through 20 have no work-coordinate face either. The ISO face reserves six rows
+for the
G54series and enumerates no id pastG59, so fourteen preset rows and all twenty shift +rows are reachable from this page and from nowhere else on the branch.
+ - So
G54means a table lookup or a literal shift depending on what follows it in the block.
+ - The two leaves themselves, per cell or per row. +
- Work Coordinates, for preset rows 1 through 6 only, through the alias above. +
- A brand switch onto Heidenhain with its carry option on, through that same ISO face and into +the table the incoming preset resolves: at most six offsets, into preset rows 1 through 6 — +Brand Switch. +
- A project-load migration. Opening a project file that carries the superseded controller +environment element and no per-case list populates the materialized per-case tables from that +element, and the datum half runs only when the legacy brand element reads Heidenhain. It copies +preset entries and shift entries by row number, overwriting a row it names and leaving every other +row as seeded. The legacy work-coordinate table is replayed first, through the same ISO face that +aliases onto preset rows 1 through 6, so where the two overlap the explicit datum copy is what +stands. +
- General Setup Control Tree — the left dock of
/general-setup+-
+
- Controller Node Row
+
-
+
- Program Data Group Row —
equipment/controller/program-data+-
+
- Datum Presets (Q339) Node Row —
equipment/controller/program-data/datum-presets
+ - Datum Shifts (D) Node Row —
equipment/controller/program-data/datum-shifts
+
+ - Datum Presets (Q339) Node Row —
+ - Program Data Group Row —
+ - Controller Node Row
+
- Editor Row — the panel of whichever node is selected, one at a time
+
-
+
- Datum Presets (Q339) / Datum Shifts (D) Panel — one component, its role taken from the
+selected id's last segment
+
-
+
- No-Runner Body — "No NC runner — load a project first."; the shared empty state, shown +instead of everything below while the snapshot reports no runner +
- Absent-Table Body — "No Heidenhain datum table on the active runner."; shown instead of +everything below while the panel's own read reports no table +
- Description Caption — grey, one line: "Datum presets (CYCL DEF 247, Q339 = row). Rows 1–6 +double as G54–G59." or "Datum shifts (CYCL DEF 7)." +
- Datum Table — dense, flat, bordered; no sort, no pagination, no column menu
+
-
+
- Header Row —
Q339orD, thenX (mm),Y (mm)andZ (mm), then an unlabelled action +column
+ - Datum Row, one per row the read returned, in row-number order
+
-
+
- Row Number Label — bold plain text, never editable +
- Value Numeric Field, one per axis column — no minimum, no maximum, no unit suffix +
- 0 Button — flat, dense, centred, tooltip “Reset to zero” +
+
+ - Header Row —
+ - Toast — negative, three seconds, the panel's context followed by the server's own message +
+ - Datum Presets (Q339) / Datum Shifts (D) Panel — one component, its role taken from the
+selected id's last segment
+
wwwroot-src/src/components/controlTree/SoftNcDatumTablePanel.vue— the one component behind both +leaves: the role taken from the node id's suffix with its preset default, the four things that +role decides, the literal column headers, the whole-row cell commit with its non-finite guard, and +the non-optimistic row reset.
+wwwroot-src/src/components/controlTree/softNcItemTypes.ts— the branch builder: the two node ids +and label keys pushed together inside one flag test, and the single item type both carry.
+wwwroot-src/src/components/controlTree/PrimarySlavePanel.vue— the editor row: the remount key +that makes a move between the two nodes a re-fetch, and the events this panel declares none of.
+wwwroot-src/src/components/controlTree/SoftNcEmptyState.vue— the shared no-runner line the panel +opens with.
+wwwroot-src/src/components/widgets/NumericInput.vue— the value cell: commit on blur or Enter, +the empty-text-to-null parse, and the infinity and NaN literals the panel's handler then rejects.
+wwwroot-src/src/api/softNcRunner.ts— the read that returns both tables and its row shape, the +two role values, the per-row setter and the reset call, and the snapshot's datum flag.
+wwwroot-src/src/composables/useSoftNcRunner.ts— the shared snapshot the first empty layer reads.
+wwwroot-src/src/api/http.ts— the envelope helper that turns a success-flagged failure body into +a thrown error.
+wwwroot-src/src/components/controlTree/SoftNcWorkCoordinatesPanel.vue— the second face on the +preset rows: the same six cells addressed by coordinate id, and the caption that names the mapping.
+wwwroot-src/src/i18n/en/softNc.ts— the two node labels, the two descriptions, the absent-table +body and the three error contexts.
+wwwroot-src/src/i18n/en/common.ts— the reset button's tooltip.
+Mech/SoftNcRunnerController.cs— the REST surface: the datum flag's probe in the snapshot +builder, the read that returns both dictionaries ordered by row number, the per-row writer and the +zeroing route behind one shared table-segment guard, the work-coordinate routes that reach the same +object through the ISO interface, and the brand switch's capture-swap-carry-sweep.
+HiMech/NcParsers/Dependencys/Heidenhain/HeidenhainDatumTable.cs— the two dictionaries, their +twenty seeded rows, the accessors both cycles read through, and the ISO face: theG54–G59map +onto preset rows 1–6, the synthetic preset ids it also resolves, and the id enumeration that stops +at six.
+HiMech/NcParsers/Dependencys/Heidenhain/HeidenhainDatumTableProxy.cs— the seedless get-or-create +placeholder the preset carries, and the bare table it installs into a project holding none.
+HiMech/NcParsers/Dependencys/IIsoCoordinateConfig.cs— the offset-provider contract whose first +implementer the work-coordinate face resolves.
+HiMech/NcParsers/Dependencys/Heidenhain/HeidenhainParameterTable.cs— the other Heidenhain +per-case table: it derives from the controller parameter base, which declares no coordinate +contract, which is why the datum table is the preset's only implementer of one.
+HiMech/NcParsers/SoftNcRunner.cs— the Heidenhain preset's dependency list and the position of the +datum proxy in it, the proxy resolution every read goes through, and the legacy per-case populate +that fills both dictionaries by row number after replaying the legacy coordinate table.
+HiMech/NcParsers/ParsingSyntaxs/Heidenhain/CyclDefSyntaxs/HeidenhainDatumSettingSyntax.cs— the +CYCL DEF 247parser and the DIN/ISOG247spelling it claims for the same cycle record.
+HiMech/NcParsers/ParsingSyntaxs/Heidenhain/CyclDefSyntaxs/HeidenhainDatumShiftSyntax.cs— the +CYCL DEF 7parser: the#row index, the direct-value form, and the axis-word test that decides +whether aG54becomes a direct shift or stays on the ISO path.
+HiMech/NcParsers/LogicSyntaxs/Heidenhain/HeidenhainCoordinateOffsetSyntax.cs— the resolver: the +preset lookup and its synthetic id, the additive shift in its own transform entry, the modal carry +that re-resolves a numbered shift from the table, the shift reset a successful preset selection +performs, and the two validation warnings.
+HiMech/NcParsers/LogicSyntaxs/IsoCoordinateOffsetSyntax.cs— theG54-series path and the modal +lookback that re-queries the table for the carried id.
+HiMech/NcParsers/LogicSyntaxs/CoordinateOffsetUtil.cs— the offset resolution and the translation +composed onto the block's transform chain.
+HiMech/NcParsers/Syntaxs/HeidenhainSyntaxUtil.cs— the Heidenhain syntax list carrying both the +ISO coordinate syntax and the datum-cycle syntax.
+HiMech/NcParsers/Initializers/StaticInitializer.cs— the Heidenhain initializer that seeds no +coordinate section, against the Fanuc and Siemens ones that do.
+HiGeom/Geom/Vec3d.cs— the three-component translation a datum row stores, and the zero it +defaults to.
+HiNc/MachiningProcs/MachiningProject.cs— the load path that wires the proxies and then runs the +legacy per-case populate on a project carrying no per-case list.
+- Program Data Plane — the plane these two leaves sit on, where their table is +stored, and the per-case tables beside them a brand switch keeps or sweeps +
- Work Coordinates — the second face on the preset rows: the same six cells
+addressed as
G54–G59, on the one brand where two nodes edit one object
+ - The settable frames are the table. Ninety-nine ids are allocated by the constructor —
+
G54–G57plus every id in +ExtendedCoordinateSeries, which is +G505throughG599— each seeded to zero, matching a control where every$P_UIFR[n]is +allocated with an initial value of zero. Every one is editable on this leaf.
+ G500is computed, not stored. The accessor answers a zero offset for it and drops a write, +and the constructor deliberately keeps it out of the dictionary, so it is a row on neither this +leaf nor Work Coordinates. The description above the table says as much: "Settable frames +($P_UIFR, translation only); G500 cancels and stores nothing."
+- The programmable frame —
TRANS/ATRANS/ROT/AROTand their solid-angle forms, +together with theCYCLE800tilt that shares its slot — is computed per block from the program +and has no table, no node and no stored value anywhere in this branch.
+ - A frame's rotation and fine-offset components reach neither this table nor the simulation. Only
+the
TRcomponent of a$P_UIFRaccess is bridged into the frame entry; a write to any other +component is recorded on the block and reported as recognised-but-not-simulated by the Siemens +system-variable catch-all.
+ - The programmable-frame entry is written first, deliberately. The tilt and programmable-frame
+syntaxes run ahead of the coordinate-offset syntaxes so their shared entry lands ahead of the
+settable frame's. That is what keeps a
ROTturning the program coordinates inside the frame +rather than turning the frame's own offset with them. Tool-height compensation is written between +the two, and the kinematic pivot transform is written last so every frame-space operation is +accumulated before the final inverse kinematics.
+ - Both coordinate syntaxes are registered, and they share one entry. The Siemens preset carries
+the ISO coordinate syntax and the Siemens one side by side; each writes the chain entry under the
+same name, and a repeated name is replaced in place rather than appended. So the
G54–G59+vocabulary the ISO syntax recognises and theG500andG505–G599vocabulary the Siemens one +adds resolve into a single slot. The Siemens syntax leaves the coordinate flag in place instead of +consuming it, and falls back to the block's existing coordinate section and then to the previous +block's, which is what carries a frame selection modally.
+ - The row set. The work-coordinate reader enumerates the provider's own ids, which for this +provider is the frame dictionary's key set. The two leaves therefore list the same ids in the same +order, and gain or lose none independently. +
- The X, Y and Z cells. Both leaves write the whole triad for one id, and both land in the same +dictionary entry. Neither mutates the vector already sitting there: each write installs a fresh +Vec3d over it, so a reference taken before the write still reads the old values. A +value typed on one leaf is what the other shows at its next mount. +
- The absence of
G500. Neither leaf can show it, for the same reason: it is not in the +dictionary the ids are enumerated from.
+ - The per-axis translation components. A settable frame's translation on an axis letter other
+than X, Y or Z lives in the table's second dictionary, written and read only by the
$P_UIFR+bridge. Neither leaf returns it, and neither leaf disturbs it: both write a fresh +Vec3d into the frame entry and leave the axis dictionary untouched, so a C-axis +frame component set by a program survives every edit made from either panel.
+ - The row actions. The P0 and M0 buttons and the canvas marker belong to Work +Coordinates. This leaf has no action column at all — but P0 and M0 write through the same provider, +so pressing one there changes what this leaf shows. +
- The presence gate. This leaf gates on the concrete frame table; the other gates on the ISO +interface. On Siemens the two answers are one object and the leaves appear together; the empty +lines they would show differ all the same — "No Siemens frame table on the active runner." here, +"No work-coordinate table on the active runner." there. +
- The write path. The work-coordinate route writes through the provider's coordinate accessor,
+which drops a
G500write; the frame route assigns into the dictionary by key and would store one. +Neither is reachable from a panel, because both panels only ever send an id their own read +returned.
+ - The caption. Work Coordinates picks its caption from the snapshot's coordinate-kind field and +on this brand shows "Stored as Siemens settable frames ($P_UIFR; G500 cancels and is always +zero)." — which is that leaf naming this one. This leaf's caption is fixed. +
- A same-brand runner install keeps them. The incoming preset's proxy re-binds to the table the +project already holds rather than replacing it, and the sweep that follows keeps every per-case +table the new runner resolves through a proxy. +
- A switch to any other brand destroys them. No other preset proxies a frame table, so the sweep
+removes it, and switching back installs a fresh all-zero table rather than the one that was there.
+The brand switch's optional carry salvages a corner of it: the offsets are read from the outgoing
+provider before the swap and written into the incoming one afterwards, but only for ids the
+incoming provider already enumerates. No other shipped provider allocates a
G5xxid — the +Fanuc-family and Syntec tables enumerateG54–G59andG54.1P1–G54.1P48, the Heidenhain datum +tableG54–G59— so the carry keepsG54throughG57and the whole extended tail is lost. +What a switch keeps, resets and destroys elsewhere is Brand Switch; +which leaf exists on which brand is Brand Matrix.
+ $P_UIFR[n,axis,TR]=…writes into it. A literal numeric assignment is routed into the table: +an X, Y or Z component replaces that component of the frame entry, and any other axis letter lands +in the per-axis dictionary. A non-literal right-hand side is left for the expression evaluator +earlier in the same block, and a write to index0is consumed and ignored, mirroring the cancel +frame's own rule. With no frame table on the runner the bridge is a no-op and the assignment stays +visible as unconsumed residue.
+$P_UIFRreads come back out of it. The same mapping serves the read side, answering zero for +an allocated frame's unset axis component and for every component ofG500, and null — a +fall-through to the next lookup in the chain — for an id the table does not hold.
+- General Setup Control Tree — the left dock of
/general-setup+-
+
- Controller Node Row
+
-
+
- Program Data Group Row —
equipment/controller/program-data+-
+
- Frames (Siemens) Node Row —
equipment/controller/program-data/frames; grown while the +snapshot reports a Siemens frame table
+
+ - Frames (Siemens) Node Row —
+ - Program Data Group Row —
+ - Controller Node Row
+
- Editor Row — the panel of whichever node is selected
+
-
+
- Frames (Siemens) Panel
+
-
+
- No-Runner Body — "No NC runner — load a project first."; the shared empty state, shown +instead of everything below while the snapshot reports no runner +
- Absent-Table Body — "No Siemens frame table on the active runner."; shown instead of +everything below while the panel's own read reports no table +
- Header Strip
+
-
+
- Description Caption — "Settable frames ($P_UIFR, translation only); G500 cancels and stores +nothing.", left-aligned and grey +
- Show all Toggle — right-aligned, dense; always rendered +
+ - Frame Table — dense, flat, bordered; no sort, no pagination, no column menu
+
-
+
- Header Row — Frame, X (mm), Y (mm), Z (mm). Only Frame is translated +
- Frame Row, one per visible id — not clickable, and carrying no tooltip
+
-
+
- Frame Id Label — bold plain text, never editable +
- Value Numeric Field, one per axis column — no minimum, no maximum, no unit suffix +
+
+
+ - Toast — negative, three seconds, the panel's context followed by the server's own message +
+ - Frames (Siemens) Panel
+
wwwroot-src/src/components/controlTree/SoftNcFramesPanel.vue— the panel: the two empty layers, +the fixed description and the unconditional Show all toggle, the four-column table with its two +literal header spellings, the always-visible regular expression, and the whole-triad cell commit +with its null and non-finite guard.
+wwwroot-src/src/components/controlTree/SoftNcWorkCoordinatesPanel.vue— the other node on this +table: the same ids and values through the ISO reader, plus the wider always-visible test, the +conditional toggle, the P0 and M0 actions and the canvas marker this leaf has none of.
+wwwroot-src/src/components/controlTree/softNcItemTypes.ts— the branch builder: this leaf's node +id and label key, the snapshot flag it is pushed behind, and its position among the program-data +children.
+wwwroot-src/src/components/controlTree/SoftNcEmptyState.vue— the shared no-runner line the panel +opens with.
+wwwroot-src/src/components/widgets/NumericInput.vue— the value cell: commit on blur or Enter, +the empty-text-to-null parse, and the infinity and NaN literals the panel's handler then rejects.
+wwwroot-src/src/api/softNcRunner.ts— the frames reader and its whole-triad setter, the shared +offset-row shape and parser both leaves reuse, and the presence flag this leaf is gated on.
+wwwroot-src/src/api/http.ts— the envelope helper that turns asuccess: falsebody into a +thrown error indistinguishable from a transport failure.
+wwwroot-src/src/composables/useSoftNcRunner.ts— the shared snapshot the first empty layer reads.
+wwwroot-src/src/i18n/en/softNc.ts— the node label, the description, the Frame column header, +the absent-table line and the two error contexts.
+wwwroot-src/src/i18n/en/common.ts— the shared Show all label.
+Mech/SoftNcRunnerController.cs— the REST surface: the presence probe behind the node's gate, the +unsorted frames reader with its zero fill, the per-id writer that assigns into the dictionary +directly rather than through the coordinate accessor, the work-coordinate routes that share the +same object on this brand, and the brand switch's capture-swap-carry-sweep with its accepted-id +filter.
+HiMech/NcParsers/Dependencys/Siemens/SiemensFrameTable.cs— the table: the ninety-nine ids its +constructor seeds, the cancel frame it keeps out of them, the coordinate accessors the +work-coordinate leaf uses, the per-axis accessors the$P_UIFRbridge uses, the axis dictionary +neither panel exposes, the deliberate absence of session-reset, and the id-sorted serialization +that decides the row order after a reload.
+HiMech/NcParsers/Dependencys/Siemens/SiemensFrameTableProxy.cs— the seedless get-or-create +placeholder: what the runner file records, and the fresh table it installs into a project holding +none.
+HiMech/NcParsers/Dependencys/IIsoCoordinateConfig.cs— the offset-provider contract this table +implements, which is what makes it the work-coordinate leaf's provider on this brand.
+HiMech/NcParsers/Dependencys/Siemens/SiemensMachineDataTable.cs— theMDtable beside it, +carrying no frames and implementing no offset-provider contract.
+HiMech/NcParsers/Keywords/Siemens/SiemensKeywords.cs— the cancel frame and the extended series +the constructor seeds from.
+HiMech/NcParsers/Keywords/Generic/IsoKeywords.cs— theG54series the ISO syntax recognises, +wider than this table allocates.
+HiMech/NcParsers/SoftNcRunner.cs— the Siemens preset that carries the frame-table proxy as its +first per-case entry, behind only the brand marker; the proxy resolution every read goes through; +and the session-reset sweep this table is deliberately outside of.
+HiMech/NcParsers/ISessionResettable.cs— the marker the table does not carry, and the difference +between session state and setting data it draws.
+HiMech/NcParsers/NcRunnerSuit.cs— the per-case list the table hangs off and the proxy wiring +that installs it.
+HiMech/NcParsers/LogicSyntaxs/CoordinateOffsetUtil.cs— the resolver that walks every provider +and takes the first non-null answer, the coordinate section it writes, and the translation it +composes.
+HiMech/NcParsers/LogicSyntaxs/Siemens/SiemensCoordinateOffsetSyntax.cs— the frame-word path: the +vocabulary it detects, the flag it leaves in place, the modal lookback, and the zero fallback when +nothing answers.
+HiMech/NcParsers/LogicSyntaxs/IsoCoordinateOffsetSyntax.cs— the sibling registered beside it on +this preset, writing the same chain entry from theG54–G59.9vocabulary.
+HiMech/NcParsers/LogicSyntaxs/Siemens/SiemensProgrammableFrameSyntax.cs— the computed +programmable frame, and the entry it shares with the tilt cycle ahead of the settable frame's.
+HiMech/NcParsers/Syntaxs/SiemensSyntaxUtil.cs— the slot order that decides the composition: +tilt and programmable frame, tool height, the two coordinate syntaxes, then the pivot transform.
+HiMech/NcParsers/Syntaxs/TransformationUtil.cs— the transform chain: in-order multiplication, +replacement of a repeated entry in place, and the pivot entry that must stay last.
+HiMech/NcParsers/LogicSyntaxs/MachineCoordSelectSyntax.cs— the one-shot codes that bypass the +composed transform, widened on this preset toG153andSUPA.
+HiMech/NcParsers/EvaluationSyntaxs/Siemens/SiemensUifrWritingSyntax.cs— the write half of the +$P_UIFRbridge: the literal-only rule, the ignored cancel-frame index, and the no-op when no +frame table resolves.
+HiMech/NcParsers/EvaluationSyntaxs/Siemens/SiemensUifrVariableLookup.cs— the read half.
+HiMech/NcParsers/EvaluationSyntaxs/Siemens/SiemensVariableKey.cs— the index-to-id map behind +both halves, and theTR-only key pattern.
+HiMech/NcParsers/EvaluationSyntaxs/Siemens/SiemensSystemVariableSyntax.cs— where a non-TR+frame component goes instead: recorded on the block, reported unsupported, never reaching the +frame table.
+HiMech/NcParsers/Initializers/StaticInitializer.cs— the Siemens preset's first-block coordinate +id, and the two brands that differ from it.
+HiMech/NcParsers/Dependencys/IsoCoordinateAddressMap.cs— the Fanuc-family id set the brand +switch's carry intersects this table against.
+HiMech/NcParsers/Dependencys/Heidenhain/HeidenhainDatumTable.cs— the third id set the same carry +can meet.
+- Program Data Plane — the plane this leaf sits on, and the ownership rules that +decide what a runner install keeps and what a brand switch sweeps +
- Work Coordinates — the other node on this table, where the same ids are +edited as G54 offsets, with the row actions and the canvas marker this leaf has none of +
- Tool Offsets survives every switch. All five presets proxy the generic tool-offset table, so +the sweep's keep-set always contains it. Its rows, its tool numbering and the tool-house dependence +toggle beside it come through a brand change unchanged; only the leaf's label can change. +
- Every other table on the plane is brand-bound and is removed the moment the target brand
+proxies none of it — the Siemens
$TC_DPtable with its tool-name map, the Siemens frame table, +the Siemens R-parameter table, the Heidenhain datum table, and the Fanuc-family or Syntec parameter +table that holds the work coordinates. Switching back does not restore what was removed; the proxy +makes a fresh instance instead, empty for the tool-offset, tool-name, R-parameter and +retained-variable tables, and zero-filled for the frame and datum tables that seed a row set in +their constructor.
+ - The Fanuc and Mazak presets proxy the same parameter table, so a switch between those two +brands sweeps nothing here: the work coordinates and every other row of that table stay where they +are. +
- Retained Common Variables spans a family rather than a brand. Fanuc, Syntec and Mazak all proxy +it, so it survives any switch among those three and is removed on a switch to Siemens or +Heidenhain. +
- Work coordinates are the one set of values a switch can carry across. The brand panel's Carry +work-coordinate XYZ (G54…) into the new brand's table checkbox is on by default; the switch reads +every id the outgoing providers exposed and, after the swap, writes each back only to a provider +on the incoming runner that lists it — never to a provider that would merely accept it. The +Fanuc-family and Syntec presets expose the same set — G54–G59 and all forty-eight G54.1 P offsets +on the brand table, every address seeded, plus the extended G59.1–G59.9 on the brand-neutral +table behind it — so a switch between Syntec and either of Fanuc and Mazak carries every one of +them. A switch into Siemens carries only G54–G57, the frame table listing no G58, no G59, no +G59.x and no G54.1 P id at all; a switch into Heidenhain carries G54–G59 and drops the +forty-eight G54.1 P offsets and the nine G59.x. No other table's values are carried, and the carry +is skipped for a table the swap re-bound rather than replaced — which is the Fanuc-to-Mazak case. +
- General Setup Control Tree — the left dock of
/general-setup+-
+
- Controller Node Row
+
-
+
- Machine / Controller Group Row — the plane beside this one +
- Program Data Group Row —
equipment/controller/program-data+-
+
- Work Coordinates (G54…) Node Row +
- Tool Offsets Node Row — reads Tool Offsets (ISO G43 H) where the Siemens
$TC_DP+table resolves
+ - Tool Offsets ($TC_DP) Node Row — grown while the snapshot reports a Siemens tool-offset +table +
- Tool Names Node Row — grown on the same flag +
- Datum Presets (Q339) Node Row — grown while it reports a Heidenhain datum table +
- Datum Shifts (D) Node Row — grown on the same flag +
- Frames (Siemens) Node Row — grown while it reports a Siemens frame table +
- Retained Common Variables Node Row — grown while it reports a retained common variable +table +
- R Parameters Node Row — grown while it reports a Siemens R-parameter table +
+
+
+ - Controller Node Row
+
- Editor Row — the panel of whichever node is selected
+
-
+
- Program Data Group Panel
+
-
+
- Intro Caption — the stem's introduction line +
- Child List — one bordered, separated row per leaf above, each showing the leaf's label in the +theme's primary colour with a right chevron; a click selects that leaf +
+
+ - Program Data Group Panel
+
wwwroot-src/src/components/controlTree/softNcItemTypes.ts— the branch builder: the program-data +stem's id, label key and intro key, the two leaves it pushes unconditionally, the snapshot flag +each further leaf is pushed behind in the order the table above lists them, and the tool-offsets +relabel.
+wwwroot-src/src/components/controlTree/GroupInfoPanel.vue— the stem's editor: the intro caption +and the clickable child list that selects a leaf.
+wwwroot-src/src/components/controlTree/useControlTreeHost.ts— builds theequipment/controller+root this stem hangs from, inside the equipment group.
+wwwroot-src/src/api/softNcRunner.ts— the snapshot shape the builder reads its flags from, and +the typed wrappers over each leaf's reader and writers.
+wwwroot-src/src/composables/useSoftNcRunner.ts— the shared snapshot the leaf panels gate on, the +brand switch with its carry option, and the Object-Management install that regrows the branch.
+wwwroot-src/src/components/controlTree/SoftNcWorkCoordinatesPanel.vue— the one panel that reads +the snapshot's work-coordinate kind, the caption it selects from it, and the extended-row filter.
+wwwroot-src/src/components/controlTree/SoftNcToolOffsetsPanel.vue— the generic ledger: the +tool-house dependence toggle, the refresh it enables, and the Siemens caveat gated on the brand +string rather than on the table flag.
+wwwroot-src/src/components/controlTree/SoftNcSiemensToolOffsetsPanel.vueand +wwwroot-src/src/components/controlTree/SoftNcToolNamesPanel.vue— the two leaves over one Siemens +table: the cutting-edge rows and the name map.
+wwwroot-src/src/components/controlTree/SoftNcDatumTablePanel.vue— the one component behind both +Heidenhain leaves, choosing its role from the node id's last segment.
+wwwroot-src/src/components/controlTree/SoftNcFramesPanel.vue— the settable-frame table and its +own always-visible set, narrower than the work-coordinate panel's.
+wwwroot-src/src/components/controlTree/SoftNcRetainedVariablesPanel.vueand +wwwroot-src/src/components/controlTree/SoftNcRParametersPanel.vue— the two sparse variable +tables, and the vacant entry a cleared cell writes.
+wwwroot-src/src/components/controlTree/SoftNcEmptyState.vue— the shared no-runner line every +leaf on this plane falls back to.
+wwwroot-src/src/api/equipmentSetup.ts— the canvas marker id a work-coordinate row click writes, +outside this branch's own REST surface.
+wwwroot-src/src/i18n/en/softNc.ts— the stem's introduction line, every leaf label in the table +above, the work-coordinate storage captions and the brand-switch warning.
+wwwroot-src/src/i18n/en/common.ts— the shared column and action labels these tables reuse.
+Mech/SoftNcRunnerController.cs— the plane's REST surface: the presence flags, the per-table +readers and writers, the brand switch with its work-coordinate carry, and the sweep of per-case +tables the new runner references through no proxy.
+Mech/EquipmentSetupDisplayController.cs— the marked-coordinate id a work-coordinate row click +stores, on the user configuration rather than on the project.
+HiMech/NcParsers/NcRunnerSuit.cs— the suit: the per-case list this plane's tables live in, the +serialization that inlines it with no file reference, and the proxy wiring that materialises a +table into a project holding none of that type.
+HiMech/NcParsers/SoftNcRunner.cs— the five brand presets as literal dependency lists, showing +which proxies each brand carries, and the proxy resolution every read on this plane goes through.
+HiMech/NcParsers/Dependencys/INcDependencyProxy.cs— the maker-and-taker contract, and why a +proxy's resolved data is never written into the runner.
+HiMech/NcParsers/Dependencys/Generic/ToolOffsetTableProxy.cs— the seedless get-or-create shape +the Siemens, Heidenhain and Fanuc-family per-case proxies on this plane all repeat.
+HiMech/NcParsers/Dependencys/Fanuc/FanucParameterTableProxy.cs— the contrasting seeded proxy, +and its own account of why the Fanuc-family table mixes machine configuration with the per-case +work-coordinate offsets.
+HiMech/NcParsers/Dependencys/IIsoCoordinateConfig.csand +HiMech/NcParsers/Dependencys/IsoCoordinateAddressMap.cs— the interface the work-coordinate panel +edits through, and the address arithmetic that decides which ids a Fanuc-family or Syntec table +lists.
+HiMech/NcParsers/Dependencys/Generic/ToolOffsetTable.cs— the generic ledger every preset +proxies, and the subtraction that makes an effective value ideal minus wear.
+HiMech/NcParsers/Dependencys/Siemens/SiemensToolOffsetTable.cs— the cutting-edge map and the +name map behind two leaves, and the addition that makes an effective value geometry plus wear.
+HiMech/NcParsers/Dependencys/Siemens/SiemensFrameTable.cs— the settable frames: the ids the +constructor seeds, the ISO coordinate implementation the work-coordinate leaf shares, and the +per-axis translation components neither panel exposes.
+HiMech/NcParsers/Dependencys/Siemens/SiemensRParameterTable.csand +HiMech/NcParsers/Dependencys/Fanuc/RetainedCommonVariableTable.cs— the two persistent variable +tables, their accepted id ranges, and the vacant entry a valueless element preserves across a save.
+HiMech/NcParsers/Dependencys/Heidenhain/HeidenhainDatumTable.cs— the preset and shift rows behind +two leaves, and the mapping that aliases G54–G59 onto preset rows 1–6.
+HiMech/MachiningProcs/MachiningActRunnerConfig.cs— the tool-house dependence flag, the one +setting on this plane stored outside the per-case list.
+HiNc/MachiningProcs/LocalProjectService.cs— the facade setter every install and brand switch +assigns through, and the proxy re-binding that materialises the incoming brand's per-case tables.
+HiNc/MachiningProcs/MachiningProject.cs— where the suit and the runner configuration hang off +the project, and the load path for a project file carrying the per-case list.
+- Work Coordinates — The one uniform face over four brand +storage models, its P0 and M0 row actions, and the row click that marks a coordinate on the canvas +
- Tool Offsets — The generic ISO ledger: ideal against wear, the +renameable tool number, and what tool-house dependence takes over +
- Siemens Tool Offsets — The
$TC_DPcutting-edge table +and the tool-name map beside it, two leaves over one object
+ - Datum Tables — The Heidenhain preset and shift rows, one +component serving both, and the preset rows the work coordinates alias +
- Frames — The Siemens settable frames, the ids they seed, and what +the work-coordinate leaf shows of the same table +
- Persistent Variables — The retained common variables +and the R parameters: sparse tables, accepted ranges, and the difference between vacant and deleted +
- Controller Branch — the branch this plane is half of, its runner root and the machine plane +beside it +
- Machine and Controller Plane — the other plane, whose ownership splits between the runner and +the project where this one does not +
- Brand Matrix — which brand satisfies each gate named above, and what each +flag actually probes +
- Editing Contract — the commit, rollback and rendering rules every leaf on +this plane inherits, and the panels that depart from them +
- Work Coordinates — the ungated leaf whose backing object changes with the +brand, and the only values a brand switch can carry +
- Tool Offsets — the other ungated leaf, and the one table on this plane that +survives every brand switch +
- Siemens Tool Offsets — the second tool-offset ledger, whose presence +relabels the first +
- Datum Tables — the two Heidenhain leaves, and the preset rows the work +coordinates share +
- Frames — the Siemens frame table, read by two leaves of this plane at once +
- Persistent Variables — the two variable tables a program's arithmetic +reads, kept on the project rather than reset with the session +
- A new session does not clear them. The session-init edge that rebuilds the syntax pipeline +sweeps every dependency and syntax declaring session-scoped state — iteration counters, index +allocators — and calls each one's reset. Neither table declares any, so both are skipped. +
- A power reset does not clear them. The power-reset command sweeps the proxy-resolved dependency +list for dependencies declaring a volatile subset and then resets the session state, dropping the +per-block dataflow. None of the dependencies a brand preset carries declares a volatile subset, so +what a power reset actually discards is the dataflow — which is where the non-retained commons live +— while every value in these two tables stands. +
- Program end does not clear them. The syntax that models a control's
M02/M30reset empties +the block's volatile dictionary and cancels an active modal macro. It writes into the block's own +record and reaches no dependency at all, so neither table is in its path.
+ - A brand switch removes the whole table rather than clearing it. The sweep that follows a runner +swap keeps exactly the per-case tables the new runner resolves through a proxy, so a switch to a +brand that proxies neither drops the table outright, and switching back materialises a fresh empty +one rather than the values that were there. Retained Common Variables is the wider of the two: all +three of Fanuc, Syntec and Mazak proxy it, so it survives any switch among those three and is +removed on a switch to Siemens or Heidenhain. R Parameters survives only a Siemens-to-Siemens +re-flash. What a switch keeps, resets and destroys across the whole plane is +Program Data Plane. +
- Retained Common Variables: "Retained macro variables #500–#999 (power-off safe). Empty =
+
<vacant>. #100–#499 are volatile and live in the run's dataflow, not here."
+ - R Parameters: "Sinumerik R parameters R0–R999 (retentive). Empty =
<vacant>— a program reading +a vacant R parameter reports an error instead of silently using 0."
+ - General Setup Control Tree — the left dock of
/general-setup+-
+
- Controller Node Row
+
-
+
- Program Data Group Row —
equipment/controller/program-data+-
+
- Retained Common Variables Node Row —
+
equipment/controller/program-data/retained-variables; grown while the snapshot reports a +retained common variable table
+ - R Parameters Node Row —
equipment/controller/program-data/r-parameters; grown while the +snapshot reports a Siemens R-parameter table
+
+ - Retained Common Variables Node Row —
+
+ - Program Data Group Row —
+ - Controller Node Row
+
- Editor Row — the panel of whichever node is selected
+
-
+
- Retained Common Variables Panel
+
-
+
- No-Runner Body — the shared "No NC runner — load a project first."; shown first +
- Absent-Table Body — "No retained common variable table on the active runner." +
- Editor Body
+
-
+
- Hint Caption — "Retained macro variables #500–#999 (power-off safe). Empty =
<vacant>. +#100–#499 are volatile and live in the run's dataflow, not here."
+ - Variable Table — dense, flat, bordered
+
-
+
- Header Row — Variable, Value, and a blank third heading +
- Variable Row, one per stored id
+
-
+
- Variable Number Label — bold plain text,
#then the number, not editable
+ - Value Numeric Field — unbounded; cleared commits vacant +
- Delete Button — a bin icon, flat and dense, no label and no confirmation +
+ - Variable Number Label — bold plain text,
+ - Add Row Footer
+
-
+
- Variable # (500–999) Numeric Field — minimum 500, maximum 999 +
- Value Numeric Field — unbounded; left empty adds the row vacant +
- Add / Set Button — primary, unelevated +
+
+ - Hint Caption — "Retained macro variables #500–#999 (power-off safe). Empty =
+ - R Parameters Panel — the same shell with the Siemens strings
+
-
+
- No-Runner Body — the shared "No NC runner — load a project first." +
- Absent-Table Body — "No R-parameter table on the active runner." +
- Editor Body
+
-
+
- Hint Caption — "Sinumerik R parameters R0–R999 (retentive). Empty =
<vacant>— a program +reading a vacant R parameter reports an error instead of silently using 0."
+ - Parameter Table — dense, flat, bordered
+
-
+
- Header Row — Parameter, Value, and a blank third heading +
- Parameter Row, one per stored id
+
-
+
- Parameter Number Label — bold plain text,
Rthen the number, not editable
+ - Value Numeric Field — unbounded; cleared commits vacant +
- Delete Button — a bin icon, flat and dense, no label and no confirmation +
+ - Parameter Number Label — bold plain text,
+ - Add Row Footer
+
-
+
- Parameter # (0–999) Numeric Field — minimum 0, maximum 999 +
- Value Numeric Field — unbounded; left empty adds the row vacant +
- Add / Set Button — primary, unelevated +
+
+ - Hint Caption — "Sinumerik R parameters R0–R999 (retentive). Empty =
+
+ - Retained Common Variables Panel
+
wwwroot-src/src/components/controlTree/SoftNcRetainedVariablesPanel.vue— the#500–#999+ledger: the hint with its vacant slot, the three-column table, the value handler that sends a null +without a finiteness test, the unconfirmed delete, and the add form with its whole-number and range +check.
+wwwroot-src/src/components/controlTree/SoftNcRParametersPanel.vue— theR0–R999ledger: the +same shell with the Siemens strings, theR-prefixed key cell, and the add field bounded from 0.
+wwwroot-src/src/components/controlTree/softNcItemTypes.ts— the branch builder: the two node ids, +their labels and label keys, the two flags each is pushed behind, and the item types binding them to +the two panels.
+wwwroot-src/src/api/softNcRunner.ts— the two readers, their identical row shape with a nullable +value, the two setters and the two removers, and the two snapshot flags the builder tests.
+wwwroot-src/src/api/http.ts— the helper that turns a success-flagged failure body into a thrown +error, which is how an out-of-range write would reach a panel.
+wwwroot-src/src/components/widgets/NumericInput.vue— the value and add fields: commit on blur or +Enter, the empty-text-to-null parse both panels forward, the infinity and NaN literals it also +accepts, and the bound rejection that shows a message and emits nothing.
+wwwroot-src/src/components/controlTree/SoftNcEmptyState.vue— the shared no-runner line both +panels open with.
+wwwroot-src/src/composables/useSoftNcRunner.ts— the shared snapshot the first empty layer reads, +and the events that re-read it.
+wwwroot-src/src/i18n/en/softNc.ts— the two node labels, the two hints, the two absent-table +bodies, the two add-field labels, the two invalid-id messages and the error contexts each panel +prefixes its toast with.
+wwwroot-src/src/i18n/en/common.ts— the shared Value column label and the Add / Set button +label.
+Mech/SoftNcRunnerController.cs— the REST surface: the two flag probes in the snapshot builder, +the two reads that order rows by ascending id and report presence from the table alone, the two +upsert writers with their range guards, the two removers, and the sweep of per-case tables the new +runner references through no proxy.
+HiMech/NcParsers/Dependencys/Fanuc/RetainedCommonVariableTable.cs— the#500–#999store: the +range constants, the nullable dictionary that makes vacant a value, the writer that ignores an +out-of-range id, the variable lookup that answers only in range, and the serialization that keeps a +vacant entry as a valueless element.
+HiMech/NcParsers/Dependencys/Fanuc/RetainedCommonVariableTableProxy.cs— the seedless +get-or-create placeholder the three Fanuc-style presets carry, the bare table it installs into a +project holding none, and the legacy element name an older project file still deserializes through.
+HiMech/NcParsers/Dependencys/Siemens/SiemensRParameterTable.cs— theR0–R999store: the range +constants and the reason the upper one is 999, and the lookup that accepts an uppercase or lowercase +Rkey.
+HiMech/NcParsers/Dependencys/Siemens/SiemensRParameterTableProxy.cs— the matching seedless +placeholder carried by the Siemens preset alone.
+HiMech/NcParsers/Dependencys/Heidenhain/HeidenhainQParameterTable.cs— the third table of the same +shape: the free and permanent Q stores, the ranges each accepts, and the system and volatile ranges +it deliberately declines.
+HiMech/NcParsers/Dependencys/Heidenhain/HeidenhainQParameterTableProxy.cs— its placeholder in the +Heidenhain preset, seedless like the other two.
+HiMech/NcParsers/SoftNcRunner.cs— the five brand presets as literal dependency lists, which is +where the mutual exclusion of the two nodes is actually decided, and the proxy resolution every flag +probe and every read goes through.
+HiMech/NcParsers/NcRunnerSuit.cs— the per-case list these tables live in, and its inline +serialization beside the runner.
+HiMech/NcParsers/Dependencys/INcDependencyProxy.cs— the maker-and-taker contract, and why a +proxy's resolved table is never written into the runner file.
+HiMech/NcParsers/ISessionResettable.cs— the session-scoped contract neither table implements, and +its own statement of the boundary between session state and persistent state.
+HiMech/NcParsers/Dependencys/IPowerResettable.cs— the volatile-subset contract a power reset +sweeps, which no dependency of any brand preset declares.
+HiMech/NcParsers/LogicSyntaxs/ProgramEndCleanSyntax.cs— the program-end clear that empties the +block's volatile dictionary and names the retained range as untouched.
+HiMech/NcParsers/EvaluationSyntaxs/VolatileVariableReadingSyntax.cs— the#100–#499range that +has no node here: the per-block dictionary it carries forward, and the session that bounds it.
+HiMech/NcParsers/Dependencys/Fanuc/FanucPositionVariableLookup.csand +HiMech/NcParsers/EvaluationSyntaxs/Fanuc/FanucSystemControlVariableSyntax.cs— the two#1000-and-up +groups no dependency answers: the position variables read from the previous block's record, and the +system-control writes recorded on the block rather than emulated.
+HiMech/NcParsers/EvaluationSyntaxs/RetainedCommonVariableReadingSyntax.cs— the run-time writer for +the retained range: the literal assignment it consumes and writes straight into the table, with no +mirror kept.
+HiMech/NcParsers/EvaluationSyntaxs/Siemens/SiemensRParameterReadingSyntax.cs— the same shape for +Rnassignments.
+HiMech/NcParsers/EvaluationSyntaxs/Heidenhain/HeidenhainQParameterReadingSyntax.cs— the same shape +forQnandQRn, routing by id range into the free store, the permanent store, a read-only warning +or the volatile dataflow.
+HiMech/NcParsers/EvaluationSyntaxs/VariableEvaluatorSyntax.cs— the normaliser that resolves a +non-literal right-hand side to a literal before the reading syntaxes run, and the lookup chain these +tables join.
+HiMech/NcParsers/EvaluationSyntaxs/Evaluation/IVariableLookup.csand +HiMech/NcParsers/EvaluationSyntaxs/Evaluation/NcExpressionEvaluator.cs— the lookup contract both +tables implement, and the vacant failure an expression raises instead of reading zero.
+HiMech/NcParsers/Syntaxs/FanucSyntaxUtil.cs, +HiMech/NcParsers/Syntaxs/SyntecSyntaxUtil.cs, +HiMech/NcParsers/Syntaxs/MazakSyntaxUtil.cs, +HiMech/NcParsers/Syntaxs/SiemensSyntaxUtil.csand +HiMech/NcParsers/Syntaxs/HeidenhainSyntaxUtil.cs— the five syntax lists, each carrying the reading +syntax for its brand's persistent variable range.
+HiNc/MachiningProcs/LocalProjectService.cs— the facade setter every install and brand switch +assigns through, and the power reset that sweeps volatile subsets and then drops the session +dataflow.
+HiNc/MachiningProcs/SessionShell.cs— the scriptable power-reset entry point over that service +call.
+- Program Data Plane — the plane both leaves sit on: where their tables are stored, +the seedless proxies that make them, and what a brand switch keeps or sweeps beside them +
D0cancels. The section is written with an offset of 0 and an identity transform, with no +table lookup and no diagnostic.
+- The active tool number comes from the block's tool-change section, or from the previous +block's when this one carries none. A numeric tool id is taken as it stands. +
- A string tool id is resolved through the tool-name map on this same table. An unmapped name +raises an unsupported warning and leaves the tool number at 0. +
- The
(T, D)pair is looked up here. A configured row answers with length 1 plus its wear.
+ - A pair with no row falls back to the generic ledger, described next. +
- The tool change itself. The tool-change semantic resolves a string tool id through this map
+before it emits a tooling step. An unmapped name raises a
ToolChange--NameUnresolvedwarning and +emits no step at all, so the tool change is not simulated rather than simulated with the wrong +tool.
+ - The
Dword. The height path resolves the same name for its own(T, D)lookup and, on a +miss, raisesSiemensToolOffset--ToolUnresolvedand carries on with tool number 0.
+ - General Setup Control Tree — the left dock of
/general-setup+-
+
- Controller Node Row
+
-
+
- Program Data Group Row
+
-
+
- Tool Offsets ($TC_DP) Node Row —
equipment/controller/program-data/siemens-tool-offsets
+ - Tool Names Node Row —
equipment/controller/program-data/tool-names
+
+ - Tool Offsets ($TC_DP) Node Row —
+ - Program Data Group Row
+
+ - Controller Node Row
+
- Editor Row — the panel of whichever node is selected
+
-
+
- Tool Offsets ($TC_DP) Panel
+
-
+
- No-Runner Body — "No NC runner — load a project first."; shown instead of everything below +
- Absent-Table Body — "No Siemens tool-offset table on the active runner."; shown instead of +the body +
- Description Caption — "Sinumerik $TC_DP tool offsets, one row per (tool T, cutting edge D). A
+
D1word in the program reads this table; when a row is missing, playback falls back to the +generic Tool Offsets table and reports a warning. Wear adds onto geometry (enter a shortened +tool as negative wear).", withD1set in code style
+ - Toolbar — right-aligned
+
-
+
- Add Tool Button — primary, add icon +
+ - Offset Table — dense, flat, bordered
+
-
+
- Header Row — Tool T, Edge D, Length 1 (Z), Length 2 (X), Length 3 (Y), +Radius, Length 1 Wear, Length 2 Wear, Length 3 Wear, Radius Wear, then an +unlabelled action column +
- Offset Row, one per
(tool, edge)pair, ascending by tool then edge +-
+
- Tool Cell — bold plain text, prefixed
T
+ - Edge Cell — bold plain text, prefixed
D
+ - Eight Numeric Fields — the four geometry components, then their four wear components; no +bounds and no unit suffix +
- Add-Cutting-Edge Button — flat,
playlist_addicon, tooltipped "Add a cutting edge for +T{tool}"
+ - Delete Button — flat, delete icon +
- Verbatim-Field Badge — a grey +N; shown only where the row carries unconsumed
$TC_DP+fields, tooltipped that they are stored and saved with the project and not used by the +simulation
+
+ - Tool Cell — bold plain text, prefixed
+ - Footnote — "All values in mm. Effective length 1 = Length 1 (Z) + Length 1 Wear; same for the +other columns." +
+ - Remove-Row Confirmation Dialog — Remove tool offset row, "Remove the offset row for (T{tool}, +D{edge})?", with a cancel button +
- Tool Names Panel
+
-
+
- No-Runner Body — "No NC runner — load a project first." +
- Absent-Table Body — "No Siemens tool-name table on the active runner." +
- Hint Caption — "Maps the tool names written in the NC program (
T="D16R3Z6") to tool numbers. +Names are case-insensitive. An unmapped name cannot mount a tool and itsDword resolves no +offset.", with the call form and theDword set in code style
+ - Name Table — dense, flat, bordered
+
-
+
- Header Row — Tool Name, Tool #, then an unlabelled action column +
- Name Row, one per mapping, ordered case-insensitively by name
+
-
+
- Name Cell — bold plain text +
- Tool Number Numeric Field — minimum 1 +
- Delete Button — flat, delete icon, no confirmation +
+
+ - Add Row Footer
+
-
+
- Tool name Text Field +
- Tool # Numeric Field — minimum 1 +
- Add / Set Button — primary; the only submit path, since neither field submits on Enter +
+
+
+ - Tool Offsets ($TC_DP) Panel
+
wwwroot-src/src/components/controlTree/SoftNcSiemensToolOffsetsPanel.vue— the$TC_DPpanel: +the eight unbounded numeric cells, the whole-row write with its finite-value guard, the two add +buttons and the local append-and-sort, the confirmed delete, and the verbatim-field badge.
+wwwroot-src/src/components/controlTree/SoftNcToolNamesPanel.vue— the tool-name panel: the +plain-text key column, the bounded tool-number cell, the unconfirmed delete, and the Add / Set +footer with its two client-side refusals and its full re-read.
+wwwroot-src/src/components/controlTree/softNcItemTypes.ts— the branch builder: the single flag +test that pushes both node ids, their labels and label keys, and the relabel it applies to the +generic offset leaf at the same time.
+wwwroot-src/src/api/softNcRunner.ts— the typed wrappers: the row and mapping shapes with their +presence flags, the whole-row upsert, the add that returns a minted pair, the two deletes, and the +name-keyed upsert with its URL encoding.
+wwwroot-src/src/composables/useSoftNcRunner.ts— the shared snapshot behind the first empty layer +and behind the tree's flag test.
+wwwroot-src/src/components/controlTree/SoftNcEmptyState.vue— the shared no-runner line both +panels open with.
+wwwroot-src/src/components/widgets/NumericInput.vue— the numeric cell: commit on blur or Enter, +the minimum rejected inside the widget, and the empty-text-to-null parse both panels drop.
+wwwroot-src/src/components/controlTree/SoftNcToolOffsetsPanel.vue— the neighbouring generic +ledger, its subtracting sign rule, and the Siemens caveat it shows from the brand string.
+wwwroot-src/src/i18n/en/softNc.ts— the two node labels, the description and the hint, the ten +column headers, the footnote, the two absent-table lines, the add-strip refusals and the error +contexts.
+wwwroot-src/src/i18n/en/common.ts— the shared Tool # header and the Add / Set label.
+wwwroot-src/src/i18n/en/dialog.ts— the$TC_DPremove-row dialog's title and message, a +different key pair from the generic ledger's.
+Mech/SoftNcRunnerController.cs— the REST surface: the snapshot probe behind both nodes, the +ordered reads whose presence flag is the table object itself, the row upsert that assigns the eight +components and leaves the verbatim bag alone, the add that mints a tool or an edge, the two deletes +with their refusals, the name upsert with its blank-name refusal, and the proxy re-wire the three +writes run first.
+HiMech/NcParsers/Dependencys/Siemens/SiemensToolOffsetTable.cs— the object both leaves edit: the +pair-keyed row dictionary, the case-insensitive name map and its lookup, the additive effective +values, the row type with its typed components and verbatim bag, and the serializer that keeps a +consumed index out of the bag.
+HiMech/NcParsers/Dependencys/Siemens/ISiemensToolOffsetConfig.cs— the two-key offset contract: +the try-form that is the only miss signal, and the direction-indexed and radius accessors nothing +calls.
+HiMech/NcParsers/Dependencys/Siemens/SiemensToolOffsetTableProxy.cs— the seedless get-or-create +placeholder the Siemens preset carries, and the bare table it installs into a project holding none.
+HiMech/NcParsers/SoftNcRunner.cs— the five brand presets, of which only the Siemens list carries +that proxy, and the proxy resolution every read of the table goes through.
+HiMech/NcParsers/NcRunnerSuit.cs— the per-case list this table is stored in, and the proxy wiring +the three writes re-run.
+HiMech/NcParsers/LogicSyntaxs/Siemens/SiemensToolOffsetSyntax.cs— theDpath: the cancel arm, +the modal ownership rule, the tool-number resolution through the name map, the fallback onto the +generic ledger with its two warnings, and the suppression of both on a modal re-resolve.
+HiMech/NcParsers/LogicSyntaxs/ToolHeightOffsetSyntax.cs— the ISO sibling, and the shared +composition of the height as one replaceable entry of the transform chain.
+HiMech/NcParsers/Semantics/ToolChangeSemantic.cs— the other consumer of the name map: the string +tool id it resolves, and the tooling step it declines to emit when the name is unmapped.
+HiMech/NcParsers/PostLogicSyntaxs/RadiusCompensationSyntax.cs— the radius side, which reads the +generic single-integer table rather than this one.
+HiMech/NcParsers/Dependencys/Generic/ToolOffsetTable.cs— the fallback source: the subtracting +effective values, and the zero it answers for an offset number it does not hold.
+HiMech/NcParsers/Dependencys/Generic/ToolOffsetTableProxy.cs— the placeholder every brand preset +carries, which is why the fallback always resolves a table on Siemens.
+- Program Data Plane — the plane both leaves sit on, where this table is stored, and +what a brand switch does to it +
- Tool Offsets — the generic ledger one node above, whose label this table's +presence changes and whose wear convention is the opposite of the one stated here +
- The panel renders no description caption above its table: the Siemens caveat is the only line it +shows there, and the only other prose it carries is the closing caption the tool-house dependence +turns on below the table. +
- The two ideal columns become read-only, and a closing caption says so: "Ideal columns mirror +the Tool House; only the wear columns are editable." Read-only is a screen guarantee only — the +write is row-scoped, so the ideal values ride along on every wear edit, and the endpoint writes +all four components unconditionally. That the two ideal cells still commit on Enter is one of the +panel's catalogued departures from Editing Contract. +
- The Tool # cell is replaced by bold plain text, so the key cannot be renamed. +
- Row CRUD disappears: the Add button is not rendered and each row's delete button becomes a +grey em dash. A Refresh from Tool House button appears next to the toggle in their place. +
- There is no confirmation. The dialog that guards a single delete button does not guard this, +and there is no undo. +
- Turning the toggle on runs it immediately. Enabling the dependence is two server calls in one +attempt: the flag write, then the refresh. A project with no tool library fails the second with +“No tool house available” after the first has already committed, and the panel's local toggle +springs back while the server-side flag stays on — the two-call rollback recorded in +Editing Contract. Turning the toggle off writes the flag +and stops there — no refresh, and no re-read. +
- Playback runs it too. While the flag is on, starting an NC program refreshes the table from +the library before the first block, so a hand-typed ideal value is replaced whether or not the +panel was ever opened, and an empty library at that moment empties the table. No panel is told: +what is on screen is whatever the last read returned until the selection moves away and back. +
- General Setup Control Tree — the left dock of
/general-setup+-
+
- Controller Node Row
+
-
+
- Program Data Group Row
+
-
+
- Tool Offsets Node Row —
equipment/controller/program-data/tool-offsets; reads +Tool Offsets (ISO G43 H) while a Siemens$TC_DPtable resolves
+
+ - Tool Offsets Node Row —
+ - Program Data Group Row
+
+ - Controller Node Row
+
- Editor Row — the panel of whichever node is selected
+
-
+
- Tool Offsets Panel
+
-
+
- No-Runner Body — "No NC runner — load a project first."; shown instead of everything below +
- Absent-Table Body — "No tool-offset table on the active runner."; shown instead of the body +
- Siemens Caveat Line — orange caption, shown while the brand marker reads
Siemens, with +G43 HandDset in code style
+ - Toolbar
+
-
+
- Set ideal offset dependent on tool house Toggle +
- Refresh from Tool House Button — flat, refresh icon; only while the dependence is on +
- Add Button — primary, add icon, right-aligned; only while the dependence is off +
+ - Offset Table — dense, flat, bordered
+
-
+
- Header Row — Tool #, Ideal Height (mm), Axial Wear (mm), Ideal Radius (mm), +Radial Wear (mm), then an unlabelled action column +
- Offset Row, one per offset number, ascending
+
-
+
- Tool Number Cell — a numeric field with a minimum of 1, or bold plain text while the +dependence is on +
- Ideal Height, Axial Wear, Ideal Radius and Radial Wear Numeric Fields — the two ideal ones +read-only while the dependence is on +
- Delete Button — or a grey em dash while the dependence is on +
+
+ - Dependent Footnote — "Ideal columns mirror the Tool House; only the wear columns are +editable."; only while the dependence is on +
+ - Remove-Row Confirmation Dialog — Remove tool offset, "Remove the offset row for tool +#{id}?", with a cancel button +
+ - Tool Offsets Panel
+
wwwroot-src/src/components/controlTree/SoftNcToolOffsetsPanel.vue— the panel: the dependence +toggle and its two-call enable, the refresh button, the read-only ideal columns, the renameable +key with its duplicate guard, the confirmed delete, the locally appended add, and the Siemens +caption gated on the brand string.
+wwwroot-src/src/components/controlTree/softNcItemTypes.ts— the branch builder: this leaf's id, +its two labels and label keys, and the snapshot flag that switches between them.
+wwwroot-src/src/api/softNcRunner.ts— the typed wrappers: the row shape with its presence and +dependence flags, the whole-row write, the add that returns a minted number, the rename, the +delete, and the refresh call.
+wwwroot-src/src/components/widgets/NumericInput.vue— the numeric cell: commit on blur or Enter, +the minimum rejected inside the widget, the empty-text-to-null parse the panel drops, and the +NaNthat renders as an empty box.
+wwwroot-src/src/components/controlTree/SoftNcEmptyState.vue— the shared no-runner line the +panel opens with.
+wwwroot-src/src/composables/useSoftNcRunner.ts— the shared snapshot behind that first guard and +behind the tree's choice of label.
+wwwroot-src/src/components/controlTree/SoftNcSiemensToolOffsetsPanel.vue— the neighbouring +ledger on Siemens, and the footnote in which it states the opposite sign rule.
+wwwroot-src/src/i18n/en/softNc.ts— the two node labels, the Siemens caveat, the four column +headers, the dependent footnote, the duplicate-key toast, the absent-table line and the panel's +error contexts.
+wwwroot-src/src/i18n/en/common.ts— the shared Tool #, Add, toggle and refresh labels.
+wwwroot-src/src/i18n/en/dialog.ts— the remove-row dialog's title and message.
+Mech/SoftNcRunnerController.cs— the REST surface: the reader with its presence and dependence +flags, the whole-row write, the add that mints the next number, the delete and the rename with +their refusals, the project-level dependence write, and the refresh with its drop of rows the +library no longer holds.
+Common/ApiError.cs— the coded no-project payload the dependence write answers with, the one +answer on this leaf that can be re-rendered in the application locale.
+HiMech/NcParsers/Dependencys/Generic/ToolOffsetTable.cs— the ledger itself: the four stored +components, the subtraction that makes an effective value ideal minus wear, the integer-keyed row +dictionary, and the tool-house update with its row creation and its row removal.
+HiMech/NcParsers/Dependencys/Generic/ToolOffsetTableProxy.cs— the seedless get-or-create +placeholder every brand preset carries in place of the table, and the bare instance it installs +into a project holding none.
+HiMech/NcParsers/Dependencys/IToolOffsetConfig.cs— the single-integer offset contract the +consumers read through, and the pointer to the Siemens tool-and-edge contract beside it.
+HiMech/NcParsers/Dependencys/Siemens/SiemensToolOffsetTable.cs— the neighbouring ledger, and +the addition that makes its effective values geometry plus wear.
+HiMech/NcParsers/SoftNcRunner.cs— the five brand presets, each of which carries the tool-offset +proxy, and the proxy resolution every read of the table goes through.
+HiMech/NcParsers/LogicSyntaxs/ToolHeightOffsetSyntax.cs— the ISO height path: the H word, the +G43 / G44 / G49 arms, and the translation along the tool orientation both brand siblings compose +through.
+HiMech/NcParsers/LogicSyntaxs/G43p4RtcpSyntax.cs— the RTCP path that reads the same height for +the same H word and re-aims it through the kinematics.
+HiMech/NcParsers/LogicSyntaxs/Heidenhain/HeidenhainToolOffsetSyntax.cs— the TOOL CALL path: the +tool number as the offset id, theDLdelta added to the table height, and the absence of a +cancel word.
+HiMech/NcParsers/LogicSyntaxs/Siemens/SiemensToolOffsetSyntax.cs— theDpath's fallback onto +this table when the$TC_DPmap has no row, its warning, and theNaNcase it degrades to zero.
+HiMech/NcParsers/PostLogicSyntaxs/RadiusCompensationSyntax.cs— the radius side: the D word, the +signed offset that flips the compensated side when it goes negative, and the Heidenhain-only +warning for that case.
+HiMech/NcParsers/Dependencys/Fanuc/FanucToolOffsetVariableLookup.cs— the read-only macro window +onto the effective heights, and the address range it answers for.
+HiMech/NcParsers/Syntaxs/FanucSyntaxUtil.cs, +HiMech/NcParsers/Syntaxs/MazakSyntaxUtil.csand +HiMech/NcParsers/Syntaxs/SyntecSyntaxUtil.cs— the three syntax lists that register that window.
+HiMech/MachiningProcs/MachiningActRunnerConfig.cs— the tool-house dependence flag, its stored +default and the element the project serializes it as.
+HiMech/MachiningProcs/MachiningSession.cs— the play loop's refresh of the table from the +library before the first block, run whenever the flag is on.
+HiMech/Machining/MachiningToolHouse.cs— the tool-id-keyed library the refresh walks.
+HiMech/Machining/IMachiningTool.cs— the tool contract the library's entries answer to, and +where the spindle-buckle-to-tool-tip length the refresh reads is declared.
+HiMech/Milling/MillingTools/MillingTool.cs— the assembly walk behind that length, and theNaN+it answers when the walk does not resolve.
+HiNc/MachiningProcs/LocalProjectService.cs— the facade that owns the library and the runner +suit, and the entry point the play-time refresh reaches this table through.
+HiNc/MachiningProcs/MachiningProject.cs— where the library, the runner configuration and the +suit hang off the project, and the load path that leaves the library unset when the project file +names none.
+- Program Data Plane — the plane this leaf sits on, where its table is stored, and +what a brand switch does to the tables beside it +
- Siemens Tool Offsets — the
$TC_DPledger that appears next to this one on +Siemens, and whose wear convention is the opposite of the one stated above
+ genericneeds the brand-neutral table to be the FIRST provider the runner resolves. The +Fanuc, Mazak and Syntec presets do carry that table — seeded with the nineG59.xids — but +always behind their brand table, so the arm is reachable only from a runner composed by hand or +deserialized from a file that put the brand-neutral table first. Which brand carries which +dependency is Brand Matrix.
+nonehas two producers, and a different guard stops each of them. The snapshot's early +return leaves the field's default in place when no runner resolves at all — the same string the +client's empty snapshot carries — and the shared no-runner guard then replaces the whole body. The +type switch's own null arm answersnonefor a runner that does resolve but carries no offset +provider; that state reports a runner, so what replaces the body is the absent-table guard +instead, because the lookup that answerednoneis the one the panel's own read reports absent. +Neither path reaches the caption.
+- Siemens seeds no G58 and no G59 row. The frame table's constructor seeds G54, G55, G56 and G57
+plus the extended G505–G599 series, and nothing else, and no panel on the branch can add an id to
+the set. A
G58in a Siemens program resolves through the same ISO path as on any other brand, +finds no frame, and falls back to a zero offset.
+ - Fanuc and Syntec enumerate an id only while at least one of its three parameter addresses is
+present. IsoCoordinateAddressMap maps G54–G59 onto
#5221with a +stride of 20 and G54.1 P1–P48 onto#7001with the same stride, three consecutive addresses per +entry, and both brand tables seed all 162 of them with zero at construction. So all 54 rows exist +from the first read; the enumeration rule bites only a table whose addresses have been removed. +Native Parameters is the surface that removes one — its +row delete drops a system-parameter address outright, and dropping all three of an entry's +addresses removes that row from this leaf. The same form is where those addresses are visible under +their own numbers, each annotated with the component it holds.
+ - Heidenhain shows six ids for a twenty-row table. The datum table seeds preset rows 1–20 and +shift rows 1–20, and its ISO face aliases only preset rows 1–6 onto G54–G59. Rows 7–20, and every +shift row, have no id here at all. +
- Siemens. This leaf reads and writes through the ISO interface, so the payload is keyed by the
+G-code id. Frames reads and writes the frame dictionary directly,
+keyed by the same string. Both panels render the same id set with the same values, and a
G54edit +made on either is the same assignment. The two routes part on one id only: this leaf's write goes +through the coordinate accessor and drops aG500, where the frame route would assign it into the +dictionary — an id neither panel ever sends. Nor are the three value columns the whole of what the +frame table holds: they are the X / Y / Z translation, while the translation components for other +axis letters live in a second dictionary that only the$P_UIFRbridge writes and neither panel +exposes. What does differ between the two panels is furniture — this leaf carries the Actions +column with P0 and M0, and Frames has no counterpart for it.
+ - Heidenhain. This leaf's
G54…G59rows are preset rows 1–6. +Datum Tables shows the same six cells under the Q339 +column, alongside rows 7–20 and the whole datum-shift table, which this leaf cannot reach. So the +aliasing is one-directional in coverage: everything on this leaf is on that one, and most of that +one is not here.
+ - Fanuc, Mazak and Syntec write three consecutive parameter addresses, all three unconditionally. +
- Siemens replaces the frame entry for that id. +
- Heidenhain replaces the preset row the id aliases onto. +
- M0 sets the row to machine zero. +
- P0 sets it to the machine coordinate at which the workpiece's program-zero anchor sits with +every dynamic axis of the equipment assembly — not only the machining chain's — stepped to zero, +falling back to the machining chain's table buckle when the workpiece declares no program-zero +anchor. +
- Where it is stored. The id goes to the equipment-display surface, which assigns it on the +shared user configuration and schedules a debounced save of that file. It is device state, not +project state: it is not written into the project, it outlives the project that set it, and a +failure of the file write itself is logged rather than reported. +
- What reads it. The canvas marker resolves the first +IIsoCoordinateConfig on the active runner — the same object this +panel edits — so an offset edited here moves the marker on the next frame. A marked id the provider +does not answer draws nothing at all, which is the state after a brand switch leaves behind an id +the new provider has never heard of. The marker is also suppressed while its Scene flag is off, and +while no machining chain resolves an anchor. +
- How it fails. The handler follows the same optimistic shape as a cell commit — assign, await, +restore and toast on failure — but it is the branch's one call through the plain-JSON helper rather +than the envelope helper, so only a non-2xx status throws there. On mount the panel seeds its +highlight from the same surface and swallows any failure, so an unhighlighted table is not evidence +that nothing is marked. +
- What triggers it. The click handler is bound on the whole row and nothing inside the row stops +propagation, so editing a cell or pressing P0 or M0 marks that row as well as doing its own work. A +click on the row already marked returns immediately, which is what keeps repeated cell edits on the +marked row from re-writing the user configuration on every pass. +
- General Setup Control Tree — the left dock of
/general-setup+-
+
- Controller Node Row
+
-
+
- Program Data Group Row —
equipment/controller/program-data+-
+
- Work Coordinates (G54…) Node Row —
equipment/controller/program-data/work-coordinates
+
+ - Work Coordinates (G54…) Node Row —
+ - Program Data Group Row —
+ - Controller Node Row
+
- Editor Row — the panel of whichever node is selected
+
-
+
- Work Coordinates (G54…) Panel
+
-
+
- No-Runner Body — "No NC runner — load a project first."; the shared empty state, shown +instead of everything below while the snapshot reports no runner +
- Absent-Table Body — "No work-coordinate table on the active runner."; shown instead of +everything below while the panel's own read reports no provider +
- Header Strip
+
-
+
- Storage Caption — the one-line note for the snapshot's coordinate kind, left-aligned and grey +
- Show all Toggle — right-aligned, dense; rendered only while a row exists that the +always-visible test rejects +
+ - Coordinate Table — dense, flat, bordered; no sort, no pagination, no column menu
+
-
+
- Header Row — Id, X (mm), Y (mm), Z (mm), Actions. The three axis headers +are literals in the template; Id and Actions are the shared translated labels +
- Coordinate Row, one per visible id — the whole row is clickable and carries the tooltip
+“Click to mark this coordinate on the General Setup canvas”; the marked row is tinted
+
-
+
- Id Label — bold plain text, never editable +
- Value Numeric Field, one per axis column — no minimum, no maximum, no unit suffix +
- P0 Button — flat, dense, tooltip “Set to the machine position at program zero” +
- M0 Button — flat, dense, tooltip “Set to machine zero” +
+
+
+ - Toast — negative, three seconds, the panel's context followed by the server's own message +
+ - Work Coordinates (G54…) Panel
+
wwwroot-src/src/components/controlTree/SoftNcWorkCoordinatesPanel.vue— the panel: the storage +caption's five-way switch, the always-visible test and the conditional Show all toggle, the +whole-triad cell commit with its non-finite guard, the P0 / M0 actions and their re-read, and the +row click that writes the canvas marker.
+wwwroot-src/src/components/controlTree/softNcItemTypes.ts— the branch builder: this leaf's node +id and label key, and its position as the first ungated member of the program-data stem.
+wwwroot-src/src/components/controlTree/SoftNcEmptyState.vue— the shared no-runner line the panel +opens with.
+wwwroot-src/src/components/widgets/NumericInput.vue— the value cell: commit on blur or Enter, +the empty-text-to-null parse, and the infinity and NaN literals the panel's handler then rejects.
+wwwroot-src/src/api/softNcRunner.ts— the snapshot's coordinate-kind field and its parser, the +work-coordinate reader and its row shape, the whole-triad setter, the two zeroing actions, and the +brand switch's carry flag.
+wwwroot-src/src/api/equipmentSetup.ts— the canvas marker id: the getter the panel seeds its +highlight from and the setter the row click writes, on the equipment-display surface rather than +this branch's own.
+wwwroot-src/src/api/http.ts— the two helpers this panel mixes: the envelope helper every table +call uses, and the plain-JSON helper the marker call uses.
+wwwroot-src/src/composables/useSoftNcRunner.ts— the shared snapshot the coordinate kind and the +no-runner guard are read from.
+wwwroot-src/src/components/controlTree/SoftNcFramesPanel.vue— the other node on the Siemens +frame table: the same rows through the frame-keyed reader, with an unconditional toggle and a +narrower always-visible test.
+wwwroot-src/src/components/controlTree/SoftNcDatumTablePanel.vue— the other node on the +Heidenhain datum table: the preset rows this leaf aliases, plus the rows and the shift table it +does not reach.
+wwwroot-src/src/components/controlTree/SoftNcBrandPanel.vue— the carry checkbox and its default.
+wwwroot-src/src/i18n/en/softNc.ts— the node label, the two empty bodies, the five storage +captions, the row and button tooltips, and the five error contexts.
+wwwroot-src/src/i18n/en/common.ts— the shared Id and Actions column headers and the +Show all label.
+Mech/SoftNcRunnerController.cs— the REST surface: the coordinate-kind type switch in the +snapshot builder, the reader that enumerates the provider's ids and zero-fills a null offset, the +whole-triad writer, the two zeroing actions with the program-zero lookup's own failure message, and +the brand switch's capture-swap-carry-sweep with its accepted-id filter and its instance guard.
+Mech/EquipmentSetupDisplayController.cs— the marker id's reader and writer, the user-config slot +it lands in, and the debounced save that follows.
+Disp/EquipmentSetupDisplayee.cs— the marker's binding to the first offset provider on the active +runner, and the guards that drop it with no chain or with its Scene flag off.
+Disp/EquipmentSetupDisplayeeConfig.cs— the stored marker id, itsG54default and its +serialization.
+Environments/UserService.cs— the loose save behind the marker write, and the failure it logs +rather than returns.
+HiMech/NcParsers/Dependencys/IIsoCoordinateConfig.cs— the offset-provider contract: the id-keyed +get and set, and the id enumeration every row of this leaf comes from.
+HiMech/NcParsers/Dependencys/IsoCoordinateAddressMap.cs— the Fanuc-family address scheme shared +by the Fanuc and Syntec tables: the two base addresses and the stride, the read that treats an +entry with no address as absent, the write that lays down all three, the enumeration this leaf's +rows follow, the per-address description the native form annotates with, and the seeding of every +entry with zero.
+HiMech/NcParsers/Dependencys/Fanuc/FanucParameterTable.cs— the Fanuc and Mazak provider: its +offset accessors over the address map, its default table, and the variable lookup that also exposes +the same addresses to a macro program.
+HiMech/NcParsers/Dependencys/Syntec/SyntecParameterTable.cs— the Syntec provider: the same +address scheme without the variable lookup.
+HiMech/NcParsers/Dependencys/Siemens/SiemensFrameTable.cs— the Siemens provider: the seeded +G54–G57 and G505–G599 ids, the cancel-frame handling that keeps one code out of the row set, and +the per-axis translation dictionary neither panel exposes.
+HiMech/NcParsers/Dependencys/Heidenhain/HeidenhainDatumTable.cs— the Heidenhain provider: the +twenty preset and twenty shift rows, the G54–G59 alias onto preset rows 1–6, and the synthetic ids +the datum cycles resolve through.
+HiMech/NcParsers/Dependencys/Generic/IsoCoordinateTable.cs— the brand-neutral table behind the +genericarm, and the fifteen ids it seeds.
+HiMech/NcParsers/Dependencys/Generic/IsoCoordinateTableProxy.cs— its get-or-create proxy, the +other shape a runner can carry it in.
+HiMech/NcParsers/Dependencys/Siemens/SiemensFrameTableProxy.cs, +HiMech/NcParsers/Dependencys/Heidenhain/HeidenhainDatumTableProxy.cs— the seedless proxies those +two brands carry: each installs a fresh table into a project holding none, which is why those two +brands open on their constructors' defaults.
+HiMech/NcParsers/Dependencys/Fanuc/FanucParameterTableProxy.cs— the seeded proxy the Fanuc and +Mazak presets share, and the reason those two resolve one instance.
+HiMech/NcParsers/SoftNcRunner.cs— the five brand presets and the offset provider each carries, +the proxy resolution every read goes through, and the legacy import that replays an older +coordinate table into whichever provider resolves.
+HiMech/NcParsers/LogicSyntaxs/CoordinateOffsetUtil.cs— the run-time resolver that walks every +provider and takes the first non-null answer, and the translation it composes.
+HiMech/NcParsers/LogicSyntaxs/IsoCoordinateOffsetSyntax.cs— the G-word path: the flag vocabulary +it consumes, the modal lookback that keeps a coordinate alive across blocks, and the zero fallback +when nothing answers.
+HiMech/NcParsers/LogicSyntaxs/Heidenhain/HeidenhainCoordinateOffsetSyntax.cs— the datum-cycle +path into the same table, and the synthetic ids it writes.
+HiMech/NcParsers/Keywords/Generic/IsoKeywords.cs— the recognised G-word series.
+HiMech/NcParsers/Keywords/Siemens/SiemensKeywords.cs— the extended frame series and the cancel +frame.
+HiMech/NcParsers/Initializers/StaticInitializer.cs— the three brand defaults for the coordinate +active at the first block.
+HiMech/Machining/MachiningEquipmentUtils/MachiningEquipmentUtil.cs— the program-zero machine +position P0 writes: the reflection that zeroes every dynamic branch of the equipment assembly, and +the anchor fallback when the workpiece declares none.
+- Program Data Plane — the plane this leaf opens, and the per-case tables beside it +that a brand switch keeps or sweeps +
- Frames — the other node on the Siemens frame table, where the same rows are +edited as settable frames rather than as G54 offsets +
- Datum Tables — the other node on the Heidenhain datum table, carrying the +preset rows this leaf aliases plus the fourteen rows and the shift table it cannot reach +
- the
equipment/fixturebranch of the General Setup page's Control Tree +(/general-setup?tree=equipment/fixture), an equipment child of the General Setup group between +Coolant and Workpiece./fixtureand anything below it redirects there;
+ - in the WPF client, a sub-window opened from the Main Panel. +
- Key Model:
+
-
+
- Fixture +On the web it is addressed by an IndexService key handed to the +Object Management Menu Button and installed back onto +the project through the controller's UpdateFixture. On the WPF client the same button is wired +with a TargetObjectGetter / TargetObjectSetter pair (see that page for the design pattern). +
+ - Assistant Model:
+
-
+
- LocalProjectService — owns the fixture as +Fixture. +
+ - Fixture Root Panel —
equipment/fixture, item typeFixtureRoot+-
+
- Object Management Menu Button — file extension
+
.Fixture, load typeHi.NcMech.Fixtures.Fixture, HiMech, rel fileFixture.xml, based at the +project directory. Load / Save As / Copy / Paste / XML.
+ - Geometry Type Badge — the attached geometry's kind name,
nonewhen the slot is empty.
+ - Intro caption, and an empty-state caption while no fixture key is minted. +
+ - Object Management Menu Button — file extension
+
equipment/fixture/geometry— “Geometry”, a Geometry slot backing +Geom. The slot's own panel is the kind picker +(Geometry Management Control, selector only); the picked kind's +editor is the slot's child item. Kinds offered:Box3d,Cylindroid,StlFile, +TransformationGeom,GeomCombination, plus None. The geometry must satisfyIStlSource, so the +runtime-only voxelCubeTreeFileis not among them.
+equipment/fixture/anchor— “Anchor”, a Group stem whose info text states how the fixture is +placed. Two Transformer slots hang from it, each a kind picker +(Transformer Manage Panel) over the picked kind's child editor: +-
+
equipment/fixture/geom-to-workpiece— “Geom To Workpiece”, +GeomToWorkpieceTransformer, which places the workpiece buckle +— where the workpiece attaches — relative to the fixture geometry (see +Workpiece).
+equipment/fixture/geom-to-table— “Geom To Table”, +GeomToTableTransformer, which pins the fixture geometry onto +the machine table buckle.
+- Kinds offered on both:
StaticTranslation,StaticRotation,StaticFreeform, +DynamicTranslation,DynamicRotation,GeneralTransform,NoTransform.
+
+- Shared slave view: the General Setup equipment canvas in the page's MAIN column, shared with +Machine Tool, Workpiece and Controller (see General Setup Page). Its +Display Options dropdown carries this branch's share of the scene — the Fixture +Solid / Edge / Hide radio group, and the Fixture Geom Anchor, Workpiece Buckle and Table Buckle +flags in the Anchors group. +
- Head Line
+
-
+
- Object Management Menu Button — file extension
+
.Fixture, load type Fixture, rel fileFixture.xml. The pointed +Editor Panel is the Management Tabs Panel, a ContentPresenter the button swaps between the tab +stack and XML mode.
+ - Title Label +
+ - Object Management Menu Button — file extension
+
- Management Tabs Panel — the same content the tree renders as nodes, stacked as tabs:
+
-
+
- Geometry Tab — Geometry Management Control over +Geom. +
- Anchor Tab — a nested tab stack, each tab a
+Transformer Manage Panel:
+
-
+
- Geom To Workpiece Tab +
- Geom To Table Tab +
+
+ - Viewer Panel
+
-
+
- Viewer ToolBar
+
-
+
- RenderingCanvas Tool Bar +
- Display Options menu — the Show Geom Anchor, Show Workpiece Buckle and Show Table Buckle +checkboxes and the Solid / Edge / Hide rendering-mode radio group, all writing +FixtureEditorDisplayeeConfig. +
+ - RenderingCanvas — its DispEngine.Displayee is +FixtureEditorDisplayee. +
+ - Viewer ToolBar
+
- ClearGeomCache() runs after any change at or below the branch. On +the web each node's afterChange chain ends in the controller's ClearGeometryCache, which calls it; +in WPF the page calls it directly from the geometry control's setter and update callbacks. +
- The two anchor slots re-commit their swap-in before clearing the cache: an inner-value edit +re-posts UpdateGeomToWorkpieceTransformer / UpdateGeomToTableTransformer, so the object the +switchboard produced is always the one installed on the owning field when the redraw happens. +
- Load / Paste / XML-Apply only swap the IndexService entry. The tree root then installs the result +onto the project through UpdateFixture — the equipment setter re-attaches the buckles itself — +re-runs Initialize to re-mint the branch's keys, and clears the geometry cache. The button's XML +dialog re-emits its object-loaded event on Apply, so that install and refresh chain runs exactly +once. +
- Kind pickers are parent-aware. Picking a geometry kind calls CreateGeometry, which installs the
+new object on Geom server-side and maps the literal
Noneto +null; picking a transformer kind creates the object and then rebinds the owning field.
+ - View snapping differs by client. The WPF page snaps its canvas to the isometric view when the +displayee is bound, and again whenever a new geometry object is set, on the assumption that a +shape swap changes the scene more than an edit within one shape does. The shared web canvas snaps +once, when its displayee is bound — the display controller's initialize does it server-side. +Afterwards a geometry set only clears the cache, and the operator re-frames from the +RenderingCanvas Tool Bar's view picker. +
- Both canvases draw the live model, so an edit shows on the next frame with no further calls. +
wwwroot-src/src/components/controlTree/useControlTreeHost.ts— builds theequipment/fixture+root (item typeFixtureRoot, keyed on the IndexService fixture key) with itsgeometryslot and +theanchorGroup holdinggeom-to-workpieceandgeom-to-table; owns the clear-cache and +transformer-rebind afterChange chains, the create hooks and the Object Management install +handlers.
+wwwroot-src/src/components/controlTree/PrimarySlavePanel.vue— renders the root editor inline: +the Object Management button, the geometry-type badge and the intro / empty-state captions.
+wwwroot-src/src/components/controlTree/itemTypes.ts— the ItemType registry that binds the +branch's Geometry and Transformer slots and every concrete kind to their panels, and grows the +sub-tree from the indexed object's type.
+wwwroot-src/src/components/controlTree/GeometrySlotPanel.vue— the Geometry node's kind picker.
+wwwroot-src/src/components/controlTree/TransformerSlotPanel.vue— the Geom To Workpiece and +Geom To Table nodes' kind picker.
+wwwroot-src/src/components/controlTree/SoleEditorPanel.vue— hosts the concrete kind's own +editor once a geometry or transformer kind is chosen.
+wwwroot-src/src/components/controlTree/ControlTreeDock.vue— the left dock: the Control Tree row +over the PRIMARY editor row, with a draggable height divider.
+wwwroot-src/src/pages/GeneralSetupPage.vue— the three-column host page (dock / content column / +canvas) that provides the equipment-scope control-tree host.
+wwwroot-src/src/components/mech/EquipmentSetupPanel.vue— the shared canvas column: the +RenderingCanvasToolBar, the Display Options dropdown and the RenderingCanvas.
+wwwroot-src/src/components/widgets/ObjectManagementMenuButton.vue— the reused +object-management dropdown, keyed entirely on itsmodelKey(see +Object Management Menu Button).
+wwwroot-src/src/components/geom/GeometryEditor.vue— the generic geometry kind switchboard +reached from the Geometry slot panel, including its None (unset) entry.
+wwwroot-src/src/components/topo/TransformerSelectPanel.vue— the generic transformer kind +switchboard reached from the Transformer slot panels.
+wwwroot-src/src/api/fixture.ts— typed client for/api/Fixture/*: Initialize, UpdateFixture, +the three Get…Type reads, UpdateGeometry, the two Update…Transformer rebinds, CreateGeometry and +ClearGeometryCache.
+wwwroot-src/src/api/equipmentSetup.ts— typed client for the shared canvas: binds the +equipment-setup displayee onto the connection's engine and drives its display options.
+wwwroot-src/src/router/routes.ts— declaresgeneral-setupand thefixture/:rest(.*)*+redirect onto?tree=equipment/fixture.
+Mech/FixtureController.cs— REST surface at/api/Fixture: Initialize, UpdateFixture, +GetGeometryType, GetGeomToWorkpieceType, GetGeomToTableType, UpdateGeometry, +UpdateGeomToWorkpieceTransformer, UpdateGeomToTableTransformer, CreateGeometry, +IndexCurrentGeometry and ClearGeometryCache.
+Mech/EquipmentSetupDisplayController.cs—/api/mech/equipment-setup-display: binds and +configures the displayee per rendering connection and serves its whole option set — the three +rendering-mode endpoints (fixture, raw-geom, ideal-geom), show-machine, show-tool, +show-dimension-bar, show-fixture-geom-anchor, show-workpiece-buckle, show-table-buckle, +show-workpiece-geom-anchor, show-program-zero-anchor, show-meshed-geom, +show-controller-coordinate and controller-coordinate-id.
+Disp/EquipmentSetupDisplayee.cs— the displayee the General Setup canvas renders: the merged +fixture + workpiece scene with the anchor, buckle and controller-coordinate overlays.
+HiMech/NcMech/Fixtures/Fixture.cs— the key model: the geometry, the table and workpiece +buckles, the geom anchor, the two anchor transformers and ClearGeomCache.
+HiMech/NcMech/Fixtures/FixtureEditorDisplayee.cs— the WPF fixture canvas's displayee.
+HiMech/NcMech/Fixtures/FixtureEditorDisplayeeConfig.cs— its option set: ShowGeomAnchor, +ShowWorkpieceBuckle, ShowTableBuckle and the rendering mode.
+- Mechanism Builder Page — reuses this page’s parent-aware transformer rebind pattern +
- Replaces the URL's
treequery withequipment.
+ - Moves the selection to the
equipmentgroup root.
+ - A mission Program File command whose effective kind is that kind — its explicit kind, or, for
+the default Auto, the kind detected from the file extension:
.cl,.clsand.clsfare CL, +.csvis CSV, and every other extension is brand NC code +(DetectByPath(API)).
+ - A mission script command whose text contains
CsvFile(orClFile(. It is a bare substring +test rather than a list of verbs. The play verbs end in it — +PlayCsvFile(API) and +PlayClFile(API) — and on the CSV side so do +the two sensor-mapping verbs, +MapSingleByCsvFile(API) and +MapSeriesByCsvFile(API), so a script that +only maps recorded telemetry onto steps counts as CSV evidence though it plays no control table. +The CL side carries no such neighbour: every member whose call text ends inClFile(plays or runs +a CLSF file.
+ - For CL only, a machining chain that is a ClMillingDevice, since a +pure-CL project plays nothing else. +
- The cutter-location prefix is stored, serialized and editable, and no syntax in the shipped CSV +pipeline reads it. Columns under it are not consumed as coordinates; they survive into the +residual telemetry below. +
- Naming a column consumes it. Each syntax removes the columns its tags name from the decoded +row, and whatever is left over is carried onto the step as recorded data — sensor channels, file +and line bookkeeping, anything else the file holds. So clearing a tag does more than stop the value +being used: it moves that column into the residual set. +
- The duration cell is read as a time span rather than as a number of seconds, despite the
+field's own
(s). A cell that does not parse falls back to the difference between this row's +actual time and the previous parseable one, and a resolved duration longer than one minute is +clamped to one minute, on the reasoning that a longer gap is spliced recordings rather than +machining time.
+ - An empty field is a real edit. The write applies every field the payload carries, and the panel +sends exactly the field that changed, so blanking a box clears that tag on the model rather than +leaving it alone. +
- App Menu Bar
+
-
+
- Preference ▾ Dropdown — fetches both runner snapshots as it opens, and only with a project
+open
+
-
+
- CSV Controller CheckBox
+
-
+
- Usage Caption — “This project plays CSV”, “Not used by this project”, or blank +
+ - CL Controller CheckBox
+
-
+
- Usage Caption — the same three states, for CL +
+
+ - CSV Controller CheckBox
+
+ - Preference ▾ Dropdown — fetches both runner snapshots as it opens, and only with a project
+open
+
- General Setup Control Tree — the left dock of
/general-setup+-
+
- General Setup Group Row —
equipment+-
+
- Controller Node Row — the brand branch, built ahead of the two rows below +
- CSV Controller Node Row —
equipment/controller-csv; present only under the conditions +above, and pushed before the CL row when both are
+ - CL Controller Node Row —
equipment/controller-cl; the last row the group can carry. Each +is a plain label with no icon, no checkbox and no children
+
+
+ - General Setup Group Row —
- Editor Row — the panel of whichever node is selected
+
-
+
- General Setup Group Panel — where a switched-off node's selection lands: the group's intro +caption over a bordered, separated list of its children, one clickable row each +
- CSV Controller Panel
+
-
+
- No-Project Body — "No project loaded." +
- No-Config Body — "No CSV column config on the CSV runner." +
- Editor Body
+
-
+
- Intro Caption — the column-tags line, with
PlayCsvFile("…")set in code
+ - Tag Text Field, one per row, in this order: Machine coordinate prefix (hint e.g. “MC.” → +MC.X / MC.Y / MC.Z), Cutter location prefix (hint e.g. “CL.” → CL.X / CL.Y / CL.Z), +Tool id column, Spindle speed column (rpm), Spindle direction column, +Feedrate column (mm/min), Step duration column (s) (hint Overrides feedrate-derived +timing.), Actual time column (hint Wall-clock instant of the row.), Coolant column +(hint Flood / Mist / Off or on/off.), Line-begin C# script column (hint Runs before the +row.), Line-end C# script column (hint Runs after the row.) +
+ - Intro Caption — the column-tags line, with
+ - CL Controller Panel
+
-
+
- No-Project Body — "No project loaded." +
- No-Config Body — "No CLSF config on the CL runner." +
- Editor Body
+
-
+
- Intro Caption — the plays-CLSF line, with
PlayClFile("…")set in code
+ - Rapid feedrate (assumed) Numeric Field — suffixed
mm/min, minimum 1
+ - Rotary rapid feedrate (assumed) Numeric Field — suffixed
deg/min, minimum 1
+ - Prefer Tool House on LOAD/TOOL Toggle +
- Policy Caption — the On: … Off: … explanation, always shown +
- Excluded record words Chip Field — free-text entry with no dropdown list, hint Record +words consumed silently as intentional skips (case-insensitive). Type + Enter to add. +
+ - Intro Caption — the plays-CLSF line, with
+ - Toast — negative, three seconds, the panel's context followed by the server's own message +
+ wwwroot-src/src/components/controlTree/useControlTreeHost.ts— the build rule that mints the two +nodes and where in the equipment group they sit, the checkbox watch that steps the selection off a +node losing its box before the rebuild, and the route-adoption guard that refuses an id the built +tree lacks.
+wwwroot-src/src/components/controlTree/runnerSuitItemTypes.ts— the two item types and the panels +they bind to; its own docblock names the usage evidence and the persisted selection as reveal +conditions, which the host's build rule does not.
+wwwroot-src/src/components/controlTree/CsvRunnerPanel.vue— the column-tag form: the two guards, +the field list with its labels and hints, the blur-and-Enter commit with its equality guard against +the fetched mirror, and the single-field payload.
+wwwroot-src/src/components/controlTree/ClRunnerPanel.vue— the CLSF form: the two rapid rates, +the cleared and non-finite values it drops before the request, the equality guard on each rate and +on the tool-house toggle, and the unguarded whole-list chip write.
+wwwroot-src/src/composables/useViewPrefs.ts— the device-local browser-storage record behind both +checkboxes: their default, their persistence, and the record they share with the tree's expansion +and last-selection memory.
+wwwroot-src/src/components/AppMenuBar.vue— the Preference dropdown's two checkboxes, and the +caption refresh that fetches both snapshots as the menu opens.
+wwwroot-src/src/api/csvRunner.tsandwwwroot-src/src/api/clRunner.ts— the two snapshot shapes, +their pre-fetch empty values, and the typed wrappers over each REST surface.
+wwwroot-src/src/api/http.ts— the plain-JSON read that inspects no envelope, beside the envelope +write that throws asuccess: falsemessage.
+wwwroot-src/src/components/widgets/NumericInput.vue— the field behind the two rapid rates: commit +on blur or Enter, and the minimum that reports inline without emitting.
+wwwroot-src/src/components/controlTree/PrimarySlavePanel.vue— the editor row: the remount key +that refetches when the selection moves, and the select-an-item hint a refused deep link leaves +behind.
+wwwroot-src/src/components/controlTree/GroupInfoPanel.vue— the General Setup group's editor, +whose clickable child list gains and loses these two rows with the tree.
+wwwroot-src/src/components/controlTree/itemTypes.ts— the registry the two item types are spread +into, and the panel lookup the editor row resolves through.
+wwwroot-src/src/router/treeRoutes.ts— the id migration applied to a?tree=value before the +reveal test reads it, and the page roots a foreign id is redirected by.
+wwwroot-src/src/pages/GeneralSetupPage.vue— the route that creates the equipment-scoped tree host +and calls its initialization.
+wwwroot-src/src/layouts/MainLayout.vue— the project epoch that rebuilds the page when a project +loads, which is what lets a link opened against an empty application land afterwards.
+wwwroot-src/src/i18n/en/tree.ts— the two node labels, both panels' intros, empty lines, field +labels, hints and error contexts.
+wwwroot-src/src/i18n/en/menu.ts— the two checkbox labels and the two usage-caption strings.
+wwwroot-src/src/i18n/en/common.ts— the shared “No project loaded.” line both panels open with.
+Mech/CsvRunnerController.cs— the CSV suit's REST surface: the snapshot, the write that applies +only the fields the payload carries, and the success envelope a missing config is reported in.
+Mech/ClRunnerController.cs— the CL suit's REST surface: the same envelope, the positivity checks +on the two rates, and the trim-and-replace of the excluded-word list.
+Mech/NcSuitUsage.cs— the usage scan behind the two captions: the mission walk that reads through +a command's enable wrapper, the two script substrings, the extension detection for an Auto Program +File, and the CL-only chain test.
+HiNc/MachiningProcs/MachiningProject.cs— the two resident suits as property-initialized members, +their load paths with the fall back to a fresh pipeline, and the migration of the older flat CSV +column element into the suit.
+HiNc/MachiningProcs/LocalProjectService.cs— the re-bind that wires both suits to the host +alongside the brand suit, and the chain walk that clears a generic axis configuration before +stamping the chain's axes onto it.
+HiNc/MachiningProcs/SessionShell.cs— the script verbs whose call text the caption's evidence +scan matches: the two play verbs, and the two CSV sensor-mapping verbs that end in the same +substring.
+HiNc/SessionCommands/NcFileCommand.cs— the Program File command whose kind the scan reads, and +its Auto default.
+HiNc/SessionCommands/EnablingWrapper.cs— the enable wrapper the scan walks through without +reading, which is why a switched-off command still counts as evidence.
+HiMech/MachiningProcs/NcKind.cs— the kind enum and the closed extension sets behind the Auto +detection.
+HiMech/Numerical/CsvParsers/GeneralCsvRunner.cs— the factory that assembles the suit's runner as +the CSV pipeline, and the place its column configuration sits in that pipeline.
+HiMech/Numerical/CsvParsers/CsvRunnerConfig.cs— the eleven tags, their serialization, and the +constants they default to.
+HiMech/Mapping/MappingUtil.cs— those constants.
+HiMech/Numerical/CsvParsers/CsvSegmenter.cs— the header row the tags are matched against, and +the quote-then-whitespace trim applied to each title.
+HiMech/Numerical/CsvParsers/RowToSyntaxs/RowToMachineCoordinateSyntax.cs— the six axis names the +machine-coordinate prefix is composed with, and the consumption of those columns whether or not +they resolved.
+HiMech/Numerical/CsvParsers/CsvSemantics/CsvTimingUtil.cs— the duration parse, the actual-time +fallback and the one-minute clamp.
+HiMech/Numerical/CsvParsers/CsvSemantics/CsvActDataSemantic.cs— the residual columns carried onto +the step, which is where a column no tag names ends up.
+HiMech/Numerical/ClsfParsers/NxClRunner.cs— the factory that assembles the CLSF pipeline, and +the configuration's place in it.
+HiMech/Numerical/ClsfParsers/ClsfRunnerConfig.cs— the four values, their defaults, and the +axis-uniform rapid-rate provider the machine-coordinate motion semantics read.
+HiMech/Numerical/ClsfParsers/ClMotionValveSemantic.cs— the router that sends a block carrying +machine coordinates to the semantics reused from the NC pipeline and every other block to the +pure-CL one, which is what decides whether the rotary rate is read at all.
+HiMech/Numerical/ClsfParsers/ClMotionSemantic.cs— the pure-CL motion semantic, timing a rapid +from the linear rate taken directly off the configuration.
+HiMech/Numerical/ClsfParsers/ClsfRecordCleanupSyntax.cs— the excluded-word match, and the +validation warning it replaces with a silent consume.
+HiMech/Numerical/ClsfParsers/ClsfToolBuildSemantic.cs— the tool-house policy: the entry that +wins, the file's tool data that builds a missing one, and the error when neither answers.
+HiMech/Milling/ClMillingDevice.cs— the chain type that is CL usage evidence on its own.
+- General Setup Page — the page whose tree these two nodes are minted into, and the +equipment items they sit after +
- Preference Menu Dropdown — the dropdown holding the two checkboxes and their usage captions, +and the other per-user settings beside them +
- Brand Matrix — the other kind of missing node: which leaf the brand branch +grows on which brand, and the snapshot flag behind each one +
- Machine Tool — The kinematic chain the whole scene hangs from, and the standalone route that loads and previews it +
- Spindle Capability — The spindle envelope: thermal condition, gear shift, dry run, power and torque +
- Background / Coolant — The two scene branches that are not machine parts: the backdrop and the coolant model +
- Fixture — What holds the workpiece, its geometry and the two anchors that place it +
- Workpiece — Raw and target geometry, the anchors that locate them, the mesh, and the material +
- Controller — The SoftNcRunner-native controller branch: its two planes, the brand matrix behind which nodes appear at all, and the contract every leaf edits by +
- Hidden Controller Branches — The two runner-suit nodes a fresh installation never builds, the device-local checkboxes that reveal them, and the second kind of invisibility a link cannot undo +
- Execution Page — the other Control-Tree page, and the one this was split out of +
- Tool House Page — the third equipment surface, a tab page rather than a tree page +
- Main Panel — the shell whose Page menu reaches this route +
- Control Tree — the engine behind this page's tree: how a branch is built, rebuilt and selected +
- Controller Branch — the branch this page hosts whose node set changes with the controller brand +
- Hidden Controller Branches — the two equipment nodes this page withholds until a preference or a link asks for them +
- Legacy Controller — the superseded controller screen at its own route, and the three settings only it edits +
- the
equipment/machine-toolbranch of the General Setup page's Control Tree +(/general-setup?tree=equipment/machine-tool), the first equipment child of the General Setup +group and a leaf: chain structure belongs to the +Mechanism Builder Page, so the root carries no child slots;
+ - the
/machine-toolroute, a standalone surface pairing a chain-only canvas with a load button +and a GUI / XML view of what is loaded. Its fields are read-only, but the route is not: its Load +installs the picked chain file onto the project. It carries no Page-menu entry and is reached by +URL;
+ - in the WPF client, a sub-window opened from the Main Panel. +
- Key Model:
+
-
+
- IMachiningChain +On the web the chain is addressed by an IndexService key handed to the +Object Management Menu Button and installed back onto +the project through the controller's Update. On the WPF client the same button is wired with a +TargetObjectGetter / TargetObjectSetter pair (see that page for the design pattern). +
+ - Assistant Model:
+
-
+
- LocalProjectService — owns the chain as the +MachiningChain / +MachiningChainFile pair. +
- MachiningProject +
+ - Machine Tool Root Panel
+
-
+
- Object Management Menu Button
+
-
+
- New Items: one entry,
New ClMillingDevice(“CL-driven blank device”). Its parameterless form +is the complete CL-driven device; any other chain type arrives by Load, Paste or XML.
+ - Load / Save As / Copy / Paste / XML, over the project directory and the admin directory. +
- File extensions are
.MachineTooland.mt, so the Load / Save As filter reads +*.MachineTool / *.mt / *.xml; the load type is IMachiningChain.
+
+ - New Items: one entry,
- Type Badge — the chain's runtime type name,
nonewhen no chain is attached.
+ - Read-only caption lines, shown once a chain is attached: + + +
- Intro caption, and an empty-state caption while no project is open. +
+ - Object Management Menu Button
+
- Shared slave view: the General Setup equipment canvas, whose Solid group's Machine flag draws
+this chain (see General Setup Page). That flag starts cleared, so
+the shared canvas shows a correctly attached chain only after it is ticked; the
/machine-tool+route's own canvas has no such flag and always draws it.
+ - Header Row
+
-
+
- Title Label “Machine Tool” +
[relFile]caption — the loaded chain's project-relative path, when it has one.
+- Folder-icon Load Button — opens the server-side file explorer dialog, restricted to the
+
ProjectDirectoryandResourceDirroots and filtered toMachine Tool (.MachineTool|.mt). +Disabled until a project is open.
+ - Read-only Name Field, shown once a chain is loaded. +
- GUI / XML View Toggle, disabled until a chain is loaded. +
+ - Body — two columns
+
-
+
- Left: Identity Card or XML Source
+
-
+
- GUI mode — a read-only Name input, a read-only auto-grow Note textarea, and a Type chip. +
- XML mode — the chain serialised to XML, badged
read-only, with a refresh button. The text is +fetched lazily on the first switch to XML and re-fetched after a load while XML mode is open.
+
+ - Right: Viewer Panel
+
-
+
- RenderingCanvas Tool Bar and a connection badge. +
- RenderingCanvas, cache id
MachineToolCanvas— this route's own canvas.
+
+
+ - Left: Identity Card or XML Source
+
- Head Line
+
-
+
- Object Management Menu Button
+
-
+
- file extension is
mt; the load type is IMachiningChain.
+ - The pointed Editor Panel is Management Panel — a ContentPresenter the button swaps between the +built GUI panel and an AvalonEdit text editor for XML mode. +
+ - file extension is
- Title Label “Machine Tool” +
+ - Object Management Menu Button
+
- Management Panel
+
-
+
- If the key model inherits INameNote:
+
-
+
- Name Setting Line
+
-
+
- Name Label +
- Name TextField — its TextChanged writes straight onto the chain. +
+ - Note Setting Line
+
-
+
- Note Label +
- Note TextField — multi-line, same write-through. +
+
+ - Name Setting Line
+
- Type Line — italic
Type: {chain type}.
+ - Empty-state label when no chain is loaded. +
+ - If the key model inherits INameNote:
+
- Load / Paste / XML-Apply only swap the IndexService entry. The tree root then installs the result +onto MachiningChain — the setter re-attaches the +fixture and workpiece buckles and wires the runtime hooks itself — re-mints the key and rebuilds +the branch. The button's XML dialog re-emits its object-loaded event on Apply, so that install and +refresh chain runs exactly once. +
- A chain the file describes wrongly is refused, and which of the two steps refuses it depends on
+the file. A machine tool file asking for its collision pairs to be generated is walked from the
+ground anchor to each end while it is being read, so a chain carrying no anchor named exactly
t+or exactlywnever reaches the IndexService at all: Object Management's Load answers +Load failed: This kinematic chain has no worktable-end anchor named 'w'.A file that lists its +collision pairs instead is read without that walk and does reach the index; the install that +follows builds the kinematics solver, which requires the same two anchors, so the same sentence +arrives one step later asInstall machine tool: …and the project keeps the chain it had. The +shipped machine tool files all ask for generated pairs, so the first form is the one usually met. +The worktable end is checked before the tool end, so a chain missing both names only'w'until +that one is fixed. The/machine-toolroute runs both steps inside its single call, so either +refusal reaches it asLoad machine tool: …, and — like the no-project refusal behind the +disabled folder button — leaves the loaded chain untouched.
+ - The IndexService key is minted even on a project with no chain attached, so Load and Paste stay +usable from an empty root. +
- The
/machine-toolroute's own Load takes the shorter path: one call materialises the picked file +server-side and assigns the chain to MachiningChain +in the same request, with no IndexService swap in between. It re-points +MachiningChainFile too — project-relative when the +picked file sits under the project directory, and the path relative to the chosen root otherwise. +It is refused while no project is open, which is why the folder button is disabled there.
+ New ClMillingDevicebuilds and installs the blank chain in one server call and clears the +project's chain-file reference, so a later project save embeds the fresh chain inline instead of +overwriting the previous chain's file.
+- When the replacement came from a server-file Load, the install also re-points +MachiningChainFile: project-relative when the file +sits under the project directory, resource-root-relative when it sits under the resource root, +absolute otherwise. An absolute path there would make every later project save rewrite the shared +resource file in place instead of copying it into the project. Paste and XML applies leave the +file reference unchanged. +
- Both canvases draw the live model, so a replacement shows on the next frame with no further calls.
+The
/machine-toolcanvas snaps to the isometric view when it is bound and again after each load; +the shared equipment canvas snaps once, when its displayee is bound.
+ wwwroot-src/src/components/controlTree/useControlTreeHost.ts— builds the +equipment/machine-toolnode (item typeMachineToolRoot, keyed on the IndexService chain key) +and owns the install / create / rebind handlers.
+wwwroot-src/src/components/controlTree/PrimarySlavePanel.vue— renders that branch inline: the +Object Management button, itsNew ClMillingDeviceentry, the type badge and the read-only +Name / Note / File captions.
+wwwroot-src/src/components/widgets/ObjectManagementMenuButton.vue— the reused object-management +dropdown, keyed entirely on itsmodelKey(see +Object Management Menu Button).
+wwwroot-src/src/components/mech/EquipmentSetupPanel.vue— the shared equipment canvas the tree +root's chain is drawn on.
+wwwroot-src/src/pages/MachineToolPage.vue— the/machine-toolroute.
+wwwroot-src/src/components/widgets/FileExplorerDialog.vue— the server-side file picker that +route opens, seeded to the resource root'sMachineToolfolder when nothing is loaded and to the +current file's directory otherwise; it emits"{rootName}:{relativePath}"selection keys.
+wwwroot-src/src/api/machineTool.ts— typed client for/api/mech/machine-tool/*: snapshot, +initialize, update, create, load, XML, plus the display initialize and reset-view calls. Shared by +the route page and the tree root.
+wwwroot-src/src/router/routes.ts— declares themachine-toolroute.
+wwwroot-src/src/components/AppMenuBar.vue— the Page menu, which lists General Setup but not +/machine-tool.
+Mech/MachineToolController.cs— REST surface at/api/mech/machine-tool: the snapshot DTO, +initialize/update/create/loadand the XML read. OnlyClMillingDeviceis offered +for blank creation.
+Mech/MachineToolDisplayController.cs—/api/mech/machine-tool/display: binds the project's +chain onto a rendering connection's DispEngine through a delegating displayee, and serves the +isometric reset-view the route page calls after a load.
+- Mechanism Builder Page — the user-scoped editor for the same anchor topology +
- Key Model: SpindleCapability +
- Assistant Model:
+
-
+
- SetupEquipment — owns the capability as +SpindleCapability and its optional +side-file reference +SpindleCapabilityFile. +
- MachiningProject — carries that face across the
.hincprojsave.
+
+ - Spindle Capability Root Panel —
equipment/spindle, item typeSpindleCapabilityRoot+-
+
- Object Management Menu Button — Load / Save As /
+Copy / Paste / XML over file extension
.SpindleCapability, load type +Hi.Milling.SpindleCapability, HiMech, rel fileSpindleCapability.xml, based at the project +directory. This is the whole file surface of the editor.
+ - Caption — the capability's name, or a “no capability” note. +
- Empty state, while nothing is attached: an hourglass, a “not attached to project” line and a +hint pointing at the ⋮ menu's Load entry. +
- Name TextField — Name. +
- Note TextField — Note. +
+ - Object Management Menu Button — Load / Save As /
+Copy / Paste / XML over file extension
equipment/spindle/thermal— “Thermal / Energy”, item typeSpindleScalars+-
+
- Energy Efficiency NumberField — EnergyEfficiency, clamped to +0 – 1. +
- Working Temperature Upper Boundary NumberField (°C) — +WorkingTemperatureUpperBoundary_C. +
+equipment/spindle/gear-shift— “Gear Shift”, item typeSpindleScalars+-
+
- Has-Gear-Shift CheckBox — flips
+GearShiftSpindleSpeed_rpm between
null(no mechanism) and +0(mechanism present; the operator dials in the speed).
+ - Gear Shift Spindle Speed NumberField (rpm) — always rendered, disabled until the checkbox is on. +
+- Has-Gear-Shift CheckBox — flips
+GearShiftSpindleSpeed_rpm between
equipment/spindle/dry-run— “Dry-Run Coefficients”, item typeSpindleScalars+-
+
- Friction Power Coefficient NumberField (mW/rpm) — +DryRunFrictionPowerCoefficient_mWdrpm. +
- Windage Power Coefficient NumberField (pW/rpm³) — +DryRunWindagePowerCoefficient_pWdrpm3. +
+equipment/spindle/power— “Power Contours”, item typeSpindleContour+-
+
- Count line and an Add button. +
- Contour Selector — a select over the contour list, each entry labelled by its workable duration
+(
n min, or Continuous for the infinite one).
+ - Edit-duration Button — opens the modal below. +
- Delete Button — confirms, then removes the selected contour. Disabled at one remaining contour. +
- Points Table — the selected contour's points, one row per point: a spindle-speed cell (rpm,
+
min: 0), a value cell, an insert-next button that clones the row, and a double-click remove +that is disabled at one remaining point. A cell edit replaces the whole point list server-side.
+ - Empty state, when the axis has no contour yet. +
- Edit-duration Modal
+
-
+
- Continuous (∞) CheckBox — disabled when another contour already holds the continuous key. +
- Workable Duration NumberField (min), shown while Continuous is off,
min: 0.
+ - Save rejects a non-positive duration and is a no-op when the key is unchanged. +
+
+equipment/spindle/torque— “Torque Contours”, item typeSpindleContour, the same shape over +the torque list.
+- Shared content view, registered by the root and by all five children: the two contour charts in
+the General Setup page's CONTENT column.
+
-
+
- Power chart and Torque chart, each with a chip legend — one chip per contour, showing its +duration and, on hover, its point count. Clicking a chip selects the contour the PRIMARY-row +editor edits. +
- A vertical marker line at the gear-shift spindle speed, while one is set. +
- Its own not-attached empty state, mirroring the root panel's. +
+ - Rpm ↔ cycles/s conversion lives server-side. SpindleCapability stores +spindle speed in cycles/s (Hz); the controller converts to rpm in its DTOs and back on write. The +frontend only ever sees rpm. +
- Nullable gear shift. The Has-Gear-Shift checkbox writes
0when checked andnullwhen +cleared, which is how the model distinguishes “mechanism present, speed not yet dialled in” from +“no gear-shift mechanism”.
+ - One state, thin views throughout. Every panel on the branch — root, the three scalar +sections, the two contour sections and the charts — reads and writes one module-singleton +composable. A point edit therefore redraws the chart in the same tick, and a legend click moves +the editor. The composable installs a single project-change watch that reloads on open and clears +on close. +
- No cache chain. These scalars feed the cutting-force and thermal physics, not the setup +canvas, so the branch's afterChange does nothing — unlike the geometry branches, nothing here +clears a geometry cache. +
- Add Contour. The prompt is pre-filled with
60; a blank entry means continuous (∞). A +duration already present is refused, a second continuous contour included. The new contour is +seeded from the currently selected contour, falling back to the first in the list, and to a +built-in default curve when the axis is empty — so the operator rarely draws from scratch.
+ - Project-scoped, and forwarded to the run. The capability lives on the authored equipment face
+and is persisted with the
.hincprojsave; setting +SpindleCapabilityFile externalizes it +to a side-file instead. The runtime equipment face holds the same object by reference, so a scalar +or point edit is visible to a run immediately; a replacement (Load, Paste, XML Apply) re-stamps +that reference through the project service's ForwardSetupEnvironmentToExecution.
+ - Load / Paste / XML-Apply only swap the IndexService entry. The root panel then installs the
+result onto the equipment through the controller's
update, and reloads the snapshot and the key. +The controller'sinitializeindexes a blank placeholder when nothing is attached yet, which is +what keeps the ⋮ menu usable — and Load reachable — on a project with no capability.
+ wwwroot-src/src/components/controlTree/useControlTreeHost.ts— builds theequipment/spindle+root (item typeSpindleCapabilityRoot) and its five children: thermal, gear-shift and dry-run as +SpindleScalars, power and torque asSpindleContour.
+wwwroot-src/src/components/controlTree/itemTypes.ts— registers those three item types and gives +all of them the same CONTENT-column view.
+wwwroot-src/src/components/controlTree/SpindleCapabilityPanel.vue— the branch root panel: the +Object Management button, the name caption, the not-attached empty state, and the Name / Note +fields.
+wwwroot-src/src/components/controlTree/SpindleScalarsPanel.vue— one scalar section per role +path: thermal, gear-shift (checkbox plus the rpm field, disabled while the value is null) and +dry-run.
+wwwroot-src/src/components/controlTree/SpindleContourPanel.vue— the power / torque child: the +count line, Add, the contour selector, the edit-duration modal and the delete button, hosting the +point editor.
+wwwroot-src/src/components/spindle/SpindleContourEditor.vue— the selected contour's numeric +points table; its own header is hidden when the tree panel drives it.
+wwwroot-src/src/components/spindle/SpindleContoursPanel.vue— the CONTENT-column view every +spindle item registers: both charts.
+wwwroot-src/src/components/spindle/SpindleContoursChart.vue— one plotted chart with the +clickable chip legend that selects the contour the editor edits, plus the gear-shift marker line.
+wwwroot-src/src/components/controlTree/ContentSlavePanel.vue— the General Setup page's CONTENT +column, which mounts whichever content panel the selection's item type registers; it deliberately +leaves that component unkeyed so the charts survive a move between the branch's six nodes.
+wwwroot-src/src/composables/useSpindleCapability.ts— the module-singleton state and every +mutation on the branch: the scalar handlers, add / remove / rename contour, the point updates and +the object-management install hooks.
+wwwroot-src/src/components/widgets/ObjectManagementMenuButton.vue— the reused ⋮ menu carrying +Load / Save As / Copy / Paste / XML for the.SpindleCapabilityfile surface.
+wwwroot-src/src/api/spindleCapability.ts— typed client for/api/mech/spindle-capability/*.
+wwwroot-src/src/router/routes.ts— thespindle-capability/:tab?redirect (the tab mapped onto +the branch child id) and theequipment/spindleredirect, both landing ongeneral-setup?tree=….
+wwwroot-src/src/router/treeRoutes.ts— defines the tab segments that redirect accepts: thermal, +gear-shift, dry-run, power, torque.
+Mech/SpindleCapabilityController.cs— REST surface at/api/mech/spindle-capabilityover the +authored equipment face's capability: +-
+
GET /— flat snapshot: metadata plus the contour keys of each axis.
+GET /power-contours,GET /torque-contours— per-axis contour lists with their points.
+PUT /name | /note | /energy-efficiency | /working-temperature-upper-boundary-c | /gear-shift-spindle-speed-rpm | /dry-run-friction-power-coefficient-mwdrpm | /dry-run-windage-power-coefficient-pwdrpm3— one scalar each.
+POST /power-contours | /torque-contours— add one contour.
+DELETE /power-contours/{key} | /torque-contours/{key}— remove one contour ({key}accepts +numeric strings and"inf"/"infinity").
+PUT /power-contours/{key}/points | /torque-contours/{key}/points— replace one contour's +points; this is what backs per-point editing.
+PUT /power-contours/{key}/key | /torque-contours/{key}/key— rename a contour's +workable-duration key, refusing a key that already exists; this is what backs the edit-duration +modal.
+GET /xml— serialise to XML text.
+POST /load,POST /load-file,POST /reload— install from XML text, from a file under a +named root, or from the file already stamped on the equipment.
+PUT /capability-file— set the side-file reference without reloading.
+POST /initialize,POST /update— index the capability for the Object Management button, and +install the swapped-in object back onto the equipment.
+
+HiMech/Milling/SpindleCapability.cs— the model: EnergyEfficiency, +WorkingTemperatureUpperBoundary_K with its_Caccessor, the nullable GearShiftSpindleSpeed_cycleds +with its_rpmconvenience property, the two dry-run coefficients and the power / torque contour +dictionaries keyed by workable duration.
+HiMech/Machining/MachiningEquipmentUtils/SetupEquipment.cs— the authored equipment face that +owns the capability and its file reference and serializes them, inline or as a side-file +reference.
+- Mechanism Builder Page — same file-level IO pattern (Load / Reload / Save As) but user-scoped rather than project-scoped. +
- Background / Coolant — sibling branch of the same Control Tree, editing the same authored equipment face. +
- Spindle Capability — what the model represents physically and how the per-step ratios come out of it. +
- Workpiece Root — a summary panel, rendered inline by the dock's primary editor pane. It carries no
+Object Management button: the workpiece has no standalone
.Workpiecefile surface here, and its +geometry, anchors, mesh and material are all edited through the child items below. +-
+
- Raw Geometry Type Badge and Target Geometry Type Badge (
nonewhen the slot is empty)
+ - Intro caption, and an empty-state caption while the project has no workpiece +
+ - Raw Geometry Type Badge and Target Geometry Type Badge (
equipment/workpiece/raw-geometry— “Raw Geometry”, a Geometry slot backing +InitGeom. The slot's own panel is the kind picker +(Geometry Management Control, selector only); the picked kind's +editor is the slot's child item. Kinds offered:Box3d,Cylindroid,StlFile, +TransformationGeom,GeomCombination,CubeTreeFile, plus None.ExtendedCylinderis an +IMakeXmlSourceand was offered here until its start section — wired by a host, never serialized +— was found to reload a saved project degenerate; see +Extended Cylinder Panel for what an older project holding +one still shows. The source +choice folds into that one picker — picking the voxelCubeTreeFile, which the picker shows as +MeshedGeomFile, is the meshed source (see +Meshed Geometry Panel).
+equipment/workpiece/target-geometry— “Target Geometry”, the same slot shape backing +IdealGeom. Kinds offered:Box3d,Cylindroid,StlFile, +TransformationGeom, plus None. The target must satisfyIGetStl, soCubeTreeFileis not among +them.
+equipment/workpiece/anchor— “Anchor”, a Group stem whose info text states how the workpiece is +placed. Two Transformer slots hang from it, each a kind picker +(Transformer Manage Panel) over the picked kind's child editor: +-
+
.../anchor/geom-to-fixture— “Geom To Fixture”, which positions the workpiece geometry on the +fixture's workpiece buckle (see Fixture).
+.../anchor/geom-to-program-zero— “Geom To Program Zero”, which places the program zero, the +NC origin, relative to the workpiece geometry.
+
+equipment/workpiece/runtime— labelled Mesh in the tree. +-
+
- Initial Resolution Select (InitResolution, in mm), a
+fifteen-step powers-of-two ladder:
+
-
+
- 0.0009765625 +
- 0.001953125 +
- 0.00390625 +
- 0.0078125 +
- 0.015625 +
- 0.03125 +
- 0.0625 +
- 0.125 +
- 0.25 +
- 0.5 +
- 1 +
- 2 +
- 4 +
- 8 +
- 16 +
+ - Hint caption: a smaller resolution means a finer voxel grid and a slower runtime simulation. +
- The engine's own ladder bottoms out at
0.001953125, so the first entry builds the same mesh +as the second. Values typed in from elsewhere are rounded to the next finer rung rather than +used as given — see Mesh Resolution.
+ - The panel self-gates on the project having a workpiece. +
+- Initial Resolution Select (InitResolution, in mm), a
+fifteen-step powers-of-two ladder:
+
equipment/workpiece/material— “Material”, a Group stem whose info text names the two +pre-prepared resources loaded by reference. Present whenever the project has a workpiece; nothing +on this branch gates it on a physics flag. Its two children share one panel, which reads its role +from the node's own path: +-
+
.../material/workpiece-material—.WorkpieceMaterialfiles, seeded to the resource root's +WorkpieceMaterialfolder.
+.../material/cutting-parameter—.mpfiles, seeded to the resource root's +CuttingParameterfolder.
+- Each child is one Resource-only File Path Input — the project-directory Browse entry is +suppressed, leaving the Resource-root pick — plus a readonly Name field +(Name) and a readonly Note field +(Note). +
+- The General Setup page lays the tree dock, the content column and the equipment canvas out in +nested splitters with device-local widths; inside the dock, a height divider between the Control +Tree row and the primary editor row trades their heights. See +General Setup Page. +
- Canvas Column — one RenderingCanvas bound to the equipment-setup displayee
+(
Disp/EquipmentSetupDisplayee.cs): the merged fixture + workpiece setup scene, with optional +machine and tool solids. +-
+
- RenderingCanvas Tool Bar +
- Display Options Menu — six groups; the workpiece's own entries are in bold:
+
-
+
- Solid — Machine, Tool, Meshed Geometry +
- Fixture Rendering Mode — Solid / Edge / Hide +
- Raw Shape Rendering Mode — Solid / Edge / Hide +
- Target Shape Rendering Mode — Solid / Edge / Hide +
- Anchors — Fixture Geometry Anchor, Workpiece Buckle (the one flag for the +fixture↔workpiece pair), Table Buckle, Workpiece Geometry Anchor, +Program-Zero Anchor, Controller Coordinate +
- Display Aids — Dimension Bar +
+
+ - Workpiece Page
+
-
+
- Management Panel
+
-
+
- Head Line
+
-
+
- Object Management Menu Button
+
-
+
- file extension is
.Workpiece; the load type is Workpiece
+ - The pointed Editor Panel is Management Tabs Panel +
+ - file extension is
- Title Label +
+ - Object Management Menu Button
+
- Management Tabs Panel
+
-
+
- Raw Shape Tab
+
-
+
- Raw Geometry Source DropDown (Common Geometry and Meshed Geometry are EXCLUSIVE)
+
-
+
- Common Geometry +Apply Geometry Management Control +
- Meshed Geometry +Apply Meshed Geometry Panel +
+
+ - Raw Geometry Source DropDown (Common Geometry and Meshed Geometry are EXCLUSIVE)
+
- Target Shape Tab
+
-
+
- Geometry Management Control +
+ - Anchor Tab
+
-
+
- Geom To Fixture Tab + + +
- Geom To Program-Zero Tab
+
-
+
- Transformer Manage Panel +
+
+ - Runtime Tab
+
-
+
- Initial Resolution Dropdown — the same powers-of-two ladder as the Mesh item above. +
+ - Material Tab
+Visible if
UserService.EnablePhysicsis true. +-
+
- Workpiece Material File Selector + + +
- Cutting Parameter File Selector + + +
+
+ - Raw Shape Tab
+
+ - Head Line
+
- Viewer Panel
+
-
+
- Viewer ToolBar
+
-
+
- RenderingCanvas Tool Bar +
- SetupDisplayee Options ToolBar
+
-
+
- Options of WorkpieceEditorDisplayee +
+
+ - RenderingCanvas
+
-
+
- The DispEngine.Displayee is WorkpieceEditorDisplayee (Apply the model WorkpieceEditorDisplayeeConfig). +
+
+ - Viewer ToolBar
+
+ - Management Panel
+
- “Resource/WorkpieceMaterial” +
- “Resource/CuttingParameter” +
A geometry slot's edit re-commits the swap-in before clearing the cache. The canvas draws the +runtime face's solids, and that face follows an authored edit only by rebuild, so a bare cache +clear would re-solidify a stale face. The re-commit is skipped when the slot is cleared to None: +None leaves the old object indexed, and re-committing would resurrect it.
+
+Call WorkpieceService.ClearRawGeomCache() on Raw Shape set or changed.
+
+Call WorkpieceService.ClearIdealGeomCache() on Target Shape set or changed.
+
+Both clients reach that one service by different routes: the web branch posts the controller's +cache-clear endpoints, and the WPF page calls +WorkpieceEditorDisplayee, which forwards to the same service.
+
+Anchor edits re-commit their transformer and deliberately do not clear the geometry cache, so +a placement change forces no
+CubeTreeFilere-mesh.
+The Mesh item's Initial Resolution feeds the runtime voxel simulation, not the setup canvas, so no +cache chain runs after a change.
+
+The equipment canvas snaps to the isometric view when its displayee is bound to a rendering +connection; a shape edit on the branch re-commits and clears the cache so the next frame +re-solidifies, and leaves the view alone. The WPF page snaps its own canvas to isometric when a +Raw or Target shape is set to a different object, and whenever its meshed-geometry panel updates +its content — the assumption being that setting a shape changes the viewer more than changing its +content does, so only the setter event adjusts the view.
+
+Keep Portability of the Material properties.
+
+wwwroot-src/src/components/controlTree/useControlTreeHost.ts— builds the whole +equipment/workpiecebranch: the root, the two geometry slots, the anchor group with its two +transformer slots, the Mesh item and the material group with its two resource items — including +each slot's re-commit-then-clear chain and the two allowed-kind whitelists.
+wwwroot-src/src/components/controlTree/PrimarySlavePanel.vue— renders the root inline: the Raw +and Target type badges plus the intro.
+wwwroot-src/src/components/controlTree/itemTypes.ts— the ItemType registry mapping the branch's +children to their panels.
+wwwroot-src/src/components/controlTree/GeometrySlotPanel.vue— the kind picker behind the raw +and target geometry slots.
+wwwroot-src/src/components/controlTree/TransformerSlotPanel.vue— the kind picker behind the two +anchor slots.
+wwwroot-src/src/components/controlTree/WorkpieceRuntimePanel.vue— the Mesh item's editor: the +Initial Resolution ladder and its hint.
+wwwroot-src/src/components/controlTree/WorkpieceResourcePanel.vue— the one panel both Material +children share, choosing its extension and resource subfolder from the node's role path.
+wwwroot-src/src/components/widgets/FilePathInput.vue— the file field those children use; its +project Browse entry is suppressed underresourceOnly, leaving the Resource-root pick.
+wwwroot-src/src/components/mech/EquipmentSetupPanel.vue— the canvas column and its Display +Options menu.
+wwwroot-src/src/pages/GeneralSetupPage.vue— the/general-setuppage hosting the tree dock, +the content column and the canvas in nested splitters.
+wwwroot-src/src/api/workpiece.ts— typed client for/api/Workpiece/*: initialize, status, the +geometry re-commit and cache-clear calls, the two anchor transformer updates,init-resolution, +the two resource loads and the diff settings.
+wwwroot-src/src/api/equipmentSetup.ts— typed client for the shared canvas: the three +rendering-mode targets and the ten display flags, several of which are the workpiece's own +overlays.
+Mech/WorkpieceController.cs— the model REST surface at/api/Workpiece: geometry install and +cache clears, the anchor transformer updates,init-resolution,workpiece-material/load, +cutting-parameter/load, and the geometry-diff settings pair (visual radius and detection radius) +whose menu rides the Execution Extended Tool Bar rather +than this branch.
+Mech/EquipmentSetupDisplayController.cs—/api/mech/equipment-setup-display: binds the +equipment-setup displayee to a rendering connection, framing it isometrically at bind time, and +serves the rendering-mode and flag endpoints.
+Disp/EquipmentSetupDisplayee.cs— the merged fixture + workpiece setup scene the canvas draws.
+Disp/EquipmentSetupDisplayeeConfig.cs— its config: the fixture and workpiece display flags in +one place.
+- Edit Mode — a selector over three modes. Its internal values are
MinMax,MinDimensionand +CenterDimension; the web labels them “Min / Max”, “Min + Dimension” and “Center + Dimension”, +the desktop client “Min and Max”, “Min and Dimension” and “Center and Dimension”.
+ - Min, Max, Dimension, Center — four vector rows, one X / Y / Z input each. The web +captions Dimension “Dimension (Max − Min)” and Center "Center ((Max + Min) / 2)"; the Min and Max +captions come from the shared label set rather than the geometry one. +
- A row commits on blur or Enter, not per keystroke. The vector widget emits only from its blur +handlers, suppresses the emit when the parsed value is unchanged, and reverts an unparseable entry +to its previous value. +
- The web editor has no read-only variant. The desktop control additionally carries an
+
IsInfoModeproperty that a host sets to turn the whole control into a display: every field goes +read-only and the Edit Mode selector is hidden outright. Two hosts use it, the STL file control and +the geometry combination control. The web equivalent is not this editor — the STL file editor draws +its own read-only bounding-box rows.
+ - Four endpoints ship without a caller. The controller exposes
UpdateByMinDimension, +UpdateByCenterDimension,IndexDimensionandIndexCenter; the shipped SPA calls none of them, +because the editor resolves every mode to a plainUpdate.
+ wwwroot-src/src/components/geom/Box3dEditor.vue— the editor: the Edit Mode selector, the four +vector rows and their per-mode read-only rules, and the client-side arithmetic behind the two +derived modes. Its only prop is the IndexService key.
+wwwroot-src/src/components/widgets/Vec3Input.vue— the X / Y / Z row widget, in standard or +single-field text mode; itsreadonlyprop is what the edit mode drives.
+wwwroot-src/src/components/geom/geometryEditors.ts— maps theBox3dkind to this editor, the +single map both the switchboard and the Control Tree resolve through.
+wwwroot-src/src/components/geom/GeometryEditor.vue— the kind picker that creates a Box3d and, +outside selector-only mode, embeds this editor beneath itself.
+wwwroot-src/src/components/controlTree/itemTypes.ts— registersBox3dagainst the sole-editor +panel and lists it among the kinds a container may hold.
+wwwroot-src/src/components/controlTree/SoleEditorPanel.vue— the kind node's panel, resolving +Box3dthrough the kind map and binding the editor to the node's key.
+wwwroot-src/src/components/controlTree/GeometrySlotPanel.vue— the Geometry slot's picker, from +which a Box3d is chosen.
+wwwroot-src/src/api/geometry.ts—getBox3d,indexBox3dMin,indexBox3dMaxand the create +entry. There is deliberately no update wrapper here; the editor posts the update itself.
+wwwroot-src/src/i18n/en/geom.ts— thebox.*andbounds.*strings, and +wwwroot-src/src/i18n/en/common.ts— the shared Min and Max row captions.
+Geom/Box3dController.cs— REST endpoints at/api/Box3d/*:New,NewWithValue,Get, +Update,IndexMin,IndexMax,IndexDimension,IndexCenter,UpdateByMinDimensionand +UpdateByCenterDimension.
+Common/IndexService.cs— the keyed object store every box key resolves against.
+- Geometry Management Panel — the switchboard that offers this kind and +hosts this editor +
- Z-R pairs — a header row carrying the section label and an Add button. Add appends a row +ten millimetres above the last one, at the same radius. +
- The table — one row per pair, columns
#,Z (mm)andR (mm). Each Z and R cell is an +inline text field parsed as a number; there is no spinner. A per-row remove button sits at the end +of each row, disabled once only two pairs remain.
+ - The caption — a one-line statement of the type: a solid of revolution defined by (Z, R) pairs, +minimum two. +
- A cell commits on blur or Enter. An empty or non-finite entry is dropped rather than written, +and a negative radius is clamped to zero — on the client and again on the server. +
- The floor is two pairs, and both ends enforce it. The web disables the remove button at two +and the controller refuses the call below two. The desktop client blocks only at one, with a +warning dialog. +
- Creating one seeds three pairs. The create endpoint starts a new cylindroid at (0, 20), +(30, 20) and (60, 10) rather than empty. +
- Four endpoints ship without a caller. The controller exposes
GetPairCount,GetPairAt, +UpdatePairAtandSortByZ; the shipped SPA uses none of them, committing the whole list instead.
+ wwwroot-src/src/components/geom/CylindroidEditor.vue— the editor: the header row and Add +button, the Z / R table with its per-cell commit, and the per-row remove and its two-pair floor. +Edits by IndexService key and emitschangedso the owner can resync.
+wwwroot-src/src/components/geom/geometryEditors.ts— maps theCylindroidkind to this editor.
+wwwroot-src/src/components/geom/GeometryEditor.vue— the kind picker that creates a Cylindroid +and embeds this editor beneath itself.
+wwwroot-src/src/components/controlTree/itemTypes.ts— registersCylindroidagainst the +sole-editor panel and lists it among the kinds a container may hold.
+wwwroot-src/src/components/controlTree/SoleEditorPanel.vue— the kind node's panel.
+wwwroot-src/src/components/controlTree/toolhouse/HolderSectionPanel.vue— the second host: it +embeds this editor on the holder's cylindroid key for the Geometry node, and renders the linear +and angle resolution fields for the sibling Resolution node.
+wwwroot-src/src/components/controlTree/toolHouseItemTypes.ts— grows those two holder children, +and only for a cylindroid holder.
+wwwroot-src/src/api/geometry.ts—getCylindroid,updateCylindroidPairs,addCylindroidPair, +removeCylindroidPairAtand the create entry.
+wwwroot-src/src/api/cylindroidHolder.ts— the holder side: the cylindroid key it exposes, the +geometry update, and the resolution setter.
+wwwroot-src/src/i18n/en/geom.ts— thecylindroid.*strings, and +wwwroot-src/src/i18n/en/toolhouse.ts— the Resolution node's own labels and hint.
+Geom/CylindroidController.cs— REST endpoints at/api/Cylindroid/*:New,Get, +GetPairCount,GetPairAt,UpdatePairAt,UpdateAllPairs,AddPair,RemovePairAtand +SortByZ.
+Mech/CylindroidHolderController.cs— the holder surface at/api/CylindroidHolder/*behind the +Tool House host.
+Common/IndexService.cs— the keyed object store every cylindroid key resolves against.
+- Geometry Management Panel — the switchboard that offers this kind and +hosts this editor +
- Cylindroid Holder Panel — the second host, and the surface +that owns the tessellation resolution +
- An explanatory block stating what the type is and, in bold, how the length is measured. +
- Full Length — a single numeric field in millimetres, with a hint under it. +
- The hint states a lower bound only when that bound is positive. The bound is the Z of the
+start section, served with the value as
minFullLength; above zero the hint reads “must exceed +the start-section Z”, and at zero it degrades to a plain “full length from Z=0”. The test is the +number, not the wiring — a geometry with no start-section source and one whose source sits at +Z=0read identically here, because the controller answers0for both. There is no upper bound +anywhere — not in the editor and not in the controller.
+ - A live bound belongs to the cutter alone. The bare create endpoint installs a constant start
+pair at
Z=0, and the container-aware create switches install no source at all; both answer a +bound of zero. Only the cutter upper beam wires a real one — setting the beam links it to the +flute top — so only there does a bound appear.
+ - The guard is two layers, and they differ by one. The numeric field rejects a value below the +minimum — its minimum is inclusive — so a value exactly equal to the bound passes the field and is +caught one layer up by the editor, which raises an error to its host as a banner rather than an +inline field message. The controller then rejects non-finite, non-positive, and at-or-below-bound +values with a 400. +
- The field commits on blur or Enter. The desktop control does not: it writes on every +keystroke, and performs no minimum, positivity or finiteness check at all. The range guard is a +web and backend feature. +
- Creating one through the cutter is not the same as creating one bare. The Upper Beam creates +through the cutter's own endpoint so the backend seeds a valid full length and can return +geometry-issue warnings — which the Upper Beam panel renders as persistent banners. The bare +create endpoint seeds a length of 100 and a single start pair. +
wwwroot-src/src/components/geom/ExtendedCylinderEditor.vue— the description block and the Full +Length field, its hint, and the at-or-below-bound check that raises an error to the host.
+wwwroot-src/src/components/widgets/NumericInput.vue— the shared numeric field: an inclusive +minimum rejected on blur, and the hint slot under it.
+wwwroot-src/src/components/toolhouse/UpperBeamDiv.vue— the component's real home: the cutter's +Upper Beam, which creates through the container-aware cutter endpoint and renders the returned +geometry-issue warnings.
+wwwroot-src/src/components/controlTree/toolhouse/CutterSectionPanel.vue— mounts that panel for +the cutter's Upper Beam node, and deliberately skips its own fetch there.
+wwwroot-src/src/components/controlTree/toolHouseItemTypes.ts— builds the Upper Beam child of +the Cutter node.
+wwwroot-src/src/components/geom/geometryEditors.ts— maps theExtendedCylinderkind to this +editor.
+wwwroot-src/src/components/geom/GeometryEditor.vue— the kind picker.
+wwwroot-src/src/components/controlTree/itemTypes.ts— registersExtendedCylinderagainst the +sole-editor panel, is where its absence from the container kinds is declared, and holds the +Geometry slot's child builder — the half that tests the whole kind map rather than the whitelist.
+wwwroot-src/src/components/controlTree/useControlTreeHost.ts— the workpiece raw-geometry +whitelist, and the comment recording why the kind is no longer in it.
+wwwroot-src/src/api/geometry.ts—getExtendedCylinder, +updateExtendedCylinderFullLengthand the create entry.
+wwwroot-src/src/api/toolHouse.ts— the upper-beam get, create and resync wrappers, and the +warnings contract.
+wwwroot-src/src/i18n/en/geom.ts— theextCylinder.*strings: the label, the description and +measure rule, both hints, and the below-minimum error.
+Geom/ExtendedCylinderController.cs— REST endpoints at/api/ExtendedCylinder/*:New,Get, +UpdateFullLengthandGetFullLength.GetcarriesminFullLengthalongside the value.
+Mech/CutterController.cs— the upper-beam routes the Tool House host actually goes through, and +the geometry-issue warnings it surfaces.
+Mech/WorkpieceController.cs— the raw-geometry create switch, which still carries an +ExtendedCylinderarm that no picker can now reach.
+- Geometry Management Panel — the switchboard that offers this kind and +hosts this editor +
- Milling Cutter Panel — the cutter whose Upper Beam is this +editor's real home, and the one surface that supplies a start section +
- Add bar — a child-kind picker beside an Add button, and a Clear all button. +
- One card per child, each with a
#nindex badge, the child's type name, its own remove +button, and — in the card body — that child's full editor, rendered recursively. All children are +editable at once.
+ - Web — pick the kind first, then Add; the request carries the kind. +
- Desktop — Add takes no kind and always appends a 100 mm cube. The kind is switched afterwards, +on that child's own Geometry Type combo. +
- A type switch rewires the slot, not just the alias. Changing a child's kind goes through
+
SetItemAt, which replaces the entry in StlSources itself.
+ - The aggregate STL is cached, and a child's own controller does not invalidate it. Editing a
+child through its own endpoints leaves the combination returning the mesh it had already built, so
+both web faces call
CleanCachebefore bubbling the change, and compose that call into the +ancestor chain. This is the behavioural fact the rest of the page depends on.
+ - Removing a child does not renumber the ones after it. Item keys are minted as
+
{key}-item-{index}and the remove endpoint leaves later indices pointing at the old aliases, +which is why every structural change rebuilds the branch and re-mints them.
+ - Clear all confirms only on the desktop. The web posts it directly. +
wwwroot-src/src/components/geom/GeomCombinationEditor.vue— the inline face: the Add bar, the +per-child cards, and the recursive editor in each. Type switches route throughSetItemAt, a +cache clean precedes everychangedbubble, and item aliases are released on refresh and unmount.
+wwwroot-src/src/components/controlTree/GeomCombinationTreePanel.vue— the Control-Tree face: +list management only, emitting a structure change so the host re-mints the item aliases.
+wwwroot-src/src/components/controlTree/itemTypes.ts— registers the kind against that panel with +a child builder that mints oneItemnode per element, and declares the five container kinds a +child may be.
+wwwroot-src/src/components/geom/geometryEditors.ts— maps theGeomCombinationkind to the +inline editor.
+wwwroot-src/src/components/geom/GeometryEditor.vue— the recursive kind picker each child card +embeds.
+wwwroot-src/src/components/controlTree/GeometrySlotPanel.vueand +wwwroot-src/src/components/controlTree/SoleEditorPanel.vue— anItemnode's picker and, once a +kind is chosen, that kind's editor.
+wwwroot-src/src/components/geom/TransformationGeomEditor.vue— the nesting host: a combination +is an allowed inner geometry.
+wwwroot-src/src/components/controlTree/useControlTreeHost.ts, +wwwroot-src/src/pages/MechBuilderPage.vueand +wwwroot-src/src/components/toolhouse/UpperBeamDiv.vue— the slots that admit the kind: the +fixture geometry, the workpiece raw geometry, a mechanism anchor's geometry and the cutter upper +beam. The workpiece's target geometry deliberately does not.
+wwwroot-src/src/api/geometry.ts—getGeomCombination,addGeomCombinationItem, +setGeomCombinationItemAt,removeGeomCombinationItemAt,clearGeomCombination, +cleanGeomCombinationCacheandindexGeomCombinationItemAt, with the child-kind and combination +types and the create entry.
+wwwroot-src/src/api/index-service.ts— the index release the editor uses to drop item aliases.
+wwwroot-src/src/i18n/en/geom.ts— thecombination.*strings, and +wwwroot-src/src/i18n/en/tree.ts— the tree panel's own strings and theItemnode label.
+Geom/GeomCombinationController.cs— REST endpoints at/api/GeomCombination/*:New,Get, +GetCount,AddItem,SetItemAt,RemoveItemAt,Clear,CleanCache,IndexItemAtand +GetItemTypeAt.AddItemandSetItemAtshare one five-arm kind switch and both clean the +cache.
+Geom/TransformationGeomController.cs,Mech/FixtureController.cs, +Mech/WorkpieceController.cs,Mech/MechBuilder/GeneralMechanismController.csand +Mech/CutterController.cs— the container-aware create switches behind each host'sonCreate.
+- Geometry Management Panel — the switchboard that offers this kind, and +the panel each desktop child card embeds +
wwwroot-src/src/components/geom/GeometryEditor.vue— the switchboard: the kind picker over the +allowed kinds, the opt-in unset entry, the immediate commit through the create hook, and the +active kind's editor.
+wwwroot-src/src/components/geom/geometryEditors.ts— the single kind → editor map both this +picker and the Control Tree's sole-editor panel resolve through.
+wwwroot-src/src/components/controlTree/GeometrySlotPanel.vue— the slot node's panel: this +switchboard in selector-only mode, fed its allowed kinds, its unset flag and its create hook from +the node.
+wwwroot-src/src/components/controlTree/SoleEditorPanel.vue— the kind child's panel.
+wwwroot-src/src/components/controlTree/itemTypes.ts— registersGeometryas a slot type with +its child builder, registers each kind against its panel, and holds both the container-kind list +and the file-backed node labels.
+wwwroot-src/src/components/controlTree/useControlTreeHost.ts— the three General Setup Geometry +slots and their per-host kind whitelists.
+wwwroot-src/src/pages/MechBuilderPage.vueand +wwwroot-src/src/components/toolhouse/UpperBeamDiv.vue— the two inline, non-tree hosts.
+wwwroot-src/src/api/index-service.ts— the type probe the picker calls to discover which kind is +currently behind the key.
+wwwroot-src/src/api/geometry.ts— the kind union, the create dispatcher and its per-kind +endpoint map, and every per-kind call.
+wwwroot-src/src/api/fixture.ts,wwwroot-src/src/api/workpiece.ts, +wwwroot-src/src/api/toolHouse.tsandwwwroot-src/src/api/generalMechanism.ts— the +container-aware create wrappers each host's hook posts through.
+wwwroot-src/src/i18n/en/common.tsandwwwroot-src/src/i18n/en/geom.ts— the picker's label and +its unset and empty-state strings. Geometry type names are deliberately not translated.
+- The seven per-kind controllers under
Geom/—Geom/Box3dController.cs, +Geom/CylindroidController.cs,Geom/ExtendedCylinderController.cs,Geom/StlFileController.cs, +Geom/CubeTreeFileController.cs,Geom/TransformationGeomController.csand +Geom/GeomCombinationController.cs— each at/api/{Kind}/*. There is no single geometry +controller and no geometry hub.
+ Common/IndexController.cs— the type probe at/api/Index/GetType.
+Common/IndexService.cs— the keyed object store all of the above read and write. There is no +per-panel session object.
+Mech/FixtureController.cs,Mech/WorkpieceController.cs,Mech/CutterController.csand +Mech/MechBuilder/GeneralMechanismController.cs— the container-aware create endpoints that set +the host's own field, and the ones that acceptNoneand store null.
+- Mechanism Builder Page — embeds this control under its anchor +editor +
- Box3d Control — the kind editors this switchboard resolves +
- Cylindroid Control — the same, for a solid of revolution +
- Extended Cylinder Panel — the same, and the kind exactly one host offers +
- STL File Control — the same, for an STL reference +
- Meshed Geometry Panel — the same, for a voxel cube tree +
- Transformation Geometry Control — the container kind this panel +can wrap into on the desktop client +
- Geometry Combination Control — the other container kind, whose +every child embeds this panel again +
wwwroot-src/src/components/geom/— the geometry editors, plus +wwwroot-src/src/components/geom/GeometryEditor.vue, the geometry switchboard itself.
+wwwroot-src/src/components/topo/— the transformer editors, plus +wwwroot-src/src/components/topo/TransformerSelectPanel.vue.
+wwwroot-src/src/components/geom/geometryEditors.tsand +wwwroot-src/src/components/topo/transformerEditors.ts— the two kind → editor maps every host +resolves an editor through.
+wwwroot-src/src/components/controlTree/GeometrySlotPanel.vueand +wwwroot-src/src/components/controlTree/TransformerSlotPanel.vue— the two slot panels, each its +switchboard in selector-only mode.
+wwwroot-src/src/components/controlTree/SoleEditorPanel.vue— the kind node's panel, resolving the +node's kind through both maps.
+wwwroot-src/src/components/controlTree/itemTypes.ts— registersGeometryandTransformeras +slot types; the five leaf geometry kinds and all seven transformer kinds againstSoleEditorPanel; +andTransformationGeomandGeomCombinationagainst their own tree panels, which grow further +slot children instead of one editor.
+Geom/Box3dController.cs,Geom/CylindroidController.cs,Geom/ExtendedCylinderController.cs, +Geom/StlFileController.cs,Geom/CubeTreeFileController.cs, +Geom/TransformationGeomController.cs,Geom/GeomCombinationController.cs.
+Mech/Topo/StaticTranslationController.cs,Mech/Topo/StaticRotationController.cs, +Mech/Topo/StaticFreeformController.cs,Mech/Topo/DynamicTranslationController.cs, +Mech/Topo/DynamicRotationController.cs,Mech/Topo/GeneralTransformController.cs, +Mech/Topo/NoTransformController.cs.
+- A caption naming what the field takes,
.wct.
+ - The file selector — the shared file-path widget, filtered to
.wct, with an empty-state hint +when nothing is chosen.
+ - A standing description: the geometry is a pre-computed voxel cube tree, and loading it is deferred +until it is needed. +
wwwroot-src/src/components/geom/CubeTreeFileEditor.vue— the editor: the caption, the.wct+file selector and the deferred-loading description.
+wwwroot-src/src/components/widgets/FilePathInput.vueand +wwwroot-src/src/components/widgets/FileExplorerDialog.vue— the picker and the in-app browser it +opens.
+wwwroot-src/src/components/geom/geometryEditors.ts— maps theCubeTreeFilekind to this +editor.
+wwwroot-src/src/components/geom/GeometryEditor.vue— the kind picker, and the display-only +rename applied to its option label.
+wwwroot-src/src/components/controlTree/itemTypes.ts— registers the kind against the sole-editor +panel, holds the other half of the rename, builds the node label that carries the file, and is +where the kind's absence from the container kinds is declared.
+wwwroot-src/src/components/controlTree/SoleEditorPanel.vueand +wwwroot-src/src/components/controlTree/GeometrySlotPanel.vue— the kind node's panel and the +parent slot's picker.
+wwwroot-src/src/components/controlTree/useControlTreeHost.ts— the workpiece raw-geometry slot, +the only slot whose allowed kinds admit it, and the node-label refresh when the file changes.
+wwwroot-src/src/api/geometry.ts—getCubeTreeFile,setCubeTreeFileSourceFileand the create +entry.
+wwwroot-src/src/i18n/en/geom.ts— themeshedFile.*strings, and +wwwroot-src/src/i18n/en/tree.ts— the Raw Geometry slot label.
+Geom/CubeTreeFileController.cs— REST endpoints at/api/CubeTreeFile/*:New,Getand +SetSourceFile, and nothing else. None of them loads the voxel data.
+Mech/WorkpieceController.cs— the raw-geometry create path, and the classification that still +reports this type as a meshed geometry.
+Common/IndexService.cs— the keyed object store every cube-tree-file key resolves against.
+Disp/EquipmentSetupDisplayee.csandMech/EquipmentSetupDisplayController.cs— the scene that +builds the meshed geometry for the canvas, and the display toggle that shows it.
+- Geometry Management Panel — the switchboard that offers this kind and +hosts this editor +
- STL File Control — the other file-backed kind, and the one this slot offers +beside it +
- File reference — a read-only field showing the path currently referenced, with an empty-state +hint when there is none. +
- The picker — on the web a Select dropdown whose menu holds Browse…, Browse Resource…
+and, only while a path is set, Clear. Either Browse entry opens the in-app File Explorer dialog
+filtered to
.stl. In the desktop client this is a Browse button opening the operating +system's file dialog, beside a Reload button and a status line; the web has no Reload.
+ - STL info — behind an info icon on the web, opening a dialog; a collapsed Information +expander in the desktop client. Both show the triangle count and the bounding box as read-only +vector rows, and the web dialog states an empty case when nothing is loaded. +
- The picker browses the server's named roots, not a file system. Three roots are exposed — +Project, Resource and Admin — and nothing outside them is reachable. +
- A pick outside the current project is silently re-homed. The update endpoint stores
+
Geom/<filename>rather than the picked location, so saving the project copies the geometry into +the project folder. The consequence is visible: after such a pick the read-only field shows the +re-homed path, not the one that was chosen.
+ - The info dialog refetches on each open, because the bounding box is computed server-side by +walking the triangles rather than kept resident. Only Min and Max come back from the server; +Dimension and Center are derived in the browser from them. +
- The picker previews. Double-clicking an
.stlrow in the File Explorer dialog swaps its slave +panel to a 3D preview of that file before the pick is confirmed — a capability the desktop file +dialog has no equivalent of. The dialog opens with that panel hidden, which is why the double-click +is the gesture that reaches a preview there; a single click does so only once the toolbar's pencil +has revealed the panel. The preview belongs to the File Explorer rather than to this editor — see +STL Preview Pane.
+ - One endpoint ships without a caller. The controller exposes
Reload; nothing in the SPA calls +it.
+ wwwroot-src/src/components/geom/StlFileEditor.vue— the editor: the caption, the file-path +input, and the info dialog with its triangle-count badge and four read-only vector rows.
+wwwroot-src/src/components/widgets/FilePathInput.vue— the picker widget: the Select dropdown +with its Browse, Browse Resource and Clear entries beside a read-only field.
+wwwroot-src/src/components/widgets/FileExplorerDialog.vueand +wwwroot-src/src/components/FileExplorer.vue— the in-app browser the Browse entries open, and +the row gestures that swap in the STL preview.
+wwwroot-src/src/components/StlPreviewPane.vueandwwwroot-src/src/api/stlPreview.ts— that +preview and the surface behind it.
+wwwroot-src/src/components/widgets/Vec3Input.vue— the read-only vector rows in the info dialog.
+wwwroot-src/src/components/geom/geometryEditors.ts— maps theStlFilekind to this editor.
+wwwroot-src/src/components/geom/GeometryEditor.vue— the kind picker; it leaves this kind's +label verbatim.
+wwwroot-src/src/components/controlTree/itemTypes.ts— registersStlFileagainst the +sole-editor panel, lists it among the container kinds, and builds the node label that carries the +referenced file.
+wwwroot-src/src/components/controlTree/SoleEditorPanel.vueand +wwwroot-src/src/components/controlTree/GeometrySlotPanel.vue— the kind node's panel and the +parent slot's picker.
+wwwroot-src/src/components/controlTree/useControlTreeHost.ts— the three tree slots that admit +the kind, and the node-label refresh when the source file changes.
+wwwroot-src/src/components/geom/TransformationGeomEditor.vue, +wwwroot-src/src/components/geom/GeomCombinationEditor.vue, +wwwroot-src/src/components/toolhouse/UpperBeamDiv.vueand +wwwroot-src/src/pages/MechBuilderPage.vue— the inline hosts that also admit it.
+wwwroot-src/src/api/geometry.ts—getStlFile,getStlFileInfo,setStlFileSourceand the +create entry.
+wwwroot-src/src/i18n/en/geom.ts— thestl.*strings and thebounds.*captions shared with +the Box3d editor.
+Geom/StlFileController.cs— REST endpoints at/api/StlFile/*:New,NewWithPath,Get, +GetInfo,UpdateSourceFile,UpdateSource,Clear,GetFileInfoandReload.UpdateSource+is where the re-homing happens;GetInfoanswersloaded: falsewhen nothing is cached.
+Common/NamedRootResolver.cs— the three named roots the picker can reach, and the only three.
+Common/IndexService.cs— the keyed object store every STL file key resolves against.
+Mech/WorkpieceController.cs— the workpiece slots' create paths.
+Disp/StlPreviewController.csandDisp/StlPreviewService.cs— the picker's preview, loaded onto +the caller's own rendering connection and superseded as the selection moves.
+- Geometry Management Panel — the switchboard that offers this kind and +hosts this editor +
- Meshed Geometry Panel — the other file-backed kind, and the one the +workpiece's raw geometry offers beside this one +
- STL Preview Pane — the preview the picker opens on an
.stlrow +before the pick is confirmed
+ - Inner Geometry (
.../inner-geom) — the full Geometry Management Panel, +its dropdown labelled Inner geometry type, over five kinds: Box3d, Cylindroid, StlFile, a nested +TransformationGeom and GeomCombination — which is what makes recursion and nesting legal — plus a +None (unset) entry.
+ - Inner Transformer — the +Transformer Select Panel, mounted with no kind restriction at all, so +its own default of all seven transformer kinds applies. +
- The identity transformer is the floor on the web. The inner-transformer picker offers no null
+entry, and the server installs a
NoTransformwhenever the field is null, so the slot is never +unset. The desktop panel instead carries a Not Set entry, shown by default and never removed by +its type filter, which leaves the slot holding a null transformer.
+ - Creation is container-aware on both halves. Both go through this type's own create endpoints +rather than the generic per-kind ones, so the owning field is rebound and not merely the store +entry. +
- Neither client previews. There is no viewport on this control. It reports edits upward — one +event when a half is mutated, another when a half is replaced — and the hosting page's shared 3D +canvas re-renders from there. +
- A null inner geometry has a deterministic key anyway. When the inner geometry is unset the +server returns no key, so the editor falls back to a derived alias; without it the picker could +never create the first geometry. +
wwwroot-src/src/components/geom/TransformationGeomEditor.vue— the embedded editor: the two +cards and their type badges, the two container-aware create hooks, and the fallback alias for a +null inner geometry.
+wwwroot-src/src/components/controlTree/TransformationGeomTreePanel.vue— the Control-Tree face: +status only.
+wwwroot-src/src/components/controlTree/itemTypes.ts— registers the kind against that panel with +a child builder that grows the Inner Geometry and Inner Transformer slots, and is where the +override of the kind → editor map lives.
+wwwroot-src/src/components/controlTree/GeometrySlotPanel.vue, +wwwroot-src/src/components/controlTree/TransformerSlotPanel.vueand +wwwroot-src/src/components/controlTree/SoleEditorPanel.vue— the two child slots' pickers and +each kind grandchild's editor.
+wwwroot-src/src/components/geom/geometryEditors.tsand +wwwroot-src/src/components/geom/GeometryEditor.vue— the kind map that reaches this editor, and +the switchboard it re-enters for its own inner geometry.
+wwwroot-src/src/components/topo/TransformerSelectPanel.vueand +wwwroot-src/src/components/topo/transformerEditors.ts— the inner-transformer picker and the +seven kinds it offers, with the labels it renders them under.
+wwwroot-src/src/components/geom/GeomCombinationEditor.vue, +wwwroot-src/src/pages/MechBuilderPage.vueand +wwwroot-src/src/components/toolhouse/UpperBeamDiv.vue— the three embedders of the non-tree +form.
+wwwroot-src/src/api/geometry.ts— the transformation-geometry DTO, the two index calls and the +two create calls.
+wwwroot-src/src/api/transformer.ts— the transformer kind union and the per-kind endpoints the +inner transformer's editors bind through.
+wwwroot-src/src/i18n/en/geom.tsandwwwroot-src/src/i18n/en/tree.ts— the card labels, the +explanation, and the two child-node labels.
+Geom/TransformationGeomController.cs— REST endpoints at/api/TransformationGeom/*:New, +Get,IndexGeom,IndexTransformer,CreateGeom,CreateTransformer,UpdateTransformer, +GetGeomTypeandGetTransformerType.IndexGeomanswers empty for a null geometry, and +IndexTransformeris where the identity default is installed.
+Geom/Box3dController.cs,Geom/CylindroidController.cs,Geom/StlFileController.csand +Geom/GeomCombinationController.cs— the four other inner-geometry kinds' own surfaces.
+- The seven per-kind transformer controllers under
Mech/Topo/— reached by the inner transformer's +editors for their own reads and writes, not for creation, which this type's own endpoint owns.
+ Common/IndexController.csandCommon/IndexService.cs— the type probe both pickers use, and +the store the two aliases are registered into.
+- Transformers +
- Geometry Management Panel — the switchboard this editor embeds for its +inner geometry, and the only place a wrap or extract exists +
- Transformer Select Panel — the inner-transformer picker and its seven +kinds +
equipment/fixture/geom-to-workpiece— “Geom To Workpiece”
+equipment/fixture/geom-to-table— “Geom To Table”
+equipment/workpiece/anchor/geom-to-fixture— “Geom To Fixture”
+equipment/workpiece/anchor/geom-to-program-zero— “Geom To Program Zero”
+- Transformer Select Panel
+
-
+
- Transformer Type Dropdown — one dense, outlined
q-selectlisting the kinds this host allows. +Its label is the host'slabelprop, falling back to Transformer type; the inner-transformer +card labels it Inner transformer. Picking a kind commits immediately; there is no Apply button.
+ - Separator — drawn only when a kind is active. +
- Active Kind's Editor — the component the kind maps to in
TRANSFORMER_EDITORS, bound to the +IndexService key the picker resolved for the live transformer.
+
+ - Transformer Type Dropdown — one dense, outlined
- ITransformer — the interface the slot holds and the dropdown swaps. +
- StaticTranslation, StaticRotation, +StaticFreeform, DynamicTranslation, +DynamicRotation, GeneralTransform and +NoTransform — the seven implementations the web picker lists. +
- Vec3d — the axis, pivot and translation fields the static and dynamic editors bind. +
wwwroot-src/src/components/topo/TransformerSelectPanel.vue— the picker, the create call and the +optional inline editor; propsmodelKey/label/allowedKinds/onCreate/selectorOnly, +eventschanged/typeChanged/error.
+wwwroot-src/src/components/topo/transformerEditors.ts—TRANSFORMER_EDITORS, the single kind → +editor map, plusisTransformerKind()andhumanizeTransformerKind().
+wwwroot-src/src/components/topo/StaticTranslationEditor.vue
+wwwroot-src/src/components/topo/StaticRotationEditor.vue
+wwwroot-src/src/components/topo/StaticFreeformEditor.vue
+wwwroot-src/src/components/topo/DynamicTranslationEditor.vue
+wwwroot-src/src/components/topo/DynamicRotationEditor.vue
+wwwroot-src/src/components/topo/GeneralTransformEditor.vue
+wwwroot-src/src/components/topo/NoTransformEditor.vue
+wwwroot-src/src/components/controlTree/TransformerSlotPanel.vue— the slot's panel: this +switchboard in selector-only mode, fedallowedKindsandonCreatefrom the node context.
+wwwroot-src/src/components/controlTree/SoleEditorPanel.vue— the kind node's panel.
+wwwroot-src/src/components/controlTree/itemTypes.ts— registersTransformeras a slot type with +buildTransformerChildren, and each of the seven kinds againstSoleEditorPanel.
+wwwroot-src/src/pages/MechBuilderPage.vue— embeds the panel for the selected branch.
+wwwroot-src/src/components/geom/TransformationGeomEditor.vue— embeds it for the inner transformer.
+wwwroot-src/src/api/transformer.ts— theTransformerKindunion, the per-kind/api/{Kind}/New+create endpoints, and the typed wrappers for every update andIndex*call.
+wwwroot-src/src/i18n/en/topo.ts— every string on these panels, including +topo.select.noTransformer. Transformer type names are identity and stay verbatim.
+Mech/Topo/StaticTranslationController.cs
+Mech/Topo/StaticRotationController.cs
+Mech/Topo/StaticFreeformController.cs
+Mech/Topo/DynamicTranslationController.cs
+Mech/Topo/DynamicRotationController.cs
+Mech/Topo/GeneralTransformController.cs— also servesIndexRotationandIndexTranslation.
+Mech/Topo/NoTransformController.cs—NewandGet; there is nothing on aNoTransformto update.
+- Mechanism Builder Page — edits a mechanism’s per-branch transformer through this switchboard +
- Transformation Geometry Control — embeds this picker as its inner +transformer, with no kind restriction at all +
- Conventions — The rules and shared contracts every page assumes: messaging, file paths, numeric values, the hub patterns, the canvas transport +
- Platform — The machinery every route sits on and no user can point at: the Control-Tree engine, the id and route surface, session state, the locale bundles, the login gate, the log viewer and the host process +
- App Shell — The frame every route renders inside: the Main Panel, its two message bars, and the Preference menu +
- Widgets — The reusable controls pages embed rather than own, most-embedded first +
- Geometry Panels — The primitive solids, the operators that combine and mesh them, and the switchboards that host both +
- Execution Page — The run cockpit: its tool bars, the step column, the charts, and the Mission branch it hosts +
- General Setup Page — The equipment Control Tree: the machine, the spindle envelope, the scene, the fixture, the workpiece and the controller branch +
- Tool House Page — The tool library and the per-tool editor tabs, cutter and holder +
- Legacy Controller — The superseded HardNcEnv controller surface and its REST implementation +
- Utility Pages — The two
/util/routes: the File Explorer and the Mechanism Builder
+ - By Source Directory — The same knowledge keyed on the source tree instead of the screen: one directory index per half of the flagship +
- Name the surface it documents in prose — the route, and where the component lives in a
+Control Tree, its
?tree=id. Make no claim that the id is stable.
+ - Layout — the widget tree as the user meets it, nested as the UI nests. +
- Key Model — the backing HiAPI types, as
<xref:>so the API reference is one click away.
+ - Source Code Path — the implementing files in the web service. Backtick every path, with
+its extension:
tools/check-source-paths.ps1resolves a full path against the source tree and a +bare file name by its base name, and refuses an entry carrying no backticks at all. Do not add +desktop-client files.
+ ## See Also— mandatory, and audited for reciprocity. An entry is not landed until the +target links back. +Anatomy is authoritative on component and source facts: when Manual and Anatomy disagree, +Anatomy is corrected first and Manual follows.
+- Anatomy by Source Directory — the inverse index: enter by the directory a +change landed in rather than by the screen it shows up on +
- Manual — the same shipped screens as an operating procedure rather than a +component breakdown +
- 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,
Afor 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. +
- Legacy Controller Page —
/controller/:tab?, a two-pane splitter, left pane 55 % and draggable +between 25 % and 75 % +-
+
- Management Pane
+
-
+
- Head Line
+
-
+
- Object Management Menu Button (
⋮) — Load, Save +As, Copy, Paste, XML Mode
+ - Controller Title +
- Status Badge — ready / no project +
+ - Object Management Menu Button (
- 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 +
+
+ - ISO Coordinate Table Panel
+
+ - Head Line
+
- 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 +
+ - Viewer Toolbar
+
+ - Management Pane
+
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.vueand +wwwroot-src/src/components/controller/DatumShiftTab.vue— the two Heidenhain tables: theQ339+andDkey 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.vueand +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.
+HiUniNc/Numerical/HardNcEnv.cs— the model this page edits: the coordinate, datum and offset +tables, the stroke boxes that construct infinite, the rapid rate and tooling time, the +shortest-rotary flag with its Heidenhain exclusion, and the master-axis character over its integer +direction.
+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/HardNcLine.cs— the legacy consumers of two of those settings: the +shortest-rotary path application and theSEQsolve that reads the master-axis direction.
+HiUniNc/Numerical/MillingToolOffsetTable.cs— the offset table and the tool-house recompute the +Refresh button calls.
+HiMech/NcParsers/Dependencys/Generic/IsoCoordinateTable.cs— the coordinate table type and the +fifteen G-code keys it constructs with, shared by name with the runner's brand-agnostic table.
+HiMech/Machining/MachiningEquipmentUtils/MachiningEquipmentUtil.cs— the alignment itself: the +translation written into the fixture's geometry-to-table transformer.
+HiMech/NcParsers/SoftNcRunner.cs— the legacy import: which legacy fields are funnelled into +which runner dependency, including the rotary speed conversion into the rapid-feedrate config.
+HiMech/NcParsers/LogicSyntaxs/PolarInterpolationUtil.cs— the rotary speed ceiling the runner +pipeline reads, and why it is the rapid-rate bucket rather than a setting of its own.
+HiMech/NcParsers/LogicSyntaxs/Heidenhain/HeidenhainPlaneTiltSyntax.cs— the derived master rotary +on the runner pipeline: the first declared rotary axis, with no configured alternative.
+HiNc/MachiningProcs/MachiningProject.cs— where both models hang off the project, and the load +path that derives a runner from the legacy element only when no runner element is present.
+HiNc/MachiningProcs/LocalProjectService.cs— the legacy runner built over a delegate onto the +project's model, the switch that selects which pipeline is active, and the per-step stroke check +that falls back to the legacy boxes when no runner-side stroke config resolves.
+HiNc/Numerical/FilePlayers/HardNcRunner.cs— the legacy runner itself.
+HiNc/MachiningProcs/SessionShell.cs— the scripting face of the pipeline switch and of the +alignment, and the optimisation route that passes the legacy model directly.
+- 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 +
id— the node's role path, slash-separated, whose first segment is the owning page's scope +(execution/…,equipment/…,toolhouse/…). This is the addressing surface: the?tree=query, +the persisted expansion list and the persisted last selection all hold ids.
+label,labelKey,labelParams— the display text; see Display Labels.
+itemType— the registry key that decides the node's editor and its child builder.
+key— the bound object. In the geometry and equipment branches it is an IndexService key, +re-minted on every re-index; in the Mission branch it is the Mission API command path (0,1, +the dotted0.2of a nested list entry); in the Tool House branch it is the tool id as a string. +Waves whose panels read a module-level state singleton rather than an indexed object leave it +empty — the SoftNc controller leaves, the spindle sections, the Background and Coolant leaves and +the Program branch all do.
+ctx— the parent-providedSlotCtx: theafterChangecommit chain, a slot'sonCreate+create-and-rebind hook, and a slot picker'sallowedKinds/allowNoneconstraints.
+children— grown by the builders, not declared by the tree column.
+selectable,info/infoKey, and themission/programbookkeeping records the Mission and +Program waves stamp on their own nodes.
+panel— the editor mounted in the dock's editor row for a node of this type.
+contentPanel— a large view for the General Setup page's content column.
+buildChildren— an async builder that returns this node's children.
+scopeId— the branch to rebuild. Without it the scope is the selected node itself, which is +correct only while the change stays inside the emitting panel's own branch.
+selectId— the selection to adopt afterwards. It is assigned directly, bypassing the +dirty-switch gate, because the emitting panel is being replaced on purpose. Anullclears the +selection.
+- A mission command's title is re-read from the entry list and written onto the node's label. +
- A file-backed geometry leaf's label is re-read when its source file changes. +
- on a
listentry it flushes the debounced title save while the command path is still live, then +allows the switch;
+ - on any other entry it forwards its inner editor's gate. Today only the script editor has one: with +unsaved text it asks save, discard or cancel, returning false on cancel and on a flush that ends in +an error. +
- The three inline root types —
MachineToolRoot,FixtureRoot,WorkpieceRoot— are rendered +by the panel itself as a summary, and are never resolved from the registry. The two with a +stand-alone file surface, Machine Tool and Fixture, carry an Object-Management button above it; +the workpiece has none, since it is authored entirely through its child tree items.
+ - Otherwise the registry's
panelfor the selection's item type, mounted with the node as its +only prop, withchanged,type-changed,structure-changed,select-nodeanderrorwired to +the host.
+ - A spinner while the first build is still running, and the select-an-item hint after that when +the selection's type registers no panel. +
- Left Dock — both tree pages, one nav-bar button toggling the whole column
+
-
+
- Control Tree Expansion Row — collapses in place, keeping its header; stays mounted while
+collapsed, so the tree keeps its scroll position
+
-
+
- Tree — one root row per page: Execution or General Setup
+
-
+
- Node Row — the label is a real link to this node's
?tree=URL on the page, so the browser's +context menu offers open-in-new-tab and copy-link; a plain left click keeps the in-app +selection instead of navigating, while a modified or middle click opens a tab and leaves the +current selection where it is. No icons are rendered.
+ - Node CheckBox — mission command rows, and mission section rows carrying an enable flag +
- Execution Status Badge — on the Execution root row only +
+ - Node Row — the label is a real link to this node's
- Spinner and “Loading project…” — shown instead of the tree until the first build lands +
+ - Tree — one root row per page: Execution or General Setup
+
- Row Divider — a 5 px bar between the rows, shown only while both are open; dragging it upwards +grows the editor row, which keeps a stored pixel height while the tree above absorbs the change, +down to a 120 px editor floor and a 100 px tree floor +
- Editor Expansion Row — its header carries the selection breadcrumb, the node path joined with
+slashes, and falls back to Editor; stays mounted while collapsed
+
-
+
- Execution Transport Bar — Execution page, while an Execution-scope node is selected +
- The selected node's editor panel, or “Select an item in the Control Tree to edit it here.” +
+
+ - Control Tree Expansion Row — collapses in place, keeping its header; stays mounted while
+collapsed, so the tree keeps its scroll position
+
- Content Column — General Setup page only, one nav-bar button
+
-
+
- The selected node's large content view, or “The selected item has no expanded content.” +
+ wwwroot-src/src/components/controlTree/useControlTreeHost.ts— the host: the two scopes' root +builders, the selection gate, the?tree=sync, the mission ticks, the rebuild entry points, the +re-index and Object-Management handlers, and the reactive surface the panes consume.
+wwwroot-src/src/components/controlTree/itemTypes.ts— theControlNodeandTreeItemDefshapes, +theSlotCtxhooks, the structure-change payload,nodeDisplayLabel/nodeDisplayInfo, the +geometry and transformer builders, theITEM_TYPESmap andbuildSubtree.
+wwwroot-src/src/components/controlTree/ControlTreePanel.vue— the tree column: the node anchors, +the click handling that keeps a plain click in-app, and the execution-status badge.
+wwwroot-src/src/components/controlTree/ControlTreeDock.vue— the two-row dock and the height +divider that sets the editor row's stored pixel height.
+wwwroot-src/src/components/controlTree/PrimarySlavePanel.vue— the panel host: the three inline +root types, the registry lookup, the remount key, the gate registration and the transport header.
+wwwroot-src/src/components/controlTree/ContentSlavePanel.vue— the General Setup content column +and its unkeyed content panel.
+wwwroot-src/src/components/controlTree/GroupInfoPanel.vue— a group stem's intro and its +navigating child list.
+wwwroot-src/src/components/controlTree/SoleEditorPanel.vue— a kind node's editor, resolved from +the geometry and transformer editor maps.
+wwwroot-src/src/components/controlTree/NodeTabCascade.vue— the same branches rendered as nested +tabs, with the remembered per-role tab and the cascade host injection.
+wwwroot-src/src/components/controlTree/missionItemTypes.ts— the Mission wave: node bookkeeping, +the section table and its enable-flag readers and writers, the per-kind editors and display names.
+wwwroot-src/src/components/controlTree/programItemTypes.ts— the Program wave: the file tree from +one response, and the writeback conversion nodes.
+wwwroot-src/src/components/controlTree/softNcItemTypes.ts— the controller wave: the two planes, +the core leaves every runner grows, and the snapshot flags that decide which brand leaves join them.
+wwwroot-src/src/components/controlTree/toolHouseItemTypes.ts— the Tool House wave and its +tool-<id>role paths.
+wwwroot-src/src/components/controlTree/runnerSuitItemTypes.ts— the two runner-suit leaves.
+wwwroot-src/src/components/controlTree/MissionCommandSlavePanel.vue— the command control bar, and +the only panel that exposes a dirty-switch gate.
+wwwroot-src/src/components/mission/ScriptCommandPanel.vue— the autosaving editor whose gate is +forwarded, with its save / discard / cancel prompt.
+wwwroot-src/src/components/panels/PanelExpansion.vue— the expansion row both dock rows are built +from.
+wwwroot-src/src/composables/useViewPrefs.ts— the device-local expansion list, last selections, +dock sizes and the two runner-suit checkboxes.
+wwwroot-src/src/composables/useCleanupHub.ts— the key registration the builders call.
+wwwroot-src/src/router/treeRoutes.ts— the id migration hop and the page a given id belongs to.
+wwwroot-src/src/pages/ExecutionPage.vue— creates and provides theexecutionhost.
+wwwroot-src/src/pages/GeneralSetupPage.vue— creates and provides theequipmenthost, and adds +the content column.
+wwwroot-src/src/pages/ToolHousePage.vue— the registry consumer that renders tabs instead of a +tree.
+wwwroot-src/src/layouts/MainLayout.vue— the project epoch that decides when a host is rebuilt.
+wwwroot-src/src/i18n/en/tree.ts— the dock labels, the group intros and the node role labels.
+- Tree Ids and Routes — how a node id becomes a URL, which page owns a given id, and how +an older id keeps resolving +
- Session State — the project epoch and the stores a host is rebuilt around +
- Execution Page — the first live host: the run cockpit's Mission and Program branches +
- General Setup Page — the second live host: the equipment branches and the content column +
- Controller Branch — the one branch whose node set is not fixed: it is regrown from a +snapshot, so which children exist depends on the controller in the project +
- sets the vue-i18n instance's
localeref;
+ - calls Quasar's
Lang.set()with the mapped language pack;
+ - writes
document.documentElement.lang, which is what selects the CJK glyph variant;
+ - rebuilds the exported
collator, anIntl.Collatorfor the new locale with base sensitivity and +numeric ordering;
+ - writes the locale into
localStorageunderhinc.lang, inside atry/catchbecause storage can +be unavailable;
+ - calls the re-title hook the router registered, so a browser tab parked on a page without navigating +still gets its title in the new language. +
- Cache hit. The cached locale is applied immediately, so the first painted frame is already +translated, and a reconcile against the server is fired without being awaited. The server value is +the source of truth, so a disagreement — the language was changed from another browser — repaints +shortly after mount. +
- Cache miss. The file returns a promise racing the language request against a four-second +timeout, and mount waits on it. A dead backend rejects quickly; a hung one is capped by the timeout +rather than blanking the application. Either failure falls back to the normalized browser language, +and then to English. +
- Component text. Every
t()call in a template or computed.
+ - Quasar's built-in texts, through the language pack the same call sets. +
- The tab title. Route records carry an i18n key in
meta.title; the router resolves it against +the active locale and composes the title from it. It re-resolves both after a navigation and from +the hookapplyLocalecalls.
+ - Control-Tree node labels, described below. +
- Engine session messages, which are re-resolved because the localizing helper reads the locale +ref, so rows built inside a computed rebuild on a switch. +
- Coded API errors. Ten error codes are mapped onto
apiErrors.*keys; a failed response carrying +one of them renders the bundle value with the server's interpolation arguments. English is passed +straight through to the server's own message on purpose, because that message is the English +rendering and keeps per-site nuance the generic bundle value flattens.
+ - Engine notifications. Session messages arrive with a structured id and English text. The client
+swaps in an
engineMessages.<id>bundle entry, but only when the wire text agrees with the English +bundle entry for that id: a templated message must match the canonical English template exactly +before its arguments are re-interpolated, and an untemplated one is swapped only when the bundle +entry has no interpolation holes. Any mismatch — an id emitted with different templates at different +sites, or an engine built against a different bundle — falls back to the wire English rather than +rendering wrong text. The EnglishengineMessagesvalues are therefore load-bearing code, not a +description of the Chinese ones.
+ - Key isomorphism. The three flattened key sets must be identical, compared in both directions, +so an extra key in a Chinese bundle fails as loudly as a missing one. +
- Bidirectional key integrity. Every bundle-shaped string literal under the source tree —
+excluding the bundles themselves — must resolve to an English key, and every English key must be
+referenced from somewhere. A literal naming a namespace prefix counts as a reference to everything
+beneath it; a template literal with an interpolation hole is expanded into a wildcard, which is how
+
tree.mission.kind.${kind}is matched; and@:linked.keyreferences inside bundle values count as +well. A double-quoted literal in a Vue template is treated as a key only when its attribute is one +of the four the check knows to be key-valued, so an expression attribute whose local variable +happens to share a namespace name is not mistaken for one. The orphan half is what makes the check +bidirectional, and its allow-list is currently empty.
+ - Shape preservation. Never-translate tokens present in an English value must survive verbatim in +both Chinese values — product and brand names, file formats and unit symbols by literal match, and +NC codes, controller parameter names and function-key shortcuts by pattern. Placeholder sets must +match in both directions, so a Chinese value can neither lose a placeholder nor invent one. +
- Per-locale vocabulary. An adjudicated forbidden-term table per locale, each entry naming its +replacement and its reason, plus a check for a Chinese value identical to its English source that +contains no CJK at all. Both carry narrow exemptions: named keys where a term is used in a +different sense, and a small identity set for values that legitimately are the identifier. +
- The shared numeric field's validation text.
NumericInput.vueimports nothing from vue-i18n and +builds three messages as template literals directly: an invalid-number message quoting what was +typed, a “must be ≥” message naming the field's minimum, and a “must be ≤” message naming its +maximum. All three are produced on blur — the field commits on blur or on Enter — and every panel +that embeds the widget shows them in English. Awidgetsnamespace exists for exactly this kind of +shared control text; this component does not use it.
+ - The geometry and topology editors' error prefixes. Those editors emit their failure context as a +template literal that leads with the type name and the operation, so the leading phrase is English +whatever the locale. The allowlist records the family as a ledger note rather than as matched +entries. +
- The API-error default. The generic fallback message in the HTTP helper is allowlisted +deliberately, on the grounds that it sits in the same stream as the server's own English messages. +
wwwroot-src/src/i18n/index.ts— the vue-i18n instance and its fallback chain,SUPPORTED_LOCALES, +normalizeLocale,applyLocale, the Quasar language-pack map, the shared collator, the storage key +and the re-title registration.
+wwwroot-src/src/i18n/schema.ts— the English bundle asMessageSchema.
+wwwroot-src/src/i18n/schema.d.ts— the ambient augmentation that registers that shape as +vue-i18n's global message schema.
+wwwroot-src/src/i18n/en/index.ts— the English bundle: the twenty-three namespaces assembled into +one object.
+wwwroot-src/src/i18n/zh-Hant/index.tsandwwwroot-src/src/i18n/zh-Hans/index.ts— the same +assembly, each annotated withMessageSchema.
+wwwroot-src/src/i18n/en/engineMessages.ts— the engine-notification renderings keyed by structured +id; its English values are the eligibility gate for every swap.
+wwwroot-src/src/i18n/en/routes.ts— the strings the routes'meta.titlekeys resolve to.
+wwwroot-src/src/i18n/en/widgets.ts— the shared-widget namespace.
+wwwroot-src/src/i18n/README.md— the invariants written beside the bundles: the fallback rule, the +never-translate list, the number and sorting rules and the vocabulary table.
+wwwroot-src/src/i18n/glossary.yaml— generated term data, imported by nothing.
+wwwroot-src/src/boot/i18n.ts— the plugin installation, the cached-locale fast path with its +unawaited reconcile, the awaited race on a cache miss, and the guards around every locale step.
+wwwroot-src/quasar.config.ts— the boot list whose order places i18n after the authentication +fetch patch.
+wwwroot-src/index.html— the static shell whoselangattribute is corrected at mount.
+wwwroot-src/package.json—buildas the i18n scripts followed byquasar build, andvue-tsc+as a separate script.
+scripts/lint-glossary.mjs— the four bundle checks and the adjudication tables they read.
+scripts/census.mjs— the raw display-string extraction, the allowlist subtraction and the per-file +ratchet.
+scripts/i18n-allowlist.json— the classified exemptions, each with its reason.
+scripts/i18n-baseline.json— the per-file residual ceiling the census compares against.
+wwwroot-src/src/stores/appState.ts— the language reference, the available-code list, and the +action that POSTs before applying the locale and rolls back on failure.
+wwwroot-src/src/api/preference.ts— the typed wrappers over the language endpoints and the +?lang=-bearing step-present key request.
+wwwroot-src/src/api/http.ts—currentLang(), the coded-error map and the app-locale rendering +that skips English.
+wwwroot-src/src/api/sessionMessages.ts— the engine-notification localizer, its template-equality +gate and the repeat-fold wrapper.
+wwwroot-src/src/components/controlTree/itemTypes.ts— the node'slabel,labelKeyand +labelParamsfields and thenodeDisplayLabelandnodeDisplayInforesolvers.
+wwwroot-src/src/components/controlTree/useControlTreeHost.ts— the rendered-tree computed that +calls the resolver, and the locale watcher that re-pulls the server-composed mission command titles +without rebuilding the branch.
+wwwroot-src/src/components/controlTree/missionItemTypes.ts— the kind display names and their +tree.mission.kind.*key twin, and the rule that drops the key whenever the entry carries a title.
+wwwroot-src/src/components/widgets/NumericInput.vue— the shared numeric field: blur-and-Enter +commit, and the three hard-coded validation messages.
+wwwroot-src/src/components/AppMenuBar.vue— the language submenu, its hard-coded self-name map and +the action it calls.
+wwwroot-src/src/components/explorerSort.ts— the only consumer of the shared collator, calling it +in place so a switch is picked up.
+wwwroot-src/src/router/index.ts— the key-valuedmeta.titleresolution and the hook the locale +switch calls to re-title a parked tab.
+Environments/PreferenceController.cs— the language endpoints, the supported-code list and the +400 on an unlisted code.
+Environments/UserConfig.cs— the persisted language code, its English default and its XML +round-trip.
+Environments/PresentCatalogService.cs— the per-request language resolution chain, the tag +normalizer and the shipped catalog overlay.
+Missions/MissionController.cs— the per-request UI culture taken from the same chain and injected +into command title composition.
+- Language Selection SubMenu — the menu gesture that drives this mechanism, and the only +place a user changes the locale +
- Translation Remarks — the terminology contract the bundles are written to, and the +readings the vocabulary lint enforces +
- Control Tree — The engine both tree pages share: the node shape, the item-type registry, branch building, the selection gate and the panel host +
- Tree Ids and Routes — The URL surface: the route table, the redirect-only paths, and how a
?tree=id is migrated and landed on the page that owns it
+ - Session State — The project epoch behind the keep-alive, the stores and module singletons outside it, and what is device-local +
- Internationalization — Three locales over one English schema: the bundles, the single switch point, the build-gating lint, and the strings that never reach a bundle +
- Login and Authentication — The optional login gate: the sign-in screen, the config switch that turns it on, the cookie scheme and fallback policy it registers, and the router guard in front of them +
- Log Viewer Page — The read-only
/preference/logscreen: today's server log file, its auto-refresh, and the daily file sink behind it
+ - Program and Hosting — The ASP.NET Core host: the HiAPI start/stop pair, the all-singleton container, the SignalR hubs, the SPA fallback, and the configuration that pins the listening address +
- Conventions — the other cross-screen folder: the rules an author follows rather than the machinery an author reads +
- Anatomy by Source Directory — the inverse index, for entering by the directory a change landed in +
- Log Viewer Page — a viewport-locked page sized from the layout's measured header and footer, so
+the window itself never scrolls
+
-
+
- Top Tool Bar
+
-
+
Log ViewerTitle
+- Date Badge — outlined; carries the date the last answer reported, and reads
todayonly +before any answer has set one. Coloured as the primary accent while content is loaded and grey +otherwise
+ - Last-Loaded Caption —
last loaded: {time}, shown once a load has succeeded. The time is +formatted in the application locale rather than the browser's, so it re-renders on a +language switch; the hover title carries the same instant as an ISO 8601 UTC string
+ - Line Count Caption — shown only while content is loaded, where it reads
{count} lines. The +plural string'sno linesandone lineforms are out of reach: a zero count is exactly the +state that hides the caption, and every writer ends its entry with a newline. The count is the +number of newline-separated segments, so a file ending in a newline counts one more than it +shows, and a multi-line exception counts as several
+ AutoToggle — arms auto-refresh; off by default. TooltipRe-fetch the log file periodically
+- Interval Select —
2 s/5 s/10 s/30 s, defaulting to5 s; disabled whileAuto+is off
+ RefreshButton — reloads at once, and shows a spinner in place of its label while the +request is in flight. TooltipReload the log file
+CopyButton — disabled without content. TooltipCopy log content to clipboard
+DownloadButton — disabled without content. TooltipDownload today's log file
+
+ - Separator +
- Log Area — a dark, monospaced, scrolling region filling the rest of the page. Long lines are not
+wrapped, so it scrolls in both directions. It shows exactly one of four things:
+
-
+
- Error Panel — an error icon, the failure message, and a
RetryButton that repeats the load
+ - Empty Panel — a document icon,
No log file for today., and the hint +Log file will appear once the webservice writes its first entry.
+ - Log Content — the file's text, preformatted and read-only, with no folding, no line numbers and +no per-line affordance of any kind +
- Loading Spinner — with
Loading log...beneath it, shown while a load is in flight and +nothing has been loaded yet
+
+ - Error Panel — an error icon, the failure message, and a
+ - Top Tool Bar
+
wwwroot-src/src/pages/LogViewerPage.vue— the whole page: the toolbar, the four states of the +log area, the auto-refresh timer and its watcher, the stick-to-bottom scroll rule, and the copy +and download handlers.
+wwwroot-src/src/api/logs.ts— the typed wrappers over the two endpoints, the404-to-empty +normalisation with its browser-clock date fallback, and the error type carrying the HTTP status.
+wwwroot-src/src/router/routes.ts— thepreference/logpath, thepreference-logroute name and +the title key the tab and the menu bar's active-page indicator resolve.
+wwwroot-src/src/router/index.ts— the login guard the route passes through, and the retitle hook +that names the browser tab.
+wwwroot-src/src/components/AppMenuBar.vue— the always-visibleShow Logbutton in the bar's +right-hand group, and the Preference dropdown that carries no log entry.
+wwwroot-src/src/layouts/MainLayout.vue— the keep-alive that caches this page rather than +unmounting it, and the project epoch that rebuilds the cache.
+wwwroot-src/src/stores/appState.ts— the message action that forwards a load failure to the +footer's foreground channel.
+wwwroot-src/src/stores/routineProgress.ts— the foreground-message store that failure lands in.
+wwwroot-src/src/utils/pageFit.ts— the page-height function that locks the page to the measured +viewport so only the log area scrolls.
+wwwroot-src/src/i18n/en/logs.ts— the page's own English strings: the title, the empty and +loading states, the plural line count and the tooltips.
+wwwroot-src/src/i18n/en/menu.ts— theShow Loglabel and its tooltip.
+wwwroot-src/src/i18n/en/common.ts— the sharedAuto,Refresh,CopyandDownloadlabels.
+wwwroot-src/src/i18n/en/routes.ts— theLog Viewerroute title.
+Environments/ProjectController.cs— the two endpoints the page reads, both resolving today's file +under the working directory, plus the append endpoint nothing calls.
+Common/DailyFileLoggerProvider.cs— the sink: the per-day file, the entry format, the lock, the +swallowed I/O failures and the null scope.
+Program.cs— registers that provider and its level filters, and writes the one startup entry +right after the host is built.
+- Main Panel — the menu bar whose right-hand
Show Logbutton is the only navigation into this page
+ - Preference Menu Dropdown — the dropdown this route's path is named after, and which carries no log entry of its own +
- The class default is off.
Enabledinitialises tofalse, so a service whose configuration +carries noAuthsection — or an empty one — runs with no login at all.
+ - The shipped configuration turns it on.
appsettings.jsonand the Development overlay both set +Enabledtotrueand both supply one credential entry, so a service started from the repository +as it ships demands a sign-in.
+ - The authentication controller itself, marked at class level, so all three of its endpoints stay +reachable under the policy that locks everything else. +
- Both SPA fallbacks, so the client shell — which carries no data of its own — can load and show its +own login screen. +
- If the store is not ready yet, hydrate it from the status endpoint, inside a
try/catchthat +swallows the failure. The ready flag is set only on success, so a failed probe is retried on the +next navigation.
+ - If the gate reports disabled, allow the navigation. +
- If the target is the login route, allow it. +
- If the session is not authenticated, redirect to the login route with the blocked target's full
+path — query arguments,
treeincluded — parked in aredirectargument.
+ - Otherwise allow it. +
- Login Page — a single card centred on an empty page
+
-
+
- Brand Section
+
-
+
- Brand image, above the literal title
HiNC
+ - Version caption,
vfollowed by the version string — drawn only when the status probe returned +one
+ - Prompt caption —
Please sign in to continue
+
+ - Brand image, above the literal title
- Separator +
- Sign-In Form
+
-
+
UsernameText Field — autofocused on arrival, disabled while a sign-in is in flight
+PasswordText Field — masked, with a trailing eye icon that toggles the text visible; the icon's +accessible label alternates betweenShow passwordandHide password
+- Error caption — drawn in the negative colour, and only after a failed attempt +
Sign InButton — submits the form and shows a spinner while the request is in flight
+
+
+ - Brand Section
+
Common/AuthConfig.cs— the bound shape: the enable flag and its class default, the session length, +the sliding-expiration flag, and the clear-text user list.
+Common/AuthController.cs— the anonymousapi/authcontroller: the status projection, the +first-match credential scan with its message and code pair, the single name claim it signs in, and +the sign-out.
+Program.cs— the flag-guarded registration of the cookie scheme and the fallback policy, the cookie +name and flags, the 401 / 403 event overrides, the forwarded-headers configuration ahead of them, and +the two anonymous SPA fallbacks.
+wwwroot-src/src/pages/LoginPage.vue— the screen: its own Quasar layout, the card and its fields, +the bounce on mount, and the full page load after a successful sign-in.
+wwwroot-src/src/router/routes.ts— the login route outside the shell layout, and the title key the +tab shows.
+wwwroot-src/src/router/index.ts— the navigation guard: the once-only hydration, the disabled and +login-route short circuits, and the redirect carrying the full path; also the retitle that composes +the tab text from the route's title key.
+wwwroot-src/src/stores/auth.ts— the client-side state, the four actions, and the hub gate each of +them re-drives.
+wwwroot-src/src/api/auth.ts— the three typed endpoint wrappers, and the mapping of the server's +refusal code onto the localized message.
+wwwroot-src/src/boot/auth.ts— the globalfetchwrapper that turns a 401 into a logged-out store +and a push to the login route.
+wwwroot-src/quasar.config.ts— the boot-file order that puts the interceptor ahead of the locale +probe.
+wwwroot-src/src/boot/i18n.ts— the locale resolution the login screen paints in, and its fallback +chain when the language endpoint answers 401.
+wwwroot-src/src/composables/useSharedHub.ts— the tri-state hub gate and what each of its states +does to a connection.
+wwwroot-src/src/App.vue— the one-shot wiring held behind the auth predicate, which is what the +post-sign-in page load re-runs.
+wwwroot-src/src/components/AppMenuBar.vue— the logout control and the version badge, both drawn +from the auth store.
+wwwroot-src/src/i18n/en/auth.ts— the screen's English strings.
+wwwroot-src/src/i18n/en/menu.ts— theLogoutlabel the control falls back to, and itsLog out+tooltip.
+wwwroot-src/src/i18n/en/routes.ts— theLoginroute title the browser tab resolves.
+HiNc/MachiningProcs/MachiningProject.cs— the assembly version the status endpoint reports and the +screen shows.
+- Program and Hosting — the host that binds the
Authsection and registers the scheme, +the fallback policy and the anonymous carve-outs described here
+ - Main Panel — the shell this screen renders outside of, and the menu bar that carries +the logout control and the version badge +
IHostApplicationLifetime.ApplicationStopping— the ordinary graceful stop.
+AppDomain.CurrentDomain.ProcessExit— which still fires when a console window is closed outright.
+Console.CancelKeyPress— Ctrl+C and Ctrl+Break, which cancel the immediate termination and ask +the lifetime for a graceful stop instead, so the first path runs.
+LocalProjectService.Reg()fills XFactory's default generator table with +every type the simulation pipeline may deserialise, which is what every project XML read resolves +against.
+UserConfig.Reg()adds the per-user preference file's own type to the same table; without it, a +persisted preference file cannot be read back and everyUserServiceresolution fails once one +exists.
+- Lang is appended to StringLocalizer's extended type list, so +session-command titles resolve through that assembly's satellite resources. The type list is +static, but each localizer builds its own resource-manager list lazily on its first lookup and +keeps it, so a type added after a given localizer has been used never reaches that localizer. +Registering before the builder exists is what puts the addition ahead of every first lookup. +
UserServiceis built by a factory rather than by type, so that its configuration path is +assigned unconditionally — including when no preference file exists yet, which is the only way the +first save can create one. When the file does exist, the factory deserialises it through +XFactory.
+ProxyConfigis bound by hand into a plain singleton from theProxyConfigconfiguration +section. A separateservices.Configure<ProxyConfig>call also registers it through the options +system, but nothing resolvesIOptions<ProxyConfig>; the plain singleton is what +ProxyProjectService and the startup code receive. That binding happens +once at startup and is not reloaded.
+- ProxyProjectService is registered twice — once under its own type and +once as IProjectService — so the container builds one instance per +registration. Both hold nothing but the same two injected singletons, +LocalProjectService and ProxyConfig, which is where all +the state lives. +
AuthConfigis bound from theAuthsection and registered as an instance, so the +authentication controller and the pipeline decision below read the same object. See +Login and Authentication for what it switches on.
+- Forwarded headers first, honouring
X-Forwarded-ForandX-Forwarded-Protoso that every +later stage sees the real client scheme. Both the known-proxy and the known-network lists are +cleared, so those headers are accepted from any caller.
+ - Swagger — the Swashbuckle document and its UI, both middleware.
MapOpenApibeside them +registers the framework's own document as an endpoint instead, so that one is reached at +step 8 with everything else that is routed.
+ - Default files, then static files, serving the built SPA and everything else physically present +in the web root. +
UseRouting()— placed here on purpose. An application that never calls it gets routing +inserted at the front of the pipeline instead, which would select the SPA fallback endpoint before +the static-file middleware ran; that middleware stands down once an endpoint is chosen, so a +directory URL under the web root would be answered by the Vue router's not-found view rather than +by the directory's own default document.
+- CORS, applying the
AllowAllpolicy described below.
+ - HTTPS redirection, skipped in the Development environment. +
- Authentication, then authorization. Both are no-ops when the login gate is off, because no +scheme and no fallback policy are registered in that case. +
- Endpoints — controllers, the eight hubs, and the two SPA fallbacks. +
util/file-explorer/{**location}— an explicit pattern with no file-name constraint.
+- The bare fallback, for everything else. +
Program.cs— the whole subject of this page: the pre-builder registrations, every service +registration, the JSON and Swagger options, the CORS policy, the pipeline order, the eight +MapHubcalls, the two SPA fallbacks, theAppBegin/AppEndpair and the three shutdown +triggers.
+appsettings.json— the shipped listening address, the admin directory and the login section.
+appsettings.Development.json— the Development overlay, which declares noKestrelsection.
+Properties/launchSettings.json— the two launch profiles whoseapplicationUrltheKestrel+section overrides.
+Common/DailyFileLoggerProvider.cs— the per-day file logger the log endpoint reads back.
+Common/AuthConfig.cs— the shape bound from theAuthsection.
+Common/AuthController.cs— the anonymous carve-out that keeps login reachable under the fallback +policy.
+Common/NamedRootResolver.cs— resolves the admin, project and resource roots the seeder and the +file endpoints work against.
+Common/CleanupHub.cs— the cleanup hub, and the per-invocation dictionary its disconnect +handler walks.
+Common/IndexController.cs— the index remove endpoint the client calls before unmount.
+Disp/RenderingHub.cs— the canvas hub.
+Execution/SessionSinkHub.cs— the shared base and the four per-sink message hubs.
+Execution/ExecutionStatusHub.cs— the status, cursor and session-message hub.
+Execution/ClStripHub.cs— the strip-chart hub.
+Environments/UserConfig.cs— the preference type registered before the builder is created.
+Environments/ProjectController.cs— the log endpoints that read the daily file.
+wwwroot-src/src/composables/useCleanupHub.ts— the client's own key set and the index-remove +calls that release it.
+wwwroot-src/src/api/mission.ts— the option-snapshot reader that maps the named-literal strings +back to numbers, and the string-bodied setters that send them.
+wwwroot-src/quasar.config.ts— the build output directory that puts the bundle in the web root, +the history router mode, and the dev-server proxy list.
+wwwroot-src/package.json— the build script that runs the i18n lint before the Quasar build.
+HiNc/HiNcKits/LocalApp.cs—AppBeginandAppEnd: licence log-in and log-out, display-engine +start and finish, and the step-storage open and dispose.
+HiNc/HiNcKits/HiNcHost.cs— the cache-database identifier the host assigns at startup.
+HiNc/HiNcKits/ProxyConfig.cs— the admin-directory setting and its own default.
+HiNc/HiNcKits/ResourceSeeder.cs— the marked-defaults seeding pass and its version stamp.
+HiNc/SqliteUtils/SqliteStepStorage.cs— the step cache, and the per-user default path that makes +an explicit per-instance path necessary.
+HiNc/MachiningProcs/ProxyProjectService.cs— the project service registered under two service +types, holding only the singletons it is given.
+HiGeom/Common/StringLocalizer.cs— the static extended type list, and the per-instance +resource-manager list each localizer builds on its own first lookup.
+- Session State — what survives a project change inside the singletons this host +registers, and what is rebuilt +
- Login and Authentication — the
Authsection this host binds, and the cookie scheme and +fallback policy it registers when the gate is on
+ - Rendering Canvas on Web Service Application — the canvas hub this host maps, and the per-connection +display engine behind it +
projectPathis hydrated once at boot by the store's status fetch, assigned by New, Load and +Save As from the response each returns, cleared outright by Close, and driven thereafter by a +watcher inside the project store on the shared execution-status hub's status payload. A project +change made in another tab, from a different browser, or outside the browser altogether therefore +bumps the epoch here as well — and so, once, does the boot hydration on a service that already has +a project open.
+projectVersionis a counter the store raises in exactly one place: ReLoad. The backend raises +its project-changed event with the path it already had, so the broadcast cannot be told apart from +a no-op; the client-side counter is what makes a reload remount.
+- A cached page's watchers keep firing while another page is showing. The Control-Tree host compares
+the current route name against the one its scope owns before it writes the
?tree=query back, so +an off-screen host cannot rewrite the visible page's URL.
+ - A global listener has to be dropped on deactivation, not on unmount. The Execution transport binds +its F5–F8 shortcuts on mount and on activation and removes them on deactivation and unmount, so on +any other page F5 falls back to the browser's own reload instead of starting the run. +
- The Execution, General Setup and Tool House pages each track their own activation in a flag that
+feeds the rendering canvas's
activeprop. Such a page keeps its canvas mounted and its connection +open while the backend engine stops rendering for it.
+ - project holds
projectPath,projectVersion, the admin and project directories, a loading flag +and thehasProjectcomputed. Its actions post to the project endpoints and each throws a typed busy +error on HTTP 409, which the backend returns when another project file operation is already running — +the request is refused rather than queued. The store also owns the subscription to the shared +execution-status hub and the watcher that adopts the path that hub broadcasts.
+ - appState holds the debug flag (initialised from whether the page is served from
localhost), the +physics-options and physics-licensed flags, the language code with the list of codes the server +offers, and the Execution division-visibility record.loadServerPreferences, called from the +layout's mount hook, hydrates those three groups in independently guarded steps, so a failing +endpoint leaves the rest usable. A division-flag write is applied optimistically and rolled back on +failure, with a monotonic sequence number so a late response cannot overwrite newer state. Its +setMessageis a forwarder into the footer's foreground channel.
+ - auth holds whether the login gate is enabled, whether this session is authenticated, the user +name, the version string the login screen shows, and a ready flag set once the first status probe +resolves. Every transition re-drives the global hub gate described below. +
- routineProgress backs the footer's two channels. The foreground channel keeps the latest entry +plus a history capped at one hundred entries, which the footer's history button lists; the background +channel is a single in-flight job with a message and an optional fraction and keeps no history at +all, so it disappears when the job ends. A boot patch mirrors every Quasar toast into the foreground +channel, so a toast stays reviewable after it fades. +
- The tree-page view preferences key holds, per tree page, the column-visibility record and the two +left-dock row states; and, shared across both pages, the dock pixel widths, the editor row height, +the strip/step ratio, the two chart legend widths, the strip x-axis mode, the per-panel resize +weights, the Control-Tree expansion list, the last selection per page, the two runner-suit +checkboxes, and the three device-local panel switches for the 3D canvas, the CWE canvas and the +sentence syntax view. Writes are debounced, and two earlier key names are read once as migration +seeds when the current key is absent. +
- The locale key is a paint-time hint only: the boot sequence applies it before mount so the first +frame is already translated, then reconciles against the server value, which wins. +
- The File Explorer key holds whether its editor pane is shown, the pane split, the auto-save switch +and the sort order. +
- Each rendering canvas opens its own connection to the rendering hub on mount and stops it on unmount.
+Because pages are cached, navigating away does not close one, and whether the backend engine stops
+drawing for it depends on the host. The Execution, General Setup and Tool House pages hand their
+activation flag to the canvas's
activeprop, which pauses the engine while the page sits off-screen; +the Controller, Machine Tool and Mech Builder canvases and the File Explorer's STL preview bind no +such prop, and the Execution page's CWE canvas pins it true, so all of those keep rendering until +something unmounts them. Collapsing a panel does close one, unless that panel keeps its content +mounted: the Execution page's 3D canvas panel does, so it survives a collapse, while the CWE panel +beside it does not and its canvas connection goes with it.
+ - The cleanup hub is opened once per Control-Tree host and once each on the Controller and Tool House +pages. Its composable also holds the set of IndexService keys that owner registered; on unmount it +posts a removal for every one of them and then stops the connection, so a teardown releases the +server-side objects the page had indexed even if the hub never connected. +
wwwroot-src/src/layouts/MainLayout.vue— the project epoch, its two watchers, the keyed +<keep-alive>around the router outlet, the footer, and the server-preference hydration call.
+wwwroot-src/src/App.vue— the auth-gated one-shot wiring: the project hub subscription and the +first status fetch.
+wwwroot-src/src/stores/index.ts— the Pinia instance the four stores are created against.
+wwwroot-src/src/stores/project.ts— the project path and version, the directories, the file actions +and their busy error, the hub subscription, and the watcher that adopts the broadcast path.
+wwwroot-src/src/stores/appState.ts— the debug and physics flags, the language state, the Execution +division config with its optimistic write and sequence guard, and the footer forwarder.
+wwwroot-src/src/stores/auth.ts— the login-gate state and the hub gate it drives.
+wwwroot-src/src/stores/routineProgress.ts— the footer's foreground history and its single live +background job.
+wwwroot-src/src/composables/useViewPrefs.ts— the device-local view preferences: the shape, the +defaults, the migration seeds and the debounced write.
+wwwroot-src/src/composables/useSoftNcRunner.ts— the controller singleton: the shared runner +snapshot, the object key, and the install-once project watch.
+wwwroot-src/src/composables/useSpindleCapability.ts— the spindle singleton on the same lifecycle +idiom.
+wwwroot-src/src/composables/useToolHouse.ts— the tool-house singleton and the coalesced reload the +page calls on mount.
+wwwroot-src/src/composables/useSharedHub.ts— the shared-hub factory: consumer counting, the +teardown grace window, the auth gate, the retry schedule and the focus recovery.
+wwwroot-src/src/composables/useExecutionStatusHub.ts— the status, cursor and message payloads the +store and the footer read.
+wwwroot-src/src/composables/useClStripHub.ts— the strip snapshot and update counter the charts +watch.
+wwwroot-src/src/composables/useSessionSinkHub.ts— the four message sinks and their +notify-and-re-pull contract.
+wwwroot-src/src/composables/useCleanupHub.ts— the per-instance cleanup connection and the key set +it releases on unmount.
+wwwroot-src/src/composables/useExecutionTransport.ts— the activation-scoped keyboard shortcuts and +the shared reset flag.
+wwwroot-src/src/composables/useExecutionRuntime.ts— the runtime flags the run page publishes.
+wwwroot-src/src/composables/useSentenceCursor.ts— the shared source cursor and its install-once +watch on step selection.
+wwwroot-src/src/composables/useStripChartGroup.ts— the group reload tick and the hovered x label.
+wwwroot-src/src/composables/useCycleSyncMark.ts— the per-group cycle-chart cursor mark the sim +and sensor charts share.
+wwwroot-src/src/composables/useConversionJump.ts— the parked cross-panel jump.
+wwwroot-src/src/components/RenderingCanvas.vue— the per-instance rendering connection, its mount +and unmount lifecycle, and the mounted guard that survives a mid-await teardown.
+wwwroot-src/src/components/panels/PanelExpansion.vue— the expansion row, and the keep-mounted flag +that decides whether a collapse unmounts its content.
+wwwroot-src/src/components/AppFooter.vue— the two footer channels and the recent-message list.
+wwwroot-src/src/components/FileExplorer.vue— the explorer's own device-local preference key.
+wwwroot-src/src/components/controlTree/useControlTreeHost.ts— the per-page host: the route-name +guard on the?tree=sync, the landing selection, and the cleanup registrations.
+wwwroot-src/src/pages/ExecutionPage.vue— the activation flag, the once-per-project initialization, +and the panel that keeps the 3D canvas mounted through a collapse.
+wwwroot-src/src/pages/GeneralSetupPage.vue— the same lifecycle for the equipment host.
+wwwroot-src/src/pages/ToolHousePage.vue— the page that reloads its singleton explicitly on mount.
+wwwroot-src/src/pages/ControllerPage.vue,wwwroot-src/src/pages/MachineToolPage.vue, +wwwroot-src/src/pages/MechBuilderPage.vueandwwwroot-src/src/components/StlPreviewPane.vue— +the canvases mounted with noactivebinding.
+wwwroot-src/src/components/execution/StepVolumePanel.vue— the CWE canvas that pinsactivetrue +and is stopped only by its own unmount.
+wwwroot-src/src/pages/FileExplorerPage.vue— the route-leave guard that settles the editor buffer.
+wwwroot-src/src/pages/LoginPage.vue— the full-page navigation after a successful sign-in.
+wwwroot-src/src/router/index.ts— the auth guard that hydrates status before the first navigation.
+wwwroot-src/src/router/routes.ts— which routes render inside the layout and which do not.
+wwwroot-src/src/boot/auth.ts— the fetch wrapper that turns a 401 into a logged-out store and a +bounce to the login screen.
+wwwroot-src/src/boot/i18n.ts— the cached-locale fast path and the server reconcile.
+wwwroot-src/src/boot/routine-toast.ts— the toast mirror into the footer channel.
+wwwroot-src/src/i18n/index.ts— the locale storage key and the single place a locale change is +applied.
+wwwroot-src/src/api/preference.ts— the typed wrappers over the preference endpoints the app-state +store hydrates from.
+Environments/ProjectController.cs— the status, new, load, save, save-as, reload and close +endpoints, and the conflict reply that becomes the client's busy error.
+Environments/PreferenceController.cs— the endpoints behind the server-held preferences.
+Environments/UserConfig.cs— the persisted user configuration: the physics switch, the language +code, the graphic-cache limits, the step-present list and the Execution division config.
+Environments/UserService.cs— the singleton that owns that configuration and writes it to file.
+Execution/ExecutionStatusService.cs— the broadcast that carries a project change to every +connected browser.
+Program.cs— where the user-configuration service is registered and its file path fixed.
+- Control Tree — the per-page host this epoch destroys and rebuilds, and the selection it +restores from device-local storage +
- Program and Hosting — how the SPA is served and hosted, and where the server-side +singletons this page reads actually live +
- Main Panel — the shell around the router outlet: the menu bar that fires the project +actions and the footer that shows their outcome +
- The change is ignored unless this page owns the current route. +
- The raw query is migrated into today's id. If its first segment names a different page and +that segment is one of the known roots, the location is replaced with the route that owns it and +the pass stops. +
- An empty query falls back to the landing selection: the persisted last id for this page when +that id exists in the tree just built, otherwise the page's root node — which is always present +once the tree is built, so the editor column always has something to show. +
- An id the built tree does not contain is ignored — the current selection stands and the URL +keeps the unrecognised value until the next selection overwrites it. This is deliberate: the +redirect in step 2 fires only for known roots, because sending an unroutable id back to the same +page would loop. +
- Otherwise every ancestor prefix of the id is added to the expanded set so the node is visible, +and the selection is requested through the same gate a tree click uses. A panel holding unsaved +edits may refuse the switch, and a refusal re-points the URL at the selection that actually +stands. +
- to the raw
?tree=query, on every read;
+ - to the persisted last selection, before it is looked up in the tree; +
- to the persisted list of expanded node ids, once when a host is constructed, so an older +expansion set still unfolds the branches it names; +
- as the first step of
routeForTreeId, so the id-to-page resolver never sees an unmigrated id.
+ toolhouse— the Tool House route, with the id's segments translated into path params (below).
+equipment— the General Setup route, carrying the migrated id as?tree=.
+- anything else — the Execution route, carrying the migrated id as
?tree=. This is a +fall-through, not a test forexecution, so an unrecognised id handed to this function lands on +the Execution page. Reaching it with a genuinely unroutable id is prevented upstream: the host +calls the function only for ids whose root is inTREE_PAGE_ROOTS.
+ - segment two contributes
toolIdwhen it istool-followed by an integer;
+ - segment three contributes
tabwhen it is one ofgeneral,cutter,holder,clamping, +intelligent;
+ - segment four contributes
subtabwhen it is one of the cutter sections —material,profile, +contours,upper-beam,opt— or one of the holder sections,geometryandresolution.
+ wwwroot-src/src/router/routes.ts— the route table, the redirect-only records, the login route +outside the shell layout, and the trailing catch-all.
+wwwroot-src/src/router/treeRoutes.ts—migrateLegacyTreeId,TREE_PAGE_ROOTS, +routeForTreeId, the three tab-name lists it shares with the Tool House page, and +SPINDLE_TABS, which only the spindle redirect reads.
+wwwroot-src/src/router/index.ts— the history-mode router factory, the authentication guard, +and the retitle hook registered with the i18n module.
+wwwroot-src/src/components/controlTree/useControlTreeHost.ts— the tree-page host: +rawTreePathOfRoute,treePathOfRoute, the two selection/URL watchers and the unguarded +controller-checkbox watcher beside them,landingSelection,applyRouteSelectionand +syncUrlBack, plus the build-time reveal of a URL-named CSV or CL controller node.
+wwwroot-src/src/composables/useRouteTabs.ts— the tab-to-param sync used by the Tool House and +Controller routes, including dormant levels and the keep-alive re-entry canonicalisation.
+wwwroot-src/src/pages/ToolHousePage.vue— consumes the shared tab lists, resolves the tool from +its path param, and navigates by route name when the tool changes.
+wwwroot-src/src/pages/FileExplorerPage.vue— mirrors the browsed location into the route's +catch-all param and back.
+wwwroot-src/src/pages/MachineToolPage.vue— the component behind the URL-only machine-tool +route.
+wwwroot-src/src/pages/NotFoundPage.vue— the catch-all component, carrying its own layout.
+wwwroot-src/src/pages/LoginPage.vue— reads the guard'sredirectargument and replays it.
+wwwroot-src/src/composables/useViewPrefs.ts— the browser-local store holding the per-page last +selected id and the expanded-node list that migration is applied to.
+wwwroot-src/src/components/AppMenuBar.vue— the menu entries that navigate by route name.
+wwwroot-src/src/i18n/en/routes.ts— the English strings themeta.titlekeys resolve to.
+wwwroot-src/src/i18n/index.ts—registerRetitleand the locale switch that calls it.
+wwwroot-src/quasar.config.ts— selects history mode and names the build output folder the +server serves.
+Program.cs— the two SPA fallbacks, the explorer-specific one ahead of the bare one.
+- Control Tree — the tree these ids name: how a node id is formed and what a branch +is made of +
- Main Panel — the shell whose menus navigate to these routes and whose keep-alive +cache makes the route-name guards necessary +
- Execution Page — the busiest consumer of
?tree=, and the page a fall-through id lands +on
+ - The
Brief Message Text Fieldcontent is updated
+ - The message is appended to the daily log file at
logs/log-{DateTime.Now:yyyy-MM-dd}.txt
+ - Message Section Bottom Bar
+
-
+
- Brief Message Text Field (selectable for copy) +
- Show Log Button +
+ - Routine Progress Footer Bar
+
-
+
- Brief Message Text Field — the latest foreground message behind its severity glyph, reading
+
Readywhile there is none, with the full text and caption on hover
+ - Recent Messages Button — a history menu over the capped foreground list, with a clear action +
- Session Status Strip — the live execution cursor and latest session message, rendering nothing +at all until there is activity +
- Background Progress Zone — one in-flight job's spinner or progress ring and its message, which +disappears when the job ends +
+ - Brief Message Text Field — the latest foreground message behind its severity glyph, reading
+
- Main Panel — the window frame this bar is docked in +
- Session Message Panel — the session-scoped message surface, as distinct from this app-level one +
- Main Panel — The window itself: the menu bar, the router outlet, and the chrome that stays put while pages come and go +
- Bottom Message Bar — The UI-notification channel docked at the bottom of the Main Panel, and how a message reaches it +
- Session Message Panel — The four session message sinks and the tabbed panel that presents them; a runtime reset clears three of them and leaves the NC Manipulation sink standing +
- Preference Menu — The Preference dropdown: the settings it carries, and where the web client keeps the ones it does not +
- Language Selection SubMenu — The nested language picker, and when the chosen locale is applied +
- Conventions — the rules the shell applies, message handling first +
- Widgets — the reusable controls the shell and every page embed +
Language Selection SubMenu — WPF application
+-
+
- English RadioButton +
- Simplified Chinese RadioButton +
- Traditional Chinese RadioButton +
Three fixed rows in one radio group, ticked at start-up to match the current language.
+
+Language Selection SubMenu — web application
+-
+
- Language Row — one plain row per code in the store's available-code list, so the rows are +whatever the server reports rather than a fixed set of three. Each carries the language's own +name from a hard-coded label map, falling back to the raw code for anything the map does not +name, with that code beneath it as a caption; the row matching the current language is drawn in +the active state. No row carries a radio button or a checkbox. +
The parent entry captions the current language, so it reads without opening the submenu.
+
+wwwroot-src/src/components/AppMenuBar.vue— the nestedPreference → Language ▸popup: the row +loop over the available codes, the hard-coded self-name map, the active-row marker, and the +handler whose single call is the store's language action.
+wwwroot-src/src/stores/appState.ts— the current-code and available-code state, the language +action with its optimistic write, its POST-then-apply order and its rollback, and the +server-preferences load the shell layout runs on mount.
+wwwroot-src/src/api/preference.ts— the typed wrappers over the two language endpoints.
+Environments/PreferenceController.cs—GET /api/preference/languageand itsPOSTtwin, both +answeringsuccess,currentandavailable;languageCodeis the name of a field in the POST +request body only. An unlisted code is rejected with 400, and an accepted one is written to the +persistedUserConfig.LanguageCodeand saved.
+- Translation Remarks — the terminology every translated label is held to +
- Preference Menu Dropdown — the dropdown this sub-menu hangs from +
- Internationalization — the bundles and the switch point this menu drives +
- Project Service
+
-
+
- WPF Single-User Desktop Application: Uses self-hosted LocalProjectService +
- Web Service Application: injects ProxyProjectService, the IProjectService implementation that reaches the session's project across the connection +
+ - User Service:
UserService
+ - Top
Navigation Menu+-
+
Brand logo, and in the web application the HiAPI version as an outlined badge beside it. The +badge carries the HiNc assembly version reported by the anonymous authentication-status probe +the router runs before the first route resolves, so it needs neither a loaded project nor a +signed-in session; it is omitted only while that probe has reported no version.
+
+
+Project Menu Dropdown-
+
Project Path Text Field— readonly; reads “No Project Loaded” until a project is loaded
+New MenuItem
+Load MenuItem
+ReLoad MenuItem(web application only)
+Save MenuItem
+Save As MenuItem
+Close Project MenuItem(web application only, below a separator)
+
In the web application ReLoad, Save, Save As and Close Project are disabled until a project is loaded, and New, Load and Save As open the shared file-picker dialog with a
+.hincprojfilter. The picker is allowed the Admin and Project roots and is denied the Resource root, which holds read-only templates the project endpoints cannot save through; it asks for the Admin root by name, so that is where it opens, and it seeds its path box with the directory holding the loaded project, or with the admin root itself when there is none. The Project root is one of the two only while a project is loaded: the roots endpoint lists a root only when its directory resolves, and the project directory resolves to nothing until a project is open, so with no project the picker offers Admin alone. The WPF client's dropdown stops at Save As.
+
+Environment Menu Dropdown(WPF application only — the web application has no such dropdown; see below)-
+
- Machine Tool MenuItem +Open Machine Tool Page +Sole window in WPF app. +The page manages MachiningEquipment.MachiningChain. +
- Controller MenuItem +Open Legacy Controller Page +
- Tool House MenuItem +
- Fixture MenuItem +Open Fixture Page +
- Workpiece MenuItem +Open Workpiece Page +
+Mission MenuItem +Open Mission Page +In the web application the Mission editor is a branch of the Execution page's Control Tree;
+/missionredirects there.
+
+Page Menu Dropdown(web application only) — the three workflow pages in setup order (fill the tool house, set up the equipment, run), then the utilities.-
+
- Tool House MenuItem +Open Tool House Page +
- General Setup MenuItem +The equipment Control Tree — Machine Tool, Spindle Capability, Background, Coolant, Fixture, Workpiece, Controller, in that order — plus the shared equipment canvas. Two further controller branches, CSV Controller and CL Controller, are hidden by default and are switched on from the Preference dropdown. +
- Execution MenuItem +Open Execution Page +
- File Explorer MenuItem (below a separator). See Util Pages. +
- Mechanism Builder MenuItem +Open Mechanism Builder Page +
- Legacy-Controller MenuItem (below a separator) +Open Legacy Controller Page +
The dropdown is not every route the application has. The Machine Tool page stays reachable by URL for its chain-only canvas preview, the Log Viewer is reached by the
+Show Logbutton, and the Fixture, Workpiece, Spindle Capability, Mission and Background / Coolant paths resolve as redirects into the two tree pages.
+- + +
+Debug Menu Dropdown(WPF application only) — Transformers and Geometry Management, visible only while the client runs in debug mode
+Help MenuItem
+-
+
- HiAPI Version label +A label to show the HiNc library version. +The web application shows this as a version badge next to the brand logo, with no Help dropdown. +
+
+ Show LogButton — a button on both clients, not a menu item. In the web application it sits on the menu bar's right side and routes to the Log Viewer page; in the WPF client it sits at the right end of the bottom message bar. +The Log Viewer presents the server's application log for the current day, with a manual refresh, a selectable auto-refresh interval, copy to the clipboard, and download of the day's log file for offline analysis.
+- Central
Page Panel— one router outlet wrapped in a keep-alive cache keyed on a project epoch: an +integer the layout owns, raised by two watchers and read by nothing else. The first watches the project +store's path, which New, Load and Save As each assign from the response they get back, which Close Project +clears, and which a store-level watcher on the shared execution-status hub adopts from every broadcast — so a +project change made in another tab, from another browser, or outside the browser altogether bumps the epoch +here as well, and so does the boot status fetch, once, on a service that already has a project open. The +second watches a counter the store raises in exactly one place, ReLoad, which is what makes a re-read of the +same path remount at all: the backend raises its project-changed event with the path it already had, so the +broadcast cannot be told apart from a no-op. Assigning the path it already holds moves neither watcher, so +Save on the current path does not remount and neither does a Save As written back onto the path already open. +A bump discards every cached page and rebuilds it, so each page initializes on its own mount hook rather than +watching for project changes itself.
+ - Bottom footer — the routine-progress bar: the latest foreground message with a recent-messages history menu on the left, the live session status in the middle, and in-flight background job progress on the right. See Message Section on Main Panel. +
- Only a single instance of each sub-window (Mission, Workpiece, Fixture) can exist at a time +
- There is no menu entry for the run cockpit: the Main Panel itself is the player +
- The Execution Page is the landing route —
/redirects to/execution
+ - The page URL and panel state are synchronized (bi-directional navigation) +
- There is no Environment dropdown. Machine Tool, Fixture, Workpiece, Controller, Background / Coolant and Spindle Capability are branches of the General Setup page's Control Tree, and Tool House and Execution are pages of their own; the remaining paths resolve as redirects into those pages. The menu bar is therefore
Project ▾ | Page ▾ | Preference ▾, and the Controller page is reached fromPage ▾asLegacy-Controller.
+ Environments/PreferenceController.cs— the endpoints behind the Preference dropdown.
+Environments/ProjectController.cs— the project endpoints: status, new, load, save, reload, saveas and close, plus the log endpoints.
+Environments/UserService.cs— the shell's user model.
+wwwroot-src/src/components/AppMenuBar.vue— the whole navigation menu.
+wwwroot-src/src/components/AppFooter.vue— the routine-progress footer the layout docks.
+wwwroot-src/src/layouts/MainLayout.vue— the shell that hosts the menu bar, the keep-alive page panel and the footer.
+wwwroot-src/src/router/routes.ts— the routes the Page dropdown targets and the redirect-only legacy paths.
+wwwroot-src/src/i18n/en/menu.ts— the menu bar's English labels.
+wwwroot-src/src/stores/project.ts— the six Project actions and the busy error a 409 becomes.
+wwwroot-src/src/stores/routineProgress.ts— the foreground-message and background-progress store the Project handlers share with the footer.
+wwwroot-src/src/composables/useToast.ts— the toasts every Project action ends in.
+wwwroot-src/src/composables/useViewPrefs.ts— the device-local per-page column visibility and the CSV / CL controller switches.
+wwwroot-src/src/composables/useConnectionsHealth.ts— the aggregate state behind the Execution connection badge.
+wwwroot-src/src/components/widgets/FileExplorerDialog.vue— the shared picker New, Load and Save As open.
+wwwroot-src/src/components/widgets/ColumnToggleIcon.vue— the glyph each column quick-toggle draws.
+wwwroot-src/src/pages/LogViewerPage.vue— the Log Viewer theShow Logbutton opens.
+- Execution Page — the landing page the shell routes to +
- Preference Menu Dropdown — the third menu on the bar +
- Bottom Message Bar — the notification bar the shell docks along the bottom +
- General Setup Page — the equipment page the Page menu reaches +
- Tree Ids and Routes — the route table this bar navigates, and how a
?tree=id lands on the page that owns it
+ - Session State — the keep-alive this shell wraps the router view in, and what a project change destroys +
- Login and Authentication — the gate in front of this shell, and the sign-out entry on this bar +
- Log Viewer Page — the screen the Show Log button opens +
- Preference Menu Dropdown
+
-
+
- Step Present Preference Button
+
-
+
- WPF application: the first entry of the menu, opening the Step Present window. +
- Web application: the editor belongs to the Execution page's Step Info column, where a small icon button in the Step Properties panel header opens it as a modal next to the list it configures. +
+ - Graphic-Cache Dropdown
+
-
+
- WPF application: a nested menu of cache fields inside the dropdown. +
- Web application: an entry of the Execution page's extended tool bar, under
Meshed Geom ▾, which gathers the workpiece rendering-cache settings.
+ - Its write is the one preference write that does not persist.
POST /api/preference/graphic-cacheclamps the requested size between the stored limits, assigns the three fields on the liveUserConfigand returns, without callingUserService.SaveUserConfig(). The new value is service-wide and takes effect at once, but it reaches the file only when some later preference save writes the config out.
+
+ - Language Selection SubMenu
+
-
+
- The persisted value is
UserConfig.LanguageCode; the web application reaches it throughGET/POST /api/preference/language, whose response also names the language codes the server supports.
+ - In the web application the parent row captions the current language and the sub-menu marks the active code. A successful switch flips the interface text at once, so the confirmation toast already reads in the just-picked language. +
+ - The persisted value is
- CSV Controller CheckBox
+
-
+
- Web application only. The model is
useViewPrefs().showCsvController— device-local, stored in the browser'slocalStorage, not inUserConfig. It is off by default.
+ - Checking it adds the
CSV Controllernode to the General Setup page's Control Tree; unchecking removes it, moving the selection away first when that node is the selected one.
+ - The caption under the box says whether the loaded project plays CSV —
This project plays CSV, orNot used by this project— read from thereferencedflag ofGET /api/mech/csv-runner. Opening the dropdown fetches both runner snapshots in parallel, but the fetch is guarded on a loaded project: with none, neither request is made and both captions are cleared to blank. A snapshot that fails to arrive leaves its caption blank as well, so a blank caption states nothing about the project. The guard reaches the captions only — neither checkbox is disabled by it.
+
+ - Web application only. The model is
- CL Controller CheckBox
+
-
+
- The same, for
useViewPrefs().showClController, theCL ControllerControl Tree node andGET /api/mech/cl-runner.
+
+ - The same, for
- Show Physics Options CheckBox
+
-
+
- The model is
UserConfig.ShowPhysicsOptions, reached in the web application throughGET/POST /api/preference/show-physics-options.
+ - The checkbox is disabled and unchecked if
UserService.IsPhysicsLicensedis false: the GET returns the flag ANDed with the licence and the POST forcesfalsewithout it.
+
+ - The model is
- Show Log Button
+
-
+
- See Message Section. +
- It is not a Preference-dropdown entry on either platform: the WPF application puts it on the bottom message bar, and the web application puts it on the menu bar's right side as an always-visible button that opens the Log Viewer page. +
+
+ - Step Present Preference Button
+
wwwroot-src/src/components/AppMenuBar.vue— thePreference ▾dropdown itself and the menu bar'sShow Logbutton.
+wwwroot-src/src/stores/appState.ts— the model of the two server-backed items, with the boot-time hydration and the write-back actions.
+wwwroot-src/src/composables/useViewPrefs.ts— the device-locallocalStoragesingleton behind the two controller checkboxes.
+wwwroot-src/src/api/preference.ts— typed wrapper over/api/preference/languageand/api/preference/show-physics-options.
+wwwroot-src/src/api/csvRunner.ts,wwwroot-src/src/api/clRunner.ts— thereferencedflag each controller caption reports.
+wwwroot-src/src/components/controlTree/useControlTreeHost.ts— builds or omits the two controller nodes and rebuilds the tree when either checkbox flips.
+wwwroot-src/src/i18n/en/menu.ts— the dropdown's labels and captions.
+wwwroot-src/src/pages/LogViewerPage.vue— the page theShow Logbutton routes to.
+Environments/PreferenceController.cs— the endpoints. The step-present, show-physics-options, language and execution-layout writes persist throughUserService.SaveUserConfig(); the graphic-cache write is the exception, and returns without one.
+Environments/UserConfig.cs— the persistedLanguageCodeandShowPhysicsOptionsproperties.
+Environments/UserService.cs— owns the singleUserConfigthe service holds, writes it toUserConfigPath, and answers the physics licence check.
+Program.cs— the oneUserServiceregistration behind every server-backed item here, and the configuration path it resolves against the process working directory.
+- Main Panel — the menu bar this dropdown sits on +
- Language Selection SubMenu — the only sub-menu this dropdown nests +
- Graphic-Cache SubMenu — one of the two entries the web client hosts elsewhere +
- Step Present Preference Page — the other entry the web client hosts elsewhere +
- Log Viewer Page — the log screen this menu's namesake route serves +
- Hidden Controller Branches — the two equipment nodes the CSV and CL entries reveal, and what a link to one does while its box is off +
- ShellProgress — session-level routine / lifecycle messages (ShellProgress). Session-scoped: the property is null outside
BeginSession/EndSession.
- - NcDiagnosticProgress — NC-pipeline diagnostics (NcDiagnosticProgress), each anchored to its NC source sentence. +
- NcDiagnosticProgress — play-time NC-pipeline diagnostics (NcDiagnosticProgress), anchored to the NC source block that raised them wherever the diagnostic has one.
- StepDiagnosticProgress — diagnostics anchored to a motion step (StepDiagnosticProgress). +
- NcManipulationDiagnosticProgress — NC-manipulation diagnostics: a second NcDiagnosticProgress, written by ConvertClToNcFiles and OptimizeNcFiles, held apart from the play-time sink so writeback findings never mix into the play pipeline's.
- Tab Bar +
- Tab Bar (each tab carries a floating count badge, drawn only while its sink holds messages)
- Shell Tab
- NC Diagnostics Tab
- Step Diagnostics Tab +
- NC Manipulation Tab
- Per-Tab Content
-
+
- Filter Toolbar
+
-
+
SeverityFilter Dropdown
+CategoryFilter Dropdown- Message Text Filter Input +
ResetButton (this tab's three filters, nothing else)
+ExportButton
+- Matched / Total Badge (also the tab's hub connection indicator) +
- Message Table
- Filter Toolbar
+
Severity(colour-coded via GetSeverity())
-Anchor— the kind-specific position: the NC sentence index (Sn) for NC diagnostics, the step index for step diagnostics, none for shell messages
-Message— GetId() and GetNotification()
+Anchor— the kind-specific position: none for shell messages, the NC sentence ordinal (Sn <n>) for both the play-time and the manipulation NC diagnostics, and the motion step with its sentence ordinal (S<step> · Sn <n>) for step diagnostics; an NC diagnostic raised at pipeline level rather than at a source block carries none
+Message— GetCategory(), GetId() and GetNotification()- Play/SessionMessagePanel +
- wwwroot-src/src/components/execution/SessionMessagePanel.vue (tabbed panel) +
- wwwroot-src/src/components/execution/SessionMessageTab.vue + MessageRow.vue (per-tab list) +
- wwwroot-src/src/composables/useSessionSinkHub.ts (the four hub composables) +
- Execution/SessionSinkHub.cs (the SignalR hub base; one hub per sink) +
- Execution/SessionSinkBroadcastService.cs + ShellMessageBroadcastService.cs, NcDiagnosticBroadcastService.cs, StepDiagnosticBroadcastService.cs, NcManipulationDiagnosticBroadcastService.cs (subscribe-and-rebroadcast) +
- Execution/SessionSinkDtos.cs (typed DTOs)
- wwwroot-src/src/components/player/SessionMessagePanel.vue (tabbed panel) -
- wwwroot-src/src/components/player/SessionMessageTab.vue + MessageRow.vue (per-tab list) -
- wwwroot-src/src/composables/useSessionSinkHub.ts (the three hub composables) -
- Players/SessionSinkHub.cs (SignalR hubs for the three sinks) -
- Players/SessionSinkBroadcastService.cs + {Shell,Nc,Step}*BroadcastService.cs (subscribe-and-rebroadcast) -
- Players/SessionSinkDtos.cs (typed DTOs) +
- Execution Page — the page whose main column hosts this panel +
- Bottom Message Bar — the app-level notification bar, as distinct from these session sinks
- Flute Profile Section
+
-
+
- Profile Type Selection Dropdown
+
-
+
- General APT (GeneralApt) +
- Ball APT (BallApt) +
- Column APT (ColumnApt) +
- Cone APT (ConeApt) +
- Taper APT (TaperApt) +
+ - First row, always present
+
-
+
- Diameter Input Field (mm) +
- Length of Cut Input Field (mm) +
+ - Type-dependent rows, one field per row, in this order
+
-
+
- Round Radius (Rc) Input Field (mm) +
- Round Ring Radius (Rr) Input Field (mm) +
- Round Ring Height (Rz) Input Field (mm) +
- Bottom Cone Angle (Alpha) Input Field (deg) +
- Top Cone Angle (Beta) Input Field (deg) +
+
+ - Profile Type Selection Dropdown
+
- APT Profile Panel
+
-
+
- Diameter Input Field (mm) +
- Round Radius Input Field (mm) +Visible if Apt is IAptRc +
- Round Ring Radius Input Field (mm) +Visible if Apt is IAptRr +
- Round Ring Height Input Field (mm) +Visible if Apt is IAptRz +
- Bottom Cone Angle Input Field (deg) +Visible if Apt is IAptAlpha +
- Top Cone Angle Input Field (deg) +Visible if Apt is IAptBeta +
- Length of Cut Input Field (mm) +
+ wwwroot-src/src/components/controlTree/toolhouse/CutterSectionPanel.vue— the shipped home of these fields: the Profile Type selector, Diameter, Length of Cut, the per-type field table, and the whole-profile commit.
+wwwroot-src/src/components/controlTree/toolHouseItemTypes.ts— builds theprofilesection node this panel renders.
+wwwroot-src/src/router/treeRoutes.ts— maps that node's role path onto the page's:subtabparam.
+wwwroot-src/src/components/widgets/NumericInput.vue— the numeric field behind every APT input: unit as a suffix, commit on blur or Enter, noG4.
+wwwroot-src/src/i18n/en/toolhouse.ts— the exact labels: "Round Radius (Rc)", "Round Ring Radius (Rr)", "Round Ring Height (Rz)", "Bottom Cone Angle (Alpha)", "Top Cone Angle (Beta)", “Diameter”, “Length of Cut”, and the five APT type names.
+wwwroot-src/src/api/toolHouse.ts—setShaperProfileand the shaper-profile DTO shape, which carries only the fields the selected APT type lists.
+Mech/CutterController.cs— the shaper-profile endpoint: it builds aColumnApt,ConeApt,BallApt,TaperAptorGeneralAptfrom the DTO, assigns a new AptProfile, and runs the cache-clear and re-align hook.
+Mech/CutterDtoBuilder.cs— the read side that emitsaptType,diameter_mm,fluteHeight_mmand the five interface-cast fields.
+- Cutter Node Panel
+
-
+
- Title Label — Cutter +
- Cutter Type Selector — Milling Cutter / Freeform Remover / None +
- Milling Cutter branch, rendered only while the type is Milling Cutter
+
-
+
- Shank Mass Input Field (g) +
- Hone Radius Input Field (um) +
- Relief Angle Input Field (deg) +
- Sections Hint Caption — points at the section tabs below +
+ - Freeform Remover branch — one caption,
toolhouse.cutter.freeformRemoverUnavailable, sending the reader to the WPF client or HiNcRcl
+ - No-cutter branch — one hint,
toolhouse.cutter.noCutterHint, inviting the reader to pick Milling Cutter
+
+ - Cutter Management Panel
+
-
+
- Head Line
+
-
+
- Object Management Menu Button
+
-
+
- File extension is
.Cutter, load type ICutter
+ - The pointed Editor Panel is the cutter content presenter below +
+ - File extension is
- Title Label +
- Cutter Type Selection Dropdown
+
-
+
- Options: Milling Cutter, Freeform Remover, Unset +
+
+ - Object Management Menu Button
+
- Cutter Content Presenter +Varies by the Cutter Type. It can be: + + +
+ - Head Line
+
wwwroot-src/src/components/controlTree/toolhouse/ToolCutterPanel.vue— the Cutter node's panel: the Milling Cutter / Freeform Remover / None selector, the Shank Mass / Hone Radius / Relief Angle fields, the Freeform Remover caption and the no-cutter hint. Commits a type change throughensureCutterorclearCutterfollowed byclearToolCache.
+wwwroot-src/src/components/controlTree/toolHouseItemTypes.ts— registers this panel as theToolCutteritem type; its cutter child builder grows the section tabs only for a MillingCutter, and gates Material on the Advanced Physics preference.
+wwwroot-src/src/components/controlTree/toolhouse/CutterSectionPanel.vueandwwwroot-src/src/components/controlTree/toolhouse/CutterContoursPanel.vue— the panels those section nodes render.
+wwwroot-src/src/components/controlTree/toolhouse/ToolHouseRootPanel.vue— the tool-house root panel, which carries the Object Management Menu Button for the.MachiningToolHousefile.
+wwwroot-src/src/components/widgets/ObjectManagementMenuButton.vue— the reused object-management dropdown that root panel mounts.
+wwwroot-src/src/components/widgets/NumericInput.vue— the shared numeric field behind the three General inputs; it commits on blur or Enter and reverts an unparseable entry.
+wwwroot-src/src/pages/ToolHousePage.vue— the routed page that hosts the tool list and the tab cascade this panel appears in; the route is/tool-house/:toolId?/:tab?/:subtab?.
+wwwroot-src/src/i18n/en/toolhouse.ts— thetoolhouse.cutter.*strings this panel renders: cutterType, shankMass, honeRadius, reliefAngle, sectionsHint, freeformRemoverUnavailable, noCutterHint, millingCutter, freeformRemover. The None option's label comes fromcommon.options.noneinwwwroot-src/src/i18n/en/common.ts.
+wwwroot-src/src/api/toolHouse.ts— the typed wrapper:/api/ToolHousefor the house and the tool-level fields,/api/Cutterfor every cutter mutation, plusensureCutter,clearCutter,setGeneralandclearToolCache.
+Mech/CutterController.cs— the cutter backend at/api/Cutter:EnsureCutter/ClearCutter, the shaper-profile, fluting, general and opt-limit endpoints, the upper-beam endpoints and the material and coating-layer endpoints. Every field and geometry mutator clears the cutter cache before it answers.
+Mech/CutterDtoBuilder.cs— the single source of truth for the read-side cutter DTO (integralMode, general, shaperProfile, fluting, optLimit, material), shared byCutterControllerand the tool detail inToolHouseController.
+Mech/ToolHouseController.cs— the tool detail that embeds that cutter DTO and that this panel reads on mount, plus the tool-cache clear the panel calls after every commit.
+- Freeform Remover Panel
-
@@ -96,7 +98,7 @@
- Strut Geometry Tab
-
-
- Geometry Management Panel +
- Geometry Management Panel
- Manages StrutGeom
- Shaper Geometry Tab
-
-
- Geometry Management Panel +
- Geometry Management Panel
- Manages ShaperGeom
- Label: Geometry Anchor To Holder Buckle
- KeepHolderBuckleOnTop Checkbox -
- Transformer Manage Panel +
- Transformer Manage Panel
- Model is GeomToHolderTransformer
- Enabled if KeepHolderBuckleOnTop is true. @@ -145,32 +147,45 @@
This panel calls ClearCache() itself after a geometry or anchor change, so the cached solids are dropped before the tool is redrawn.
+Web Layout
+-
+
- Cutter Node Panel, the Cutter tab of the Tool House Page at
/tool-house/:toolId/cutter— the Control-Tree nodetoolhouse/tool-<id>/cutter+-
+
- Cutter Type Selector — its second option, Freeform Remover, is this cutter +
- Freeform Remover Caption (
toolhouse.cutter.freeformRemoverUnavailable) — the panel's whole body while that option is showing, directing the reader to the WPF client or HiNcRcl
+
+
Nothing else on the web belongs to this cutter. The Shank Mass / Hone Radius / Relief Angle fields on that panel are gated to the Milling Cutter branch, and the cutter's child builder grows section tabs only for a MillingCutter, so the Cutter node is a leaf here: no Material, Flute Profile, Flute Contours, Upper Beam or Optimization section appears. See Cutter Panel for the panel these live on.
Geometry Definitions
-
-
- Strut Geometry - The non-cutting portion (holder/shank) +
- Strut Geometry — The non-cutting portion (holder/shank)
- Used for collision detection
- - Shaper Geometry - The cutting portion +
- Shaper Geometry — The cutting portion
- Defines cutting surfaces
- Used for material removal simulation
Implementation Note
-Remember to call ClearCache() after geometry changes.
+Features
+Picking Freeform Remover on the web is a no-op that does not stick. The panel makes no API call for that option — it sets the selector locally, clears the tool cache, then re-reads the tool, and the re-read is what puts the selector back to Milling Cutter or None. The model on the tool is left exactly as it was.
+A tool that already carries a freeform remover reads as None. The tool detail casts the tool's cutter to a MillingCutter and reports both
+hasCutterand the embedded cutter DTO from that one cast, so a non-milling cutter comes back as no cutter at all, and the selector — which derives its value from those two fields — shows None. The Freeform Remover option is therefore reachable by the user's click but never by a load.That makes one sequence worth knowing before using the web page on such a tool: picking Milling Cutter calls
+EnsureCutter, which returns the tool's existing cutter only when it is already a MillingCutter and otherwise assigns a new one — so on a tool holding a freeform remover, that choice replaces it. Leaving the selector alone leaves the freeform remover intact, which is what a round trip through the web page does by default.Cache clearing needs no user action on either client. The WPF panel clears the remover's cache after each geometry or anchor edit; on the web there is nothing to clear for this cutter, since no endpoint mutates it, and the tool-cache clear the selector performs only drops the tool's and the holder's cached solids so the canvas redraws.
Source Code Path
-See this page for git repository.
-WPF Application Source Code Path
+See HiNC App Anatomy for git repository links.
+HiNC-2025-webservice (Quasar CLI SPA):
-
-
- Mech/ToolHouse/FreeformRemoverPanel -
Web Page Application Source Code Path
--
-
- wwwroot/mech/cutter/freeform-remover-panel.js -
- Controller/Mech/MechController.cs +
wwwroot-src/src/components/controlTree/toolhouse/ToolCutterPanel.vue— the only web surface this cutter has: the selector option, the caption shown in its place, and the branch of the type handler that makes no call.
+wwwroot-src/src/components/controlTree/toolHouseItemTypes.ts— its cutter child builder grows sections only for a MillingCutter, so this cutter's Cutter node is a leaf.
+wwwroot-src/src/i18n/en/toolhouse.ts—toolhouse.cutter.freeformRemover, the option label, andtoolhouse.cutter.freeformRemoverUnavailable, the caption; the zh-Hans and zh-Hant files carry the same keys.
+Mech/ToolHouseController.cs— the tool detail, which casts the cutter to a MillingCutter and reportshasCutterfrom that cast; this is why the selector cannot come back showing Freeform Remover.
+Mech/CutterController.cs—EnsureCutter/ClearCutterand every cutter mutation, all MillingCutter-only; the backend has no freeform-remover endpoint.
- Strut Geometry Tab
- Cutter Panel
-
-
-
- Head Line
-
-
-
- Object Management Menu Button
-
-
-
- File extension is
.Cutter
- - The pointed Editor Panel is Cutter Management Panel +
- Cutter Panel — The tab itself: the cutter type selector and the fields common to every cutter +
- Milling Cutter Panel — The shipped cutter type: material, profile, flute contours, upper beam and optimization limits +
- Freeform Remover Panel — The freeform removal volume, and where it is edited +
- APT Profile Panel — The five APT profile types the shaper profile offers, shared by the cutter editors
- - File extension is
- Title Label -
- Cutter Type Selection Dropdown
+
See Also
-
-
- Options: Milling Cutter, Freeform Remover, Unset -
-
- - Object Management Menu Button
-
- - Head Line
-
- Cutter Management Panel -Varies by the Cutter Type. It can be: - - -
- Mech/ToolHouse/CutterManagementPanel -
- wwwroot/mech/cutter/cutter-management-panel.js -
- Controller/Mech/MechController.cs +
- Tool House Page — the page these tabs belong to +
- Holder Tab — the sibling tab, and the other half of a tool +
- Stick Tool Panel — the tool node whose tab cascade this tab belongs to
- Cutter Section Tabs, in this order
+
-
+
- Material Section — grows only while the Advanced Physics preference is on +
- Flute Profile Section +
- Flute Contours Section +
- Upper Beam Section +
- Optimization Section +
+ - Material Section (
.../cutter/material) +-
+
- Flute Material
+Applies CutterMaterial
+
-
+
- Intro caption naming the
.CutterMaterialextension
+ - Material File Selector
+
-
+
- Menu Dropdown — Browse Resource…, plus Clear once a file is picked +
- Readonly path TextBox with the placeholder “No flute material selected” +
+ - Name and note caption beneath the picker, the two joined on one line +
+ - Intro caption naming the
- Shank Material, rendered only while the cutter's integral mode is Insert End
+Applies IStructureMaterial
+
-
+
- Intro caption naming the
.xmlstructure material and the.CutterMaterialalternative
+ - Material File Selector, filtered to
.xml— same Browse Resource… menu, with the placeholder “Default: AlloySteel42CrMo”
+ - Name and note caption beneath the picker +
+ - Intro caption naming the
- Solid-End Note, shown in place of that block for a solid-end cutter — the shank material mirrors the flute material, so it has no separate setting +
- Coating Layers
+Manages CoatingLayerList
+
-
+
- Add Layer Button +
- One sequence hint above the whole list: the sequence starts from the surface,
#0is the outermost / air-exposing layer, and each layer is a.CoatingMaterialplus a thickness in um
+ - One row per ThermalLayer1D, all controls on that one line
+
-
+
- Index Badge
#i
+ - Coating Material File Selector — Browse Resource…, placeholder “No coating material” +
- Thickness Input Field (um) — Length_um +
- Move Toward Surface Button, disabled on the first row +
- Move Toward Body Button, disabled on the last row +
- Delete Layer Button +
+ - Index Badge
- Empty state caption “No coating layers.” +
+
+ - Flute Material
+Applies CutterMaterial
+
StructureMaterial— the Shank Material picker
+CutterMaterial— the Flute Material picker
+CoatingMaterial— the coating rows' pickers
+- Flute Profile Section (
.../cutter/profile) +-
+
- Profile Type Selection Dropdown
+
-
+
- General APT (GeneralApt) +
- Ball APT (BallApt) +
- Column APT (ColumnApt) +
- Cone APT (ConeApt) +
- Taper APT (TaperApt) +
+ - The APT fields themselves, rendered inline in this same panel +
+ - Profile Type Selection Dropdown
+
- Flute Contours Section (
.../cutter/contours) +-
+
- Title Label — Fluting +
- Fluting Type Selection Dropdown
+
-
+
- Uniform (shared baseline) — UniformFluting +
- Free (per-flute) — FreeFluting +
— unset —
+
+ - A caption describing the chosen kind, or “Fluting unset.” while none is set +
- Fluting Node, grown only once a fluting is set — labelled Uniform Fluting or Free Fluting
+
-
+
- For a uniform fluting: Flute Number Input Field (minimum 1) +
- For a free fluting: a “Flutes: n” caption and an Add Flute Button, with a “No flutes yet” hint while the list is empty +
- Flute Contour Node(s)
+
-
+
- One Baseline Contour node (
.../fluting/baseline) for a uniform fluting; one Flute n node (.../fluting/flute-{i}) per flute for a free fluting
+ - Setup Angle Input Field (deg) +
- Delete This Flute Button — free fluting only +
- Side Contour Sub-Node (
.../baseline/side,.../flute-{i}/side) +-
+
- Kind Selector — Const Helix / Freeform /
— unset —
+ - Const Helix Side Contour form: Helix Angle (deg), Radial Rake Angle (deg), Radial Relief Angle (deg) +
- Freeform Side Contour form: the span-position table, ordered by Z +
+ - Kind Selector — Const Helix / Freeform /
- Bottom Contour Sub-Node (
.../baseline/bottom,.../flute-{i}/bottom) +-
+
- Kind Selector — Slide / Freeform /
— unset —
+ - Slide Bottom Contour form: Outer Radius (mm), Cutter Length on Bottom Projection (mm), Eccentric Angle (deg), Disk Angle (deg), Axial Rake Angle (deg) +
- Freeform Bottom Contour form: the span-position table, ordered by R +
+ - Kind Selector — Slide / Freeform /
+ - One Baseline Contour node (
+
+ - Upper Beam Section (
.../cutter/upper-beam) +-
+
- Intro caption +
- Warnings Banner — one orange banner per geometry issue the backend reports, e.g. an ExtendedCylinder beam whose FullLength sits below the flute top +
- Geometry Management Control, limited to the six IGetStl kinds an upper beam can be + + +
+ - Optimization Section (
.../cutter/opt) +-
+
- Enable Optimization Checkbox
+
-
+
- Controls whether the limits below are shown and active +
+ - When optimization is enabled:
+
-
+
- Limit by Theoretical Minimum Feed Per Tooth Checkbox +
- Limit by Relief Angle Checkbox +
- Min Feed Per Tooth Input Field (mm) +
- Max Feed Per Tooth Input Field (mm) +
- Yielding Utilization Factor Input Field
+
-
+
- The reciprocal of the Yielding Safety Factor the server stores; the panel converts in both directions +
+ - Hint caption: typical values run 0.33 (conservative) to 1.0 (aggressive) +
+
+ - Enable Optimization Checkbox
+
- Milling Cutter Panel
+
-
+
- Tabs
+
-
+
- Flute-Profile Tab — the profile type dropdown plus the APT panel, and the
CustomSpinningProfileoption with its own geometry-management panel
+ - Upper-Beam Tab
+
-
+
- Geometry Management Control, with the ExtendedCylinder option enabled +
+ - Property Tab
+
-
+
- Integral Mode Selection Dropdown — Solid End / Insert End +
- Cutter/Shank Mass Input Field (g), with an Auto Update CheckBox driven by ShankMassAssignmentMode: when enabled the field turns readonly and shows the mass computed from the inner-beam and upper-beam volume and the density +
- Hone Radius (um) and Relief Angle (deg) Input Fields +
- Minimum Available Cutting Thickness (um), readonly, from GetMinimumUncutChipThickness_um(ICuttingPara) with the project workpiece's CuttingPara, and a note label naming that cutting parameter +
+ - Insert-Cutter Tab
+
-
+
- Insert Number, Insert Mass (g) and Insert Thickness (mm) Input Fields; the thickness feeds heat transfer +
+ - Material Tab +
- Flute-Contours Tab +
- Flute-Inner-Beam Tab
+
-
+
- Profile Type Selection Dropdown for InnerBeamProfile — Flute Dependent Ratio (FluteDependentRatioProfile), Const Ratio (ConstRatioProfile), Custom Spinning (CustomSpinningProfile) +
- For Flute Dependent Ratio: a readonly Radius Ratio field whose label also names the flute number it depends on +
- For Const Ratio: an editable Radius Ratio field +
- For Custom Spinning: a Geometry Management Control +
+ - Optimization Tab — the same limits as the web section, plus the computed minimum uncut chip thickness beside the minimum-feed checkbox +
- Info Tab
+
-
+
- Name TextField (editable) +
- AbstractNote TextField (readonly) +
- Note TextField (editable) +
+
+ - Flute-Profile Tab — the profile type dropdown plus the APT panel, and the
+ - Tabs
+
wwwroot-src/src/components/controlTree/toolHouseItemTypes.ts— the item-type registry and the child builders: the cutter's five sections, the Material physics gate, and the fluting / flute / side / bottom sub-tree.
+wwwroot-src/src/router/treeRoutes.ts— role-path to route translation;CUTTER_SECTIONSpinsmaterial,profile,contours,upper-beamandopt, and it also translates a…/contours/tray…id onto theflutingsegment the node carries, so both spellings of the engine's Fluting type family resolve.
+wwwroot-src/src/components/controlTree/toolhouse/ToolCutterPanel.vue— the parent Cutter node: the type selector plus the cutter's General fields.
+wwwroot-src/src/components/controlTree/toolhouse/CutterSectionPanel.vue— the one panel shared by the material / profile / upper-beam / opt sections, keyed off the node's last role segment. It renders the Flute Profile fields inline and delegates the other three.
+wwwroot-src/src/components/toolhouse/MaterialDiv.vue— the Material section: flute material, the insert-end-only shank material, the solid-end note, and the coating list.
+wwwroot-src/src/components/toolhouse/CoatingLayersDiv.vue— the coating rows: index badge, material picker, thickness, move toward surface / body, delete.
+wwwroot-src/src/components/controlTree/toolhouse/CutterContoursPanel.vue— the Flute Contours stem: the Fluting Type selector.
+wwwroot-src/src/components/controlTree/toolhouse/FlutingPanel.vue— the concrete fluting node: Flute Number for uniform, the flute count and Add Flute for free.
+wwwroot-src/src/components/controlTree/toolhouse/FluteContourPanel.vue— one flute contour node: Setup Angle, and Delete This Flute for a free flute.
+wwwroot-src/src/components/controlTree/toolhouse/FluteSideContourPanel.vueandwwwroot-src/src/components/controlTree/toolhouse/FluteBottomContourPanel.vue— the two sub-nodes: kind selector plus the matching form.
+wwwroot-src/src/components/controlTree/toolhouse/fluteContourNode.ts— the shared load and commit logic behind those three panels, and the rule that a contour is committed whole.
+wwwroot-src/src/components/toolhouse/fluting/ConstHelixSideContourDiv.vue,wwwroot-src/src/components/toolhouse/fluting/SlideBottomContourDiv.vue,wwwroot-src/src/components/toolhouse/fluting/FreeformSideContourDiv.vueandwwwroot-src/src/components/toolhouse/fluting/FreeformBottomContourDiv.vue— the four contour parameter forms.
+wwwroot-src/src/components/toolhouse/fluting/SpanContourPosListDiv.vue— the R / A / Z / R.Ang span-position table both freeform forms share.
+wwwroot-src/src/components/toolhouse/UpperBeamDiv.vue— the Upper Beam section: the six allowed geometry kinds, the container-aware create, the resync-on-edit and the warnings banner.
+wwwroot-src/src/components/geom/GeometryEditor.vue— the shared geometry editor that section reuses.
+wwwroot-src/src/components/toolhouse/MillingCutterOptLimitDiv.vue— the Optimization section, including the Yielding Utilization Factor conversion.
+wwwroot-src/src/components/widgets/NumericInput.vue— the numeric field every cutter form uses: noG4, commit on blur or Enter, revert on an unparseable entry.
+wwwroot-src/src/components/widgets/FilePathInput.vue— the picker behind the material selectors; its resource-only mode hides the absolute Browse item and leaves Browse Resource….
+wwwroot-src/src/stores/appState.ts— holdsisShowPhysicsOptions, the Advanced Physics preference that gates the Material section.
+wwwroot-src/src/i18n/en/toolhouse.ts— every label quoted above:cutter.*,material.*,contour.*,fluting.*,upperBeam.*andopt.*.
+wwwroot-src/src/api/toolHouse.ts— the typed wrapper over/api/Cutter: shaper profile, fluting, the free-contour CRUD, the material and coating endpoints, the upper beam and the opt limit.
+Mech/CutterController.cs— the cutter backend: the APT construction from the profile DTO, the fluting builders, the opt-limit defaults, the solid-end shank-material mirroring, and the cache clear every field and geometry mutator performs.
+Mech/CutterDtoBuilder.cs— the read-side cutter DTO: integral mode, general, shaper profile, fluting, opt limit and material.
+Mech/ToolHouseController.cs— the tool detail that embeds that DTO, which every cutter section refreshes from.
+- Extended Cylinder Panel — the Upper Beam's kind that exists +for this cutter, and the one surface that gives it a start section +
- Geometry Section
+
-
+
- Cylindroid Panel — the reusable Z-R pair table, driven by a model key +
- Initializing Caption — stands in for the editor until the holder's cylindroid has been published under a key +
+ - Resolution Section
+
-
+
- Resolution Hint Caption — names the STL tessellation resolution used for display and collision meshing +
- Linear Resolution (mm) Input Field +
- Angle Resolution (deg) Input Field +
+ - Cylindroid Holder Panel
+
-
+
- Head Line
+
-
+
- Title Label +
+ - Tabs
+
-
+
- Geometry Tab + + +
- Resolution Tab +Model: PolarResolution2d +Polar Resolution 2d +
- Info Tab
+
-
+
- Name TextField (editable) +
- AbstractNote TextField (readonly) +
- Note TextField (editable) +
+
+
+ - Head Line
+
wwwroot-src/src/components/controlTree/toolhouse/HolderSectionPanel.vue— both sections in one panel, chosen by the last segment of the node's role path: the Cylindroid editor on the indexed key plus the resync on change, and the linear-mm / angle-deg fields with their positive-value guard.
+wwwroot-src/src/components/geom/CylindroidEditor.vue— the reusable Z-R profile editor the Geometry section wraps, driven by model key.
+wwwroot-src/src/components/controlTree/toolhouse/ToolHolderPanel.vue— the parent Holder node's panel, which hosts this holder's Name, readonly Abstract Note and Note and commits them throughsetCylindroidHolderName/setCylindroidHolderNote.
+wwwroot-src/src/components/controlTree/toolHouseItemTypes.ts— grows the two child sections for aCylindroidHolderand routes both to theHolderSectionitem type.
+wwwroot-src/src/api/cylindroidHolder.ts— the typed wrapper over/api/CylindroidHolder:getCylindroidHolder,updateCylindroidHolderGeometry,setCylindroidHolderName,setCylindroidHolderNote,setCylindroidHolderResolution.
+wwwroot-src/src/i18n/en/toolhouse.ts— the two section labels, the tessellation hint, the two field labels and the initializing caption.
+Mech/CylindroidHolderController.cs—GET Get(name / note / abstract note, the polar resolution, and — with a session key — the indexed cylindroid key) plusPOST UpdateGeometryContent | SetName | SetNote | SetPolarResolution.
+- Polar Resolution 2D Panel — the resolution editor this panel embeds +
- Cylindroid Control — the shape editor this panel's Geometry surface +embeds, and the shape whose tessellation the Resolution surface governs +
- Geometry Section
+
-
+
- Geometry Type Selector — None / Box3d / Cylindroid / StlFile / TransformationGeom / GeomCombination +
- The chosen kind's editor, on the child node the kind grows +
+ - Geom To Spindle Section — where the holder sits relative to the spindle
+
-
+
- Transformer Type Selector — Static Translation / Static Rotation / Static Freeform / Dynamic Translation / Dynamic Rotation / General Transform / No Transform +
- The chosen kind's editor, on the child node the kind grows +
+ - Geom To Cutter Section — where the cutter sits relative to the holder
+
-
+
- Transformer Type Selector and the chosen kind's editor, as above +
+ - Resolution Section
+
-
+
- Resolution Hint Caption — names the STL tessellation resolution used for display and collision meshing +
- Linear Resolution (mm) Input Field +
- Angle Resolution (deg) Input Field +
+ - Freeform Holder Panel
+
-
+
- Head Line
+
-
+
- Title Label +
+ - Tabs
+
-
+
- Geometry Tab + + +
- Anchor Tab
+(Apply Transformer Manage Panel to set the following tabs)
+
-
+
- Geom To Spindle Tab +
- Geom To Cutter Tab +
+ - Resolution Tab +Model: PolarResolution2d +Polar Resolution 2d +
- Info Tab
+
-
+
- Name TextField (editable) +
- AbstractNote TextField (readonly) +
- Note TextField (editable) +
+
+
+ - Head Line
+
wwwroot-src/src/components/controlTree/toolHouseItemTypes.ts— grows the four child sections for aFreeformHolder: readsGetto publish the three members, binds the Geometry section to the genericGeometryitem type and the two placements to the genericTransformeritem type (with the create hooks and the resync chain), and routes Resolution to theHolderSectionitem type.
+wwwroot-src/src/components/controlTree/GeometrySlotPanel.vue/TransformerSlotPanel.vue— the kind pickers on the three shape sections; the chosen kind's editor lives on the child node (wwwroot-src/src/components/geom/geometryEditors.ts,wwwroot-src/src/components/topo/transformerEditors.ts).
+wwwroot-src/src/components/controlTree/toolhouse/HolderSectionPanel.vue— the Resolution section, dispatching on the holder type toFreeformHolderControllerfor a Freeform holder.
+wwwroot-src/src/components/controlTree/toolhouse/ToolHolderPanel.vue— the parent Holder node's panel, which hosts this holder's Name, readonly Abstract Note and Note and commits them throughsetFreeformHolderName/setFreeformHolderNote.
+wwwroot-src/src/api/freeformHolder.ts— the typed wrapper over/api/FreeformHolder:getFreeformHolder,createFreeformHolderGeometry,updateFreeformHolderGeometry,resyncFreeformHolder,updateFreeformHolderGeomToSpindle,updateFreeformHolderGeomToCutter,setFreeformHolderName,setFreeformHolderNote,setFreeformHolderResolution.
+wwwroot-src/src/i18n/en/toolhouse.ts— the two placement section labels (toolhouse.node.geomToSpindle/geomToCutter) and the Freeform sections hint; the zh-Hans and zh-Hant files carry the same keys.
+Mech/FreeformHolderController.cs—GET Get(name / note / abstract note, the polar resolution, and — with a session key — the three member keys) plusPOST CreateGeometry | UpdateGeometry | IndexCurrentGeometry | UpdateGeometryContent | UpdateGeomToSpindleTransformer | UpdateGeomToCutterTransformer | SetName | SetNote | SetPolarResolution.
+- Polar Resolution 2D Panel — the resolution editor this panel embeds +
- Fixture Page — the other user of the generic Geometry and Transformer slots this holder's shape sections are made of +
- Cylindroid Holder: Represents holders with a cylindrical geometry. See CylindroidHolder. +
- Freeform Holder: Represents holders with more complex, freeform geometry, often defined by STL files. See FreeformHolder. +
- Holder Node Panel
+
-
+
- Title Label +
- Holder Type Selector — None / Cylindroid Holder / Freeform Holder +
- Holder branch, rendered while the type is Cylindroid or Freeform
+
-
+
- Name TextField (editable) +
- Abstract Note TextField (readonly, derived by the model) +
- Note TextField (editable) +
- Sections Hint Caption — names the sections below for the current type +
+ - No-holder branch — one hint,
toolhouse.holder.noHolderHint, inviting the reader to pick Cylindroid Holder or Freeform Holder
+
+ - Holder Panel
+
-
+
- Head Line
+
-
+
- Object Management Menu Button
+
-
+
- File extension is
.Holder, load type IHolder
+ - The pointed Editor Panel is the Holder Sub Management Panel. +
+ - File extension is
- Title Label +
+ - Object Management Menu Button
+
- Holder Management Panel
+
-
+
- Holder Type Selection Bar — None / Cylindroid / Freeform +
- Holder Sub Management Panel +A ContentPresenter whose content varies by the Holder Type: the Cylindroid Holder Panel, the Freeform Holder Panel, or nothing for None. +
+
+ - Head Line
+
wwwroot-src/src/components/controlTree/toolhouse/ToolHolderPanel.vue— the Holder node's panel: the None / Cylindroid / Freeform selector, the holder's Name / readonly Abstract Note / Note (committed through the per-type controller) and the no-holder hint. Commits a type change throughsetHolderTypefollowed byclearToolCache.
+wwwroot-src/src/components/controlTree/toolHouseItemTypes.ts— registers this panel as theToolHolderitem type; its holder child builder grows Geometry and Resolution for aCylindroidHolder, Geometry / Geom To Spindle / Geom To Cutter / Resolution for aFreeformHolder, and nothing for None.
+wwwroot-src/src/components/controlTree/toolhouse/HolderSectionPanel.vue— the panel behind the Cylindroid holder's two sections and both holders' Resolution section, dispatching on the last segment of the node's role path and on the holder type.
+wwwroot-src/src/components/controlTree/toolhouse/ToolHouseRootPanel.vue— the tool-house root panel, which carries the Object Management Menu Button for the.MachiningToolHousefile.
+wwwroot-src/src/api/toolHouse.ts—getHolder,setHolderTypeandclearToolCache, plus theHolderTypeunion (None/CylindroidHolder/FreeformHolder) the selector's three values map onto.
+wwwroot-src/src/api/cylindroidHolder.ts/wwwroot-src/src/api/freeformHolder.ts—get…Holder,set…HolderNameandset…HolderNotefor each holder type, behind the three identity fields.
+wwwroot-src/src/i18n/en/toolhouse.ts— thetoolhouse.holder.*strings this panel renders: holderType, abstractNote, sectionsHint, freeformSectionsHint, noHolderHint, cylindroidHolder, freeformHolder.
+Mech/ToolHouseController.cs—POST SetHolderType,GET GetHolderandPOST ClearToolCache, the three endpoints behind the selector. Per-holder-type editing lives in the dedicated holder controllers.
+Dual Input Modes
+The Holder tab of the Tool House page, reached as
+/tool-house/:toolId/holderand, for its inner +editors,/tool-house/:toolId/holder/:subtab. The tab's own panel chooses the holder type; the type +chosen decides which editor grows below it, and each type carries its own geometry and resolution.Ordered with the tab's own panel first, then one page per holder type.
+Pages
-
-
- Grid Mode: Input 4x4 matrix elements in a grid layout (formatted display with 4 significant digits) -
- Text Mode: Input complete matrix in text format (full precision, no information loss) +
- Holder Panel — The tab itself: the holder type selector and the fields common to every holder +
- Cylindroid Holder Panel — The revolved Z-R profile holder, its geometry and its polar resolution +
- Freeform Holder Panel — The freeform holder, and where its geometry is edited
-Matrix Operations
+See Also
-
-
- Identity: Set matrix to identity matrix -
- Transpose: Transpose the matrix -
- Inverse: Compute the inverse matrix -
-Special Value Handling (Web)
--
-
- Supports Infinity, -Infinity, and NaN values -
- Uses Numeric Input/Output Utilities for display and parsing -
-- wwwroot/widget/mat4d-control.js -
- Widget/Mat4dHub.cs +
- Tool House Page — the page these tabs belong to +
- Cutter Tab — the sibling tab, and the other half of a tool +
- Stick Tool Panel — the tool node whose tab cascade this tab belongs to
- Tool House Page
+
-
+
- Tool List Column
+
-
+
- Tool House Root Panel
+
-
+
- Object Management Menu Button
+
-
+
- file extension is
.MachiningToolHouse, load typeHi.Machining.MachiningToolHouse, HiMech
+ - The managed object is the whole tool house, not one tool +
+ - file extension is
- New Tool Button (disabled until the project has a tool house) +
- Ready / No-Project Badge +
- Tool-count caption with the pick hint — replaced, while the project has no tool house, by an empty-state block naming New Tool and the Object Management button's
.MachiningToolHouseextension
+
+ - Object Management Menu Button
+
- Tool List
+
-
+
- one router-link row per tool: the Tool ID badge, then the tool's note — or the auto abstract note as an italic caption when the note is empty +
+
+ - Tool House Root Panel
+
- Selected Tool Editor Column
+
-
+
- Stick Tool Panel — the tool's five tabs: General, Cutter, Holder, Clamping, Int. Holder +
+ - Viewer Column
+
-
+
- Viewer ToolBar
+
-
+
- RenderingCanvas Tool Bar +
- Display Options Menu Dropdown — three flat groups, no submenus
+
-
+
- Head Label: Cutter
+
-
+
- Show Cutter CheckBox +
+ - Head Label: Holder
+
-
+
- Show Holder CheckBox +
- Show Geometry Anchor CheckBox +
- Show Spindle Buckle CheckBox +
- Show Cutter Buckle CheckBox +
+ - Head Label: Holder Rendering Mode
+
-
+
- Solid / Edge / Hide Radio Buttons +
+
+ - Head Label: Cutter
+
- Selected-Tool Badge +
- Rendering-Connection Badge +
+ - RenderingCanvas
+
-
+
- The DispEngine.Displayee is MillingToolEditorDisplayee. +
+
+ - Viewer ToolBar
+
+ - Tool List Column
+
wwwroot-src/src/router/routes.ts— declares the route itself: pathtool-house/:toolId(\d+)?/:tab?/:subtab?, nametool-house.
+wwwroot-src/src/components/AppMenuBar.vue— thePage ▾dropdown; its first item targets thetool-houseroute.
+wwwroot-src/src/router/treeRoutes.ts— declaresTOOL_TABS,CUTTER_SECTIONSandHOLDER_SECTIONS, androuteForTreeId/migrateLegacyTreeId, which map a Control-Tree id onto this page's tab params.
+wwwroot-src/src/composables/useRouteTabs.ts— the:tab?/:subtab?URL sync, defaulting togeneral, and toprofileunder Cutter /geometryunder Holder.
+wwwroot-src/src/composables/useToolHouse.ts— the module-singleton state every tool-house panel and the canvas share: the tool list, the active tool, the tool-house key and the rendering connection id.
+wwwroot-src/src/pages/ToolHousePage.vue— routed page:ToolHouseRootPanel+ tool list on the left, the tool's own five-tab strip in the middle,ToolHouseSetupPanelon the right, in two nested resizable<q-splitter>panes. Cutter and Holder grow a second, URL-synced sub-tab strip from the tab node's children, andNodeTabCascade.vuehosts the active tab's panel and the levels nested below it.
+wwwroot-src/src/components/controlTree/NodeTabCascade.vue— the nested-level host the page mounts inside the active tab.
+wwwroot-src/src/components/toolhouse/ToolHouseSetupPanel.vue— the canvas column: one RenderingCanvas bound server-side toMillingToolEditorDisplayee, following the page's selected tool, with the Display Options dropdown (Show Cutter / Show Holder + the 3 anchor-and-buckle flags + the Holder Rendering Mode radios).
+wwwroot-src/src/components/widgets/DisplayOptionsMenu.vue— the generic groups/items dropdown that panel feeds, which is why the menu is flat groups rather than nested submenus.
+wwwroot-src/src/components/controlTree/toolHouseItemTypes.ts— the tool house's ItemType registry: one tool node per tool, and each tool node's…/cutter,…/holder,…/clamping,…/intelligentchildren.
+wwwroot-src/src/components/controlTree/toolhouse/ToolHouseRootPanel.vue— the tool-house root node: Object Management over theMachiningToolHouse+ New Tool. The button's own entries are Load, Save As, Copy, Paste and XML; the Load Resource entry is conditional on aresourceDirectoryprop this panel does not pass, so it is absent here.
+wwwroot-src/src/components/controlTree/toolhouse/ToolNodePanel.vue— one tool's General tab: Tool ID / Note / auto Abstract Note + Duplicate / Delete. Mounting it points the tool canvas at that tool.
+wwwroot-src/src/components/controlTree/toolhouse/ToolCutterPanel.vue— the Cutter stem: cutter type selector plus the MillingCutter identity fields (Shank Mass / Hone Radius / Relief Angle).
+wwwroot-src/src/components/controlTree/toolhouse/CutterSectionPanel.vue— the cutter's inner tabs, keyed off the node's role path (material/profile/upper-beam/opt). Flute Profile is edited inline; the others dispatch towwwroot-src/src/components/toolhouse/MaterialDiv.vue(which growsCoatingLayersDiv.vue),wwwroot-src/src/components/toolhouse/UpperBeamDiv.vueandwwwroot-src/src/components/toolhouse/MillingCutterOptLimitDiv.vue.
+wwwroot-src/src/components/controlTree/toolhouse/CutterContoursPanel.vue— the Flute Contours stem: the fluting type selector only. The selected fluting's editor grows below it.
+wwwroot-src/src/components/controlTree/toolhouse/FlutingPanel.vue— the concrete fluting node and its fluting-level fields; uniform fluting grows one Baseline Contour, free fluting one tab per flute.
+wwwroot-src/src/components/controlTree/toolhouse/FluteContourPanel.vue— one flute contour node (baseline, orflute-<i>for a free fluting): hosts Setup Angle, with Side Contour and Bottom Contour as sub-tabs.fluteContourNode.tscarries the shared re-commit.
+wwwroot-src/src/components/controlTree/toolhouse/FluteSideContourPanel.vue/FluteBottomContourPanel.vue— kind selector + editor, dispatching towwwroot-src/src/components/toolhouse/fluting/ConstHelixSideContourDiv.vue,FreeformSideContourDiv.vue,SlideBottomContourDiv.vueandFreeformBottomContourDiv.vue; the two freeform editors shareSpanContourPosListDiv.vuefor the per-flute position list.
+wwwroot-src/src/components/controlTree/toolhouse/ToolHolderPanel.vue— the Holder stem: holder type selector plus the holder's Name / Note / auto Abstract Note, committed through the per-type holder controller.
+wwwroot-src/src/components/controlTree/toolhouse/HolderSectionPanel.vue— the Cylindroid holder's Geometry (the Z-R profile, viawwwroot-src/src/components/geom/CylindroidEditor.vue) and both holders' Resolution tabs; resyncs the Cylindroid holder (UpdateByCylindroid()) after each geometry edit. A Freeform holder's Geometry / Geom To Spindle / Geom To Cutter tabs are the genericGeometry/TransformerControl-Tree slots (wwwroot-src/src/components/controlTree/GeometrySlotPanel.vue/TransformerSlotPanel.vue), wired bytoolHouseItemTypes.tstoFreeformHolderController's keys and resync.
+wwwroot-src/src/components/controlTree/toolhouse/ToolClampingPanel.vue— the Clamping tab:ExposedCutterHeight=PreservedDistance+FluteHeight; changing either field updates the other on the server.
+wwwroot-src/src/components/controlTree/toolhouse/ToolIntelligentPanel.vue— the Intelligent Holder tab; a thin wrapper overwwwroot-src/src/components/toolhouse/IntelligentHolderDiv.vue(Observation Location:ObservationAnchorReferencedropdown + relative Z + ring radius).
+wwwroot-src/src/api/toolHouse.ts— typed wrapper over three server modules:ToolHouseController(/api/ToolHouse),CutterController(/api/Cutter) andToolHouseDisplayController(/api/mech/tool-house-display).
+wwwroot-src/src/api/cylindroidHolder.ts/wwwroot-src/src/api/freeformHolder.ts— typed wrappers overCylindroidHolderController/FreeformHolderController.
+Mech/ToolHouseController.cs— the tool-house collection and the tool-level fields. REST endpoints:GET /,GET /{id},GET /GetHolder,GET/PUT /{id}/observation,PUT /{id}/note | exposed-height | preserved-distance,POST /Initialize | Update | SelectTool | CreateTool | DuplicateTool | RenameTool | SetHolderType | ClearToolCache,DELETE /DeleteTool.
+Mech/CutterController.cs— cutter editing, the/api/Cuttersibling ofToolHouseController. REST endpoints at/api/Cutter/*:POST EnsureCutter | ClearCutter,PUT /{id}/shaper-profile | fluting | general | opt-limit,FreeFlutingchild CRUD at/{id}/fluting/contours[/{index}],GET/POST /{id}/upper-beam[/create | /resync], and the material endpoints/{id}/flute-material/*,/{id}/shank-material/*,/{id}/coating-layers/*.
+Mech/ToolHouseDisplayController.cs— REST endpoints at/api/mech/tool-house-display/*for display options (initialize, select-tool, show-cutter / show-holder / show-geom-anchor / show-spindle-buckle / show-cutter-buckle, holder-rendering-mode, cutter-shape-mode, clear-cache).initializeattaches theMillingToolEditorDisplayeeand snaps to the isometric view;select-toolre-points theMillingToolGetterand snaps to the home view. It also reportsenablePhysicsfromUserService.
+Mech/CylindroidHolderController.cs— holder-aware layer over a tool'sCylindroidHolder. REST endpoints at/api/CylindroidHolder/*:GET Get(returns name / note / abstract note / resolution + indexes the holder'sCylindroid),POST UpdateGeometryContent | SetName | SetNote | SetPolarResolution.UpdateGeometryContentis the post-edit resync (UpdateByCylindroid()+ClearCache()) that the genericCylindroidControllercannot perform.
+Mech/FreeformHolderController.cs— the Freeform counterpart. REST endpoints at/api/FreeformHolder/*:GET Get(name / note / abstract note / resolution + indexes the holder's geometry and two placement transformers under per-session keys),POST CreateGeometry | UpdateGeometry | IndexCurrentGeometry | UpdateGeometryContent | UpdateGeomToSpindleTransformer | UpdateGeomToCutterTransformer | SetName | SetNote | SetPolarResolution. Every shape edit ends inUpdateByGeom()+ClearCache()+AlignAnchorByExposedCutterHeight(), the resync the generic geometry and transformer controllers cannot perform.
+Environments/UserService.cs— the web client's own per-user service, living on the server;ToolHouseDisplayControllerreadsEnablePhysicsfrom it.
+- Cutter Tab — the cutter half of a tool, and the editors each cutter type grows +
- Holder Tab — the holder half, and the editors each holder type grows +
- Stick Tool Panel — the selected tool’s tab cascade, which those two tabs belong to +
- General Setup Page — the equipment these tools are mounted into +
- Tool Node (
toolhouse/tool-<n>) +-
+
- General Tab
+
-
+
- Tool ID TextField — the T-number; committing it renames the tool +
- Note TextField (editable) +
- Abstract Note (read-only, auto-derived, with a one-click copy button) +
- Duplicate Button and Delete Button (icon buttons on the tab's title bar) +
+ - Cutter Tab
+
-
+
- Cutter Panel +
+ - Holder Tab
+
-
+
- Holder Panel +
+ - Clamping Tab
+
-
+
- Exposed-Cutter-Height numeric field +
- Preserved-Distance-Between-Flute-and-Spindle-Nose numeric field +
+ - Int. Holder Tab
+
-
+
- Observation Location
+
-
+
- Reference Anchor Dropdown (
ObservationAnchorReference) — its options are theMillingToolAnchorReferencenames the server sends with the observation: None, Tool Tip, Holder Anchor, Spindle Buckle Anchor
+ - Relative-Z-From-Reference numeric field (
RelativeHeightFromObservationAnchor_mm)
+ - Observation-Ring-Radius numeric field (
ObservationRingRadius_mm)
+
+ - Reference Anchor Dropdown (
+ - Observation Location
+
+ - General Tab
+
wwwroot-src/src/router/treeRoutes.ts— declaresTOOL_TABS(general / cutter / holder / +clamping / intelligent) withCUTTER_SECTIONSandHOLDER_SECTIONS, androuteForTreeId, which +turns atoolhouse/tool-<n>/<tab>/<subtab>Control-Tree id into the/tool-houseroute params.
+wwwroot-src/src/pages/ToolHousePage.vue— renders the five-tab strip, mounts only the active +tab's panel, and keeps the tab segments when the selected tool changes.
+wwwroot-src/src/components/controlTree/toolHouseItemTypes.ts— the Control-Tree ItemType +registry:buildToolChildrenemits the tool node's…/cutter,…/holder,…/clampingand +…/intelligentchildren, General being the tool node's own panel.
+wwwroot-src/src/components/controlTree/toolhouse/ToolNodePanel.vue— the General tab: Tool ID / +Note / read-only auto Abstract Note with copy, plus Duplicate and Delete. Mounting it points the +tool canvas at that tool.
+wwwroot-src/src/components/controlTree/toolhouse/ToolCutterPanel.vue— the Cutter tab's type +selector (Milling Cutter / Freeform Remover / None) plus the MillingCutter identity fields.
+wwwroot-src/src/components/controlTree/toolhouse/ToolHolderPanel.vue— the Holder tab's type +selector (None / Cylindroid / Freeform) plus the Cylindroid holder's Name / Note / auto Abstract +Note.
+wwwroot-src/src/components/controlTree/toolhouse/ToolClampingPanel.vue— the Clamping tab and +its two coupled fields.
+wwwroot-src/src/components/controlTree/toolhouse/ToolIntelligentPanel.vue— the Int. Holder tab; +a thin wrapper overwwwroot-src/src/components/toolhouse/IntelligentHolderDiv.vue, which holds +the Observation Location controls and reads no physics flag.
+wwwroot-src/src/components/controlTree/NodeTabCascade.vue— hosts the active tab's panel and the +levels nested below it.
+wwwroot-src/src/composables/useRouteTabs.ts— syncs the:tab?/:subtab?params with the URL.
+wwwroot-src/src/composables/useToolHouse.ts— the module-singleton state every tool-house panel +is a thin view over: the tool list, the active tool, and the rendering connection id.
+Mech/ToolHouseController.cs—[Route("api/[controller]")], so/api/ToolHouse: the tool-level +fields the tool node edits —PUT /{id}/note | exposed-height | preserved-distance, +GET/PUT /{id}/observation,POST DuplicateTool | RenameTool | SetHolderType, +DELETE DeleteTool.
+Mech/CutterController.cs(/api/Cutter) andMech/CylindroidHolderController.cs+(/api/CylindroidHolder) — the server surface behind the Cutter and Holder tabs.
+wwwroot-src/src/api/toolHouse.ts— the typed client for those endpoints.
+- Tool House Page — the page this tool node sits in, and where the tool list and the route live +
- Cutter Tab — the cutter half of the tab cascade +
- Holder Tab — the holder half +
- AdminDirectory — server-wide admin area. +
- ProjectDirectory — the currently-loaded project directory (omitted from the root list when no project is loaded). +
- ResourceDir — the shared resource tree under
AdminDirectory + "Resource/".
+ - File Explorer Page
+
-
+
- Toolbar
+
-
+
- Root Selector Dropdown — display-name only, sourced from
GET /roots; a caller that locks the root gets the name as plain text instead.
+ - Up Button +
- Path Input — editable;
Enternavigates.
+ - Refresh / New Folder / New File / Upload Buttons. +
- Sort Dropdown — sort key (Name / Size / Modified / Type), Ascending / Descending, and a “Folders first” checkbox. +
- Editor Panel Toggle (
edit_notepencil) — shows or hides the editor slave panel.
+
+ - Root Selector Dropdown — display-name only, sourced from
- Breadcrumb Row — single-click navigation up the tree, plus a file-type filter select whenever the host supplies filters. The page supplies none, so the select belongs to picker mode. +
- Explorer | Editor Splitter — the divider collapses to zero width while the editor panel is hidden.
+
-
+
- Listing Tree (
<q-tree>) — lazy: a folder lists its children the first time it is expanded. Each row is icon + name + a fixed-width size / modified tail, with the hover action strip laid over that tail so nothing reflows when the pointer enters a row. Ordering follows the toolbar's sort control. +-
+
- Row select:
+
-
+
- Directory → expand it and make it the current directory (the toolbar ops and the URL follow). +
- Text file → load it into the editor panel;
.stl→ open the STL preview in that same slot. Both only while the panel is shown.
+
+ - Row double-click on a file opens it in the editor panel — or the preview, for an
.stl— even when the panel is hidden. This is the gesture that opens a file for editing; the action strip deliberately carries no Edit button, since a second pencil beside Rename'sdrive_file_rename_outlinewould be two near-identical icons meaning different things. The empty editor's own hint has not followed that decision — it reads "Click a file (or its Edit action) to load it here.", naming an action no row offers.
+ - Actions: Download (files), Download ZIP (folders), Extract ZIP (
.ziponly), Rename, Duplicate, Delete (double-click to confirm).
+ - Pick Column (picker mode only) — a radio or a checkbox on every pickable row, rendered as an empty cell elsewhere so unpickable rows keep the same indent. +
+ - Row select:
+
- Editor Slave Panel (right)
+
-
+
- Bar —
Root / relative/pathlabel with a red*while the buffer is dirty, language select,Auto Savecheckbox,Savebutton (disabled while Auto Save is on), close icon.
+ - Body —
<TextEditor>wrapping CodeMirror 6, filling the panel.
+ - STL Preview — an
.stlrow hands this slot to<StlPreviewPane>, a server-rendered 3D view that replaces the editor's bar and body and stays up until it is closed; see STL Preview Pane. The text buffer and any unsaved edits survive underneath and return when the preview closes.
+
+ - Bar —
+ - Listing Tree (
+ - Toolbar
+
- Path-traversal defence. Every request is resolved via
Path.GetFullPathand validated withHi.Common.PathUtils.PathUtil.IsDescendant(root, absolute)before any IO. Attempts likerelativePath=../outsideare rejected with HTTP 400.
+ - Duplicate.
POST /copytries{name}-Copy-00through{name}-Copy-19and returns the first free slot; 400 if all 20 are taken.
+ - UTF-8 without BOM.
WriteTextuses a cachednew UTF8Encoding(encoderShouldEmitUTF8Identifier: false)so round-tripped files do not accumulate a 3-byte BOM on every save.
+ - Binary gating.
/read-textreports a binary file rather than refusing it: it answerscontent=null,isBinary=true, and the panel toasts. The client decides before the round-trip — an extension set (.stl,.zip,.dll,.exe, images,.pdf, Office documents,.sqlite/.db, …) keeps a click on a binary row from opening the editor at all, and.stlis routed to the STL preview instead.
+ - Line endings. The buffer is LF-normalized on load, because CodeMirror 6 joins lines with LF and would otherwise echo a normalize-only change that falsely dirties a freshly opened CRLF file. Save converts back to the style the file was read with. +
- Auto Save. Checked, the panel writes 800 ms after typing stops and the
Savebutton disables. Switching files, closing the panel and leaving the page each flush a pending write first; with Auto Save off, dirty edits raise a discard confirm instead, andsettleEditorBeforeClose()keeps the user on the page when that confirm is cancelled.
+ - Per-device view prefs. Editor shown, splitter position, Auto Save and the sort spec persist in
localStorageunderhinc.fileExplorer.viewPrefs.v1. Nothing is written until one of them is changed, and the page's own default is editor shown — so a first visit, a private window or a cleared browser all open with the editor panel up and single-click file opening already live. Dialog instances seed the splitter, Auto Save and the sort from that blob and write those three back, but they neither read nor write the panel-visibility part: a picker always opens with the editor panel hidden, and a one-off edit inside one does not rearrange the page.
+ codemirror+@codemirror/state+@codemirror/view(core).
+@codemirror/lang-xml+@codemirror/lang-json+@codemirror/lang-markdown+@replit/codemirror-lang-csharp+ the in-repo mission-script mode fromwwwroot-src/src/components/widgets/missionScriptLanguage.ts(5 language modes).
+@codemirror/autocomplete, wired only when a completion source is supplied.
+wwwroot-src/src/components/FileExplorer.vue— the browser itself: toolbar, sort control, breadcrumb + filter, the lazy tree with its hover actions, the editor slave panel with Auto Save, the STL preview slot, and the view prefs.
+wwwroot-src/src/pages/FileExplorerPage.vue— routed page at/util/file-explorer; parses and mirrors the location in the URL and guards the route leave.
+wwwroot-src/src/components/widgets/FileExplorerDialog.vue— modal wrapper: apply bar, save mode,"{rootName}:{relativePath}"picks.
+wwwroot-src/src/components/explorerSort.ts— sort keys, comparators,DEFAULT_SORTandcoerceSortSpecbehind the Sort dropdown.
+wwwroot-src/src/components/widgets/fileFilter.ts— theFileFiltershape driving the file-type select.
+wwwroot-src/src/utils/collapsibleSplit.ts— collapses the editor divider while the panel is hidden.
+wwwroot-src/src/components/StlPreviewPane.vue— the 3D preview that takes over the panel for.stlfiles, overDisp/StlPreviewController.cs.
+wwwroot-src/src/components/widgets/TextEditor.vue— CodeMirror 6 wrapper.
+wwwroot-src/src/components/widgets/editorLanguage.ts— extension →EditorLanguagemapping.
+wwwroot-src/src/components/widgets/missionScriptLanguage.ts— the in-repo mission-script mode.
+wwwroot-src/src/api/fileExplorer.ts— typed wrapper over/api/file-explorer/*.
+wwwroot-src/src/router/routes.ts—/util/file-explorerentry (route nameutil-file-explorer).
+wwwroot-src/src/components/AppMenuBar.vue—Page → File Explorerentry, below the separator that follows the three workflow pages.
+Common/NamedRootResolver.cs— resolvesAdminDirectory/ProjectDirectory/ResourceDir; shared with every other controller that reads or writes under a named root.
+Common/FileExplorerController.cs— REST endpoints under/api/file-explorer:
+File Explorer (manual) — the end-user task: the roots, and renaming a file to move it
+
+Object Management Menu Button — shares the XML editor dialog pattern and targets the same project / resource folders through a different endpoint family.
+
+Mechanism Builder Page — drives its Load and both Save As operations through this browser's dialog wrapper.
+
+STL Preview Pane — the 3D preview this page's editor column hands over to for an
+.stlrow.
+- Primary: PlayerCommand -
- Supporting:
-
-
-
- MachiningProject -
- UserService +
- File Explorer — server-side filesystem browser over three named roots (Admin +/ Project / Resource), with a lazy-loading tree, an editor slave panel running CodeMirror 6, an +STL preview, zip pack / extract, and rename / duplicate / delete. The same component is the app's +modal file picker. +
- Mechanism Builder — editor for a standalone GeneralMechanism: +anchor / branch graph, per-branch transformer, per-anchor geometry and display colour, with +server-side Load and Save As through the File Explorer's picker. +
- STL Preview Pane — the 3D preview that takes over the File Explorer's
+editor column for an
.stlrow: its cancellable load, the rendering connection it owns, and the +transform it can bake into the file.
+ - Legacy-Controller — the legacy controller screen, reachable from
+
Page → Legacy-Controllerbelow the second separator. The General Setup page's Controller branch +is the settings face for the SoftNcRunner; this page stays reachable while it carries settings +that tree has no editor for.
- - Mission Page
-
-
-
- Head Line
-
-
-
- Object Management Menu Button
-
-
-
- file extension is ShellCommand -
- The pointed Editor Panel is Mission Edit Panel -
- - Mission Type Selection Section
-
-
-
- Mission Type Label -
- Mission Type ComboBox -
-
- - Object Management Menu Button
-
- Mission Edit Panel
-
-
-
- Content depends on the Mission Type Selection. -
-
- - Head Line
-
- Script Command Panel for ScriptCommand. -
- List Command Panel for ListCommand. -
Mission/MissionWindow
-Mission/MissionPanel
-wwwroot-src/src/pages/MissionPage.vue- Mission page (List / Script mode toggle + drag-and-drop command list + per-kind editor pane)
-wwwroot-src/src/components/mission/PreSettingCommandPanel.vue
-wwwroot-src/src/components/mission/NcFileCommandPanel.vue
-wwwroot-src/src/components/mission/NcCodeCommandPanel.vue
-wwwroot-src/src/components/mission/ScriptCommandPanel.vue
-wwwroot-src/src/components/mission/NcOptOptionCommandPanel.vue
-wwwroot-src/src/components/mission/PostExecutionCommandPanel.vue
-wwwroot-src/src/api/mission.ts- Typed wrapper for every/api/Mission/*endpoint
-wwwroot-src/src/router/routes.ts-/missionroute entry
-Missions/MissionController.cs- REST API endpoints (per-property PUTs; fixed reorder + move)
-Missions/NcOptOptionEndpoints.cs- NcOptOption per-property PUT endpoints
+- Cutter editor — a Tool House Control-Tree branch, not a util page:
+
wwwroot-src/src/components/controlTree/toolhouse/ToolCutterPanel.vuewith its section, contour +and flute panels, overMech/CutterController.cs, reached through thetool-houseroute. The +WPF client hasMech/ToolHouse/CutterManagementPanel.xamlandMech/ToolHouse/MillingCutterPanel.xaml.
+ - Rake-face angles — plain cutter fields:
radialRakeAngle_degin +wwwroot-src/src/components/controlTree/toolhouse/FluteSideContourPanel.vueand +axialRakeAngle_degin +wwwroot-src/src/components/controlTree/toolhouse/FluteBottomContourPanel.vue. A.MillingPara+or.mpfile opens as XML in the File Explorer's editor panel.
+ - Color Index Time Chart — a strip chart on the Execution page:
+
wwwroot-src/src/components/execution/charts/StripIndividualChart.vue, mounted by +wwwroot-src/src/pages/ExecutionPage.vueunder thecolorIndexTimeChartpanel flag that +Environments/ExecutionDivConfig.cspersists (Environments/PlayerDivConfig.cson the WPF side). +It plots the user-picked inspecting key live per step. - Key Model: GeneralMechanism +
- Related Model:
+
-
+
- GeneralXyzabcChain + GeneralXyzabcMachineTool (used by “Save As Machine Tool”) +
+ - Mechanism Builder Page
+
-
+
- Three Fixed Columns — equal width, separated by 1px borders, none of them draggable. Every column keeps its header pinned; the graph and the editor cards scroll inside their own region below it. +
- Column 1: Graph
+
-
+
- Title + File Menu + Add Anchor Button.
+
-
+
- File Menu
+
-
+
- New — discards the current mechanism and starts an empty one with a root anchor. +
- Load… — opens the server file picker (Admin / Project / Resource) filtered to
*.GeneralMechanism/*.xml; the backend parses the picked file in place and records its directory so ReLoad can re-read it. That filter matches none of the shipped mechanisms, which are named.general-mechbeside the machine tool they belong to; the dialog's appended All Files entry is what reaches them, and the backend parses whatever is picked.
+ - ReLoad — re-parses the file last loaded (disabled until a file has been loaded). The entry is spelled
ReLoad, with the second capital.
+ - Save As General Mechanism — writes a
.GeneralMechanismXML to the picked server location and retargets ReLoad at it.
+ - Save As Machine Tool — wraps the mechanism in a GeneralXyzabcMachineTool and writes a
.MachineToolXML to the picked server location, leaving the open-mechanism pointer where it is. The envelope is the Xyzabc machine tool; the menu entry names neither that nor the file type.
+
+ - Add Anchor — creates a standalone anchor, connected to nothing, and selects it. +
+ - File Menu
+
- File Line — the loaded file's relative path, or an “unsaved” caption while the mechanism has no file. +
- Graph — mermaid diagram of anchors (nodes) + branches (edges), scrolling inside its own region; node / edge click selects. The selected anchor is filled green and the selected branch's edge is stroked green. +
+ - Title + File Menu + Add Anchor Button.
+
- Column 2: Selected Item Editor — content depends on selection:
+
-
+
- Anchor selected: header with a Root badge on the root anchor, an Extend button, and a delete button (double-click; absent on the root).
+
-
+
- Identity Card — the anchor's Guid, an inline name field (debounced), and an “Add Branch” select that connects this anchor to the picked one. +
- Geometry Card — a Geometry checkbox attaches or removes the anchor's
Solid, and a badge shows the current geometry type. While attached the card carries a display-colour control and mounts the geometry editor over five kinds:Box3d,Cylindroid,StlFile, TransformationGeom andGeomCombination.CubeTreeFileandExtendedCylinderare absent because the backend'screate-geomdoes not construct them.TransformationGeomstill exposes its own inner geometry and inner transformer through the nested editor.
+
+ - Branch selected: header with a delete button (double-click).
+
-
+
- Identity Card — the branch's Guid, an inline name field (debounced), and the fletch → arrow anchor chips naming the two anchors it joins. +
- Transformer Card — Transformer Select Panel (7 transformer kinds). +
+
+ - Anchor selected: header with a Root badge on the root anchor, an Extend button, and a delete button (double-click; absent on the root).
+
- Column 3: Display — RenderingCanvas Tool Bar, a rendering / disconnected badge, and
RenderingCanvasbound toDelegateFuncDisplayee(() => MechService.GeneralMechanism as IDisplayee).
+ - Server File Picker — one
FileExplorerDialogshared by Load and both Save As actions.
+
+ - New-anchor auto-naming. Newly-created anchors receive placeholder names (
NewAnchor-001,NewAnchor-002, …) — the firstNewAnchor-{i:000}no descendant anchor already carries. Inline rename is debounced 400 ms.
+ - Extend. The anchor editor's Extend button creates a new anchor and the branch from the selected one to it in a single call, then selects the new anchor. This is the normal way a chain grows; Add Anchor leaves the new anchor unconnected. +
- Branch add filter. The “Add Branch” select inside the anchor editor filters out the selected anchor itself and anchors already directly connected, in either direction. A new branch starts on
NoTransform.
+ - Root protection. The root anchor cannot be deleted; double-click delete on any other anchor works. +
- Branch transformer swap.
TransformerSelectPaneluses a parent-awareonCreatehook that callsPOST /branch/{id}/update-transformer, so the new transformer takes effect on the next frame without a re-init round-trip. Same idiom as Fixture Page.
+ - Geometry cache invalidation. Geometry edits POST to
anchor/{id}/refresh-geom-cache, which triggers the sameSolid.ClearCache()pattern used across the project.
+ - Anchor display colour. The colour input authors an
#rrggbbthat persists inside the mechanism's<Solid>element, so every consumer of the file shows the same colour; a reset button drops the authored value and an “auto” badge marks the stable Guid-seeded fallback the 3D view otherwise renders with.Solid.Displayreads the colour per frame, so the canvas follows without a cache refresh.
+ - DelegateFuncDisplayee. The
RenderingCanvasis wired through a delegate so edits render next frame without IndexService churn.
+ wwwroot-src/src/pages/MechBuilderPage.vue— routed page at/util/mech-builder: File menu, the three columns, the anchor / branch editors, the display-colour control, the picker wiring and the canvas.
+wwwroot-src/src/components/mech/MechBuilderGraph.vue— lazy mermaid loader + click-proxy onto mermaid node / edge DOM. Mermaid is dynamic-imported so the first bundle stays slim (~1 MB raw split into its own chunk); labels are emitted quoted, which the parser requires for CJK text.
+wwwroot-src/src/components/widgets/FileExplorerDialog.vue— the server file picker behind Load and both Save As actions.
+wwwroot-src/src/components/geom/GeometryEditor.vue— the geometry switchboard the anchor's Geometry card mounts.
+wwwroot-src/src/components/topo/TransformerSelectPanel.vue— the branch transformer switchboard.
+wwwroot-src/src/api/generalMechanism.ts— typed wrapper over/api/general-mechanism/*, including the three server-file operations and the anchor colour endpoints.
+wwwroot-src/src/api/transformer.ts— the seven transformer kinds and theirNewendpoints.
+wwwroot-src/src/api/geometry.ts—GeometryKind, the union the anchor's allowed kinds are drawn from.
+wwwroot-src/src/utils/path.ts—stripDefaultMarker, which sheds the.defaultownership marker from the name offered on save.
+wwwroot-src/src/router/routes.ts—/util/mech-builderentry (route nameutil-mech-builder).
+wwwroot-src/src/components/AppMenuBar.vue—Page → Mechanism Builderentry.
+Mech/MechBuilder/GeneralMechanismService.cs— DI singleton that holds the current mechanism + lastBaseDirectory+RelFile.
+Mech/MechBuilder/GeneralMechanismController.cs—/api/general-mechanism/*CRUD over anchors / branches / per-anchor geometry and colour; server-side Load, Reload and XML Save As for bothGeneralMechanismandGeneralXyzabcMachineToolenvelopes.
+Mech/MechBuilder/GeneralMechanismDisplayController.cs—/api/general-mechanism/display/*view init and the isometric reset-view.
+Common/NamedRootResolver.cs— resolves the three named roots the picker offers.
+Program.cs— registersGeneralMechanismServiceas a DI singleton.
+general-mechanism.current— the mechanism itself (informational;DelegateFuncDisplayeebypasses IndexService at render time).
+general-mechanism.branch.{guid:N}.transformer— stable per-branch transformer key used byTransformerSelectPanel.
+general-mechanism.anchor.{guid:N}.geom— stable per-anchor key for whatever geometry the anchor'sSolidholds, used byGeometryEditor.index-geomindexes that geometry without replacing it, so re-selecting an anchor leaves a non-TransformationGeomgeometry intact.
+- Fixture Page — parent-aware
onCreatetransformer rebind pattern reused for Branch.
+ - Transformers — the shared 7-transformer switchboard. +
- Geometry Management Control — embedded under the anchor editor. +
- Machine Tool — where a project's chain is loaded, replaced and previewed, next to this user-scoped builder. +
- File Explorer — the server-side browser this page's Load and Save As dialogs are built on. +
- Spindle Capability Page — the project-scoped editor with the same Load / Reload / Save As pattern. +
- Mechanism Builder (manual) — the end-user task: driving this editor, and what a machine chain built in it must be named. +
- Selecting the row, but only while the editor slave panel is showing. +
- Submitting the file's path in the toolbar's path field, under that same panel-showing guard. +
- Double-clicking the row header, which shows the panel first. +
- STL Preview Pane
+
-
+
- Preview Bar
+
-
+
- Location Label — the root's display name and the root-relative path, ellipsized, with the +full string as its hover title. +
- Triangle Count Badge — an outlined badge rendered only once a load has reported a count, and +cleared again at the start of every new load. The number is formatted for the app locale. +
Scene ▾Menu — the shared display-options dropdown, carrying one checkbox here, +Origin axes.
+View ▾Menu — the shared +RenderingCanvas Tool Bar, bound to this pane's canvas.
+- Transform Button — shows and hides the Transform section; it reads primary-coloured while +the section is open, and starts off on every mount. +
- Close Button — a round
closeicon titled Close preview.
+
+ - Transform Section — rendered only while the Transform button is on.
+
-
+
- Header Row — a Transform caption and a Save button. Save stays disabled until a load +has published a transform key, and shows a loading state while the write is in flight. +
- General Transform Editor — the standard transformer editor, scrolling inside a capped height
+so the canvas keeps most of the pane:
+
-
+
- Scale numeric field. +
- Rotation sub-transformer card — Rotation axis as three components with a normalize +button, Angle in degrees, and Pivot in mm. +
- Translation sub-transformer card — a Translation caption over three mm components. +
- A caption naming the composed transform as
T × R × scale × I.
+ - Every field here commits on blur or on Enter, one write per field. +
+ - Failure Text — with no transform key held, the section reads “Failed to load STL preview.” in +place of the editor. +
+ - Canvas Body
+
-
+
- Rendering Canvas — fills the rest of the pane. +
- Loading Overlay — a centred spinner over a dim, click-through wash. The previously loaded +model stays visible beneath it rather than being cleared. +
- Error Banner — pinned to the foot of the canvas, carrying the failed load's message. +
+
+ - Preview Bar
+
- A monotonic show token in the pane. Every request takes the next number; a response arriving +under a stale number is discarded, and the spinner is lowered only by the newest request. Without +it the slower of two racing round-trips would win the badge. +
- An abort controller in the pane. Each request aborts the previous one's controller before +issuing its own, and swallows the resulting abort error. The same controller is aborted when the +pane unmounts. +
- Supersede in the preview service. Before reading anything the controller asks the service to +supersede the connection, which cancels whatever load is still running and installs a fresh +cancellation source. That source doubles as the commit ticket. The request then reads under a +token linked from the ticket and from the request-aborted token, so a browser abort and a newer +selection cancel the same read. +
- NativeTopoStl3d — the native topology built from the file's triangles, and one +of the two objects here that have to be disposed by hand. +
- TransformationWrapper — wraps that topology and carries the pose matrix. +
- CoordinateDrawing — the origin axes, created on first enable and kept across +file switches, because the flag is a per-connection viewing preference rather than a property of +the file. It is the other hand-disposed object, and only the connection's death frees it. +
- DispList — the composition assigned to Displayee: the +axes drawing, when shown, followed by the wrapper. +
- GeneralTransform — the pose the Transform section drives. +
wwwroot-src/src/components/StlPreviewPane.vue— this pane: the bar, the Transform section, the +canvas body with its overlay and banner, the show token and abort controller, and the optimistic +origin-axes toggle.
+wwwroot-src/src/components/FileExplorer.vue— the sole host: the preview slot in the editor +slave panel, the extension test and the three opening gestures, the pinned root, the close paths, +and the folder refresh after a save.
+wwwroot-src/src/components/widgets/FileExplorerDialog.vue— the modal wrapper, whose picker +props are what leave the editor panel hidden inside the picker.
+wwwroot-src/src/pages/FileExplorerPage.vue— the routed host at/util/file-explorer.
+wwwroot-src/src/api/stlPreview.ts— the typed wrapper over the five endpoints, the result shape, +and the abort signal threaded intoshow.
+wwwroot-src/src/api/http.ts— the shared response helper that wrapper uses: a non-2xx throws an +error whose text is the status followed by the server's message, while a 200 body carrying +success: falsereaches the caller as data, which is what lets thecanceledanswer be ignored +silently.
+wwwroot-src/src/components/RenderingCanvas.vue— the connection the pane owns, its reconnect +loop and the re-emitted connection id.
+wwwroot-src/src/components/RenderingCanvasToolBar.vue— theView ▾menu in the bar.
+wwwroot-src/src/components/widgets/DisplayOptionsMenu.vue— theScene ▾dropdown; the pane +passes one checkbox group and no label, so the button falls back to the localized “Scene”.
+wwwroot-src/src/components/topo/GeneralTransformEditor.vue, +wwwroot-src/src/components/topo/StaticRotationEditor.vueand +wwwroot-src/src/components/topo/StaticTranslationEditor.vue— the Transform section's editor and +its two sub-transformer cards.
+wwwroot-src/src/components/widgets/NumericInput.vueand +wwwroot-src/src/components/widgets/Vec3Input.vue— the fields inside them, committing on blur or +Enter.
+wwwroot-src/src/api/transformer.ts,wwwroot-src/src/api/geometry.tsand +wwwroot-src/src/api/index-service.ts— the standard transform surface those editors write +through.
+wwwroot-src/src/i18n/en/explorer.ts— theexplorer.preview.*strings: the close title, the +failure text, Origin axes, Transform, and the save-confirm dialog.
+wwwroot-src/src/i18n/en/geom.ts— the pluralized triangle-count phrase in the badge.
+wwwroot-src/src/i18n/en/topo.tsandwwwroot-src/src/i18n/en/widgets.ts— the transform-editor +captions, and theSceneandViewmenu labels.
+Disp/StlPreviewController.cs— the five endpoints, the root resolution and descendant check, the +off-thread read, the commit-then-swap ordering, thecanceledanswer, and the failure replies +that pass an exception message through unaltered.
+Common/FileExplorerController.cs— the root-prefix scrub over exception text that the File +Explorer's own endpoints apply and this surface does not share.
+Disp/StlPreviewService.cs— the per-connection slot: supersede and the commit ticket, the display +composition, the origin-axes flag that survives file switches, the bake-and-write save, and the +engine-removed handler that frees everything.
+Disp/RenderingService.cs— the per-connection engine store, the non-creating lookup HTTP +endpoints must use, and the engine-removed event.
+Disp/RenderingHub.cs— the hub whose disconnect disposes the engine and raises that event.
+Common/NamedRootResolver.cs— the three named roots and their availability rules.
+Common/IndexService.cs— the keyed object store the transform is published in.
+Mech/Topo/GeneralTransformController.cs— the standard transform endpoints, including the two +that index the rotation and translation sub-transformers under derived keys.
+HiGeom/Geom/Stl.cs— the reader: content-sniffed ASCII / binary detection, the cancellation check +every 4096 triangles or pre-scan lines, and the binary writer the save uses.
+HiDisp/Geom/Topo/NativeTopoStl3d.cs— the native topology, and the locked triangle snapshot the +save takes, which answers nothing once the topology has been disposed.
+HiDisp/Disp/Treat/TransformationWrapper.cs— the display-time pose applied to that topology.
+HiDisp/Disp/Flag/CoordinateDrawing.cs— the origin-axes drawing.
+HiDisp/Disp/DispList.cs— the one- or two-element composition the engine is pointed at.
+HiDisp/Disp/DispEngine.cs— the engine: its displayee, its cache clear, its home view, and a +dispose that frees the native engine alone.
+HiMech/Mech/Topo/GeneralTransform.cs— the pose model, its uniform scale and its two +sub-transformers.
+- File Explorer — the one screen that mounts this pane, and the editor column it +takes over +
- STL File Control — the geometry editor whose file picker is where this preview is +most often met, and the surface that owns the bounding-box readout this pane does not show +
- RenderingCanvas Tool Bar — the
View ▾andScene ▾menus the preview bar embeds
+ - Rendering Canvas on Web Service Application — the hub connection the pane opens, and the +engine-removed event that frees the preview's native geometry +
- Numeric Input — The single-value numeric field every editor embeds: blur-and-Enter commit, inclusive bounds, and the Infinity and NaN spellings; the most widely embedded control in the app +
- Object Management Menu Button — Load / Save / Paste / XML for any object that can produce an XML source +
- RenderingCanvas Tool Bar — The
View ▾camera-preset menu every 3D canvas docks
+ - Vec3dControl — The three-component vector editor: per-axis and single-field text modes, with local special-value handling +
- Polar Resolution 2D Panel — The editor for PolarResolution2d, embedded by the two holder editors that tessellate a revolved profile; the one control here with no SPA widget of its own +
- Mat4dControl — The sixteen-cell 4x4 matrix grid, with its Identity and Invert events +
- Numeric Input — the single-value numeric field the other editors on this shelf are built beside +
- Conventions — the file-path and numeric-value contracts two of these controls implement +
- App Shell — the frame these controls are embedded inside +
- Mat4dControl
+
-
+
Matrix Grid— four rows of four numeric cells, each cell right-aligned
+Identity Button— present when the identity flag is set, which is the default
+Invert Button— present only when the invert flag is set
+
+ - Identity — the button raises an
identityevent and writes nothing itself; the host supplies the +matrix. StaticFreeformEditor pushes its own identity constant throughupdateStaticFreeformMat.
+ - Invert — the button raises an
invertevent and computes nothing itself. StaticFreeformEditor +callsinvertStaticFreeformMat, which posts to/api/StaticFreeform/InvertMatand feeds the +sixteen numbers that come back into the model.
+ wwwroot-src/src/components/widgets/Mat4Input.vue— the widget itself: the column-major +sixteen-cell grid, the Identity and Invert buttons, and the local format, parse and commit helpers
+wwwroot-src/src/components/topo/StaticFreeformEditor.vue— the host, and the full binding +contract in one place:v-model, the two button flags,@update:model-value,@identity,@invert
+wwwroot-src/src/api/transformer.ts—updateStaticFreeformMatandinvertStaticFreeformMat
+Mech/Topo/StaticFreeformController.cs— the backend behind those two endpoints: Update takes a +sixteen-element array, InvertMat returns the inverted sixteen numbers
+Widget/Mat4dController.cs— the/api/Mat4dREST surface: New, NewWithValue, Get, Update, +UpdateAt, ParseAndUpdate, SetIdentity, Transpose and Inverse. The SPA reaches none of them; the +widget's host persists through the StaticFreeform endpoints instead.
+wwwroot-src/src/i18n/en/widgets.ts— the Invert label and both button tooltips underwidgets.matInput
+- Vec3dControl Component — the sibling numeric-geometry editor, and the one a reader usually wants next +
- Numeric Input/Output — why NaN and the infinities are text on the wire, and why NaN does not survive this grid +
update:modelValuecarries a number ornull.nullis a real outcome rather than an error +signal, so a host that must not receive one either turnsallowEmptyoff or filters what it +gets: the cutter section panel returns early onnullso that clearing a field cannot write a +zero into the profile, and the graphic-cache menu rejectsnulland every non-finite value +before it calls the server.
+parseErrorcarries the raw text that failed to parse. No shipped host listens for it, so a +parse failure is visible only as the message under the field.
+- A number outside the bounds, or text that does not parse, stays in the box under its error +message. Nothing restores it until the host pushes a different value. +
- A commit the host accepts and the server then refuses leaves the box showing the refused number
+unless the host had already applied it locally, because assigning the unchanged value back to
+
modelValueis not a change and moves nothing.
+ - Numeric Input Field
+
-
+
- Label — the host's
label, positioned inside the field's outline: on the input line while the +field is empty and unfocused, floated to the top of the outline once it has a value or the +focus; absent when none is given
+ - Text Box — one line, free text until committed +
- Unit Suffix — the host's
unit, inside the field at the right; display only
+ - Append Slot — an optional trailing slot for a host-supplied control +
- Bottom Strip — the host's
hint, replaced by the validation message while the field is in +error.hideBottomSpacestops space being held below the field but does not suppress the +strip: a hint or a validation message still renders, and with nothing reserved for it, it +grows the field instead of filling a gap already left below.
+
+ - Label — the host's
wwwroot-src/src/components/widgets/NumericInput.vue— the widget: the raw-text buffer, the +local format and parse pair, the blur-and-Enter commit, the inclusive bounds check that skips +non-finite values, and the three English validation strings.
+wwwroot-src/src/components/preference/GraphicCacheMenu.vue— a compact host of three instances, +one of them bounded by the values of the other two; every handler rejectsnulland non-finite +numbers, and the current-size handler is the one that rounds before sending.
+wwwroot-src/src/components/controlTree/toolhouse/CutterSectionPanel.vue— a host that returns +early on thenulla cleared field emits, so blanking a cutter dimension cannot write a zero.
+wwwroot-src/src/components/spindle/SpindleContourEditor.vue— a host that turnsallowEmpty+off and keeps its own draft copy of the contour points around the blur commit.
+wwwroot-src/src/api/mission.ts— the module that turns a committed non-finite number into the +Infinity,-InfinityorNaNstring the endpoint takes, and reads the two infinity spellings +back; aNaNarriving from the endpoint falls through to the caller's default instead.
+- Numeric Input/Output — the cross-boundary rule this widget implements the client half of, and how the three numeric inputs differ from one another +
- Widgets — the other controls that pages embed rather than own +
- Editing Contract — the branch that embeds this field most heavily, and the +commit rules its panels inherit from it +
- File Save/Load
- Object Copy/Paste @@ -146,9 +146,9 @@ Action<TargetObject> TargetObjectSetter{get;set;}
- Object Copy/Paste (i.e. Select/Set or Duplicated-Set)
- Copy (i.e. Select) -Set the model to SelectedItem. +Set the model to
UserService.SelectedItem. - Paste
-Set SelectedItem to the model.
+Set
UserService.SelectedItemto the model. Set by reference is default. Apply Duplicated-Set if explicitly required. - File browser
Use IMakeXmlSource.MakeXmlSource(string, string, bool) with
exhibitionOnlytrue,baseDirectorydestination folder,relFiledestination file name (maybe xxx.class-name) to paste the file.
- - Common/ObjectManagementMenuButton -
wwwroot-src/src/components/widgets/ObjectManagementMenuButton.vue— Quasar dropdown + XML editor dialog. Emitsupdate:modelKey,objectLoaded,objectPasted,xmlApplied,error. Supports Ctrl+C / Ctrl+V.wwwroot-src/src/api/objectManagement.ts— typed wrapper over/api/ObjectManagement/*.Widget/ObjectManagementController.cs— REST endpoints:Load,LoadResource,SaveAs,Copy,Paste,GetXml,ApplyXml,SupportsXml,GetSelectedItem.- File Explorer — reaches the same project / resource folders through a different endpoint family +
- Polar Resolution 2D Panel
+
-
+
- Enable Custom Resolution CheckBox
+
-
+
- checked if the host model is not null; the checkbox itself is always clickable. +
- unchecking sets the host model to null; checking a null model publishes a fresh PolarResolution2d of 0.5 mm / 5 deg through the setter. +
+ - Linear Resolution Input Field, with an
mmsuffix label +-
+
- enabled if model not null +
+ - Angle Resolution Input Field, with a degree-sign suffix label
+
-
+
- enabled if model not null +
+
+ - Enable Custom Resolution CheckBox
+
- Holder Resolution Section
+
-
+
- Resolution Hint Caption — names the STL tessellation used for display and collision meshing +
- Linear Resolution (mm) Input Field +
- Angle Resolution (deg) Input Field +
+ wwwroot-src/src/components/controlTree/toolhouse/HolderSectionPanel.vue— thesection === 'resolution'branch: the two number fields, seeded fromGetand committed on blur / Enter.
+wwwroot-src/src/components/controlTree/toolHouseItemTypes.ts— registers that panel as theHolderSectionitem type and grows the Resolution node only for aCylindroidHolder.
+wwwroot-src/src/components/controlTree/toolhouse/ToolHolderPanel.vue— the Holder stem above it, whose holder-type selector decides which sections appear.
+wwwroot-src/src/api/cylindroidHolder.ts—PolarResolutionDto(linear_mm/angle_deg) andsetCylindroidHolderResolution.
+wwwroot-src/src/router/treeRoutes.ts— maps the holder's section ids onto the Tool House route params.
+wwwroot-src/src/i18n/en/toolhouse.ts— the two field labels and the tessellation hint caption.
+Mech/CylindroidHolderController.cs—POST /api/CylindroidHolder/SetPolarResolution, which rejects a non-positive value, assigns a freshPolarResolution2dand callsClearCache();GET Getreports a null resolution as 0 / 0.
+- Cylindroid Holder Panel — the holder editor that embeds this panel to tessellate its profile +
- Freeform Holder Panel — the other holder editor that embeds it +
{ kind: 'check', label, modelValue, disable?, onUpdate }— a checkbox row.
+{ kind: 'radio', label, groupValue, value, disable?, onUpdate }— a radio row; rows sharing agroupValuebehave as one group, each row bound to its ownvalue.
+wwwroot-src/src/components/widgets/DisplayOptionsMenu.vue— the shared<q-btn-dropdown>+<q-list>implementation behind four of the app's five Scene menus. The Controller viewer is the exception:wwwroot-src/src/components/controller/ControllerExtendedToolBar.vuebuilds its Scene dropdown inline against the same/api/rendering-flagssurface.
+- Callers (each reads a
DisplayGroup[]computed from its page-local state and forwardsonUpdateto its existing handlers): +-
+
wwwroot-src/src/components/execution/ExecutionExtendedToolBar.vue
+wwwroot-src/src/components/mech/EquipmentSetupPanel.vue
+wwwroot-src/src/components/toolhouse/ToolHouseSetupPanel.vue
+wwwroot-src/src/components/StlPreviewPane.vue
+
+ - Backends — one per caller, each a different surface:
ExecutionExtendedToolBar.vuereaches/api/rendering-flags(Common/RenderingFlagsController.cs), which flips bits inExecutionDisplayee.RenderingFlagBitArray;EquipmentSetupPanel.vuereaches/api/mech/equipment-setup-display/*(Mech/EquipmentSetupDisplayController.cs);ToolHouseSetupPanel.vuereaches/api/mech/tool-house-display/*(Mech/ToolHouseDisplayController.cs);StlPreviewPane.vuereaches/api/stl-preview/set-coordinate/*(Disp/StlPreviewController.cs). Those last three route each POST by the caller'srenderingConnectionId, so the toggle lands on that canvas's own engine; the rendering-flags surface carries no connection id and acts on the project-wideExecutionDisplayeeinstead.DisplayOptionsMenu.vueis UI-only; it does no REST work itself — the caller owns theonUpdatehandlers, so optimistic update and error reversion stay page-local.
+ Play/RenderingFlagSubmenu.xaml— the shared flag submenu, wrapped per surface byNumerical/Controller/ControllerExtendedRenderingCanvasToolBar.xamlandPlay/PlayerExtendedRenderingCanvasToolBar.xaml. Behaviour parity with the webScene ▾menu; no schema-driven composition.
+wwwroot-src/src/components/RenderingCanvasToolBar.vue— theView ▾menu. Eight callers embed it, one per canvas:wwwroot-src/src/pages/ExecutionPage.vue(teleported into the canvas panel's header, besideExecutionExtendedToolBar),wwwroot-src/src/pages/ControllerPage.vue(besideControllerExtendedToolBar),wwwroot-src/src/pages/MachineToolPage.vue,wwwroot-src/src/pages/MechBuilderPage.vue,wwwroot-src/src/components/mech/EquipmentSetupPanel.vue(the General Setup canvas column, mounted bywwwroot-src/src/pages/GeneralSetupPage.vue),wwwroot-src/src/components/toolhouse/ToolHouseSetupPanel.vue(mounted bywwwroot-src/src/pages/ToolHousePage.vue),wwwroot-src/src/components/StlPreviewPane.vue(the File Explorer preview pane, mounted bywwwroot-src/src/components/FileExplorer.vue) andwwwroot-src/src/components/execution/StepVolumePanel.vue(the CWE footprint canvas, teleported into its host expansion's header). Four of those sit beside aScene ▾menu — the Execution, General Setup, Tool House and STL preview canvases; the Machine Tool, Mechanism Builder and CWE canvases carry theView ▾menu alone, and the Controller viewer pairs it with an inlined Scene dropdown.
+wwwroot-src/src/components/widgets/DisplayOptionsMenu.vue— schema-driven dropdown described above.
+wwwroot-src/src/components/RenderingCanvas.vue— SignalR-hosted WebSocket canvas whose exposedsetView/setViewTo*Viewmethods the tool bar calls (see Rendering Canvas on Web Service).
+wwwroot-src/src/i18n/en/widgets.ts— thewidgets.canvas.*keys:view, the seven preset labels, andscene.
+- Backends:
+
-
+
Disp/RenderingHub.cs— theSetView(string)hub method that maps each preset name onto engine calls.
+Disp/RenderingService.cs—GetOrCreateEngine(connectionId), the per-connectionDispEnginestore the hub resolves against.
+Common/RenderingFlagsController.cs— the/api/rendering-flagssurface (GET,POST update,POST batch) behind the Execution page's Scene menu and the Controller viewer's inlined one, wrapped bywwwroot-src/src/api/renderingFlags.ts. It reads and writesExecutionDisplayee.RenderingFlagBitArraythroughProjectDisplayeeService.
+wwwroot-src/src/api/equipmentSetup.ts,wwwroot-src/src/api/toolHouse.tsandwwwroot-src/src/api/stlPreview.ts— the typed wrappers the other three Scene menus post through.
+
+ - Execution Page — the page whose canvas header adopts this tool bar +
- Execution Extended RenderingCanvas Tool Bar — the run-specific controls that sit beside it +
- Rendering Canvas on Web Service Application — the SignalR transport whose hub actually moves the camera this menu asks for +
- STL Preview Pane — the one caller whose canvas is a single file rather than a project scene +
- Namespace
- Hi.Machining.MachiningEquipmentUtils
- Assembly
- HiMech.dll
- Inheritance +
-
+
+ SetupEquipment+
+
- Implements +
- + + + + + + + + + + + +
- Inherited Members +
-
+
+
+
+ + object.GetType() ++ + + +
- Extension Methods +
- + + + + + + + + + + + + + + + +
srcXElement
+ The XML element containing the equipment configuration.
+
+ baseDirectorystring
+ The base directory for resolving relative file paths.
+
+ relFilestring
+ The relative file path for XML serialization.
+
+ progressIProgress<IMessage>
+ Progress reporter for diagnostic messages emitted during construction.
+
+ - Asmb + +
- double + +
- double + +
- CoolantHeatCondition + +
- string + +
- Fixture + +
- IMachiningChain + +
- string + +
- IMachiningTool + +
- SpindleCapability + +
- string + +
- ITransformer + +
- Workpiece + +
- IDisplayee + +
- string + +
bindBind
+ Bind with DispEngine. See Bind.
+
+ dstBox3d
+ Destination box
+
+ - Anchor +
key anchor
+
+ - List<IAnchoredDisplayee> +
A list of IAnchoredDisplayee objects
+
+ - Asmb +
The key asmb.
+
+ - IMachiningChain +
The machining chain instance.
+
+ - DVec3d +
CL
+
+ - Vec3d +
if no MachiningTool or no Workpiece equiping, return null; +otherwise, return the XYZ from workpiece geomanchor to tool tip.
+
+ - Mat4d +
A 4x4 transformation matrix representing the coordinate system transformation.
+
+ baseDirectorystring
+ The base directory for resolving relative paths
+
+ relFilestring
+ The relative file path for the XML source
+
+ exhibitionOnlybool
+ if true, the extended file creation is suppressed.
+
+ - XElement +
An XML element representing the object's state
+
+ baseDirectorystring
+ The project base directory this equipment's +relative paths (e.g. MachiningChainFile) resolve against.
+
+ progressIProgress<IMessage>
+ Progress reporter for diagnostics during the copy.
+
+ - MachiningEquipment +
The materialised runtime equipment.
+
+ factoryXFactory
+
+ toolIdint
+ tool ID
+
+ toolHouseMachiningToolHouse
+ tool house
+
+ - bool +
true if the selection changed; otherwise, false.
+
+ - ToolNotFoundException +
Throw If
+toolIddoes not exist ontoolHouse.
+ - MachiningEquipment -
Machining Equipment. Include the machining chain, workpiece, tool and fixture, etc..
+The runtime (execution) face of the machining equipment: the topology +instance the runner drives — tool-change graph surgery, collision +detection, live axis poses. Materialised from the authored face +(MaterialiseMachiningEquipment(string, IProgress<IMessage>)) and NEVER +serialized: runtime data cannot reach the project file by construction.
- MachiningEquipmentUtil
Utility methods for working with machining equipment.
+
+ - SetupEquipment +
The authored (setup) face of the machining equipment: the machine chain, +workpiece, fixture, environment data and the Setup-page tool selection, +with the project XML IO. This is the ONLY face that persists — the runtime +face (MachiningEquipment) is materialised from it and never +serialized, so a mid-run save can no longer write live poses or the +runner-equipped tool into the project file.
++The XML wire name stays
"MachiningEquipment"(see XName): +shipped project files keep their layout, only the deserialized object changed. +- IMachiningEquipment -
Machining equipment.
+Machining equipment — implemented by both topology faces of the equipment +split: the authored SetupEquipment and the runtime +MachiningEquipment. XML IO is NOT part of this contract — +only the authored face serializes.
FluteZToDzListSortedList<double, double>Sorted list mapping flute Z positions to their deltas.
+
+ PreTipPoseDVec3d
+ The tip pose the previous StepMotion(bool, double, Mat4d) call fed to the +valve, on the workpiece-geometry coordinate – whether or not that call produced a step +(the valve skips a collinear return over a segment it already covered). Null on the first +call after a reset. The one-step reference for the real tip feedrate;
Seq.preis not, +it may span two steps back.
@@ -507,6 +514,42 @@ Class MachiningVolumeRemovalProc.StepMotionSnapshot
+
+
+ - DVec3d + +
Cycle-Line Charts
-The cycle-line charts visualise a per-step, per-cycle detail view of force / moment signals around the currently-selected step. They are re-fetched whenever SelectedStepInfoHub pushes a new step, so the operator can zoom into one spindle revolution (or the configured cycle window) as they scrub through the mission timeline on the strip charts.
The anatomy covers four cycle-line charts in the webservice, all sharing BaseCycleLineChart.vue:
The cycle-line charts visualise a per-step, per-cycle detail view of force / moment signals around the currently-selected step. They are re-fetched whenever the step selection changes, so the operator can zoom into one spindle revolution (or the configured cycle window) while scrubbing through the mission timeline on the strip charts.
+They sit in the Step Info column of the Execution Page, below Step Properties and CWE. The anatomy covers four, all sharing BaseCycleLineChart.vue, in the order the column stacks them:
| Chart | Source Data | +Cycle parameter | Notes | ||
|---|---|---|---|---|---|
| Force Cycle-Line Chart | +Sim Cutting Force Cycle | MachiningStep.ForceToWorkpieceOnProgramCoordinate or ForceToToolOnToolRunningCoordinate |
+spindle angle (deg) | Header dropdown picks between the two flag values; swaps the fetcher prop so BaseCycleLineChart refetches. |
|
| Sim Spindle Moment Cycle-Line Chart | +Sim Spindle Moment Cycle | MachiningStep.MomentsToToolAboutObservationPointOnSpindleRotationCoordinate_Nm |
-Simulated moment from the physics model. | +spindle angle (deg) | +Simulated moment from the physics model. Carries the locus (dartboard) mode. |
| Sensor Spindle Moment Cycle-Line Chart | -IMomentShot via MachiningProject.TimeMapping.GetShots(stepIndex) |
-Sensor-measured moment. Rendered as a sibling card to the sim chart, not a twin overlay (see Deferred). | -|||
| Dynamometer Force Cycle-Line Chart | +Sensor Cutting Force Cycle | IForceShot via TimeMapping.GetShots(stepIndex) |
-Sensor-measured force. Returns empty shape when no dynamometer data. | +time (s) | +Dynamometer-measured force. Returns an empty shape when there is no dynamometer data. | +
| Sensor Spindle Moment Cycle | +IMomentShot via MachiningProject.TimeMapping.GetShots(stepIndex) |
+time (s) | +Sensor-measured moment. Rendered as a sibling card to the sim chart rather than a twin overlay, which keeps the two uplot instances independent. Carries the locus (dartboard) mode. |
The parameter split is the reason the two pairs never share a cursor: a simulated cycle is indexed by phase, a measured one by clock.
Key Model: MachiningProject.TimeMapping + the selected MachiningStep.
Layout
-Each cycle-line chart is a card:
+Each cycle-line chart is a collapsible panel:
Behavior
-
-
Source Code Path
-See HiNC App Anatomy for git repository links.
-WPF Application Source Code Path
--
-
Web Page Application Source Code Path
+See HiNC App Anatomy for git repository links.
HiNC-2025-webservice (Quasar CLI SPA):
-
-
All four endpoints return { hasData, ts, xs, ys, zs } flattened for direct uplot consumption. Empty payloads use a shape-preserving ts=[0,360], xs/ys/zs=[NaN,NaN] fill.
Deferred
+See Also
-
-
Related Pages
--
-
Table of Contents
+ +Execution Extended RenderingCanvas Tool Bar
+ +The run-specific half of the canvas header on the Execution Page: what the scene shows, and how the tool path is drawn. The generic view controls live next to it on the RenderingCanvas Tool Bar.
+The model of the tool bar is DispEngine, assigned from the RenderingCanvas of the Execution Page. The content of Displayee here is the project displayee, whose key content is MachiningProject.
+Layout
+-
+
Fit View is not on this tool bar. It moved to the strip-chart group bar, which is where the whole-program time axis it fits against lives; see Strip Charts.
+Shipped Default Flags
+The displayee opens with four bits set — DimensionBar, WorkpieceGeom, Fixture and ClStrip — so a page that has just loaded draws the workpiece, the fixture, the dimension bar and the tool path, and draws neither the machine nor the tool until the operator ticks them. Mech is present in the initializer as a commented-out line rather than absent, so the default is a decision rather than an omission: a machine at its home view fills the frame and hides the cut. The flags live on the displayee, not in the project, so they are session state and every new rendering session starts from this set.
Tip
+The checkboxes in the Scene ▾ menu are grouped by the category of RenderingFlag — follow the link for the categories.
Behavior of the Tool-Path Buttons
+Apply the displayee's rendering-flag bit array to set the project rendering items.
+Tip
+Extract the MachiningProject from the project displayee and use it to drive the behaviors.
+Behavior of the Scene Menu
+See the DemoRenderingMachiningProcessAndStripPosSelection sample in the Hi.Sample.Wpf repository for code that completes the behavior of the buttons.
Source Code Path
+See HiNC App Anatomy for git repository links.
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
See Also
+-
+
Table of Contents
+ +Execution Tool Bar
+ +The transport controls for the run. In the web application they sit atop the Execution Page's primary editor panel, shown for the Execution root and every node under it, and this sole instance registers the F5–F8 shortcuts.
+Layout
+-
+
In the web application the status text moved out of the tool bar and onto the Execution tree item, which carries the run-state badge. The two single-advance buttons there share one skip_next icon and are separated by a letter drawn into the button's corner — L for the line button, S for the step button — rather than by colour; the tool-tips carry the key names.
Behavior
+-
+
The action of Reset Button is async, so the UI stays responsive while the session unwinds; the button shows a busy state for the duration, and the flag behind it is shared with the keyboard path so the two agree.
Enable Rules
+Every button is disabled unless a project is loaded and the page has finished initializing its rendering connection — the web transport reads that readiness from the Execution page, so a dropped canvas connection greys the whole bar with a project still open. On top of that gate each button follows the status:
+| Button | +Enabled for status | +
|---|---|
Start / Resume |
+Ready, Paused |
+
Pause |
+Running |
+
Run-One-Line, Run-One-Step |
+Ready, Paused |
+
Stop |
+Running, Paused, Finished |
+
Reset |
+any, while no reset is in flight | +
Neither single-advance button is enabled while the run is Running — stepping is a Ready-or-Paused operation, so a moving run has to be paused before it can be stepped.
The F5–F8 handler applies the same predicates before acting, and declines in two more cases: when the event target is an input, a textarea or a contenteditable element, and when the hosting page is deactivated. The second matters because the shell keep-alives the routed pages, so leaving the Execution route deactivates rather than unmounts this component; without the detach, F5 elsewhere in the app would drive the transport instead of reloading the browser. Every declined key falls through to the browser.
Tip
+Use icons rather than text on the tool-bar buttons. Run One Line and Run One Step share an icon, so they need a second mark to tell them apart — the win-desktop application seasons the icon green and blue, the web application draws an L and an S in the button corner. Either works; the default colour is enough for the rest.
Source Code Path
+See HiNC App Anatomy for git repository links.
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
SignalR Implementation (Web Service Only)
+ExecutionStatusHub is mapped at /executionStatusHub and answers GetExecutionStatus(). ExecutionStatusService watches the PacePlayer events — IsRunningChangedEvent, IsLockedChangedEvent, IsFinishedChangedEvent and ResetedEvent — and broadcasts each change, so every connected client sees the same run state without polling.
See Also
+-
+
Table of Contents
+ +Graphic-Cache SubMenu
+ +Shipped Surface
+The panel has no route and no Control-Tree node of its own. It is chrome on the Execution page (/execution): the canvas panel's expansion header carries the Meshed Geom ▾ dropdown — titled Workpiece rendering cache and geometry-diff settings — whose Graphic Cache row opens this panel in a nested menu. It hangs there because it acts on the workpiece meshed geometry's rendering cache, and its sibling row in the same dropdown is Diff Visual Radius. It is therefore only reachable while the Execution page's canvas column is shown.
In the WPF application the same submenu lives on the Preference Menu Dropdown.
+The panel is self-contained: it takes no parent-supplied model. It holds local lower / upper / current values, loads them on mount, and commits each edit to GET/POST /api/preference/graphic-cache. UserService / UserConfig is the server-side model behind that endpoint.
Layout
+Titled Graphic Cache (MB), with the caption “Memory budget for rendering cache. Lower & upper bound the slider.”
+-
+
Behavior
+-
+
Source Code Path
+See HiNC App Anatomy for git repository links.
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
See Also
+-
+
Table of Contents
+ +Execution Page
+ +The Execution page is the run cockpit: the simulation plays here, and every panel on it reads the run. It is the app's landing route — / redirects to /execution — and its Control Tree, the Execution root with Mission and Program beneath it, rides the ?tree= query, so /execution?tree=execution/mission deep-links the Mission editor.
Note
+The app says Execution throughout: the route /execution, the REST endpoints (/api/Execution/*), the status hub (/executionStatusHub) and the stored layout (UserConfig.ExecutionDivConfig). The HiAPI engine keeps its own library vocabulary — PlayerCommand (API) and PacePlayer (API) — which is a different codebase's naming, not an inconsistency here.
Key Models
+-
+
Layout
+Four columns, left to right. The two dock dividers are pixel-sized, so dragging one moves only that dock's edge while the flexible middle absorbs the change.
+-
+
The four columns are toggled from the app menu bar; the panels inside them collapse in place from their own expansion headers. The canvas stays mounted while collapsed so its rendering connection survives, and its engine is paused rather than torn down. The page itself also stays mounted while the user is on another route: the shell wraps the router view in a keep-alive keyed on the project epoch, so navigating away pauses the canvas but keeps the page — and its connection — alive until the loaded project changes.
Control Tree
+The page hosts an execution-scoped Control Tree: an Execution root with the Mission branch above the Program branch. Selecting a node fills the primary editor pane below the tree, and the Execution Tool Bar is mounted on top of that pane whenever the selected id is execution or starts with execution/ — so the transport stays reachable from every node of this tree.
Execution root is the cockpit's home: a short orientation panel, because the controls it would otherwise hold live where they act — the transport on the primary pane's header, the panel switches on each panel's own expansion header, the column switches on the nav bar. The live run state rides the tree itself: the Execution tree item carries a status badge fed by /executionStatusHub.
Mission (execution/mission) is the editable command list a run executes — the project's PlayerCommand (API), always a list, with nested lists read as sub-trees. Its ItemType registry supplies the tree checkbox that decides whether a command runs. The branch root is the entry-list editor — Add Command over a drag-reorderable row list with up / down / duplicate / delete — each command entry gets a control bar over that kind's own editor, and each of the kind's remaining setting groups becomes a section child of the command node. Every command type has its own page under Mission.
Program (execution/program) is the read-only inspection twin of Mission: one node per NC source file the session read, its passes and execution marks filled in from run data rather than typed in. Its nodes, its per-pass line and mark views, and the status changes that rebuild it are on Program Branch.
Selection and URL are synced two ways through the tree query argument: the URL's ?tree= is adopted once the tree is built, and every selection change replaces it. A ?tree= id belonging to another page's tree redirects to that page (routeForTreeId), and /mission lands on /execution?tree=execution/mission.
RenderingCanvas Behavior
+The canvas is split across the two sides: the browser component owns a canvas element and its connection, the server owns the displayee.
+-
+
Isometric is one of the view presets the user picks from the RenderingCanvas Tool Bar — alongside front, back, right, left, top and bottom — not the load-time view.
+Source Code Path
+See HiNC App Anatomy for git repository links.
+HiNC-2025-webservice (Quasar CLI SPA):
+Page, routing and Control Tree:
+-
+
Mission branch:
+-
+
Program branch (see the dedicated anatomy page for details):
+-
+
Panels and canvas:
+-
+
Charts (see the dedicated anatomy pages for details):
+-
+
Shared chart primitives under wwwroot-src/src/components/execution/charts/:
-
+
Backends:
+-
+
Building an Equivalent Page
+Tip
+When building an execution cockpit on top of HiAPI:
+-
+
Pages
+Ordered as the cockpit reads: the two tool bars across the top, the step column and the charts down +the side, the menus and dialogs they open, then the Control-Tree branch the page hosts.
+-
+
See Also
+-
+
Table of Contents
+ +List Command Panel
+ +A ListCommand is the container command: an optional title plus a list of +enable-wrapped commands. Running one walks +CommandEntryList top-down and skips a disabled entry together +with everything nested under it — the wrapper yields nothing while its flag is clear. The mission's +own command is always a list, so the Mission branch root is a +list, and every nested one is the same shape one level down.
+List is an ordinary catalog kind — category Flow — so a list is added the way any command is, and
+adding one grows a sub-tree: the node's children are its own entries, and its panel embeds the same
+entry-list editor the branch root uses, scoped to the nested list.
This page covers what a list is, how its title reads, and what moving commands in and out of one +costs. The entry-list editor itself — Add Command, the row actions, the three drag landings — is +documented once, on Mission Root Panel.
+Key Models
+-
+
The Panel of a List Entry
+Selecting a list command in the Control Tree opens, top to bottom:
+-
+
The Title input holds the raw title — empty when unset — while the label the row and the tree
+show is composed by the engine: List when the title is blank, List [title] when it is not. A
+titled list still says what it is. A whitespace-only title counts as unset and is never written into
+the project file, so it cannot come back as a changed title on the next load.
Keystrokes debounce into one title save. The pending save is flushed before anything that shifts the +entry paths — a structural operation, a duplicate, a selection change — and cancelled on delete and +on unmount, so a late write cannot land on whichever command slid into the old path. Once it is +saved the tree label refreshes in place rather than by rebuilding the branch, which would remount +the open editor mid-edit.
+The root list is the one list without a title input: the branch root reads Mission.
Enabling, and What a Disabled List Skips
+The enable switch is the checkbox on the command's own Control Tree item, not a control in the entry +row. It decides only whether the command runs: while it is clear the row and the tree node dim, +and the editor stays open and fully editable. Unticking a list dims its whole sub-tree — the dim +rides ancestor propagation in the tree while each nested entry keeps its own flag, which is exactly +what the run does: a disabled entry is skipped with everything under it.
+Paths
+A command node's key is its Mission API path — "0", "1", nested "0.2" for entry 2 inside the
+list at index 0. Every structural change re-mints them: deleting entry 2 slides the next command
+into path "2". Each branch build therefore stamps its nodes, so the editor panel remounts onto the
+new path instead of going on showing the command that used to live there.
Moving Commands In and Out
+-
+
Both are the same reparent call. The server resolves the source list and the target list to object
+references before it mutates either, so the index shift the removal causes cannot misroute the
+insert, and it refuses to move a list into itself or into one of its own descendants. That guard
+compares identity rather than path-string prefixes, because "02", "+2" and " 2" all parse to
+index 2.
Two more properties of a list follow from the same API:
+-
+
Dragging serves rearrangement only: a drag carrying text or files is ignored, because the row's drag +handling returns as soon as the drag did not start on a row. To bring several program files in at +once, open a Program File command's picker and pick them +together — the first pick lands on that command and each further pick becomes another Program File +command right after it, in pick order, inside the same list at any depth.
+Which Commands a List Can Hold
+The addable set is the server's command catalog: every engine command carrying [CommandCatalog],
+reflected into the picker Add Command opens, which searches by display label, kind key and alias.
+List is one of those commands, which is what makes nesting an ordinary act rather than a special
+case. The frontend contributes an icon per kind, and its own label where it has one; the server
+supplies the localized label otherwise, so a newly attributed engine command becomes addable with no
+frontend change.
A command without that attribute stays loadable from a project file but is never offered for +creation. General Config is the one such command with an editor of its own: it is the legacy +settings bundle, so a project file storing one loads as the individual setting commands it stands +for and saving never writes the bundle back — editable when a project still constructs one through +the API, and absent from the catalog.
+Selecting an entry row selects that command's tree node, and the command's editor opens on that +node — the kind's own panel when one exists, otherwise a generic editor built from the scalar fields +the command declares. Selection is one row at a time: there is no multi-selection here, and every +action acts on the row that carries it. Because the server composes every entry label, a command +carrying no title of its own still reads as its localized display name.
+There is no second column inside this editor: the entry list is a single column in the tree's editor +pane. The draggable vertical divider on screen belongs to the Execution page, between the left dock +— the Control Tree over the editor panel — and the central area; a second divider inside that dock +drags the height between the tree and the editor.
+The WPF Client's List Panel
+The WPF panel edits the root list only. It binds the project's player command as a list, its Add +menu offers no List item, and its content switch has no List arm, so a nested list entry selected +there renders the literal text “No editor available for ListCommand”. Nesting is a web surface.
+-
+
Source Code Path
+See HiNC App Anatomy for git repository links.
+Web Application
+-
+
HiAPI Engine
+-
+
See Also
+-
+
Table of Contents
+ +NcCodeCommand Panel
+ +The key model is NcCodeCommand.
+The command carries two values: NcText, the NC program +itself, and Title, the name that program runs under. Both +are written into the project file — the command emits a title element and an NC-text element, and +the project nests the whole command list into its own XML — so the program travels with the project +instead of existing as a file beside it. That is the whole difference from +Program File, which stores a path and leaves the program on disk. +A mission can therefore carry a short program without producing an artifact of its own.
+Title is not decoration. Run passes it to RunNc as the
+program name the run log shows, which is why the model defaults it to the command's own name,
+NC Code, rather than to an empty string. It is also the detail the mission row brackets: the row
+reads NC Code while the title is empty or still that default, and NC Code [title] once it says
+something else.
The panel renders on the command's own node in the Mission branch of the Execution page's Control +Tree, below the move / duplicate / delete control bar. A single-purpose kind like this one embeds +its whole editor on that node rather than growing section child nodes.
+Layout
+-
+
Saving
+Typing in either field schedules one save 400 ms after the last keystroke, so a burst of typing +costs one request instead of one per character. Trim Blank Lines and Clear save immediately rather +than through that timer.
+The write is a single patch endpoint carrying the NC text and the title. Each field is applied only +when the body actually carries it, which is why the client sends the field it changed and a null for +the one it did not.
+NC text is not validated while it is edited: there is no parse at edit time and no error surface on +the panel — the server stores whatever string arrives. Errors surface when the mission plays the +text.
+Source Code Path
+See HiNC App Anatomy for git repository links.
+Web Application
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
HiAPI Engine
+-
+
See Also
+-
+
Table of Contents
+ +NcFileCommand Panel (Program File)
+ +The key model is NcFileCommand.
+The command carries exactly two values: NcFile, the path of +the program to play, and NcKind, the runner that plays it. +The program text stays a file on disk — the project stores the path and nothing else, which is what +separates this command from NC Code, whose text is serialized +into the project itself.
+NcFile is normally a path relative to the project folder — that is what the browse dialog yields —
+and an absolute path on the server is accepted just as well. No base directory is stored with the
+command: at play time RunNcFile hands the stored path
+together with the project's own base directory to the local project service, and that is where a
+relative path acquires its root. The WPF panel's BaseDirectory is the code-behind assistant
+property the Load Pattern asks a file-assigning GUI to carry,
+assigned to the panel by its parent — a panel member, not a model member.
The panel renders on the command's own node in the Mission branch of the Execution page's Control
+Tree, below the move / duplicate / delete control bar. This command has no title of its own, so the
+row and tree label read Program File [path], the path being the detail the label brackets.
Note
+The GUI labels this command “Program File”: it plays NC, CL (CLSF) and CSV files — the runner is
+picked by NcKind, and
+Auto reads it off the extension
+(DetectByPath: .cl, .cls and .clsf play as CL, .csv
+as CSV, and every other extension as brand NC).
Layout
+-
+
Browsing for a Program
+Browse opens the shared server-side file explorer dialog. File reads happen on the server; the +browser never uploads file bytes.
+The Load Pattern's general web convention offers the Admin, +Project and Resource roots. This picker narrows to one: it opens on the project directory and allows +no other root, so a pick always yields a project-relative path. A file outside the project is +reached by typing its absolute path into the path field, which the server accepts.
+The dialog carries four filter groups rather than one catch-all:
+-
+
CL and CSV are split out because they are closed extension sets: those are exactly the extensions +Auto routes to the CL and CSV runners, and everything else falls through to brand NC.
+The WPF Browse button opens an OpenFileDialog with the panel's file-filter resource, starting at
+the project directory or at the current file's own directory. A pick under the project directory is
+stored as a relative path, and a pick outside it as an absolute one.
Multi-Pick Fan-Out
+The dialog is multi-select, and this is the one command editor that grows the mission. The first +picked file lands on this command. Each further pick becomes a new Program File command, added +to the list this command sits in and moved into place directly after it, in pick order — the same +list, so a command inside a nested list fans out inside that nested list. The panel then reports a +structure change, which rebuilds the owning list's branch around the new rows and leaves the +selection on this command.
+Where the Program Text Is Edited
+The two clients part company here, and the command is the same either way: it stores a path.
+-
+
Source Code Path
+See HiNC App Anatomy for git repository links.
+Web Application
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
HiAPI Engine
+-
+
See Also
+-
+
Table of Contents
+ +NC Optimization Option Panel (NC Optimization Config)
+ +The key model is NcOptOptionCommand; the options it carries are a +NcOptOption.
+The command exists so the optimizer's settings are a step in the mission rather than a global
+preference. Run assigns the command's option object onto the session shell — the whole body is
+sessionShell.NcOptOption = NcOptOption — so the settings take effect at the point the entry sits
+in the list. Everything played below it optimizes under them, and a second NC Optimization Config
+further down the list re-points them mid-mission.
The command carries no title on either client. Its row and its tree label always read
+NC Optimization Config: GetCommandTitle returns that fixed name and takes nothing from the user.
Where It Renders
+The web client has one editor component for this command and mounts it once per tree node, each +mount scoped to one group of options:
+-
+
Unlike the General Config and Post-Execution sections, none of these five section nodes carries an +enable checkbox: this kind declares no section enable flags, so the tree hides the tick on them. The +command node's own checkbox is the only switch, and it only decides whether the mission runs the +command — a disabled command is skipped during play and stays fully editable.
+Layout
+-
+
The three compensation switches are bit accessors over the model's compensation mask — forward is +bit 0, side bit 1, depth bit 2 — so all three travel as one integer in the project file.
+Saving
+Every edit writes at once. There is no Save button and no dirty state: a checkbox saves on click, a +numeric field saves when it loses focus or on Enter, and each write is one PUT for that one +property. The value is applied locally first; if the request fails, the panel re-reads the whole +command and raises a toast, so the field snaps back to what the server holds.
+The read side is a single command snapshot rather than one GET per property: the panel loads the
+command and takes its ncOptOption object. Two keys in that object drop the engine's Max prefix —
+the spindle torque and spindle power safety factors travel as spindleTorqueSafetyFactor and
+spindlePowerSafetyFactor.
Infinity and Bounds
+Infinity is a legal value for Preferred Force and Max Feed Per Tooth, and those two are exactly
+the properties whose endpoints take a string body: the client sends the literal Infinity (or
+-Infinity), and the reader parses the same spelling back. Every other property's endpoint takes a
+typed bool or double.
Every numeric field except Preferred Force is bounded at 0 — a smaller number is refused in the +field with “Must be ≥ 0” and never reaches the server.
+Tip
+The engine's XML form parses these values through XmlConvert.ToDouble, which is why an infinite
+feed per tooth or preferred force survives a project save and reload.
Defaults
+A freshly added command carries the option model's own initial values, so the panel opens on them:
+| Option | +Default | +
|---|---|
| The four enable switches | +all on | +
| Extended Pre / Post Distance | +2 mm | +
| Min / Max Feedrate | +1 / 20000 mm/min | +
| Rapid Feed | +20000 mm/min | +
| Min / Max Feed Per Tooth | +0 mm / Infinity |
+
| Feedrate Assignment Ratio | +0.01 | +
| Max Acceleration / Max Jerk | +10 mm/s² / 100 mm/s³ | +
| Preferred Force | +Infinity |
+
| Yielding / Thermal Yield Safety Factor | +0 | +
| Spindle Torque / Power Safety Factor | +1.5 | +
| The three compensation switches | +all off | +
The Two Clients
+The desktop client edits the same command in one scrolling panel of six group boxes, and it names 17 +of the model's options — the four enable switches, the two distances, the three feedrates, +acceleration and jerk, Preferred Force with the two spindle safety factors, and the three +compensation switches.
+The web client edits five more, and that is the whole difference between the two editors: Min Feed +Per Tooth, Max Feed Per Tooth and Feedrate Assignment Ratio in the Feedrate section, and Yielding +Safety Factor and Thermal Yield Safety Factor in Force & Safety.
+Source Code Path
+See HiNC App Anatomy for git repository links.
+Web Application
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
HiAPI Engine
+-
+
See Also
+-
+
Table of Contents
+ +PostExecutionCommand Panel (Post-Execution)
+ +The key model is PostExecutionCommand, the command both clients label +Post-Execution. It is the Output category's first catalog entry, and it carries five outputs +derived from what the session has played: step files, shot files, optimization files, a CL → NC +writeback, and a geometry-difference detection.
+When It Runs, and What It Covers
+Post-Execution is an ordinary entry in the command list, not an end-of-run hook. Its name describes +what it consumes, not a fixed position: the list runs its entries in order and skips any whose +Enable box is clear, and everything this command writes is derived from what the session has played +so far.
+That makes its placement the whole question. Put it after two program entries and it writes both. +Put a second one halfway down the list and it writes an interim snapshot of the same accumulating +state. The CL → NC writeback says this outright in the panel — place the CL-playing Program File +command before this command — and it re-serializes every control file the session played, so an NC +play is written back too. Read this together with +General Config, the other half of the pair: those settings +apply from their position forward, these outputs cover everything up to this position.
+The web client places the command anywhere in the list, like any other. The WPF client pins it to +the end: a new command is inserted before a trailing Post-Execution while that is the list's only +one, and the drag guard refuses to move it off the last position.
+Run executes the enabled outputs in an order of its own, which is not the order the tree and
+the list below show them in: shot files, then step files, then optimization, then the CL → NC
+writeback, then the geometry difference. Output timestamps follow that order rather than the layout.
Layout
+Every output is a section child node of the command, and each section's enable flag is that node's +own tree checkbox. The command node itself carries only a caption saying so; the section panels hold +the template and numeric fields, which stay editable whether or not the output is switched on — +the flag decides what runs, not what can be edited.
+-
+
Shot Files Output and Optimization Output are the two physics-gated sections. The condition is the +physics preference and the advanced-physics licence, served to the client already combined; when +it is off, the tree builder omits those two nodes entirely, and a preference flip shows up on the +next branch rebuild. CL → NC Writeback is deliberately not gated — writeback is a SoftNc syntax +feature, not a physics one.
+Every edit saves as it is made. A rejected save is reported and the panel reloads the command.
+Note
+Post-Execution writes run-derived files only. The meshed-geometry snapshot is a placeable command +of its own, because a snapshot can be taken at any spot in a mission: Record Meshed Geometry +(RecordMeshedGeomCommand, with its No Action / Read / Write / Read On +First Or Write modes) and Export Meshed Geometry (STL) +(ExportMeshedGeomToStlCommand) are both Output-category catalog entries +with editors of their own. Opening a project that still carries an enabled meshed-geometry output +on this command raises a system warning naming the replacement, and re-saving the project drops +the element.
+Note
+GeomDiffCommand exists as a standalone command, as do
+WriteStepFilesCommand, WriteShotFilesCommand and OptimizeToFilesCommand. None of them
+carries the catalog attribute, so they load from a project file but are not offered in the Add
+Command dialog. Post-Execution is the catalogued carrier of these outputs, which is why they are
+edited as its sections.
The WPF Client's Panel
+There four of the outputs are group boxes on one panel, each an enable checkbox over a grid that is +disabled until the box is ticked: Step Files Output, Shot Files Output, Optimization Output and +Geometry Difference Detection. The physics condition collapses the Shot Files and Optimization group +boxes rather than omitting tree nodes.
+CL → NC Writeback is the one output the web client has and this panel does not. Its two properties +live on the same engine command, and the web section and its two endpoints are where they are +edited.
+Source Code Path
+See HiNC App Anatomy for git repository links.
+Web Application
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
HiAPI Engine
+-
+
See Also
+-
+
Table of Contents
+ +PreSettingCommand Panel (General Config)
+ +The key model is PreSettingCommand, the command both clients label +General Config. It is one command carrying a bundle of session settings — the machining +resolution, the machining motion resolution, collision detection, pause on failure, physics — plus +an optional read of a meshed-geometry file.
+Where a General Config Comes From
+The two clients differ on how a mission acquires these settings, and that difference is the first +thing to know about this panel.
+The WPF client creates the bundle. Its Add menu carries a hard-coded item that constructs a
+PreSettingCommand, and the insert rule pins it to the top of the list: a new General Config lands
+at index 0 unless the list already starts with one, and while it is the list's only General Config
+the drag guard refuses to move it off that position.
The web client's Add Command dialog is served by the server's command catalog, which reflects every
+engine command carrying [CommandCatalog]. PreSettingCommand carries no such attribute, so the
+dialog cannot produce one — the catalog keeps presetting only as a readable kind key, and creating
+a command looks the kind up in the addable set alone. What the web offers instead is the same
+settings as five separate Setup commands, each placeable anywhere in the list:
-
+
Machining Motion Resolution ships an editor of its own. The other four declare their one scalar with
+[CommandField] and are served by the generic field editor, which builds their input and its
+server-localized label from that declaration — so the web ships strictly more here than one bundle
+panel, and none of the four needed frontend code.
A project file never brings a reader to this panel either. Reading a stored bundle materializes +those five commands in its place, in the order the bundle applies them, preceded by a Read-mode +Record Meshed Geometry entry whenever the meshed-geometry read is enabled or a file is set. The +bundle's own enable state is consumed into every entry the expansion produces, and the Record entry +additionally keeps the bundle's read flag. A bundle stored as the project's bare root command +expands the same way, and what the project saves from then on is the split commands.
+That leaves one case this panel serves: a PreSettingCommand an API caller constructed into the
+list in place, which has not yet round-tripped through a project file. It edits that command through
+the commands/{path}/presetting/* endpoints, and every field is a PUT of its own.
When the Settings Take Effect
+A list runs its entries in order and skips any entry whose Enable box is clear. General Config's
+Run then assigns straight onto the session shell, in this order: the meshed-geometry read when it
+is enabled, the machining resolution, the motion resolution, collision detection, pause on failure,
+physics.
So these are positional settings, not global pre-run settings. A program entry above this +command plays under whatever was in force before it; entries below it play under these values, +until something further down changes the session again. The five Setup commands state the same +contract one setting at a time — each is documented as taking effect “from this command on” — and +each can sit at a different point in the list, which is what having them separate buys.
+Layout
+In the web client the command owns two tree nodes: the command node carrying the machining settings, +and one section child carrying the meshed-geometry file reference.
+-
+
Every edit saves as it is made. A rejected save is reported and the panel reloads the command, so +what is on screen is what the server holds.
+Note
+The session's machining resolution is seeded from the workpiece's initial resolution when a +project is loaded or assigned — not on a runtime reset and not when the workpiece is swapped, so +an explicit setting survives both. This command carries its own value, and overwrites the +session's with it at the moment the command runs.
+The WPF Client's Panel
+There the whole bundle is one panel, in two group boxes.
+-
+
Source Code Path
+See HiNC App Anatomy for git repository links.
+Web Application
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
HiAPI Engine
+-
+
See Also
+-
+
Stick Tool Panel
+Mission
-The term stick is for not only milling, but other remover like electric discharge machining tool.
-The key model is MillingTool. -Other model: UserService.
-Layout
+Mission is a branch of the Execution page's Control Tree, reached as
+/execution?tree=execution/mission. It holds the command list a run executes: the project's own
+command is always a list, and nested lists read as sub-trees, so the branch is the mission and the
+mission is a tree of commands.
Each command type has its own editor panel. The pages below document one type each; the root panel +documents the list itself — adding, ordering and removing what the run will play.
+Ordered by where a command falls in a run: the branch root and its container first, then what is +set up before the run, what the run plays, and what happens after it.
+Pages
-
-
Note
-The Exposed-Cutter-Height and Preserved-Distance-Between-Flute-and-Spindle-Nose are directly related. Each value changed if each other value is changed.
-Step by Step Build Guide
--
-
Source Code Path
-See this page for git repository.
-WPF Application Source Code Path
--
-
Web Page Application Source Code Path
--
-
Table of Contents
+ +Mission Root Panel
+ +The Mission branch root of the Execution page's Control Tree: route
+/execution, Control-Tree path execution/mission, panel MissionRootPanel.vue. /mission
+resolves to the same place — it redirects onto /execution?tree=execution/mission.
The panel edits one list of commands: the mission's own command, which is always a +ListCommand. The branch root is therefore a list editor — Add Command, the +entries in run order, and the operations that rewrite the list. Editing a command is not this +panel's job: clicking a row selects that command's tree node, and the command's own editor renders +on that node's panel.
+Command nodes are addressed by index-derived tree ids and by dotted API paths ("0", "1", nested
+"0.2"). A command node id extends its parent's, so a root entry is execution/mission/{index} and
+an entry inside a nested list is execution/mission/{index}/{index}; a command's section children
+extend it once more, as execution/mission/{index}/{id} keyed on the section. Both forms shift on
+every structural change — deleting entry 2 slides the next command into path "2" — so each branch
+build stamps its nodes afresh and the editor panels remount onto the new paths.
Key Models
+-
+
Layout
+-
+
Enable/disable is not a control on this panel. It is the tree item's checkbox on the command node; a +disabled command is skipped when the mission plays and stays fully editable.
+Add Command
+The button opens a search-first picker. The search box matches a command's display label, its kind +key, or one of its declared aliases — each alias searchable both by its English key and by its word +in the request language. Arrow keys walk the results and Enter takes the highlighted one. Results +are grouped by category, in the catalog's own display order.
+The picked command is appended to the list this editor is scoped to, and the branch rebuilds in +place: the selection stays on the list rather than jumping into the new command's panel.
+The addable set is served by the backend rather than mirrored in a frontend menu. Every engine
+command carrying [CommandCatalog] is reflected into the catalog, so attributing a new engine
+command is all it takes for it to appear in the dialog. Thirteen kinds ship:
-
+
Note
+Program File plays NC, CL or CSV. The runner is picked from the file extension, and the command +can override that choice.
+Ordering, Duplicating and Deleting
+Move Up and Move Down move an entry within its own list; they never change which list owns it. +Duplicate deep-clones the entry through the same XML round-trip the project file uses — so a nested +list copies with its whole subtree — and the clone lands right after the source. Delete asks for +confirmation in a dialog naming the command.
+Dragging a row has three landings:
+-
+
The last two are the same reparent call. The server resolves both lists to object references before +it mutates either, so the index shift the removal causes cannot misroute the insert, and it refuses +to move a list into itself or into one of its own descendants.
+Nesting
+A list entry grows the same structure one level down: its children are its own entries, so nested
+lists read as sub-trees at any depth, and the very same panel edits them. The differences are the
+scope — the root list, versus the node's own dotted path — and the drop-out zone, which appears only
+in a nested editor. A nested list's node adds an optional title above the embedded editor, and that
+title is appended to the List name in the row and tree label.
Because the mission's own command is always a list, the root and a nested list are one editor at two +scopes. A project file whose stored command is something else loads with that command wrapped into +the root list, so the panel always has a list to edit.
+Where a Command Is Edited
+A command's editor renders on its own tree node, below the control bar carrying the operations that +rewrite the parent list (Up, Down, Duplicate, Delete):
+-
+
General Config is the one asymmetry. Its editor ships and +reads a command, but the catalog does not offer it: a project file that stores the bundle loads as +the individual commands it stands for, and the panel serves projects that construct one through the +API.
+The multi-card kinds — General Config, NC Optimization Config and Post-Execution — put their extra +cards on section child nodes of the command, one node per card, with the card's own enable flag +surfaced as that section node's tree checkbox. Post-Execution's Shot Files Output and Optimization +Output sections appear only while the physics preference is on.
+Source Code Path
+See HiNC App Anatomy for git repository links.
+Web Application
+-
+
HiAPI Engine
+-
+
See Also
+-
+
Table of Contents
+ +Script Command Panel
+ +The key model is ScriptCommand.
+The command holds two values: ScriptText, a C# script, and
+ScriptTitle, the name it runs under. Both are written into
+the project file, so the script travels with the project. Run evaluates the text against the
+session shell as the globals object, and when the script returns a sequence of actions that sequence
+is yielded into the run.
The title is what the mission row and the tree node show: the label is composed as Script [title],
+falling back to a bare Script while the title is empty. A newly added command starts empty on both
+clients — no title, no text.
The panel renders on the command's own node in the Mission branch of the Execution page's Control +Tree, below the move / duplicate / delete control bar. A single-purpose kind like this one embeds its +whole editor on that node rather than growing section child nodes. Its tree checkbox only decides +whether the run plays the command; a disabled script is skipped during play and its editor stays +fully usable.
+Layout
+-
+
The Editor
+The web editor is CodeMirror 6 running the mission-script mode: a stream tokenizer written for
+the dialect a mission script actually is — top-level statements, not a compilation unit. It colours
+keywords and boolean literals, strings (plain, verbatim @"", interpolated $"" and $@""),
+character literals, hex and floating-point numbers, line and block comments, and it tags a call site
+apart from a plain identifier. Because it is linear rather than a parser, every identifier of the
+same kind gets the same colour no matter where it sits in the file.
The C# Lezer grammar the client also ships is not what this editor runs: its compilationUnit rule
+rejects top-level statements, so it would recover from an error on line 1 and tag identical names
+inconsistently. That grammar serves the separate csharp mode a .cs file opens in elsewhere in
+the app.
Nothing in the highlighting is semantic. Everything the editor knows about types and members comes +from the backend, through completion.
+Completion
+Typing an identifier — or asking explicitly — sends the whole script text plus the zero-based cursor
+offset to POST /api/script/completions, and the response is an items array. Each item carries a
+label, a kind already normalised to CodeMirror's own completion type names, a detail (the
+signature, shown dim beside the label), a documentation (the XML <summary>, rendered to plain
+text by Roslyn and shown in the popup's info pane) and an insertText.
Completion is Roslyn in-process, over the very ScriptOptions — the same references and imports —
+that the run compiles the script with, so the session shell's members and the runtime API surface
+complete for real rather than by name matching. The service prepends the synthesised
+using / using static prefix those imports need and offsets the cursor by its length, so what the
+list offers is what the evaluator will see. A cancelled request is answered 499, and a result that
+arrives after the editor has moved on is dropped rather than shown.
Picking a method inserts the server-formatted call with each argument as a Tab stop, so the cursor
+lands on the first parameter and Tab walks the rest; a method with no parameters inserts as ().
+Properties, fields, types and keywords insert as their plain text — a snippet there would be noise.
Saving
+There is no Save button. Every keystroke in either field updates the model and schedules one save +500 ms after the last edit, and the pill beside the title reports the state: Idle, Dirty, Staging…, +Staged, or Error with the message in its tooltip. Staged means the server runtime holds the value — +its tooltip says so, because a staged script is not yet a committed project.
+The write is a single PUT carrying the script text, the title and the content hash the last load or +save handed back. Each field is applied only when the body carries it. The response returns the new +hash to chain into the next save.
+Two prompts guard the edges, each with three buttons because neither is a yes/no question:
+-
+
The panel snapshots its command path at mount, and the autosave closes over that snapshot rather +than over the live selection, so a flush can never land on whichever command happens to be selected +when it fires. After each successful save the tree relabels the node in place instead of rebuilding +the branch, which would remount the editor mid-edit.
+The Compile Gate
+A script that does not compile does nothing at run time: the command reports the compile failure +into the run's message stream and yields no work, so the run continues without it. Starting a run +therefore compile-checks every enabled script first — with the same script options and globals type +the evaluator uses — and refuses to start when any of them has an error, naming the first offender's +title, path, diagnostic id, message and line. The SPA's Start sends nothing to bypass that check, so +the refusal is what a user sees. The same check is also an endpoint of its own, per command and for +the whole mission.
+The Two Clients
+The desktop client edits the same command in AvalonEdit: a bold Script Title label with its text +box, a Script Text label, and the editor with C# syntax highlighting and a line-number gutter, +bound to the command through an attached behaviour. There is no completion popup and no autosave — +the binding writes straight into the command as the text changes.
+Source Code Path
+See HiNC App Anatomy for git repository links.
+Web Application
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
HiAPI Engine
+-
+
See Also
+-
+
Table of Contents
+ +Program Branch
+ +Program is the lower of the two Control-Tree branches the Execution page hosts: route /execution,
+Control-Tree path execution/program, one node per NC source file the session read. It is a
+read-only view of a run — the file's text, the passes that went over it, the per-line marks the run
+left behind, and the writeback files the run wrote — assembled from session data rather than from
+anything typed into it. Its node ids are positional, so an id addresses a node without surviving the
+branch growing around it.
Program Against Mission
+Mission is what a run executes; Program is what the run read. That one difference sets the rest of +the branch:
+-
+
Before anything runs the branch is not empty. The server seeds it by walking the mission root-first +through its nested lists — skipping a disabled entry along with everything beneath it — and taking +the NC file of every Program File command and the title of every NC Code command. It seeds +one placeholder per distinct string, not one per command: both loops append only where no root +already carries that string under the same case-insensitive, slash-normalised comparison the sibling +merge uses, and they share one namespace, so a Program File path and an NC Code title that normalise +alike collapse into one node. A Program File command whose path is still unset is skipped and seeds +nothing. A placeholder carries no invocations, so its panel shows the file's text with no marks and +an empty, disabled pass selector.
+The transport bar is the one control the branch does carry, and it is not the branch's own: the +primary editor pane pins the Execution Tool Bar above the panel of the +Execution root and of every node beneath it, so a run can be started, stepped and reset while a +program file is on screen.
+The Nodes the Branch Mints
+Three item types, each keyed by position rather than by identity.
+The branch root (execution/program) carries the summary panel and the child builder that
+fetches everything.
A file node is one source file as reached through one call edge. The server groups executed
+sentences by the file index stamped on them, resolves each invocation's caller from the call-stack
+record on the executed piece, and nests the callee's node under the caller's; the same subprogram
+called from two different files therefore appears once under each. Sibling nodes merge on a
+case-insensitive, slash-normalised path comparison, so repeated passes over one file under one
+caller collapse into a single node holding several invocations. Placeholders are appended after the
+nodes a run produced, and one whose path a run node already holds is skipped rather than doubled —
+that skip is also what keeps an inline command to one node, because the run stamps the command's
+title as the path of every piece it plays, so the placeholder and the run node carry the same
+string. A later pass flags every root whose path matches an enabled NC Code command's title as
+inline; that flag drives three presentational choices — the label prefix, the panel's inline caption
+and which missing-text wording it shows — and one behavioural one: the root's child builder matches
+a node against the writeback conversions only when the node is not inline, so an inline root is
+never stamped with a conversion, never gains the forward links to a converted file, and cannot be
+the target a conversion's converted from header jumps back to. The tree label is the path's base
+name, prefixed (inline) for an inline node and suffixed with a multiplication sign and a count
+when the node holds more than one invocation.
A conversion node is one NC writeback conversion held by the session, labelled with an arrow and +the written file's base name. Conversion nodes are appended after all file nodes.
+A file node's id is its parent's id plus its index among that parent's children
+(execution/program/0, execution/program/0/1); a conversion node's is the branch root's id plus
+dst- and the conversion's position in the session's list. Both are positional, and the tree those
+indices count into grows as a run discovers files, so a ?tree= link into this branch names a
+position and not a file.
The whole branch costs two requests. The root's child builder takes the file tree in one response +and the conversion list in another, stashes each file's raw subtree on the node it mints, and the +file nodes' builder only maps what is already there — no node fetches for itself. A failed +file-tree request yields an empty branch rather than an error; a failed conversion request leaves +the file tree standing without cross-links.
+Invocations
+An invocation is one pass over one file, keyed on the file index the run stamps onto every executed +sentence. A fresh index is allocated for each top-level play of a file and for every subprogram +call, call repetition and loop or jump re-segmentation, so a file entered or looped over more than +once contributes one invocation per pass, and the file panel's pass selector is exactly a pick among +them. Each entry reads as a hash-prefixed ordinal and a trigger, and the trigger is derived from the +piece that opened the pass:
+-
+
The selector opens on the last invocation in the node's list — the pass that started most recently — +and re-defaults whenever a rebuild leaves the current selection absent from the list. It is disabled +while the node has none, and it takes typed text as a filter over the entries. Everything below it +belongs to the selected pass alone: switching passes drops the mark and link caches and refetches +both for every page of text already loaded, not only for the pages on screen. The footer counts the +file's lines and, for the selected pass, the number of distinct source lines that executed in it.
+What a Run Marks
+The line viewer is virtualized. Text arrives in 1000-line pages covering the visible window plus two +pages either side; the server caps one request at 2000 lines and keeps a small most-recently-used +cache of whole files keyed on path and last-write time, so paging through a long program does not +re-read it once per page. A path with no file on disk is answered as non-existent, the miss is +cached so scrolling does not re-fire it, and the panel captions the file as missing — or, for an +inline node, as inline text that is no longer available, because an NC Code command's text is served +from the mission command itself and goes when the command is removed or retitled. A disk file wins +over a command title that shadows it.
+Marks are fetched the same way and per selected pass. A line the pass executed carries the sentence +index it ran and the range of machining steps that sentence produced; a line absent from the answer +did not execute in that pass and is greyed — but only once the node holds at least one invocation, +so a placeholder's text renders plain rather than uniformly grey. A line whose sentence produced +steps carries a step badge, single-valued or a range.
+Marks grow as a run advances, so a page cached early would keep later lines looking unexecuted. The +panel drops its mark cache and re-fetches the visible window on every throttled CL-strip update +broadcast.
+Clicking a line points the shared sentence cursor at that source position, which is what fills the
+Step Info column's Sentence Syntax panel; when the line's sentence produced steps, the click
+also selects that sentence's first step, moving the charts and the 3D strip with it. A line with no
+steps — a G54, a comment, a line that never ran — moves the cursor alone. Hovering a line sends
+the same anchor as the entered step on a 50 ms leading-edge throttle, and leaving the viewer clears
+it.
The traffic runs the other way too. A step picked on a chart or the 3D strip resolves to a source +anchor and lands on the cursor; a file panel whose path matches switches to the pass that step +belongs to and scrolls the line into view. The Follow toggle beside the pass selector is the +live variant: while it is on, the panel scrolls to whatever line the execution-status hub's cursor +reports for this file. It is off by default, it belongs to the mounted panel rather than to stored +preferences, and it does not change the selected pass.
+Writeback Conversions
+The session retains the destination piece streams and source-to-destination maps of its latest +CL-to-NC writeback run — NcConversions(API), +refilled by ConvertClToNcFiles(API). This +branch only displays them; the writeback itself is switched on by the mission's Post-Execution +command, in its CL → NC Writeback section. Because the conversions live on the session, they go +when it does.
+A conversion node shows the written file's lines, each line whose piece has a registered source +carrying a back-arrow badge naming that source line. The panel's header names this conversion's +source file; the link on that name lands on the file's node when a root file node matched the +conversion's source path, and otherwise falls back to the first entry of the branch's file-index +map, so a conversion whose source was reached only as a nested or inline node can name one file and +open another. Clicking a badge selects the source node, switches it to the invocation the piece came +from, and scrolls to the line; clicking anywhere else on a row only highlights it. +The viewer has no missing-file caption of its own, so a written file no longer on disk renders as an +empty viewer.
+The reverse direction is carried by the file panel. A root file node whose path matches a +conversion's source path gets forward-arrow badges naming the destination line, with a count when +one source line produced several destination lines, and clicking one selects the conversion node and +scrolls to the first of them. That matching is done on root nodes only, and never on an inline node, +so a subprogram file reached through a call carries no forward badges of its own even though a +backward jump can land on it and switch its pass.
+The jump itself rides one shared slot, because only one editor panel is mounted at a time: the +requesting panel parks the target line and emits a selection, and the twin takes the parked jump +when it mounts — or through a watcher, when it was already the selected panel. A target consumed +before the first page of text resolves is parked again and re-applied once the line array is sized.
+When the Branch Is Rebuilt
+-
+
A reset is the emptying event: it ends the session and clears the index, so the next read of the +branch returns the mission's placeholders and nothing else.
+Layout
+-
+
Source Code Path
+See HiNC App Anatomy for git repository links.
+Web Application
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
HiAPI Engine
+-
+
See Also
+-
+
Selected-Step Info Panel
-The panel locates on the Player Panel.
-The model is MachiningStep and UserService.
+The panel that shows the selected machining step's properties. It sits in the Execution Page's Step Info column, where the app labels it Step Properties.
+The model is MachiningStep and UserService.
The MachiningStep is assigned by ClStrip.PosSelected.
-Show step infomation from DisplayedStepPresentAccessList.
-The resx of MachiningStep contains the translation of PresentAttribute.Name, apply the translation to the GUI. If the translation not existed, use the original value.
-See Also Step Present Preference Page.
+Show step information from UserService.DisplayedStepPresentAccessList.
The resx of MachiningStep contains the translation of PresentAttribute.Name; apply the translation to the GUI, and fall back to the original value when no translation exists.
+Which properties are listed is chosen on the Step Present Preference Page, opened from the Step Present button on this panel's title bar.
+Layout
+A plain key / value list, not a q-list: the value cell has to shrink and wrap, because a step carries arbitrarily long values (a file path, a whole NC line) and a non-shrinking cell would widen the entire column.
Each row is labelled shortName, falling back to name and then to the raw key, with the full name as the row's tool-tip; the unit follows the value, and an absent value renders as -. With nothing selected the panel shows No step selected. Click a step in the canvas to inspect its info.
The StepIndex row is filtered out even when the configured list contains it — the column's group bar already carries the step index as a badge.
Behavior
+Selection bursts (a SetSelectedPos per step while a mission runs) are coalesced into one trailing fetch on an 80 ms timer, and responses are sequence-guarded so a slower earlier fetch cannot overwrite a newer one. A language change re-pulls, because the field names arrive already localized.
Sample Code
-Refer the code to show step infomation.
+Refer to this code to show step information.
internal static void ShowStepPresent(
UserService userEnv, MachiningStep machiningStep)
{
@@ -105,21 +111,23 @@
Console.WriteLine($"{present.ShortName}: {valueText} {present.TailUnitString} ({present.Name} [{entry.Key}])");
}
}
-SignalR Implementation (Webapi Only)
-SelectedStepInfoHub provides real-time step updates with method GetSelectedStepInfo() and event SelectedStepInfoUpdated. SelectedStepInfoService monitors PosSelected and MachiningStepSelected events and broadcasts updates. The JavaScript component connects to /selectedStepInfoHub to receive step change notifications and update the UI accordingly.
Selection Push (Web Service Only)
+SelectedStepInfoService answers the payload on demand: the REST endpoint GET /api/Execution/selected-step-info calls it. It has no SignalR hub of its own — the “selection changed” push rides /clStripHub, whose StepSelected broadcast follows the same PosSelected event. The panel and the cycle-line charts watch that broadcast and re-pull this payload.
Source Code Path
-See this page for git repository.
-WPF Application Source Code Path
+See HiNC App Anatomy for git repository links.
+HiNC-2025-webservice (Quasar CLI SPA):
-
-
Web Page Application Source Code Path
+See Also
-
-
Table of Contents
+ +Step Present Dialog
+ +Shipped Surface
+The dialog has no route and no Control-Tree node. It is a modal on the Execution page (/execution), opened by the tune icon button on the Step Properties panel's title bar in the Step Info column — so it is reachable only while that column and that panel are shown. The button sits next to the list it configures.
In the WPF application the same editor is a window opened from the Preference Menu Dropdown.
+The dialog's own view models are the two fetched values, categories and displayedKeys. The server models they mirror are UserService.CandidateStepPresentKeyList plus UserService.StepPresentAccessDictionary (the candidates) and UserConfig.DisplayedStepPresentKeyList (the ordered displayed list). The dialog's only prop is its open/closed flag.
Layout
+-
+
Rows are labelled from the per-key payload the server sends — key, name, shortName, unit. The visible label is shortName, falling back to key, with a unit appended in parentheses when it is not None. The tooltip carries the full name above the raw key.
Behavior
+Adding is a two-step gesture on the left: tick candidates — individually, or a whole category through the header's tri-state checkbox — then press Add Selected. A key that is already displayed has its checkbox disabled and shows a check badge instead. Removing lives only on the right, per row or through Clear.
Every add, remove and reorder persists immediately: the dialog POSTs the whole ordered list, the server drops unknown keys and duplicates and answers with the effective list, and the dialog re-syncs to that answer. Reset is a DELETE that empties the list; both Reset and Clear go through a confirmation. A failure flips the header state to its error label and notifies; a failed list write additionally re-reads from the server, so the editor never drifts from persisted state.
The dialog fetches fresh on every open, and re-pulls when the UI language changes, because the category and key labels arrive already localized.
+The list this dialog configures omits StepIndex even when it is displayed: the Step Info column's group bar already shows the step index as a badge, so the panel filters that one row out rather than repeating it.
Categories
+Categories are a GUI-level grouping of keys. Each ships as a stable code plus a display label: the code is the transport identity the client compares and keys its UI state on, the label is display text only. The seven, in canonical order:
+-
+
All seven are always returned; nothing gates any of them, and the candidate list is read from UserService.CandidateStepPresentKeyList rather than from MachiningStep directly, so runtime-registered keys are included.
Each client carries its own copy of the key → category mapping: ResolveStepPresentCategory on the webservice, GetKeyCategory in the WPF window. The two are kept in step by hand, with the seven category codes as the shared contract — adding a property means editing both.
Labels and Localization
+The web app resolves name and shortName server-side for the requested language from the shipped step-present catalog, laid over the live PresentAttribute.Name and ShortName values — those are the English truth and the fallback for a missing catalog file or key. Unit is sent as-is and does not localize. The dialog pins the language with a ?lang= argument on the request and re-pulls when the UI locale changes.
The WPF window resolves the same labels locally, through a resource manager over MachiningStep; a key with no translation keeps its original value.
+Refer to the code to apply PresentAttribute:
+internal static void ShowStepPresent(
+ UserService userEnv, MachiningStep machiningStep)
+{
+ foreach (var entry in userEnv.DisplayedStepPresentAccessList)
+ {
+ var present = entry.Value.Present;
+ var valueText = string.Format("{0:" + present.DataFormatString + "}", entry.Value.GetValueFunc.Invoke(machiningStep));
+ Console.WriteLine($"{present.ShortName}: {valueText} {present.TailUnitString} ({present.Name} [{entry.Key}])");
+ }
+}
+See Also
+-
+
Source Code Path
+See HiNC App Anatomy for git repository links.
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
Table of Contents
+ +Strip Charts
+ +The strip charts visualise a windowed, downsampled view of the full mission timeline for physics / quality aspects that are computed per step across the whole NC program. They are rendered as min/max banded series over the ClStrip.GetDispBegin() .. AbsDispEnd window. Operators use them to spot which segments of the mission are loading the spindle, degrading surface quality, or hitting thermal limits.
They occupy the Strip Charts column of the Execution Page. The anatomy covers three, all sharing BaseStripChart.vue:
| Chart | +Source Aspect | +Items | +
|---|---|---|
| Availability Chart | +Availability |
+Yielding-stress / max-spindle-torque / max-spindle-power / spindle-working-temperature / thermal-yield ratios (5 series). | +
| Surface Roughness Chart | +SurfaceRoughness |
+Re-cut depth + program-side cusp + Δ tip deflection X / Y / Z in µm (5 series). | +
| Color Index Time Chart | +Individual |
+One series of a user-picked StepPropertyAccessDictionary key (≈90 keys). |
+
Key Model: MachiningProject via LocalProjectService.ClStrip + LocalProjectService.StepPropertyAccessDictionary.
Layout
+One group bar drives all three charts, and each chart is a collapsible panel below it.
+-
+
Behavior
+-
+
Source Code Path
+See HiNC App Anatomy for git repository links.
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
See Also
+-
+
Table of Contents
+ +Background / Coolant
+ +Background and Coolant are two leaves of the General Setup page's Control Tree
+(/general-setup?tree=equipment/background, /general-setup?tree=equipment/coolant), sitting
+directly under the General Setup group between Spindle Capability and Fixture. They are two tree
+items rather than a page, so the older /equipment/background-coolant path redirects onto the
+Background leaf. Both are served by one panel component, which branches on the selected node's role
+path.
They edit the thermal condition on the project's authored equipment face, +SetupEquipment, reached as +SetupEquipment:
+-
+
Key Model: SetupEquipment (+ its +CoolantHeatCondition).
+Note
+The WPF desktop app has no surface for either value — no page, no panel, and no handler for the
+.CoolantHeatCondition extension. There, both are whatever the project XML carries on the
+equipment face, or the class defaults when the XML says nothing.
Layout
+-
+
Behavior
+-
+
Source Code Path
+See HiNC App Anatomy for git repository links.
+Web Application
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
HiAPI Engine
+-
+
See Also
+-
+
Table of Contents
+ +Brand Matrix
+ +The Controller branch is the one branch of the equipment tree whose node set is not fixed: it is
+grown from a snapshot of the active NC runner, and a node whose backing dependency the runner does
+not resolve is never created at all. This page is the index of every node that branch can grow, and
+it owns no node of its own — the ids it lists sit under ?tree=equipment/controller on
+/general-setup, on two plane stems, equipment/controller/machine and
+equipment/controller/program-data.
Important
+Anatomy makes no claim that any id named here is stable across versions. The migration hop that
+keeps an older ?tree= value resolving is described in
+Tree Ids and Routes.
What Decides the Shape
+One request returns a flat snapshot of the active runner: the brand marker, the chain-driven axis +list, and a set of presence flags. Each flag is a type probe over the runner's +proxy-resolved dependency list — GetEffectiveNcDependencyList(API), +so what is probed is the concrete table a proxy stands in for, never the proxy. Every gate the tree +reads tests a single type; two of the fields no component reads test a pair instead. The branch +builder reads that snapshot once per build and pushes a node for each flag that reads true. A node +that fails its gate is absent, not hidden and not disabled; the branch is regrown from a fresh +snapshot whenever a panel swaps the runner. How a branch is built, rebuilt and selected is the Control-Tree +engine's own subject — see Control Tree.
+Five brands ship as presets: Fanuc, Siemens, Heidenhain, Syntec and Mazak. Each is a property on the +engine's runner type returning a fresh instance whose dependency list is written out literally, so +what a brand grows is decided by that list and by nothing else.
+The snapshot also carries one non-boolean field that changes a panel rather than the tree.
+workCoordinateKind names which storage model backs the work coordinates, by a type switch over the
+first IIsoCoordinateConfig in the effective list: fanuc on Fanuc
+and Mazak, syntec on Syntec, siemens on Siemens (the frame table precedes the machine-data
+table, which is not an ISO coordinate provider), heidenhain on Heidenhain, and none when nothing
+resolves. Its generic arm answers for the brand-neutral
+IsoCoordinateTable, which no brand preset carries — it is
+reachable only from a runner that already holds one, so the arm is defensive rather than dead.
The Fixed Core
+Both plane stems are the builder's return value rather than gated entries, so they appear together
+whenever a runner resolves, and neither is ever empty: each carries leaves that no flag guards.
+Both are Group stems, so each renders an intro line and a clickable child list rather than a field
+editor.
The machine plane's ungated leaves are Controller Brand, Machine Limits (Stroke), Rapid Feedrates, +Home / G28 Reference, Tool-Change Position and Controller Parameters. The program-data plane's are +Work Coordinates (G54…) and Tool Offsets.
+Nothing in that core is gated, so no dependency decides whether those leaves exist — the builder +pushes them whatever resolves. What the preset lists decide is whether they have anything to show, +and on every brand they do. +ControllerParameterTableBase declares the machine-config interfaces +itself — home reference, axis set, rapid feedrates, stroke limits, spindle control, M-code +declarations and the tool-change trigger — and every brand's parameter table derives from it, which +covers the limits, rapid, home and parameters leaves at once. The other two read dependencies each +preset carries in its own right: CncBrandDependency behind +Controller Brand, and ToolingMcConfig behind Tool-Change +Position. On the program-data plane, every preset proxies the generic +ToolOffsetTable, and each brand supplies an ISO coordinate +provider of its own.
+The axis names the per-axis leaves list are the parameter table's own axis entries, which the +machining chain adds to and re-types when a machine tool is attached — it sets a type per chain axis +and removes nothing, so an attached chain yields the union of the two sets rather than a +replacement. Each brand's default table already seeds a linear X, Y and Z, so those rows are +populated before any chain is wired.
+With No Runner
+The builder returns an empty child list, so both stems and every leaf disappear and only the branch +root survives. Its editor is the runner root panel, which then shows an hourglass over “No NC +controller runner on this project.” and a hint pointing at the ⋮ menu's Load command; the brand +badge is dropped and the file caption reads “No NC runner”.
+The same empty list is returned when the snapshot request throws, so a failed fetch and a genuine +absence produce an identical tree shape, and the root panel renders the same hourglass block for +both. What separates them is transient and comes from elsewhere: the shared snapshot's own failed +read resets it to the empty value and raises an error toast. That read is a separate request from +the builder's, so the two can disagree — a branch collapsed by a thrown build fetch can sit under a +root panel still showing the brand badge of a snapshot that loaded.
+Every leaf panel still opens with its own guard on the same flag, rendering the shared empty body +“No NC runner — load a project first.” Since a leaf node is built only when the snapshot already +reported a runner, that guard is a live defence against the shared snapshot emptying under a +mounted panel — closing the project does exactly that — rather than a state reached by navigating.
+The Matrix
+Keyed on the node id, because one tree label is itself conditional: the generic tool-offsets leaf is
+relabelled when the Siemens $TC_DP table resolves, so an index keyed on labels would be
+conditional on the very thing it indexes. Rows are in the order the builder pushes them.
Machine-plane rows name the page that documents the node. The program-data leaves have no page of +their own, so those rows point at the Layout section below, which names every one of them in tree +order.
+| Node id | +Tree label | +Gate | +Dependency probed | +Brands | +Documented by | +
|---|---|---|---|---|---|
equipment/controller/machine/brand |
+Controller Brand | +always | +— | +all five | +Controller Brand | +
equipment/controller/machine/limits |
+Machine Limits (Stroke) | +always | +— | +all five | +Per-Axis Tables | +
equipment/controller/machine/rapid |
+Rapid Feedrates | +always | +— | +all five | +Per-Axis Tables | +
equipment/controller/machine/home |
+Home / G28 Reference | +always | +— | +all five | +Per-Axis Tables | +
equipment/controller/machine/tool-change |
+Tool-Change Position | +always | +— | +all five | +Per-Axis Tables | +
equipment/controller/machine/parameters |
+Controller Parameters | +always | +— | +all five | +Interface Parameters | +
equipment/controller/machine/m-codes |
+M-Code Declarations | +hasNativeTable |
+ControllerParameterTableBase | +all five | +M-Code Declarations | +
equipment/controller/machine/canned-cycle |
+Canned Cycle (Peck) | +hasCannedCycle |
+ICannedCycleConfig | +all five | +Interface Parameters | +
equipment/controller/machine/block-skip |
+Block Skip / Delete | +hasBlockSkip |
+IBlockSkipConfig | +Fanuc, Siemens, Syntec, Mazak | +Program Reading | +
equipment/controller/machine/subprograms |
+Subprogram Folders | +hasSubprogramFolders |
+SubProgramFolderConfig | +all five | +Program Reading | +
equipment/controller/machine/indexing-positions |
+Indexing Position Tables | +hasIndexingTables |
+SiemensMachineDataTable | +Siemens | +Indexing Position Tables | +
equipment/controller/machine/native |
+Parameters (Native) | +hasNativeTable |
+ControllerParameterTableBase | +all five | +Native Parameters | +
equipment/controller/program-data/work-coordinates |
+Work Coordinates (G54…) | +always | +— | +all five | +Layout below | +
equipment/controller/program-data/tool-offsets |
+Tool Offsets, read as Tool Offsets (ISO G43 H) under hasSiemensToolOffsets |
+always; label switched by hasSiemensToolOffsets |
+— | +all five | +Layout below | +
equipment/controller/program-data/siemens-tool-offsets |
+Tool Offsets ($TC_DP) | +hasSiemensToolOffsets |
+SiemensToolOffsetTable | +Siemens | +Layout below | +
equipment/controller/program-data/tool-names |
+Tool Names | +hasSiemensToolOffsets |
+SiemensToolOffsetTable | +Siemens | +Layout below | +
equipment/controller/program-data/datum-presets |
+Datum Presets (Q339) | +hasDatums |
+HeidenhainDatumTable | +Heidenhain | +Layout below | +
equipment/controller/program-data/datum-shifts |
+Datum Shifts (D) | +hasDatums |
+HeidenhainDatumTable | +Heidenhain | +Layout below | +
equipment/controller/program-data/frames |
+Frames (Siemens) | +hasFrames |
+SiemensFrameTable | +Siemens | +Layout below | +
equipment/controller/program-data/retained-variables |
+Retained Common Variables | +hasRetainedVariables |
+RetainedCommonVariableTable | +Fanuc, Syntec, Mazak | +Layout below | +
equipment/controller/program-data/r-parameters |
+R Parameters | +hasRParameters |
+SiemensRParameterTable | +Siemens | +Layout below | +
Three gates never read false on a shipped preset. hasNativeTable is true wherever any brand
+parameter table resolves, which every preset provides. hasSubprogramFolders is true because every
+one of the five lists carries SubProgramFolderConfig.
+hasCannedCycle is true on Fanuc and Mazak through
+FanucParameterTable, on Syntec through
+SyntecParameterTable, and on Siemens and Heidenhain through
+the FallbackConfig those two presets carry. So M-Code
+Declarations, Parameters (Native), Canned Cycle (Peck) and Subprogram Folders are gated in code and
+unconditional in practice — they can only go missing on a runner composed by hand or loaded from a
+file.
Two answers are decided by list order, not only by membership, because the snapshot takes the
+first match rather than any match: the parameter table behind hasIndexingTables and the ISO
+coordinate provider behind workCoordinateKind are both first-of-type lookups over the effective
+list. Each has exactly one candidate in every shipped preset, so order settles nothing there; it
+becomes load-bearing only on a runner carrying two.
The Flag That Does Double Duty
+hasSiemensToolOffsets is the only flag that both adds nodes and rewrites one. It adds Tool Offsets
+($TC_DP) and Tool Names, and at the same time switches the ungated tool-offsets leaf from the label
+key softNc.node.toolOffsets to softNc.node.toolOffsetsIsoH, so the row reads “Tool Offsets (ISO
+G43 H)” instead of “Tool Offsets”. The node id is untouched by the relabel. The reason is that the
+two ledgers coexist on Siemens — the (T,D)-keyed $TC_DP table beside the single-index ISO G43 H
+table — and an unqualified “Tool Offsets” would blur them into one.
The One Brand-Shaped Hole
+Block Skip / Delete is the only node that exists on four brands and not the fifth. +GenericBlockSkipConfig is the sole implementer of +IBlockSkipConfig in the engine, and the Heidenhain preset is the one +brand list that does not carry it. Reads of the block-skip endpoint answer absent there, and writes +answer unsuccessful with a message naming the missing dependency.
+Mazak Resolves the Fanuc Table
+The Mazak preset's dependency list differs from the Fanuc one only in the brand token it carries: it
+proxies the same Fanuc parameter table. Both proxies resolve the same concrete per-case table, so
+every downstream consequence follows — the same native parameter numbering and prefix, the same
+fanuc work-coordinate storage model, and the same rows under a Mazak brand badge. It is also the
+one brand pair whose per-case parameter table survives a switch between them, because the sweep that
+removes tables the new runner references through no proxy finds this one still referenced.
Flags Computed and Read By Nothing
+Five snapshot fields are computed by the web service, typed and parsed by the client, and read by no +component. They stay part of what the endpoint returns, so a client other than this one still +receives them.
+-
+
The screen gets the other four elsewhere. The controller-parameters panel shows its cutter-comp, +tool-axis and iteration-guard controls only where its own read returns a non-null value for each, and +the native-parameters panel renders the prefix from its own read, which recomputes the same switch +server-side.
+Two of the snapshot's own doc comments disagree with the code that ships, and the code is what the +tree obeys.
+-
+
What This Table Cannot Check
+The two halves of every brand column live in different repositories and nothing in either build +joins them.
+-
+
The brand columns above are read from those preset lists. Re-deriving them means diffing those two +files against each other — the probe list on one side, the five preset lists on the other — and +nothing else reports the drift.
+Two narrower falsifiers sit under the same join. The interface list on +ControllerParameterTableBase is why the machine plane's ungated +leaves have a live table behind them on every brand; if a brand table ever implements +IBlockSkipConfig, the Heidenhain hole closes silently. And the label +strings in the English locale bundle currently match the hard-coded labels in the branch builder +one for one; the tree renders the label key, so a drift there changes what the tree reads without +changing any id in this table.
+Layout
+-
+
The rows marked gated are the ones this page's table keys; the rest are the fixed core. Every row +is a plain label with no icon and no checkbox, since the tick column belongs to the mission branch +alone.
+Source Code Path
+See HiNC App Anatomy for git repository links.
+Web Application
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
HiAPI Engine
+-
+
See Also
+-
+
Table of Contents
+ +Editing Contract
+ +Every leaf of the Controller branch edits a different table, and almost all of them edit it the same
+way. This page is that shared way: what a leaf panel fetches, when an edit commits, what a failed
+write does, what a panel shows while its table is absent, and what it never notices. It owns no
+Control-Tree node of its own — it holds over every panel of the Controller branch on
+/general-setup: the root at equipment/controller, and every leaf beneath
+equipment/controller/machine and equipment/controller/program-data. Each leaf page can then
+state only what is its own. The last section is the other half of the contract: every panel that
+departs from it.
Important
+Anatomy makes no claim that these ids are stable across versions. See +Tree Ids and Routes for the migration hop that keeps an older +link resolving.
+The Shape of a Leaf Panel
+Each leaf panel takes one prop, the tree node, and reads the shared runner snapshot through a +module-singleton composable whose installer it calls synchronously while its script runs rather than +on mount. The installer is idempotent, so whichever panel mounts first pays for it and the rest get +the cache. Only four panels read the node prop at all: two use its id to tell apart the sibling +leaves they serve, and two use it as the scope of the structural change they report.
+Beyond that shared snapshot, a panel owns its own table. It imports its reader and its setters
+directly from the branch's typed API module and fetches once, in onMounted. There is no store, no
+request queue and no batch: one panel, one read, one table. The two panels that fetch nothing — the
+branch root and Controller Brand — are exactly the two that have no table of their own and render
+off the snapshot alone. How the branch itself is built, rebuilt and selected belongs to the
+Control-Tree engine and is described in Control Tree.
Only one panel is mounted at a time. The editor row's remount key is composed from the node id, the +node key and the mission stamp; controller nodes carry an empty key and no mission record, so the +key reduces to the node id alone. Moving the selection therefore remounts and refetches; re-clicking +the row already selected does neither. No controller item type registers a large content view, so +the General Setup content column keeps its own empty hint for every selection in this branch.
+When an Edit Commits
+The commit rule is per control kind, and every panel that uses a given kind uses it the same way.
+Numeric fields are the shared numeric widget, and it commits on blur or on Enter and on nothing +else — never per keystroke. Its full contract, including what a bound violation does to the box, is +Numeric Input. Three consequences matter here. Enter commits without +moving focus, so leaving the field afterwards commits the same value a second time. The widget +carries no equality guard, so a field that is focused and left unchanged still emits. And the two +halves of the rule are not wired alike: blur is the input component's own event and is raised only +while the field is editable, whereas Enter is a plain listener on the underlying element and still +fires on a read-only one — which is why a read-only cell can still commit.
+Checkboxes, toggles and selects commit immediately, on the click or the pick. That covers the +block-skip layer boxes, the tool-change Stays put box, the M-code tool-change box and its Spindle +and Coolant selects, the tool-word trigger toggle, the cutter-compensation and tool-axis selects, +and the tool-offset Set ideal offset dependent on tool house toggle.
+Plain text fields have no shared widget and are wired twice over. The M-code Not-simulated +note commits on the field's own change event, which carries the string and fires on Enter or on +leaving a changed field. The two subprogram folder inputs bind blur and Enter explicitly, and commit +as a pair.
+Row actions — the datum reset-to-zero button, the work-coordinate P0 and M0 buttons, a delete +button, an add button — commit on the click, and are not optimistic: they write first and touch the +local rows only after the request resolves.
+Two panels do not commit per control at all; they stage a draft behind an explicit button, and both +are named below.
+The Optimistic Write
+A per-control commit follows one fixed shape. The handler captures the current value, assigns the +new one so the screen updates at once, awaits the setter, and on failure restores the captured value +and raises a toast. Nothing is queued and nothing is retried.
+Two details of that shape are easy to misread from the screen:
+-
+
Adds and removes are outside this shape, and the local rows never move before the request resolves.
+Two panels then append the returned row themselves — the generic tool-offset table and the Siemens
+$TC_DP table, the two whose add hands back a freshly minted key. The other five re-read their whole
+table instead: M-Code Declarations, Tool Names, R Parameters, Retained Common Variables and
+Parameters (Native). A delete usually filters the row out in place; Parameters (Native) re-reads
+there too. Three deletions are guarded by a confirmation dialog first: a tool-offset row, a Siemens
+$TC_DP row, and a native parameter. M-code declarations, tool names, R parameters and retained
+common variables delete on the click with no confirmation.
When a Write Fails
+The branch's REST surface answers a missing dependency inside a success envelope rather than with an
+error status: the dependency lookup returns HTTP 200 carrying success: false and a message naming
+the type it could not find, such as No IStrokeLimitConfig on the active runner, and an exception
+raised inside the same lookup is reported the same way. The shared fetch helper turns both a non-2xx
+status and a success: false body into a thrown error, so a panel cannot tell the two apart; the
+first arrives with the status prefixed to the message, the second as the server's own sentence.
What the user sees is one negative toast, three seconds, composed as the panel's localized context +followed by the raw server message — the locale bundle stores each error key as the bare context and +the code appends the separator and the message. A failed write leaves no inline error state, no +retry affordance and no dirty marker: once the toast expires, nothing on screen records that it +happened. (The unsaved badge and the staged-brand banner on the two draft panels mark a pending +edit, not a failed one.) A console error accompanies the toast.
+The server message is not localized. Only a coded error payload can be re-rendered in the app
+locale, and the envelope this branch answers with carries no code, so its English sentence is
+appended verbatim under any language. The two writes that replace the whole runner answer with real
+status codes instead of the envelope: installing one through Object Management, and switching brand.
+Of the codes they raise, only not found with no project loaded carries the coded payload — the
+conflict raised rather than swapping the parser under a playing NC program, and the refusals of an
+unknown key or an unknown brand, are all bare English sentences. One further status code sits on the
+datum routes, a defensive 400 for a table segment that is neither preset nor shift; the panel
+sends only those two, so nothing reachable from the branch produces it.
The Two Empty Layers
+A leaf panel opens with two guards, in this order.
+Layer one is the shared snapshot. The shared empty state renders the single line "No NC runner +— load a project first." whenever the snapshot reports no runner. Every leaf panel carries it; the +branch root is the one panel that does not, because it renders a richer block of its own.
+Reaching that line by navigating is not possible: a leaf node is created only when the snapshot +already reported a runner, and a deep link naming an id the built tree lacks is refused. The guard +is live rather than navigational — it answers the snapshot emptying under a panel that is already +mounted, which is what closing the project does.
+Layer two is the panel's own read. Where the runner resolves but its table does not, the panel +renders one line naming the absent table — "No block-skip config on the active runner.", "No +Siemens frame table on the active runner.", "No tool-offset table on the active runner.", and so +on, one string per table. Three panels key that line on one table: Controller Parameters, M-Code +Declarations and Parameters (Native) all gate on the brand's controller parameter table and all +render "No controller parameter table on the active runner." Controller Parameters is the loosest +fit — its two iteration-guard fields resolve through dependencies of their own, and only its +presence flag is the parameter table.
+Controller Brand, which reads no table, has no second layer at all; every other leaf has one. Only +after both guards does the editor body render. One panel keys its second layer on something other +than its read's presence flag, and it is named below.
+What a Mounted Panel Does Not See
+The shared snapshot is fetched once per load of the application. The installer's flag lives at module +scope, so a later call does nothing and only a browser reload starts over, and the snapshot is +re-read on exactly three events afterwards: the project store's has-a-project flag turning true, an +Object-Management install of a runner, and a brand switch. The flag turning false re-reads nothing — +it clears the snapshot in place. No panel re-reads it either; the composable exposes a reload +function that nothing in the branch calls.
+Three consequences follow, and they are the contract's sharpest edges.
+-
+
Because the shared snapshot is a reactive reference, a panel does track it live once it changes — +but with one panel mounted at a time, the change that matters in practice is a project close.
+The Table Primitive
+Where a leaf renders a table it is Quasar's markup table — dense, flat, bordered — with a +hand-written header and body and a repeat over the rows. No panel in the branch uses the data-table +component, and the four data tables left in the application are the legacy controller tabs this +branch supersedes. The practical difference is that these tables have no built-in sort, no pagination +and no column menu: what the header says is what the column is.
+Key columns are rendered as plain text and cannot be edited: the tool and edge numbers of a $TC_DP
+row, a tool name, an R number, a # variable number, a datum row index, a native parameter id, and
+the axis names of every per-axis table, which follow the machining chain rather than the panel. The
+one renameable key is the generic tool-offset row's tool number, and it is guarded against duplicates
+on both sides of the wire.
A table that can grow carries an add control, and the branch spells it three ways. Five carry a true
+footer below the table — the new row's fields, then a primary button: Add / Set on Tool Names,
+R Parameters, Retained Common Variables and Parameters (Native), Declare on M-Code Declarations.
+Two carry a fieldless button in the toolbar above the table instead, because the server mints the
+key: Add on Tool Offsets and Add Tool on the Siemens $TC_DP table. The last is Add
+Position on Indexing Position Tables, which appends an empty row to the local draft and sends
+nothing. Two tables carry a Show all toggle that hides an all-zero extended tail, on Work
+Coordinates and on Frames.
Nothing Guards a Switch Away
+No panel in the branch registers the Control-Tree host's before-switch gate, and none exposes +anything to the host at all. Selecting another node therefore always succeeds immediately. For the +per-control panels that is exactly right — every edit is already on the server. For the two draft +panels it means an unsaved draft and a staged brand are discarded silently when the selection moves, +with no prompt and no warning.
+Where the Wave Is Not Uniform
+Everything above is the rule. What follows is every place the branch departs from it, panel by +panel. A contract page without this section invites the next editor to tidy it into a uniformity that +was never true.
+Machine Limits / Rapid Feedrates / Home Reference — the presence flag is discarded
+The axis-table panel serves three leaves and is the one panel whose second empty layer is not its
+read's presence flag: it keys on the row count instead. The rows and the flag are independent on the
+server, which builds one row per chain axis whether or not the backing config resolves and reports
+presence separately. So a runner with axes but no stroke-limit, rapid-feedrate or home config still
+renders a full, editable table, and every commit fails with a toast naming the missing dependency.
+What fills the cells differs by leaf: the stroke-limit and home values are nullable and come through
+blank, while a rapid rate is a plain number the server defaults to 0, so that one leaf shows a
+table of zeros that never signals the absence at all.
Controller Brand — staged, confirmed, and destructive by design
+Controller Brand is one of two panels with no table read of its own, the only panel with no local +error handler, and one of the two whose write replaces the whole runner — the branch root's +Object-Management install is the other. The select stages a pending brand rather than applying it; +Revert drops the staging, an orange banner spells out what the switch destroys, and Apply +brand opens a confirmation dialog before the request. Its failures surface through the shared +composable's global notification rather than the panel's own, so the console line names the shared +state and not the panel.
+Controller root — its own empty block, its own toast
+The branch root panel does not use the shared empty state. It renders an hourglass over its own +no-runner title and hint. Its error path also differs: it takes an already-composed string from the +Object-Management button, omits the console line, and uses a longer toast than every other panel.
+Indexing Position Tables — draft, validate, save whole
+The one draft table. Its numeric cells bind straight into a local draft row with no handler, an
+unsaved badge appears while the draft differs from what was saved, and nothing reaches the server
+until the per-table Save Table button, which stays disabled while the draft fails validation. The
+validation mirrors the endpoint's own rules — a per-table maximum, strictly ascending values, and a
+0 ≤ position < 360 range when a modular rotary axis reads the table — and reports the first
+violation under the table. A failed save deliberately does not roll the draft back: the draft
+stays dirty so the edit is not lost, which is the opposite of every other panel.
Its axis-assignment table above the drafts is display-only. The assignment is not editable on this +panel and the endpoint exposes no writer for it; the value is edited as a per-axis integer parameter +in Parameters (Native), which is what the panel's own hint says.
+Subprogram Folders — two fields, one write
+The two folder inputs are plain text fields committed together on blur or Enter of either one, and +the guard against a redundant write is a saved snapshot of both values rather than a per-field +captured value. Editing one folder writes both.
+M-Code Declarations — a third text-commit wiring, and an add that submits on Enter
+The note cell is the only text cell in the branch that commits through its field's native change +event rather than an explicit blur binding or the numeric widget. The timing matches; the wiring is a +third variant. This is also the only add-row footer whose input submits on Enter.
+R Parameters and Retained Common Variables — the two panels that commit a null
+Every other numeric handler returns early on a null and treats a cleared cell as no edit. These two +send the null, because an empty value is a real state in both tables — the vacant entry, which a +program reading it reports as an error rather than silently taking as zero.
+Tool Offsets — a read-only column that still writes, and a two-call rollback
+With the tool-house dependence on, the two ideal columns are made read-only rather than hidden. A +read-only field is still focusable and still takes keystrokes, and the widget's Enter binding is a +plain listener on the underlying element rather than the component event blur travels on — so +pressing Enter in one of those cells re-sends the whole row unchanged, while focusing it and leaving +does not, because a non-editable field raises no blur. The tool number column, by contrast, is +swapped for plain text and is genuinely inert. The tool-change position field takes a third route — +it is disabled rather than read-only while its axis stays put, and a disabled field takes no +keystrokes either.
+Turning the dependence on also makes two server calls inside one try: the toggle write, then the +refresh from the tool house. If the second fails after the first succeeded — the refresh answers +unsuccessfully when the project carries no tool house — the catch reverts the local toggle while the +server has already committed it.
+Work Coordinates — the one panel that writes outside the branch
+Clicking a row picks which coordinate the General Setup canvas marks, and that write goes to the +equipment-display surface rather than to this branch's own. It is also the branch's one call through +the plain-JSON helper instead of the envelope helper, so only a transport failure throws there. The +row click is bound on the whole row with no click-stopping on the cells or the P0 and M0 buttons, so +acting anywhere in a row marks it; a same-id short circuit is what keeps cell editing on the marked +row from writing again on every pass through.
+Two truth sources for “this is Siemens”
+The tree relabels the generic Tool Offsets leaf from the snapshot's $TC_DP flag, while the tool
+offsets panel shows its ISO-G43-H caveat, and the controller parameters panel picks its macro-guard
+labels, from the brand string instead. The brand marker is a free-form string that can be edited
+independently of the dependency list, so the label and the caveat can disagree.
The error handler does not agree with itself
+Every panel that has a local handler composes the same toast through one signature carrying two +incompatible conventions. Nine pass a localization key and translate inside the handler; eight pass +an already-translated string. Both first parameters are typed as a plain string, so nothing separates +the two conventions at compile time. Nothing user-visible changes; the console line does — the +key-style panels log the raw key path, the string-style panels log the rendered sentence.
+The add-row footers disagree on two details
+Whether Enter submits: only M-Code Declarations binds it. Whether the fields clear after a successful +add: M-Code Declarations clears its code, Tool Names clears both fields, and R Parameters, Retained +Common Variables and Parameters (Native) leave what was typed in place.
+A source comment the code contradicts
+The shared composable's header names the Execution page as the owner of the Controller root. The root +is built by the equipment-scoped host, which only the General Setup page creates, so the code is what +ships. The shared empty state's comment reads like a second case and is not one: the number in it +counts the byte-identical copies its extraction removed, not the call sites it has today, which are +one more.
+Layout
+-
+
Source Code Path
+See HiNC App Anatomy for git repository links.
+Web Application
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
See Also
+-
+
Table of Contents
+ +Controller Branch
+ +The Controller branch is the SoftNcRunner-native settings face: the controller the project actually
+parses NC with, edited one tree node at a time. It lives on the General Setup page at
+/general-setup under the Control-Tree id equipment/controller, and grows two plane stems beneath
+it — equipment/controller/machine and equipment/controller/program-data. The superseded
+HardNcEnv surface still ships as a route of its own at /controller, and edits a different model.
Important
+Anatomy makes no claim that these ids are stable across versions. See +Tree Ids and Routes for the migration hop that keeps an older link +resolving.
+Key Models
+-
+
Every value the branch reads is resolved through +GetEffectiveNcDependencyList(API) rather than the +raw pipeline list: the proxies deliberately implement no machine-config interface, so a consumer +that read the raw list would see a placeholder instead of the table. The tree itself speaks domain +vocabulary only — brand, machine limits, work coordinates. The dependency pipeline, the proxy +indirection and the brand preset lists have no node.
+The Two Planes
+The branch root's children are two Group stems, and neither is ever empty: each carries at least
+one leaf that every runner grows. A stem's editor panel is the shared group panel — an intro line
+over a clickable list of its children.
Machine / Controller (equipment/controller/machine) introduces itself as "Machine and
+controller presets that travel with the runner file — brand, travel limits, rapid rates, home / G28
+reference, the tool-change position and the controller parameters (in both a domain-grouped
+interface form and the native parameter form)."
Program Data (equipment/controller/program-data) introduces itself as "Data that travels with
+the workpiece / project — work-coordinate offsets (G54…), tool offsets and the brand-specific
+per-case tables. Kept on the project even when the runner file is swapped."
What a runner file actually carries
+The runner's pipeline list holds two kinds of entry, and the difference decides what an installed +runner file changes.
+Plain instances are the runner's own and are replaced outright by an install: the brand marker, the +tool-change position, the block-skip, subprogram-folder and iteration-guard configs a given preset +carries, and — on Siemens and Heidenhain — the generic fallback config those two presets add +because their parameter tables declare no number for the peck clearance. That last entry is why +Canned Cycle (Peck) is runner-owned on those two brands, while on the other three the same +clearance is a row of the brand parameter table and stays with the project.
+The brand parameter table is reached through a proxy instead. The proxy carries a machine-config +seed, and that seed — not the live table — is what the runner file serializes. +WireNcDependencyProxies(API) deep-clones the seed +into the project's per-case list only when the project holds no table of that type yet; a project +that already carries one keeps it. So the per-axis limits, rapid rates, home references, M-code +declarations and native parameters a panel edits are stored on the project, and installing a +same-brand runner file leaves them where they are while it does replace the plain instances above. +The Fanuc-family and Syntec parameter tables are per-case because they mix machine configuration +with per-case work-coordinate offsets, which the two planes could not otherwise split. The Siemens +machine-data and Heidenhain tables hold no work coordinates and are per-case for the other half of +the reason: a project's own table wins over the seed, so installing a same-brand runner file +re-binds to the machine data already edited instead of resetting it to the preset's.
+What survives a swap
+The per-case list belongs to the project and survives an install or a brand switch, minus one step: +after the new runner is assigned, per-case tables its proxies resolve nothing for are removed, so a +project switched from Fanuc to Siemens does not keep a retained-common-variable table nothing reads. +The generic tool-offset table survives every switch, because every brand preset proxies it. +Switching back does not restore what the sweep removed. Only the brand parameter table returns from +a seed, deep-cloned out of the new preset's proxy; the per-case tables beside it carry no seed at +all, so their proxies create bare instances — a project switched back to Fanuc gets an empty +retained-common-variable table, not the values it held before.
+The Root Panel
+The Controller node's editor is the branch root panel. Its header row carries three things:
+-
+
Below the separator the panel shows one of two bodies. With a runner it shows the presets hint —
+"Machine / controller presets (brand, limits, rapid, home, tool change, parameters) travel with the
+runner and can be saved / loaded as a .SoftNcRunner asset via the ⋮ menu. Program data (work
+coordinates, tool offsets…) stays with this project's workpiece. Edit each group via this item's
+child tree-nodes." Both hint blocks name .SoftNcRunner, while the file picker offers .Controller
+first.
An orange line follows when the snapshot reports no axes: "No machine axes yet — per-axis rows +(limits / rapid / home) are driven by the Machine Tool chain. Attach a machine tool to populate +them." The axis set is read from the controller parameter table's axis-type rows, which the facade +setter stamps from the Machine Tool chain — adding the chain's axes and +keeping the table's persisted ones. Every brand preset's default table already declares X, Y and Z, +so on a preset-built runner the warning does not appear; it is a live guard for a runner whose table +declares no axis, or which resolves no parameter table at all.
+When No Runner Resolves
+With no project open the host skips the branch builder entirely, so the Controller node stands alone
+with no children, and the root panel shows its own no-runner block: an hourglass, "No NC controller
+runner on this project.", and the hint "Load a project, or use the ⋮ menu → Load to attach a
+.SoftNcRunner file." This block is the panel's own, not the shared empty state the leaf panels use.
With a project open the builder runs and returns no children when the snapshot reports no runner — +and returns exactly the same empty result when the snapshot request throws. Nothing in the branch +shape distinguishes a failed fetch from an absent runner, and neither does the root panel: a failed +load resets the shared snapshot to the empty one, so the panel renders the same no-runner block. The +only signal is the error toast that failed load raises. The builder's own fetch failure raises +nothing, so a request that fails for the builder alone leaves a childless branch under a panel still +showing the brand badge.
+In practice a loaded project resolves a runner, since the suit's runner property constructs on the +Fanuc preset and a project file with no runner element keeps that default. The empty branch is +therefore what a reader meets before a project is open rather than a state a loaded project sits in.
+How the Branch Regrows
+Two panels in the whole branch report a structural change, and both scope it to the branch root:
+-
+
The host resolves the named scope, re-runs the branch builder over it, and adds the node to the +expansion list so the fresh children are visible. Because a brand switch replaces the whole runner, +this is what makes the brand-driven leaves appear and disappear.
+A brand switch that fails after the server has already assigned the preset leaves the two halves +out of step: the shared snapshot is reloaded either way, so the badge and the brand select read the +new brand, while the branch was not regrown and still lists the old brand's leaves.
+The tree shape and the panels' own gate come from two independent requests for the same snapshot. +The builder calls the endpoint directly; the panels read a module-singleton cache filled once per +SPA session and refreshed on a project-presence change, an Object-Management install and a brand +switch. No panel refreshes it, so an edit that empties a table changes neither the cache nor the +branch until one of those happens.
+Both structural writes are refused while an NC program is playing: installing a runner and switching +brand each answer a conflict rather than swapping the parser under a running session.
+The Superseded Route
+The legacy controller page still ships at /controller/:tab? and is reached from the app menu bar's
+Page → Legacy-Controller entry. It edits HardNcEnv through its own REST
+surface, which is a different model from the runner this branch edits — the two are not two views of
+one object, and an edit on one is invisible to the other. Its anatomy is
+Legacy Controller Page.
Layout
+-
+
Which brand grows which leaf is Controller Brand Matrix; how a +leaf panel commits an edit is Controller Editing Contract.
+Source Code Path
+See HiNC App Anatomy for git repository links.
+Web Application
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
HiAPI Engine
+-
+
Pages
+Ordered as the branch is read: the two references that hold across every node in it, then the two +plane folders whose leaves have editors of their own.
+-
+
See Also
+-
+
Table of Contents
+ +Controller Brand
+ +Controller Brand is the first leaf of the Controller branch's machine plane, and the one leaf whose
+write replaces the entire NC runner instead of one field of one table — the branch root's
+Object-Management Load and Paste replace it by the other route, described below. It sits on
+the General Setup page at /general-setup under the Control-Tree id
+equipment/controller/machine/brand, and it is the only leaf whose write changes which of the
+branch's other nodes exist. The tree row reads Controller Brand; the select inside the panel is
+labelled Controller brand.
Important
+Anatomy makes no claim that the id named here is stable across versions. The migration hop that
+keeps an older ?tree= value resolving is described in
+Tree Ids and Routes.
What the Brand Actually Selects
+The brand marker itself is a plain string carried by +CncBrandDependency, and nothing on the switch path reads it: the preset +that answers a pick is chosen by the token the request carries, and what the installed runner does +afterwards follows from that preset's own dependency list. What the panel writes is a preset: a +whole SoftNcRunner built fresh from the engine's brand property, carrying its own +syntax list, its own segmenter, its own initializers and its own dependency list. Four of the five +segment one block per line and Heidenhain brings a segmenter of its own; Fanuc, Syntec and Mazak +share an ISO initializer while Siemens and Heidenhain each bring theirs. So a brand switch changes +the dialect a program is read in, not only the tables the branch shows.
+Away from this panel the marker is not inert. NC optimization reads it back out of the session's
+effective dependency list for the writeback patch grammar, which takes five things from the marker:
+its variable prefix, its comment spans, its keyword set, the place an inserted F word lands — on
+Heidenhain after the rightmost DR+/DR- and RL/RR/R0, so the element order a TNC enforces
+holds — and the shape of the source note the optimizer embeds, a ; comment on Heidenhain and a
+parenthesized one on the rest. The radius-compensation syntax all five presets
+carry reads it too, raising its negative-radius validation warning only where the marker is
+Heidenhain. The piece classifier does not read it: none of its re-interpolation guards is
+keyed on the brand, and the guard that singles a klartext arc out is keyed on where the arc's
+centre came from. So an arc centred on a modal CC line splits like any other, and the one
+refused (NcOpt--SplitionStartPointCenterUnsupported) is the arc whose CC chain left an
+in-plane coordinate for its own start point to supply. An arc whose centre falls entirely on its
+start point never reaches that guard: with no radius it is not an arc at all, so it warns
+Arc-CircleCenter--OnStartPoint and is degraded to a chord as the program is read.
Each of the five properties returns a new instance per read, so no two projects can share one +preset and the returned runner is safe to mutate before it is installed. The five brand tokens are +declared once as constants on the brand dependency and are then enumerated again, independently, in +the engine's preset writer, in the REST action's own switch and in the client's option constant — +the first two by reference to the constants, the client by spelling the names out. Nothing joins +those lists, and they are not the only places in the engine where the five are spelled out again, so +the option a reader picks and the preset that answers it agree by convention rather than by +construction.
+The option list this panel offers is the client-side constant: Fanuc, Siemens, +Heidenhain, Syntec, Mazak, in that order. Brand names never translate.
+What Names the Current Brand, and What Names the Target
+Every string on the panel and in its dialog is either the brand in force or the brand about to +replace it, and the same interpolation token means the opposite thing in two adjacent places. The +select is seeded from the snapshot's brand and re-seeded by a watch whenever the snapshot's brand +changes, so before any pick it reads the brand in force; from the moment a different option is +picked, nothing left on the panel names the brand in force — only the confirmation dialog names +both at once.
+| Where it is read | +What it names | +
|---|---|
| The Controller brand select, before a pick | +the brand in force, from the shared snapshot | +
| The Controller brand select, after a pick | +the target — the staged brand, not yet applied | +
The orange banner's {brand} |
+the target | +
The dialog's {brand} |
+the brand in force, or the literal word current when the snapshot carries no brand marker | +
The dialog's {next} |
+the target | +
The success toast's {brand} |
+the target | +
The select is not a placeholder surface. It has no placeholder text: with a brand marker present it
+shows that marker, and with none it shows only its own label. Because the select maps its value
+through the option list and falls back to the raw value when no option matches, a runner whose marker
+is a string outside the five — a controller file authored elsewhere, or a marker edited by hand —
+displays that string verbatim in the closed select even though no option in the list can reproduce
+it. Such a marker is not only cosmetic: the marker's other readers compare it against the five
+tokens, so the writeback grammar falls back to its Fanuc-family form in every choice it makes — the
+Fanuc variable prefix and comment spans, no keyword set, the conventional F position and a
+parenthesized source note — and the negative-radius validation warning stays off.
Staging, and the Two Buttons
+The panel is one of the branch's two draft panels: the pick stages, it does not write.
+-
+
Two consequences follow from that enablement rule. First, the REST action's same-brand re-flash — a +POST naming the brand already in force, which the action documents as per-case lossless — cannot be +issued from this node, because staging the current brand is not a change. Second, on a runner whose +marker is blank or outside the five options, every option counts as a change, so all five become +applicable.
+Nothing guards a switch away from the node. The panel registers no before-switch gate, so selecting +another tree row discards the staging silently; returning to the node re-seeds the select from the +snapshot. The carry checkbox is not part of that staging and is not persisted anywhere: it is on when +the panel mounts and keeps whatever state it was left in for as long as the panel stays mounted.
+The Confirmation Flow
+Apply brand opens a confirm dialog before any request is sent. Its title is Switch controller +brand, and it declares no button labels of its own, so both buttons come from Quasar's language +pack — which follows the app locale, and reads OK and Cancel in English. Cancel closes +the dialog and leaves the staging untouched. The body is one of two whole sentences chosen by the +carry checkbox — the two are separate strings rather than a concatenation, because the clause order +differs between languages:
+-
+
While a different brand is staged, an orange banner stands above the buttons with the same warning in +the target's voice: "Switching brand replaces the whole runner with the {brand} preset. Machine +settings reset to that preset's defaults and the old brand's program-data tables are removed — +switching back does not restore them."
+Both sentences generalise, in opposite directions, and the exact reading is +What Survives below. The sweep is narrower than the old brand's +program-data tables are removed: the generic tool-offset table is program data and is never removed, +the retained common-variable table survives a switch among the three brands that proxy it, and +between Fanuc and Mazak the brand parameter table survives too. The reset is broader than a +cross-brand switch: the runner-owned machine entries return to the preset's defaults on every +apply, including the ones where nothing at all is swept.
+What the request can be refused with
+The action refuses before it touches anything in three cases, and each reaches the same toast:
+| Refusal | +Answer | +
|---|---|
| No project loaded | +not found, with the coded no-project payload — the one refusal here that a non-English locale re-renders | +
| An NC program is playing | +conflict, rather than swapping the parser under a running session | +
| A brand token outside the five | +bad request, naming the token it was given | +
The third is unreachable from this node: the select offers only the five, and staging is only +possible for a value that differs from the marker in force, so the request always carries a token the +action's own switch recognises. It stays live for any other client of the endpoint.
+Anything thrown after the work has begun is reported inside a success envelope instead: HTTP 200 +carrying an unsuccessful flag and the exception's own message. The shared fetch helper raises both +shapes as the same kind of error, so the panel cannot tell a refusal from a failure part-way through.
+What the operator sees afterwards
+Success raises an informational toast, "Controller brand switched to {brand}", naming the target. +Failure raises a negative toast composed as “Switch controller brand” followed by the server's own +sentence — untranslated, because the envelope carries no code to re-render. Controller Brand is the +one panel in the branch with no local error handler at all: its failures surface through the shared +runner state's global notification, so the console line names that shared state rather than this +panel.
+What the Switch Does
+The action runs four steps in a fixed order, and the order is what decides the outcome.
+-
+
What Is Carried
+The carry moves only the work-coordinate XYZ offsets. No other table's values cross, and nothing +else on either plane is copied.
+The accepted set is the intersection of what the outgoing provider currently holds with what the +incoming provider exposes, and the four providers do not expose the same ids. What each one allocates +by default:
+| Provider | +Brands | +Coordinate ids it exposes | +
|---|---|---|
| FanucParameterTable | +Fanuc, Mazak | +G54–G59 and G54.1 P1–P48, all seeded | +
| SyntecParameterTable | +Syntec | +the same set, through the same address map — spelled Pr rather than # |
+
| SiemensFrameTable | +Siemens | +G54–G57, plus the extended series G505–G599 | +
| HeidenhainDatumTable | +Heidenhain | +G54–G59, aliased onto preset rows 1–6 | +
So the carry is lossy in ways the checkbox's caption does not say. A switch out of Fanuc, Syntec or +Mazak into Siemens keeps G54 through G57 and drops G58, G59 and all forty-eight G54.1 P offsets. +The reverse switch keeps G54 through G57 and drops the whole G505–G599 extended series. Into +Heidenhain from one of those three, G54 through G59 cross and the G54.1 P offsets do not; from +Siemens, only G54 through G57 have anywhere to land. Between Fanuc, Syntec and Mazak the two id sets +match, so nothing is lost.
+Whatever is dropped is dropped silently: an id the target does not expose is skipped without a +message, and the table that held it is removed by the sweep in the same request.
+What Survives and What Is Swept
+The sweep's keep set is the new runner's proxy-resolved list, so a per-case table survives exactly +when the target brand's preset proxies its type.
+| Per-case table | +Proxied by | +Behaviour on a switch | +
|---|---|---|
| ToolOffsetTable | +all five | +survives every switch | +
| RetainedCommonVariableTable | +Fanuc, Syntec, Mazak | +survives among those three; swept into Siemens or Heidenhain | +
| FanucParameterTable | +Fanuc, Mazak | +survives between those two; swept otherwise | +
| SyntecParameterTable | +Syntec | +swept on leaving Syntec | +
| SiemensMachineDataTable | +Siemens | +swept on leaving Siemens | +
| SiemensFrameTable | +Siemens | +swept on leaving Siemens | +
| SiemensRParameterTable | +Siemens | +swept on leaving Siemens | +
| SiemensToolOffsetTable | +Siemens | +swept on leaving Siemens, tool-name map included | +
| HeidenhainDatumTable | +Heidenhain | +swept on leaving Heidenhain | +
| HeidenhainParameterTable | +Heidenhain | +swept on leaving Heidenhain | +
| HeidenhainQParameterTable | +Heidenhain | +swept on leaving Heidenhain, and exposed by no node or endpoint in the branch | +
Switching back does not restore a swept table. The returning proxy finds nothing of its type on the +project and clones a fresh table from its seed, so the rows come back at the preset's defaults and +the previous values are gone. The tool-name map is worth naming separately because it is not a table +of its own: it lives on the Siemens tool-offset table and leaves with it.
+Beside the per-case tables, the preset's plain entries are replaced outright on every apply, +whatever the target brand. Those are the runner-owned machine settings, and they reset to that +preset's defaults even in the Fanuc-to-Mazak case where nothing at all is swept:
+-
+
The Object-Management Load and Paste on the branch root install a runner through the same setter and +the same sweep, so the same table survives or is lost by the same rule — but that path has no carry +step and no confirmation of its own.
+What the Tree Does Afterwards
+The panel derives its rebuild scope from its own id by stripping the fixed /machine/brand suffix,
+so the scope is the branch root rather than this leaf, and it reports the structural change only on a
+successful switch. The Control-Tree host then rebuilds that branch and adds the root to the expansion
+list so the fresh children are visible. The rebuild makes its own request for the runner snapshot,
+independent of the shared one the panels gate on.
The shared snapshot is re-read either way, in the call's finally block, together with the runner's +index key. Three things follow:
+-
+
Layout
+-
+
No controller item type registers a large content view, so the General Setup content column shows its +own empty hint while this node is selected.
+Source Code Path
+See HiNC App Anatomy for git repository links.
+Web Application
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
HiAPI Engine
+-
+
See Also
+-
+
Table of Contents
+ +Machine and Controller Plane
+ +Machine / Controller is the first of the Controller branch's two plane stems: what describes the
+machine and the control that drives it, edited one leaf at a time. It lives on the General Setup
+page at /general-setup under the Control-Tree id equipment/controller/machine, and each of its
+leaves takes that id plus one segment — brand, limits, rapid, home, tool-change,
+parameters, m-codes, canned-cycle, block-skip, subprograms, indexing-positions,
+native. The stem is not gated: it is part of the branch builder's return value rather than a
+conditional entry, so it appears wherever a runner resolves and always carries the leaves no flag
+guards.
Important
+Anatomy makes no claim that any id named here is stable across versions. The migration hop that
+keeps an older ?tree= value resolving is described in
+Tree Ids and Routes.
Selecting the stem shows the shared group editor: its own introduction over a clickable list of its +children. The introduction reads "Machine and controller presets that travel with the runner file — +brand, travel limits, rapid rates, home / G28 reference, the tool-change position and the controller +parameters (in both a domain-grouped interface form and the native parameter form)."
+What “Travels With the Runner File” Means
+The phrase names an ownership boundary, not a file on disk, and the boundary does not fall where +the introduction's list suggests. Half of what this plane edits is stored on the project.
+The suit's two members
+NcRunnerSuit carries the runner, SoftNcRunner, and the +project's own PerCaseNcDependencyList, and serializes the two side +by side. The runner half honours SoftNcRunnerFile: with a +project-relative path recorded there the runner XML is written to that side file and referenced from +the project; with none recorded it is inlined in the project file. The per-case half is always +inlined — the suit writes it as a nested element with no file reference of its own.
+The web service reads that recorded path and never writes one. On a project that has never been
+given a side file, both halves therefore sit inside the same project file, in two slots whose
+contents behave differently the moment a runner is swapped. A controller file is one serialized
+runner. Its canonical extension is .Controller
+(FileExtension), and the branch root's Load browser
+accepts the earlier .SoftNcRunner spelling beside it.
Which half a value lands in
+A runner's PipelineNcDependencyList holds two shapes of entry.
+A plain dependency is the runner's own. Its edited values serialize with the runner, and +installing another runner replaces the instance outright.
+A proxy — INcDependencyProxy — carries no live data. Each brand +parameter-table proxy holds a fixed machine-config seed and serializes only that seed; +WireNcDependencyProxies(API) deep-clones the seed +into the project's per-case list only when the project holds no table of that type, and an existing +table wins. Reads and writes then resolve through +GetEffectiveNcDependencyList(API), which +substitutes the project's table for the placeholder. All four brand tables' proxies are written to +the same shape, and the seed is set once at construction and never rewritten by an edit.
+Two consequences follow, and they are what the introduction's phrasing hides:
+-
+
Why the table is per-case at all splits by brand. The Fanuc-family and Syntec tables mix machine +configuration with the per-case work-coordinate offsets the program-data plane edits, which the two +planes could not otherwise separate. The Siemens machine-data and Heidenhain tables carry no work +coordinates — those two brands keep their offsets in a separate per-case table — and are per-case +for the other half of the same reason: the project's own table wins over the seed, so machine data +and machine parameters already edited survive a same-brand runner install instead of resetting to +the preset's.
+The Plane's Leaves
+Ordered as the branch builder pushes them: the ungated leaves first, then each gated leaf behind the +snapshot flag that grows it. Which brands satisfy each flag is +Brand Matrix; the column that page does not carry is the last +one here.
+| Node segment | +Label the tree shows | +Gate | +What backs it | +Stored on | +
|---|---|---|---|---|
brand |
+Controller Brand | +ungated | +CncBrandDependency | +runner | +
limits |
+Machine Limits (Stroke) | +ungated | +the brand parameter table's stroke-limit rows | +project | +
rapid |
+Rapid Feedrates | +ungated | +the brand parameter table's rapid-rate rows | +project | +
home |
+Home / G28 Reference | +ungated | +the brand parameter table's reference-position rows | +project | +
tool-change |
+Tool-Change Position | +ungated | +ToolingMcConfig | +runner | +
parameters |
+Controller Parameters | +ungated | +the brand parameter table, plus the runner's iteration guards | +split | +
m-codes |
+M-Code Declarations | +hasNativeTable |
+the brand parameter table's declaration map and tool-change trigger | +project | +
canned-cycle |
+Canned Cycle (Peck) | +hasCannedCycle |
+the brand parameter table, or FallbackConfig | +brand-dependent | +
block-skip |
+Block Skip / Delete | +hasBlockSkip |
+GenericBlockSkipConfig | +runner | +
subprograms |
+Subprogram Folders | +hasSubprogramFolders |
+SubProgramFolderConfig | +runner | +
indexing-positions |
+Indexing Position Tables | +hasIndexingTables |
+SiemensMachineDataTable | +project | +
native |
+Parameters (Native) | +hasNativeTable |
+the brand parameter table, as its raw dictionaries | +project | +
Every label above is a translated role string rather than a type name, so the tree renders the +translation and a locale change rewrites the rows without touching an id.
+One flag grows two of these leaves: hasNativeTable gates both M-Code Declarations and Parameters
+(Native), because the declarations live on the same table the native form exposes.
Two leaves that straddle the boundary
+Controller Parameters is the one leaf whose fields do not share a storage half. Max spindle +speed, the cutter-compensation startup type and the tool-axis direction are written onto the brand +parameter table, so they are project-owned; the macro loop guards under Macro loop guards +(advanced) are written onto the runner's own iteration-guard dependencies, so they are +runner-owned. The panel gates its whole body on the parameter table's presence, which is the +project-owned half.
+Canned Cycle (Peck) is the one leaf whose storage half depends on the brand. Where the brand +table supplies the clearance it is a row of that table and project-owned; where the preset supplies +it through the generic fallback config it is a plain runner entry and runner-owned. The read reports +which of the two answered, and the panel names the source in the caption under the field.
+What an Install or a Brand Switch Does
+Object-Management Load, Paste and XML apply all install a runner through +SoftNcRunner, and a brand switch assigns the brand's +preset through the same setter. Both are followed by the same sweep, and for this plane the outcome +splits exactly along the storage column.
+The runner-owned leaves take the incoming runner's values, so a brand switch returns the +tool-change position, the block-skip layers, the subprogram folders and the macro guards to the new +preset's defaults. Installing a controller file of the brand already in force replaces those same +four with the file's values while the project keeps its own parameter table — the same-brand +re-flash the REST surface's own remark calls per-case lossless, naming those four as what resets. +That re-flash is not reachable from the brand select, whose apply is enabled only while the staged +brand differs from the active one (Brand Switch); Load, Paste +and XML apply are the surfaces that issue it.
+The project-owned leaves keep the project's table: the incoming proxy re-binds to it rather than +cloning its seed over it. Before the swap, any brand parameter table baked directly into the +outgoing runner's pipeline list — the storage a project file saved before that table went per-case +still uses — is moved into the per-case list so the incoming proxy can claim it, and a moved table +no proxy claims is dropped again afterwards. The chain walk that follows the re-bind is the only +part of the swap that writes into the surviving table: it stamps the chain's axes onto it, and with +them every rotary axis's reference position and rapid rate.
+A switch to a different brand ends with the per-case tables the new runner resolves through no +proxy removed, so the previous brand's parameter table does not linger unread. Switching back does +not restore it; a fresh table is cloned from the new preset's seed instead.
+Both writes are refused while an NC program is playing, and each answers with a conflict rather than +swapping the parser under a running session.
+One Table, Two Forms
+Machine Limits, Rapid Feedrates, Home / G28 Reference, M-Code Declarations, most of Controller +Parameters and all of Parameters (Native) read and write one object: the brand parameter table. +ControllerParameterTableBase declares the role accessors — home +reference, axis set, rapid feedrates, stroke limits, spindle control, M-code declarations, the +tool-change trigger — and each brand subclass maps those roles onto its own parameter numbers. The +domain-grouped leaves call the accessors; the native leaf writes the raw dictionary cell under the +number. Editing one form changes what the other shows.
+Where a brand stores a role in its own unit the two forms differ by that unit, and the peck +clearance is the case that ships: the Syntec table stores it in microns and its accessor converts, +while the Fanuc-family table stores millimetres directly. The interface leaf therefore shows the +same number on every brand and the native leaf shows the brand's raw stored value.
+What a freshly switched brand shows is decided by that brand's default table. Every one of them +declares a linear X, Y and Z with a reference position of zero and a rapid rate per axis, so Rapid +Feedrates and Home / G28 Reference open populated. None of them declares a stroke limit, so Machine +Limits opens with empty cells on every brand; and only the Siemens default table pre-declares any +M-codes, so M-Code Declarations opens empty on the other four brands, Mazak included, because it +carries the Fanuc table. An axis with no rapid-rate row of its own reads a fixed default rather than +a blank cell.
+The axis rows those leaves list are the parameter table's own axis entries. Whenever the suit or the +machine chain is re-bound, a chain walk stamps the chain's axis codes onto that table: it adds axes +and removes none, and a linear axis keeps what the table already holds, gaining a reference position +only where it had none. A rotary axis is the exception — every walk rewrites its reference position +to 0 deg and its rapid rate to 36000 deg/min, so a tuned rotary value does not survive the next +stamp. The chain itself is edited on Machine Tool.
+Layout
+-
+
The stem's own editor holds no field, so nothing on this plane is edited from the group row itself.
+Source Code Path
+See HiNC App Anatomy for git repository links.
+Web Application
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
HiAPI Engine
+-
+
Pages
+Ordered by the first node each page owns, as the plane lists them.
+-
+
See Also
+-
+
Table of Contents
+ +Indexing Position Tables
+ +Indexing Position Tables is the machine plane's single-brand leaf: the two global position lists a
+Sinumerik control resolves coded-position words against, edited as drafts and saved whole. It lives
+on the General Setup page at /general-setup under the Control-Tree id
+equipment/controller/machine/indexing-positions, and of the five controller brands only Siemens
+grows it. The tables it edits are machine data of the Siemens machine-data table; the assignment
+that decides which axis reads which of them is shown here and edited on the sibling leaf,
+Parameters (Native).
Important
+Anatomy makes no claim that any id named here is stable across versions. The migration hop that
+keeps an older ?tree= value resolving is described in
+Tree Ids and Routes.
The One Single-Brand Node
+The branch builder pushes this leaf behind the hasIndexingTables snapshot flag, and that flag is a
+pure type probe: it reports whether the runner's brand parameter table is the
+SiemensMachineDataTable. Nothing about the tables' contents
+is examined, so the node grows on every Siemens runner whether or not a single position has been
+entered. Only the Siemens preset carries that table, which is why this leaf is the machine plane's
+one single-brand row in the Brand Matrix; the program-data
+plane beside it has several.
The flag and the panel's own read do not ask quite the same question. The flag inspects the first +brand parameter table in the resolved dependency list; the panel's reader looks for a Siemens +machine-data table anywhere in it. The two agree on every shipped preset, where the Siemens +machine-data table is the only parameter table present.
+That table is also the sole implementer of +IIndexingPositionConfig in the engine, so nothing else in a runner +can supply an indexing table and no other brand can grow this node by carrying a different type.
+What the Two Tables Are
+The leaf edits IndexAxPosTab1 and +IndexAxPosTab2 — the general machine +data MD10910 and MD10930. They describe axes that take up discrete stations rather than arbitrary +positions: Hirth-coupled tables, indexing rotaries, turret-style workholders.
+-
+
What consumes them
+The tables are read at the write stage of the parsing pipeline, through
+TryGetIndexingPosition(API)
+and TryFindIndexingAnchor(API),
+for the Siemens coded-position coordinate functions CAC(), CIC(), CDC(), CACP() and CACN()
+— the family whose argument is a position number rather than a coordinate. The Siemens
+per-word coordinate syntax unwraps those verbs only on axes that
+IsIndexingAxis(API) reports
+as usable, and the rotary-only members of the family additionally require a rotary axis; the rotary
+words are then resolved by the ABC write pass and the linear words by the incremental-resolve pass,
+both through the shared coded-position helper.
An empty table is a real state with a visible consequence. An axis assigned to a table that holds +no entries is not a usable indexing axis, so its coded-position words are left unrecognised and +surface as unresolved text rather than being resolved against nothing. The engine lookups themselves +deliberately do not validate the Siemens constraints — a table that breaks them resolves nonsense +positions with no diagnostic — which is why the write endpoint, and this panel with it, is where the +rules are enforced.
+Machine Data, Not Program Data
+The tables belong to the machine plane rather than the program-data plane: they configure the +machine's stations, not a part's setup, and they carry no per-workpiece meaning the way a work +coordinate or a datum shift does. That is the classification the branch builder gives the node, and +it is the reason the node sits beside Machine Limits and Rapid Feedrates rather than beside Work +Coordinates.
+Their storage half is a separate question, and the answer is the project. The Siemens preset +carries a SiemensMachineDataTableProxy, not a table: the +proxy holds a fixed machine-config seed, deep-clones it into the project's per-case list only when +the project has no Siemens machine-data table yet, and thereafter resolves the project's own table. +Three consequences follow, and they are the ones the plane's other project-owned leaves live with — +Machine Limits, Rapid Feedrates, Home / G28 Reference, M-Code Declarations and Parameters (Native), +all rows of the same per-case table. The plane's runner-owned leaves get the opposite of all three, +and which half a leaf falls in is listed on +Machine and Controller Plane:
+-
+
Switching to any other brand removes the Siemens machine-data table from the project, because no +proxy of the incoming runner resolves it, and the positions go with it. Switching back clones a fresh +table from the seed rather than restoring the removed one — see +Brand Switch.
+The Assignment Is Shown Here and Edited Elsewhere
+Above the two drafts the panel renders one display-only table: Axis against Indexing assignment +(MD30500), the per-axis machine datum that decides what an axis does with the two global lists. +Four values are named:
+| Assignment | +Label the user reads | +Meaning | +
|---|---|---|
| 0 | +Not an indexing axis | +the axis ignores the coded-position family | +
| 1 | +Table 1 (MD10910) | +the axis reads the first draft below | +
| 2 | +Table 2 (MD10930) | +the axis reads the second draft below | +
| 3 | +Equidistant (MD30501–MD30503) | +positions come from a spacing rule, not from either table | +
Any other stored value renders as the bare number.
+The rows are the axes that carry an MD30500 entry, not the plane's usual axis set. Every other +per-axis leaf reads the table's axis-type rows — the set a machine-tool install stamps the chain's +axis codes into — while this one reads the keys of the assignment row itself, sorted by axis name. An +axis of the machine with no assignment entry does not appear here at all, and an assignment entry +written against a name the machine does not have does.
+An axis the table reports as modular rotary carries the note (wraps 0–360°) beside its name, and +its presence is what turns on the one-revolution rule for whichever table it reads. That report is +the machine-axis contract's default — +IsModularRotary(API) is not +overridden by any parameter table — so on this branch every rotary and every spindle-mode axis counts +as modular.
+Nothing on this leaf writes an assignment. The panel renders the value as plain text, and the +leaf's REST surface exposes a reader for the two tables with their per-axis assignments and a writer +for one position table; no endpoint of its own writes an assignment. MD30500 is written where every +other raw machine datum is written: on +Parameters (Native), as a row of the Axis parameters +(integer) section, added through the Section / Parameter id / Axis / Value (raw) footer with +the section set to Axis (integer). With no assignment row anywhere on the table this panel says +so and points there: "No axis declares an indexing assignment (MD30500) yet — add it as a per-axis +integer parameter in Parameters (Native)."
+The equidistant definition is edited entirely on that same sibling leaf and has no surface here. +Its three machine data — the numerator MD30501, the denominator MD30502 and the offset MD30503 — +are ordinary parameter rows, and an axis on assignment 3 consults neither of the two tables this +panel edits. On a modular rotary axis the numerator is ignored and the revolution is divided into +MD30502 positions; elsewhere the spacing is the numerator over the denominator and the position count +is unbounded.
+Draft, Validate, Save Whole
+This is the branch's one draft-then-save table, and it departs from the shared commit rule that +Editing Contract sets for its siblings. Every other table +in the branch commits per cell and rolls a failed write back; this one commits nothing until a button +is pressed, and keeps a failed edit on screen.
+-
+
The endpoint enforces the same four rules independently, so the panel's validation is live feedback +rather than the gate. A save that passes is applied to the live table and the panel does not re-read: +it marks the draft saved from what it sent.
+What a Freshly Switched Siemens Runner Shows
+Default3Axis — the seed the Siemens +proxy clones — declares a linear X, Y and Z, a reference position and a velocity for each, a maximum +spindle speed and six note-only OEM M-codes. It declares no MD30500 assignment and no entry +in either position table. So a project that has just been switched to Siemens grows this node, +opens it successfully, and finds it completely empty: the assignment caption in place of the axis +table, and two titled tables with headers, no rows and an Add Position button.
+This leaf is also the only place the two lists appear. Parameters (Native) shows every row of the +machine-data table's three parameter dictionaries, and the two position tables are not in any of +them — they are separate list properties, so this panel is the whole editable surface for MD10910 +and MD10930.
+The panel opens through the branch's two-layer empty gate: the shared "No NC runner — load a +project first." from the runner snapshot, then "No Siemens machine-data table on the active +runner." from its own read. The second line is also what a failed read leaves on screen, since the +presence flag starts false and the failure path only raises a toast.
+Layout
+-
+
Source Code Path
+See HiNC App Anatomy for git repository links.
+Web Application
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
HiAPI Engine
+-
+
See Also
+-
+
Table of Contents
+ +Interface Parameters
+ +Two leaves of the Controller branch's machine plane put the controller's non-axis parameters into
+domain vocabulary instead of parameter numbers: Controller Parameters and Canned Cycle
+(Peck). They live on the General Setup page at /general-setup under the Control-Tree ids
+equipment/controller/machine/parameters and equipment/controller/machine/canned-cycle. Each value
+they edit that is a row of the brand parameter table is also reachable as a raw numbered row on the
+sibling leaf, Parameters (Native); the values stored
+elsewhere are named below, and they reach no native form at all.
Important
+Anatomy makes no claim that any id named here is stable across versions. The migration hop that
+keeps an older ?tree= value resolving is described in
+Tree Ids and Routes.
Controller Parameters is ungated: the branch builder pushes it with the brand and the four per-axis
+leaves, so it exists wherever a runner resolves. Canned Cycle (Peck) is pushed behind the
+hasCannedCycle flag, which is true on all five shipped brand presets — see
+Brand Matrix.
The Five Fields, and Which Brand Shows Which
+Controller Parameters renders at most five controls, in one column, in this fixed order. There is no +tab, no group box and no scroll region; a field the brand does not have is not disabled but absent.
+| Field the user reads | +Control | +Backed by | +Fanuc, Mazak | +Syntec | +Siemens | +Heidenhain | +
|---|---|---|---|---|---|---|
| Max spindle speed | +numeric, rpm, minimum 0 |
+the brand parameter table | +#3741 | +Pr3741 | +MD35100 | +MP100 | +
| Cutter compensation startup / cancel type | +select — Type A / Type B / Type C | +the brand parameter table | +#5003 | +Pr5003 | +— | +— | +
| Tool-axis direction | +select — Z / Y / X | +the brand parameter table | +— | +— | +— | +MP101 | +
| GOTO — max jumps per N target † | +numeric, minimum 1 | +an iteration-guard dependency | +Fanuc guard | +Fanuc guard | +Siemens guard | +— | +
| WHILE/DO — max iterations per loop † | +numeric, minimum 1 | +an iteration-guard dependency | +Fanuc guard | +Fanuc guard | +Siemens guard | +— | +
† The Fanuc-family spelling. Siemens spells both guard labels differently — the two pairs are below.
+The last two sit under a separator and the caption Macro loop guards (advanced), and that whole +group is rendered only when at least one of the two guard values resolves. On Heidenhain neither +does, so the separator, the caption and both fields are absent together; that brand shows two fields +in total.
+The two guard labels are chosen from the brand marker, not from the dependency that answered. The
+Siemens spellings — GOTOF/GOTOB — max jumps per label and WHILE / FOR / REPEAT / LOOP — max
+iterations per loop — are used when the brand marker string reads Siemens, and the Fanuc spellings
+otherwise. Syntec and Mazak therefore read the Fanuc spellings, which is correct: both presets carry
+the Fanuc guard types. The label and the value are independent lookups, so a runner whose brand
+marker was edited apart from its dependency list can show one brand's wording over the other brand's
+guard.
The three select options Type A / Type B / Type C are translated strings; the three tool-axis
+options Z, Y and X are literal letters written into the component and are the same in every
+locale.
Visibility Is Null-Driven, Not Flag-Driven
+Which fields appear is decided per field, by a null in the read, and not by the shared runner
+snapshot the tree shape is built from. The reader answers one object with present plus five
+nullable numbers, computed by type-switching on the resolved dependencies:
-
+
The panel's own conditions are != null on those five keys. No snapshot flag decides a field on this
+leaf; the only one read at all is the shared no-runner guard. The snapshot's brand marker is read
+twice more, but only to choose between the two guard label pairs — never to show or hide anything.
Three flags in the snapshot describe exactly these fields and are read by nothing.
+hasCutterComp, hasToolAxisDirection and hasIterationGuards are computed server-side, typed,
+defaulted and parsed in the client's API layer, and no component consults any of them. They remain a
+live part of the REST snapshot for any other client. Two notes about the third:
-
+
The panel's whole body — the guards included — is hidden behind present, which reports the
+parameter table and nothing else: "No controller parameter table on the active runner." The
+guards live on separate dependencies with their own write endpoints, so a runner carrying guards but
+no parameter table would hide two reachable fields. All five brand presets carry a parameter-table
+proxy, so the combination does not arise on a shipped preset.
Units: Edited Versus Stored
+Every value but one is stored in the unit it is edited in.
+| Value | +Edited as | +Stored as | +
|---|---|---|
| Max spindle speed | +rpm | +rpm, in the table's system-parameter row | +
| Cutter compensation type | +one of three named options | +the option's index, 0 / 1 / 2, as a number in the same row set | +
| Tool-axis direction | +one of Z / Y / X |
+0 / 1 / 2 in the same row set | +
| Macro guards | +a plain count | +the same count, on the guard dependency | +
| Peck retraction clearance | +mm | +mm on Fanuc and Mazak, microns on Syntec, mm on the generic fallback | +
Syntec is the one brand whose stored unit differs from the edited one, and the peck clearance is
+the one value it happens to. The Syntec table's accessor divides its stored row by 1000 on read and
+the write multiplies by 1000, so this leaf shows 5 on every brand while the native leaf shows
+5000 under Pr4002 on Syntec and 5 under #4002 on Fanuc and Mazak. An operator copying a number
+off a real controller reads the native form; an operator entering a clearance in millimetres reads
+this one. The two are the same setting.
The two select fields are the second unit hazard, in a milder form: the native leaf shows their
+stored number, 0, 1 or 2, with the brand table's own description of that number under the id,
+and never the words Type A or Z.
Not every value reaches the native form at all. The two macro guards are properties of their own +runner-owned dependencies rather than rows of any table, so they appear there on no brand; the peck +clearance does the same on Siemens and Heidenhain, where the generic fallback supplies it.
+Peck Retraction Clearance
+Canned Cycle (Peck) is a single numeric field labelled Peck retraction clearance (G83), in +millimetres, at zero or above, with a caption naming where the value is stored. Its model is +PeckRetractionDistance_mm, and the read reports +which implementation answered. Three can, and between them they cover every shipped preset:
+| Caption the user reads | +Answered by | +Brands | +
|---|---|---|
| Stored in the Fanuc parameter table (#4002, mm). | +FanucParameterTable | +Fanuc, Mazak | +
| Stored in the Syntec parameter table (Pr4002, microns) — edited here in mm. | +SyntecParameterTable | +Syntec | +
| This brand takes the clearance per call (CYCLE83 / CYCL DEF); this value is the fallback used when a call omits it. | +FallbackConfig | +Siemens, Heidenhain | +
That third caption describes an override the mapping does not implement. The Siemens and Heidenhain +front-end syntaxes translate their own cycle vocabulary into the shared ISO cycle section, whose key +set is fixed at X, Y, Z, R, Q, F, P and K — and none of those eight is a retraction clearance. The +CYCLE83 mapping fills the R plane, the hole bottom Z and the first peck depth Q; the klartext cycle +mapping fills the same three plus a bottom dwell and a feed, and folds the klartext set-up clearance +into the R plane rather than keeping a slot of its own. The shared G83 expansion then reads the +clearance from this dependency unconditionally. So on those two brands the value here is not a +fallback for an omitted argument: it is the clearance every pecking cycle uses.
+An ICannedCycleConfig implementation outside those three answers a fourth source token, which no
+caption is written for: the field renders at the value that implementation supplies and the line
+under it is blank. It is the peck counterpart of the null a parameter table outside the four returns
+on the other leaf.
Which storage answered also decides where the value lives. On Fanuc, Mazak and Syntec it is a row of +the project's parameter table and appears in the native form; on Siemens and Heidenhain it is a field +of a plain runner-owned dependency, so it appears in no native form and a brand apply returns it +to the preset default — see Machine and Controller Plane.
+The value is consumed by two cycle expansions and they use it differently. In G83 it is the clearance +the tool rapids down to above the previous stroke bottom before feeding deeper; in G73 it is the +whole chip-break retract distance between strokes. The field's label names G83 only. G73 expands on +Fanuc, Mazak, Syntec and Siemens; the Heidenhain syntax list registers the G83 expansion without it.
+What the Values Reach
+Four of the six values are read by the parsing pipeline; the other two are stored declarations.
+-
+
Defaults a Freshly Seeded Table Opens On
+Each brand's default table decides what the fields show once that brand's table is cloned fresh into +a project; the guards are plain preset entries and are re-created at their type defaults on every +apply.
+| Value | +Fanuc, Mazak | +Syntec | +Siemens | +Heidenhain | +
|---|---|---|---|---|
| Max spindle speed | +60000 | +24000 | +60000 | +60000 | +
| Cutter compensation type | +Type A | +Type A | +— | +— | +
| Tool-axis direction | +— | +— | +— | +Z | +
| GOTO jump cap | +1000 | +1000 | +1000 | +— | +
| Loop cap | +10000 | +10000 | +10000 | +— | +
| Peck retraction clearance | +5 mm | +5 mm (5000 stored) | +5 mm (fallback) | +5 mm (fallback) | +
The table-backed values — the first three rows, and the peck clearance on the three brands whose +parameter table stores it — are reached only by an apply that leaves the project holding no table of +the incoming proxy's type: the outgoing brand's table is swept and the incoming proxy clones its seed +in place of it. Fanuc and Mazak share one table type, so the hop between those two re-binds the +project's existing table instead, and every value in it survives with its edits. The two guards and +the fallback clearance are runner-owned and return to the preset's on every apply, whichever brands +it runs between.
+The two caps differ by an order of magnitude on purpose: a jump is not the legitimate bulk-iteration +primitive, so its cap stays tight, while loops are what generated drill grids and calibration sweeps +are built from.
+Reading a Field Can Create Its Row
+Both readers reach the stored values through accessors that write a default into the table when the +row is absent. Opening either leaf on a table whose max-spindle, cutter-comp, tool-axis or peck +row was deleted from Parameters (Native) therefore +re-creates that row at the type's default, in the live project-owned table, with no edit and no +request from the user. On Heidenhain — which has neither a cutter-comp row nor a peck row — the +max-spindle and tool-axis rows are the whole set. The native leaf's delete removes the row; the next +read of this leaf puts it back.
+The guard dependencies have no such behaviour — their values are ordinary properties with field +initializers.
+Editing a Field
+Both leaves follow the branch's shared commit, rollback and toast rules, which are +Editing Contract. Four details are specific to these two +leaves:
+-
+
Layout
+-
+
Neither panel has a table, an add-row control, a delete control or a Save button.
+Source Code Path
+See HiNC App Anatomy for git repository links.
+Web Application
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
HiAPI Engine
+-
+
See Also
+-
+
Table of Contents
+ +M-Code Declarations
+ +M-Code Declarations is the leaf that gives a machine's own M-codes a meaning the parser can act on —
+what each machine-specific (OEM or PLC) code does — and it carries beside them the tool-change
+trigger mode that decides whether a bare T word changes the tool by itself. It lives on the
+General Setup page at /general-setup under the Control-Tree id
+equipment/controller/machine/m-codes, grown while the runner snapshot reports a brand controller
+parameter table. Both the declaration map and the trigger flag are stored on that one table, so this
+leaf appears and disappears together with
+Parameters (Native), and the rows declared on it travel
+with the project rather than with the runner file, which carries only the preset seed a fresh
+project's table is cloned from.
Important
+Anatomy makes no claim that any id named here is stable across versions. The migration hop that
+keeps an older ?tree= value resolving is described in
+Tree Ids and Routes.
The One Object Behind Both Halves
+ControllerParameterTableBase implements +IMCodeDeclarationConfig and +IToolChangeTriggerConfig over its own storage, so the table every +brand carries supplies the whole leaf: the declaration map keyed on the code as parsed, and the +single trigger boolean. A third face, ISpindleControlConfig, reads +the same map for spindle direction alone rather than keeping a second one, which is why one code is +never half-recognized by two separate dictionaries.
+The tree gate and the panel's own second empty layer probe the same type. The branch builder grows
+the node while the snapshot's hasNativeTable flag is set, and the read answers a presence flag
+computed from the same resolved parameter table; every shipped brand preset carries one, so in
+practice the leaf is unconditional and the "No controller parameter table on the active runner."
+line is reachable only on a runner assembled by hand or loaded from XML. Which brand satisfies which
+gate is Brand Matrix.
Because the map is a member of the brand parameter table, and that table is the project's per-case +copy rather than the runner's, declarations survive installing another runner file of the same brand +— see Machine and Controller Plane for which half of a project each +value on this plane is written into.
+What a Declaration Carries
+One row is one MCodeEffects, and a real OEM code is frequently +composite, so the row records every effect the code performs rather than a single meaning.
+| Column | +Effect | +What the code expands to | +
|---|---|---|
| Tool change | +IsToolChange | +M06 |
+
| Spindle | +SpindleDirection | +M03 for CW, M04 for CCW, M05 for STOP |
+
| Coolant | +CoolantMode | +M07 for Mist, M08 for Flood, M09 for Off |
+
| Not-simulated note | +UnmodeledNote | +nothing — it raises one informational diagnostic per occurrence | +
The two pickers each open on a dash, which is the null option: a declaration that touches neither
+the spindle nor the coolant carries neither. The note is free text and is itself an effect — a code
+whose real behaviour is a chip conveyor or a door interlock is declared with the note alone, and
+every occurrence then reports DeclaredMCode--UnmodeledEffects at message severity instead of the
+unknown-code warning. A declaration with no effect and no note is the fourth legal state and means
+consume this code silently, which the panel's hint says in as many words: "A declaration with
+nothing set consumes its code silently."
That fourth state is what a freshly declared row is. Declare writes a row whose tool-change box +is clear, whose two pickers sit on the dash and whose note is empty, so the act of naming a code +already silences it; the fields are then filled in against a live row.
+How a Declared Code Is Read
+MCodeExpansionSyntax rewrites the declared flag into the canonical +ISO flags the ordinary consumers already understand, and it runs ahead of them: +SpindleSpeedSyntax, +CoolantSyntax and +ToolChangeSyntax each then see the flag they know. Expanding once, +early, is what lets a single composite code feed several consumers without any of them fighting over +which removes the original flag.
+Three rules of the rewrite are visible in a program:
+-
+
The spindle-only row takes a different path
+A declaration whose sole content is a spindle direction is deliberately not expanded.
+IsSpindleDirectionOnly marks it, the expansion skips
+it, and SpindleSpeedSyntax resolves it in place through
+TryResolveDirection(API).
+What separates the two paths is mechanism, not reach. The resolver is consulted before the
+built-in ISO mapping, so a spindle-only row overrides a canonical code where the code stands, with
+no rewrite at all — declaring M03 as CCW makes M03 turn the spindle counter-clockwise. Add any
+second effect to the same row and it leaves that path for the expansion, which arrives at the same
+place by rewriting: the declared flag is replaced by the ISO flags its effects name, so declaring
+M08 as Mist makes the block emit M07 instead. On either path a declaration over a canonical code wins against that
+code's built-in meaning.
The split also reaches the project file. A spindle-only row serializes as the legacy
+<SpindleMCode> element so an older reader still resolves it; every other row serializes as
+<MCode> with one attribute per effect that is set. A row with nothing set writes an <MCode>
+element carrying only its code, so the silent-consume state round-trips.
What the writer refuses
+The declare endpoint validates before it stores. An empty code is answered unsuccessfully; so is a
+spindle direction that is not CW / CCW / STOP and a coolant mode that is not Mist / Flood /
+Off, and those two answers quote the rejected value back alongside the set that was expected. A
+typo therefore cannot degrade into a declaration that consumes a code silently. The panel's two
+pickers cannot produce such a value; the guard is there for the other writers of the same table.
A coolant mode that names no known mode can still arrive from a project file, because the XML reader
+keeps the attribute raw rather than normalizing it and write-then-read stays an identity. The
+expansion is where that surfaces: it reports DeclaredMCode--UnknownCoolantMode, skips the coolant
+half, and emits the rest of the declaration.
The Tool-Change Trigger Mode
+The toggle above the table reads "T word performs the tool change itself (turret / lathe)" and
+writes ToolWordTriggersChange. It defaults
+off, which is machining-centre behaviour: a T word only pre-selects, the magazine rotates without
+moving a feed axis, and the trigger M-code performs the change.
Turned on, ToolChangeSyntax treats a block carrying a T word and
+no tool-change flag as the change itself and records T as the triggering term. The test is per
+block: a block carrying both a T word and a tool-change flag is unaffected — the flag wins and
+M06 is recorded — so no single block is counted twice. Where the two words sit in different blocks
+— a T pre-select first, an M06 later — the mode counts the T block as a change on its own, and
+the M06 block then records a second one, its tool number arriving modally. The toggle therefore
+belongs on a machine whose T word really performs the change; on a machining centre it turns every
+pre-select into a change of its own.
On the Siemens machine-data table the flag is not a field of its own. That table binds the
+property to its tool-change-mode row,
+MdToolChangeMode: present, the row
+decides — a stored zero means the T word changes the tool — and the toggle writes back into it as
+0 or 1 rather than into the brand-neutral field; absent, the table behaves like every other brand.
+The write is skipped when the effective value already matches, so copying a table never rewrites a
+raw machine-data value of 2 down to 1. The same row is visible and editable by number on
+Parameters (Native), and the two forms are one storage.
The Rows
+The read returns the declarations sorted by code as text, case-insensitively, not by numeric
+value. M106 therefore sorts ahead of M12, and M6 lands after M331.
Keys are matched case-insensitively too, so m13 and M13 are one declaration and cannot both
+exist; re-declaring a code in a different spelling replaces its effects and leaves the spelling
+already stored on display. The Code cell is plain bold text with no editor — a code is changed
+by declaring the new one and deleting the old.
Only one brand's default parameter table pre-declares anything. The Siemens machine-data default
+seeds six auxiliary codes — M12, M13, M22, M23, M330 and M331 — each carrying no effect
+and the same note, “machine-specific auxiliary function (OEM/PLC); the exact behavior depends on
+the machine”. Those recur often enough in real Siemens programs to be worth silencing with an
+explanation rather than a warning, and a machine table that knows their actual effects overrides the
+seeded note. On the other four brands the table opens empty.
Editing, Adding and Removing
+Every cell commits on its own, and the write is the whole declaration: the handler merges the edited +field into the row and sends all four values, so an edit rewrites the row from what the panel is +currently showing. The checkbox and the two pickers commit on the click or the pick; the note cell +commits through its field's native change event, which is this branch's third text-commit wiring and +is recorded as such in Editing Contract — that page also +carries the optimistic-write, rollback and toast rules these cells share with the rest of the branch.
+The footer holds a single field labelled M-code, hinted M106, and a Declare button. Enter
+in that field submits, which no other add-row footer in the branch does. A blank code raises the
+toast "M-code must not be empty." locally and never reaches the server. A successful declare clears
+the field and re-reads the whole table, so the new row appears in sort position rather than at the
+end.
Removal is the trash button at the end of each row. It carries no confirmation dialog, and the row
+leaves the list only after the server has answered — the delete is not optimistic. The removed code
+returns to what it was before the declaration: Parsing--Unconsumed wherever a program uses it,
+unless a shared or brand syntax already knew the code, in which case it goes back to that built-in
+meaning.
What This Table Does Not Show
+The stored map is not the whole of what a machine honours, and three families of M-code behaviour +live outside it.
+The canonical ISO codes. M06, M03 / M04 / M05 and M07 / M08 / M09 are consumed by
+the shared syntaxes with no declaration at all; this table exists for the codes those consumers do
+not already know. Declaring a canonical code is still legal and still honoured — a note-only
+declaration on M08, for instance, consumes the raw M08 for its own declaration while a composite
+code's flood half continues to emit M08.
A brand's own M-function dialect. The Heidenhain preset's
+HeidenhainMFunctionSyntax owns M126 / M127
+(shortest-path rotary traverse) and M140 (tool-axis retract) as brand meanings, not as
+declarations, so none of them is a row here. That preset's syntax list places the expansion ahead
+of the brand syntax precisely so a machine that redefines one of those codes can say so: a
+declaration for M126 is expanded and consumed before the brand meaning is reached, and the
+declaration wins.
The Siemens tool-change M function. The Siemens machine-data table's
+MdToolChangeMCode row names the
+M function that performs a tool change, and the table overlays a tool-change declaration for it onto
+the view the parsers resolve — merged into a clone of any explicit declaration on the same code, so a
+note or coolant half is kept, and never written into the stored map. The panel reads the stored
+map, so that overlaid row is not on screen. A Siemens machine whose machine data names a
+non-standard trigger honours it in every program while this table lists nothing for it; the value
+lives on Parameters (Native) as the machine-data row, in
+the raw form an operator reads off the real controller. The overlay's own rules matter when the two
+views are compared: the raw number is zero-padded to the parsed flag form, so 6 becomes M06 and
+106 becomes M106; a value that is not a positive whole number overlays nothing; and a code already
+declared as a tool change here is left exactly as stored.
The trigger toggle above the table does not share that blind spot. It is read through the +property the Siemens table overrides, so it shows the machine data's answer, while the rows beside it +show only what was declared. The asymmetry is the single most useful thing to know about this leaf: +the mode is the effective value, the table is the stored one.
+One further consequence of the overlay is invisible in both views. Spindle-only resolution also runs +against the overlaid view, so a code that the machine data names as the tool-change trigger and that +is also declared here with a spindle direction alone stops being spindle-only: it leaves the +in-place resolver and is expanded instead, emitting both the tool change and the direction.
+Layout
+-
+
The panel mounts against one node and takes nothing from it: unlike the per-axis leaves it serves a +single tree id, so it reads no role off the node's path.
+Source Code Path
+See HiNC App Anatomy for git repository links.
+Web Application
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
HiAPI Engine
+-
+
See Also
+-
+
Table of Contents
+ +Native Parameters
+ +Parameters (Native) is the machine plane's escape hatch: the brand parameter table rendered as the
+flat numbered form an operator reads off the real control, with no domain vocabulary and no unit
+conversion in between. It lives on the General Setup page at /general-setup under the Control-Tree
+id equipment/controller/machine/native, and it is one of the two leaves the hasNativeTable
+snapshot flag grows — the other, M-Code Declarations, edits a different part of the same object.
+Every value the domain-grouped leaves beside it edit that is a row of the brand parameter table is
+a row here; the ones stored elsewhere — the tool-change pose, the block-skip layers, the subprogram
+folders and the indexing position lists among them — reach no native form at all. Several rows here
+reach no domain-grouped face.
Important
+Anatomy makes no claim that any id named here is stable across versions. The migration hop that
+keeps an older ?tree= value resolving is described in
+Tree Ids and Routes.
The leaf is pushed behind hasNativeTable, which reports whether any
+ControllerParameterTableBase resolves in the runner's effective
+dependency list. All five shipped presets carry one, so the flag is a gate in code and unconditional
+in practice — see Brand Matrix.
How a Parameter Is Addressed
+The table stores its numbers in three dictionaries, and the panel is those three dictionaries with a +heading over each:
+| Section heading the user reads | +Backing storage | +Cell | +
|---|---|---|
| System parameters | +SystemParams | +one value per id | +
| Axis parameters | +AxisParams | +one floating-point value per id per axis | +
| Axis parameters (integer) | +IntAxisParams | +one integer value per id per axis | +
An id is a bare number in storage. What the panel prints in front of it is the brand's prefix, and +that prefix is the whole of the addressing convention:
+| Brand marker | +Prefix shown | +Table that answers | +Example row | +
|---|---|---|---|
| Fanuc | +# |
+FanucParameterTable | +#1420 rapid traverse rate |
+
| Mazak | +# |
+the Fanuc table | +#1420 rapid traverse rate |
+
| Syntec | +Pr |
+SyntecParameterTable | +Pr1420 rapid traverse rate |
+
| Siemens | +MD |
+SiemensMachineDataTable | +MD32000 max axis velocity |
+
| Heidenhain | +MP |
+HeidenhainParameterTable | +MP1010 rapid traverse rate |
+
Mazak borrows the Fanuc table outright. The Mazak preset carries the Fanuc parameter-table proxy
+rather than one of its own, so the prefix resolves to # and every number on this leaf is a Fanuc
+parameter number under a Mazak brand badge. The prefix is not taken from the brand marker string
+but from the type of the parameter table that resolved, so the two cannot drift apart the way a
+brand-marker label can — see Brand Matrix.
A ControllerParameterTableBase subclass outside those four resolves an empty prefix, and the ids
+then render bare. No shipped preset produces that.
The prefix the panel renders comes from the leaf's own read of the parameter form. The shared +runner snapshot carries a prefix field of its own, and nothing renders it.
+The caption under the id
+Each id cell carries the number in bold and, beneath it, a short usage caption — Rapid traverse rate +(mm/min or deg/min), Axis type (0 linear / 1 rotary / 2 spindle), G54 X offset, Tool change M +function code. The same text repeats as the cell's hover title.
+The caption is the brand table's own description of a well-known number, produced by +DescribeSystemParam(API) +and its two per-axis counterparts, and it travels down the wire with the row. It is therefore +engine text, not an interface string: it is written in English in the brand table and belongs to +no locale bundle, so it reads the same under every language the application offers. An id the brand +table does not model has no caption at all, which is how a modelled row and a free extra are told +apart on screen.
+Raw Values, Raw Units
+The caption at the top of the panel states the rule: "Raw stored values in native units — no unit +conversion is applied here. The interface-form nodes edit the same backing table."
+No cell carries a unit suffix, and none can: the unit belongs to the number, not to the column. What +a cell means is whatever the brand stores at that address — millimetres or degrees on a stroke limit, +mm/min or deg/min on a rapid rate, rpm on a spindle limit, an enumeration ordinal on an axis type, a +count on a controlled-axis parameter.
+The peck clearance is the case where the two forms visibly disagree, and it ships. Canned Cycle
+(Peck) always speaks millimetres; the Fanuc-family table stores millimetres at #4002, and the
+Syntec table stores microns at Pr4002. A machine at a 5 mm clearance therefore reads 5 on
+Interface Parameters on every brand, and reads 5 here on Fanuc
+and Mazak but 5000 here on Syntec. The write endpoint converts on the way in; this leaf does not,
+which is the point of it — an operator copying from a real Syntec control reads the micron form.
Two further asymmetries follow from where a value is stored rather than from its unit:
+-
+
What Only This Leaf Reaches
+Most rows have a domain-grouped twin. These do not.
+The axis roster itself. Every per-axis leaf of the branch — Machine Limits (Stroke), Rapid
+Feedrates, Home / G28 Reference, Tool-Change Position — lists the axes named by the axis-type row
+of this table (#1006, Pr1006, MD30300, MP400), because
+AxisNames is that row's key set. None of
+those leaves can add an axis, rename one, remove one, or change one from linear to rotary; their
+columns are handed to them. Inside the Controller branch the axis-type row is editable here and
+nowhere else, through the footer with the section set to Axis (integer), the brand's axis-type
+number as the id, an axis name, and a value of 0 linear, 1 rotary or 2 spindle. The one other
+writer is outside the branch entirely:
+ConfigureByMachiningChain(API) stamps the machining
+chain's axis codes into the same row when a machine tool is attached to the project; an axis the
+chain does not name stays in the row. That call does not stop at the axis-type row: a chain axis it
+marks rotary also has its reference position set to 0 and its rapid rate to 36000 deg/min,
+whatever those two Axis parameters rows held.
The inverse holds, with one exception. Deleting the axis-type row leaves the table with no named +axes, so Machine Limits (Stroke), Rapid Feedrates and Home / G28 Reference have no rows left to +draw. Tool-Change Position keeps its own: its reader falls back to the tool-change configuration's +own axis keys when the axis set is empty, and every shipped preset seeds that configuration with X, +Y and Z. Rows recovered that way carry no axis type and are reported linear.
+The Siemens indexing machine data, all four numbers. MD30500, the per-axis assignment that
+decides which axis consumes which position table, is shown on
+Indexing Position Tables as display-only text and is
+written here as an Axis parameters (integer) row; the REST surface carries no assignment writer
+at all. The equidistant definition — the numerator MD30501 and the offset MD30503 as axis
+parameters, the denominator MD30502 as an integer axis parameter — has no endpoint that reads or
+writes it and no panel that offers a cell for it: on the web surface it is three ordinary rows on
+this leaf. The engine is its reader: an axis whose MD30500 is 3 resolves its station spacing from
+the trio, and Indexing Position Tables prints that axis' assignment as Equidistant
+(MD30501–MD30503), naming the three numbers it offers no cell for. The two position lists
+themselves are the mirror image, stored as list properties outside all three dictionaries and so
+absent from this form entirely.
Numbers with no interface field. The controlled-axis count (#1020, Pr1020) is read by nothing
+in the web service. The Siemens fixed-point position MD30600, the G75 target, likewise. So is the
+Siemens tool-change M function MD22560, whose presence overlays a tool-change effect onto the named
+code in the declaration view the parsers resolve against. M-Code Declarations renders the stored map
+rather than that view, so the overlaid row is on no screen at all: the effect is honoured in every
+program the machine runs, and the one number that shows anywhere is this row. The Siemens tool-change
+mode MD22550 is a half case: where the row already exists, the T word performs the tool change
+itself toggle on M-Code Declarations writes it, and where it does not, that toggle writes a
+brand-neutral flag instead; creating the row is possible only here.
Any number the model does not name. The three writers are get-or-create, so an id outside the +brand's well-known set is accepted, stored, serialized into the project file and read back. It +carries no usage caption, and the engine consumes only the numbers its brand table models — a free +extra is a record, not a setting.
+A Fractional Number Has Three Fates
+The Axis parameters (integer) section is the one place on the branch where a value can be typed, +accepted by the field, and silently discarded.
+The cell is the shared numeric widget, which commits on blur or Enter and carries no integer rule.
+Typing 2.5 parses cleanly: the widget clears its own error state, emits 2.5 to the panel, and
+rewrites its text to the parsed form. The panel's integer handler is where the value stops — it
+returns without assigning and without raising anything when the number is not whole. Because the
+bound value never changed, nothing pushes the stored integer back into the box. The cell goes on
+reading 2.5 over a table that still holds the old integer, with no error message, no toast and no
+console line. The row corrects itself only when the panel refetches: after an add, after a removal,
+or on a fresh mount when the selection leaves this leaf and comes back.
The footer does not behave the same way. Add / Set with Axis (integer) selected truncates a
+fractional value toward zero and writes the truncation, so the same 2.5 becomes a stored 2; and
+the same button refuses a fractional Parameter id outright, with the message "Enter a
+non-negative integer id and a value." That refusal belongs to the button, not to the box — the
+Parameter id field is the shared numeric widget with a minimum of 0 and no integer rule, so it
+accepts and keeps 2.5 the way any other cell does, and the one message answers an empty Value
+(raw) as well. Three controls, one number, three outcomes.
Clearing any cell ends the same way for a different reason. An emptied field commits null, and
+all three handlers return early on null, so the box is blank and the stored value is untouched.
+There is no way to unset one cell of a table; the value stands until it is overwritten or its whole
+row is removed. The general rule for a box that disagrees with its model is
+Numeric Input; what is particular here is that the widget did nothing
+wrong — the panel accepted the commit and dropped it.
Adding, Overwriting and Removing a Row
+The footer is one control set for both adding and overwriting, and the button says so: Add / Set. +The write is get-or-create at every level — an unknown id creates its row, an unknown axis name +creates its column — so setting an existing cell and creating a new one are the same request.
+-
+
Delete is per row, not per cell. The trailing button on a row of either per-axis table removes
+the id from that dictionary entirely — every axis column of it at once, not the cell it sits beside.
+A confirmation dialog comes first, titled Remove parameter and reading Remove <prefixed id> from
+the <section> section?, where the section word is the storage kind — system, axis, int-axis —
+rather than the heading printed above the table. The removal is not optimistic: the request is
+awaited and the whole form is re-read.
A deleted well-known row can come back on its own. The brand tables' modelled accessors are
+get-or-create with a default, so a read through a sibling leaf re-materializes the row it wants. The
+clearest case is the maximum spindle speed: delete #3741 here, open Interface Parameters, and that
+read stores the brand's default back — 60000 on Fanuc, Mazak, Siemens and Heidenhain, 24000 on
+Syntec — so the row reappears at that value. The cutter-compensation type, the tool-axis direction
+and the peck clearance behave the same way. A free extra id has no accessor and stays deleted.
What a Freshly Switched Brand Shows
+The seed each brand's proxy clones decides what the three tables open with. All five declare a linear +X, Y and Z in the axis-type row, a reference position of zero per axis and a rapid rate per axis, so +on every brand the Axis parameters section opens with two rows and the Axis parameters (integer) +section with one. The System section is where they part company.
+| Brand | +System parameters | +Axis parameters | +Axis parameters (integer) | +
|---|---|---|---|
| Fanuc, Mazak | +166 rows — controlled axes, max spindle speed, peck retraction, cutter-comp type, and 162 seeded work-offset addresses at zero | +reference position, rapid rate | +axis type | +
| Syntec | +166 rows — the same four under Pr numbering, the peck retraction in microns, and the same 162 offsets |
+reference position, rapid rate | +axis type | +
| Siemens | +1 row — max spindle speed | +reference position, max axis velocity | +axis type | +
| Heidenhain | +2 rows — max spindle speed, tool-axis direction | +reference position, rapid rate | +axis type | +
The 162 offset rows are +SeedAllDefaults(API) writing +every G54–G59 and G54.1 P1–P48 triad as zero, so that a managed address always has a value the way a +real control with a fresh battery reads zero rather than nothing. They dominate the System section on +the three brands that carry them, and their captions — G54 X offset, G54.1P12 Z offset — are what +separates them from machine configuration in the same list.
+No brand seeds a stroke limit, so the positive and negative stroke-limit rows are absent from the +Axis parameters section until Machine Limits (Stroke) or this leaf creates them.
+The Two Empty Layers, and Which One Is Reachable
+The panel opens through the branch's shared two-layer gate. The first layer is the shared "No NC +runner — load a project first." from the runner snapshot; the second is "No controller parameter +table on the active runner." from the leaf's own read — the same line M-Code Declarations and +Interface Parameters show, and honest in all three, because every one of those reads reports presence +from the same parameter-table lookup.
+Neither line is normally reached by navigating, because the node itself exists only while the
+snapshot reported a parameter table. The second line is also what a failed read leaves on screen: the
+presence flag starts false and the failure path only raises a toast. A failed write answers inside a
+success envelope — HTTP 200 carrying No ControllerParameterTableBase on the active runner — and
+surfaces as one negative toast, three seconds, the panel's localized context followed by that raw
+English sentence. The full rule is Editing Contract.
Layout
+-
+
The axis columns of the two per-axis tables are computed independently of each other, and each is the +union of the shared snapshot's axis names with every axis key present in that table's own rows. An +axis created here therefore gets its column from the rows even before the shared snapshot is next +refreshed, and an axis the snapshot knows about with no entry in a row shows an empty cell rather +than being omitted.
+Source Code Path
+See HiNC App Anatomy for git repository links.
+Web Application
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
HiAPI Engine
+-
+
See Also
+-
+
Table of Contents
+ +Per-Axis Tables
+ +Four leaves of the Controller branch's machine plane are one table each, keyed on the machine's
+axes: Machine Limits (Stroke), Rapid Feedrates, Home / G28 Reference and Tool-Change
+Position. They live on the General Setup page at /general-setup under the Control-Tree ids
+equipment/controller/machine/limits, equipment/controller/machine/rapid,
+equipment/controller/machine/home and equipment/controller/machine/tool-change; none of them is
+gated by a snapshot flag, so every brand grows all four. Two components serve them — one panel
+behind the first three, discriminating on the id's last segment, and a second behind Tool-Change
+Position — and the differences between those two components are what this page is mostly about.
Important
+Anatomy makes no claim that any id named here is stable across versions. The migration hop that
+keeps an older ?tree= value resolving is described in
+Tree Ids and Routes.
Where the Rows Come From
+Every row on all four leaves is one axis of the runner's machine-axis configuration, read as +AxisNames through the proxy-resolved dependency +list. On all five brand presets that configuration is the brand parameter table itself: +ControllerParameterTableBase declares +IMachineAxisConfig alongside +IStrokeLimitConfig, +IRapidFeedrateConfig and +IHomeMcConfig, so one object supplies both the row set and three of +the four value sets. Which brand carries which table is +Brand Matrix.
+The table's axis set is a single parameter row: the per-axis integer bucket under the brand's +axis-type number, whose keys are the axis names and whose values are the +AxisType — linear, rotary or spindle. Adding an axis therefore means +adding a key to that one row, and exactly one surface on this branch reaches it.
+-
+
Axes arrive from the machining chain. Whenever the active runner suit or the chain changes, the +project service re-binds the suit and calls +ConfigureByMachiningChain(API), which walks the +chain's axis codes and their transformers: a rotating transformer writes the axis as rotary — and, +on a brand parameter table, writes its reference position as 0 deg and its rapid rate as 36000 +deg/min — any other transformer writes the axis as linear, and every chain axis whose reference +position is still unset is seeded to 0. That is the mechanism behind the description above the +Home / G28 Reference table, "Axes without a value are seeded to 0 when a machine tool attaches."
+The stamp adds rows and never removes one. The generic axis configuration carried by the CSV and +CL runner suits is cleared before the walk; a brand parameter table is not, and keeps every axis it +already held. An axis dropped from the chain therefore keeps its rows on all four leaves, and no +surface removes it individually. The three-axis default table every brand preset seeds is why a +project with no machine tool attached still shows X, Y and Z.
+Two values are the exception, and only on a rotary chain axis of a brand table: that branch of +the walk is unconditional, so it rewrites the axis' reference position to 0 deg and its rapid rate +to 36000 deg/min whether or not one was stored. The walk re-runs on every suit re-bind, so an edited +Home Position or Rapid Rate on a rotary axis is back at those two numbers once the chain or the +active suit changes. A linear axis' values, and every value on an axis the chain does not carry, are +left untouched.
+The caption below the first three tables records the row half of that: "Axis rows follow the Machine +Tool chain; they cannot be added here." Where the chain itself is edited is +Machine Tool.
+The Value Columns
+Each leaf renders the axis column, its own value column or columns, and a unit column.
+| Leaf | +Value columns | +Empty cell means | +
|---|---|---|
| Machine Limits (Stroke) | ++ Limit, − Limit | +no limit set for that axis and side | +
| Rapid Feedrates | +Rapid Rate | +never empty — see below | +
| Home / G28 Reference | +Home Position | +no reference position stored | +
| Tool-Change Position | +Stays put, Position | +see Stays Put | +
The stroke-limit and home readers return an optional value and hand null straight through, so an +unset cell is blank. The rapid reader does not: it returns the stored rate, or — when the axis has +no rate of its own — the base class's fixed default, 20000 mm/min for a linear axis and 36000 +deg/min for a rotary one. So a Rapid Rate cell is always a number, and a number in it is not proof +that the axis has a row.
+A table cloned fresh from a brand's preset seed decides what the three leaves then open on, and all +four brand defaults agree in shape: a linear X, Y and Z, a reference position of 0 on each, and a +rapid rate per axis — and no stroke limit at all. Machine Limits therefore opens entirely blank on +every brand, while Rapid Feedrates and Home / G28 Reference open populated. Whether a brand switch +clones a fresh table or keeps the project's own is +Machine and Controller Plane.
+The Unit Column
+The unit column's header is the translated label Unit; its cell text is not translated and does +not come from the server. Each panel computes it in the browser from the row's rotary flag, using +literal strings:
+| Leaf | +Linear axis | +Rotary axis | +
|---|---|---|
| Machine Limits (Stroke) | +mm |
+deg |
+
| Rapid Feedrates | +mm/min |
+deg/min |
+
| Home / G28 Reference | +mm |
+deg |
+
| Tool-Change Position | +mm |
+deg |
+
The flag itself is server-side, and it is the axis type rather than a separate setting: an axis +stored as rotary or as spindle reports rotary, so a spindle-mode axis is labelled in degrees.
+The unit is a label, not a conversion. The base class writes a linear and a rotary rapid rate +into the same per-axis cell and reads them back through the same lookup, differing only in the +default returned when the cell is absent; the stroke-limit and home accessors are equally unit-blind. +Nothing on these three leaves converts, so their numbers are the raw stored numbers, and the same +values appear in Parameters (Native) under the brand's own +parameter number:
+| Role | +Fanuc, Mazak | +Syntec | +Siemens | +Heidenhain | +
|---|---|---|---|---|
| Axis type — the row set | +1006 | +1006 | +30300 | +400 | +
| Reference position (home) | +1240 | +1240 | +34010 | +410 | +
| Positive stroke limit | +1300 | +1300 | +36100 | +420 | +
| Negative stroke limit | +1320 | +1320 | +36110 | +430 | +
| Rapid rate | +1420 | +1420 | +32000 | +1010 | +
Mazak shares the Fanuc column because the Mazak preset carries the Fanuc parameter-table proxy; +Siemens spends its rapid-rate role on the max-axis-velocity machine datum.
+Tool-Change Position is the exception on both counts: its values live on +ToolingMcConfig, a plain runner-owned dependency rather than +a parameter table, so they appear in no native form at all and a brand apply returns them to the +preset defaults — see Machine and Controller Plane.
+Stays Put, and the Sentinel Behind It
+IToolingMcConfig stores one number per axis, and the contract gives +one number a second meaning: NaN means the axis stays where it is during a tool change. The read +splits that back into two fields — a stored NaN becomes the Stays put flag with no position, +anything else becomes the position — and the panel renders the flag as a checkbox and the position +as a numeric field disabled while the box is ticked.
+Writing goes the same way round. Ticking the box sends the stay flag and the reader writes NaN. +Unticking it sends a position, and because the panel clears its own cell when the box is ticked, the +position it sends on the way back is 0 — so unticking parks the axis at machine zero rather than +restoring what was there before.
+A third state exists and is not the sentinel: an axis with no entry in the map at all. Its box is +unticked and its Position cell blank, because the reader reports no position and no stay. At run +time the two are indistinguishable — the tool-change motion overlays only those axes that carry a +number and are not NaN onto the current pose, and leaves every other axis alone. So the machine +stays put either way; the tick mark distinguishes only how that was recorded.
+The overlay has a vocabulary of its own, narrower than the table. X, Y and Z always take part; every +other axis takes part only while the machine declares it rotary. A linear axis outside those three +is stored, shown and editable on this leaf, and never moved.
+The preset default is exactly this mixture: X and Y carry the sentinel, Z carries 0, and no rotary +axis carries an entry, which is why a rotary axis usually opens unticked and blank.
+Above the table sits Tool-change mechanism time, a single field in seconds bound to +ToolingTime. It becomes the duration of the tool +change step the runner emits, and it is the changer mechanism alone — the axis travel to and from +the position is timed separately from the positions in the table. It is 0 on every brand preset, and +it is written into the runner file only when non-zero.
+One Reader, Two Readings of Its Presence Flag
+All four reads answer the same shape: a present flag plus one row per axis. present reports
+whether the backing configuration resolved — stroke limit, rapid feedrate, home or tool change — and
+it is computed independently of the rows. Each reader builds the row list from the axis set
+whether or not that configuration resolved, and fills the value cells from an optional reference, so
+a missing configuration yields blank limit and home cells, a 0 rapid rate, and rows all the same.
The two components read the answer differently, and this is the sharpest divergence between them.
+-
+
What that costs: a runner that has axes but no stroke-limit, rapid-feedrate or home configuration +renders a full, editable table of blank or zero cells, and every commit fails with a toast naming the +missing dependency instead of the panel saying the table is absent. On the five shipped brand presets +the state cannot arise, because the one object that supplies the axis set supplies all three +configurations too. The reachable case is the mirror one, and it reaches the two panels differently: +deleting the axis-type parameter row from Parameters (Native) empties the axis set, and the three +axis-table leaves fall to the no-axes line while Tool-Change Position keeps its rows, because its +reader falls back to the tool-change configuration's own axis keys when the axis set is empty. Rows +recovered that way are reported as linear whatever they are.
+The tool-change flag itself is never false on a shipped preset: all five carry the tool-change +configuration as a plain entry, so its empty line is reachable only on a hand-built or file-loaded +runner.
+Above both layers sits the shared no-runner guard every leaf of the branch carries; it, and the +commit, rollback and toast rules the four leaves share with the rest of the branch, are +Editing Contract.
+Editing a Cell
+Numeric cells are the shared numeric field, so a value commits on blur or on Enter and never per +keystroke — its full contract is Numeric Input. The Stays put +checkbox commits on the click. Three details govern how these four leaves commit:
+-
+
What the Values Do When a Program Plays
+The stroke limits are checked at each played step against the chain's current machine pose while the +session's stroke-limit check is on; a position past a configured limit is reported as a +stroke-limit validation error anchored to that step, and pauses the player when pause-on-failure is +set. The check walks a fixed axis vocabulary — X, Y and Z from the machine point, A, B and C +from its orientation — so a limit stored against any other axis name is kept and shown here but never +tested. Rapid-traverse timing has the same shape and the same six names: a G00 move is timed +axis-by-axis at each axis' rate and takes the slowest, falling back to the same 20000 mm/min and +36000 deg/min defaults when no configuration answers.
+The reference positions reach further, but only at the start. The initializer that sets the machine +pose at the first block writes every declared axis, reading each axis' stored reference and +falling back to 0 for one that has none. The G28 reference return reads the same store through a +narrower window: X, Y and Z for its linear stage, and A, B or C for its rotary stage — and a rotary +letter only while the machine declares that axis rotary and a reference position is stored for it, +an unconfigured one being reported as a validation error instead. So an axis outside the six +canonical names still carries its stored reference into the pose at the first block, while its +stroke limit, its rapid rate and its G28 return are all inert.
+Layout
+-
+
Source Code Path
+See HiNC App Anatomy for git repository links.
+Web Application
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
HiAPI Engine
+-
+
See Also
+-
+
Table of Contents
+ +Program Reading
+ +Program Reading is the pair of Machine / Controller leaves that change how the runner reads a
+program rather than how the machine moves: which block-skip layers are active, and where a
+subprogram call goes looking for its file. Both live on the General Setup page at /general-setup,
+under the Control-Tree ids equipment/controller/machine/block-skip and
+equipment/controller/machine/subprograms. The pair is also unevenly branded — every shipped brand
+preset carries the subprogram-folder config, and one of the five carries no block-skip config at
+all.
Important
+Anatomy makes no claim that any id named here is stable across versions. The migration hop that
+keeps an older ?tree= value resolving is described in
+Tree Ids and Routes.
The Asymmetry
+Both nodes are grown behind a plain presence probe over the runner's proxy-resolved dependency
+list: hasBlockSkip is set when the list holds any
+IBlockSkipConfig, hasSubprogramFolders when it holds a
+SubProgramFolderConfig. Neither probe inspects a value.
| Brand preset | +Block-skip config | +Subprogram-folder config | +
|---|---|---|
| Fanuc | +yes | +yes | +
| Siemens | +yes | +yes | +
| Syntec | +yes | +yes | +
| Mazak | +yes | +yes | +
| Heidenhain | +no | +yes | +
GenericBlockSkipConfig is the only implementer of the +block-skip interface anywhere in the engine, and the Heidenhain preset's pipeline list is the one of +the five that does not carry it. That single omission is what makes Block Skip / Delete the one node +in the whole Controller branch that exists on four brands and not the fifth; every other gate on +this plane is satisfied either by all five presets or by a Siemens-only table. Which flag each row +answers to is Brand Matrix.
+The config is not the only thing missing there. The block-skip switches gate a parsing syntax, +BlockSkipSyntax, and that syntax appears in the Fanuc, Siemens, +Syntec and Mazak syntax lists and in no other — the Heidenhain list carries no block-delete parser, +so on that preset the nine switches would have nothing to gate.
+The subprogram-folder config runs the other way: all five presets carry one, and five call +statements across three brand families read the same two values. The node's own vocabulary is +Fanuc's, because the storage is, but the setting is not Fanuc-only.
+Block Skip / Delete
+The panel is a caption over nine checkboxes and nothing else. The caption reads "Enabled layers
+skip their /n-prefixed blocks (/ alone = layer 1).", the first box is labelled "Layer 1
+(bare /)" and the rest “Layer 2” through “Layer 9”.
What a layer does to a block
+BlockSkipSyntax matches a leading / optionally followed by a
+single digit 1–9; a bare slash is layer 1. It then always writes a
+BlockSkip section recording the symbol and the layer, and always
+consumes the prefix out of the block's unparsed text — so the / itself never survives as residue
+for the downstream unrecognized-text check, whatever the switches say.
What the switch decides is the block's body:
+-
+
Ordering matters for one thing and is fixed in each brand's list: the comment and NC-embedded +script syntaxes run before the block-skip syntax, so a comment on a skipped block — and any +script embedded in it — still takes effect while the motion beside it does not.
+One value, seven spellings of the same range
+Layers 1 through 9 are the whole range, and no shared constant carries it. The bound is written out +separately in the parsing syntax's regular expression, in the config's XML reader and again in its +CSV setter, in its per-layer mutator guard, in the read endpoint that enumerates the layers, in the +write endpoint that loops over them, and in the panel's checkbox repeat. A tenth layer is therefore +seven edits, not one, and a partial change would be silent — a switch the panel offered but the +config refused to store would simply never take.
+What a fully cleared set does not survive
+A freshly applied preset has layer 1 on and the other eight off. The stored form is a +comma-separated list of the enabled layers, and an empty set serializes as an empty element. The +reader treats a blank list and a missing element identically — both mean layer 1 only — so +clearing every box and then reloading the project brings layer 1 back on. Every other combination +round-trips exactly.
+The write
+Each box commits on the click, and the request is the whole enabled set rather than the one +layer that changed: the panel sorts its set and sends it, and the endpoint walks 1 through 9 setting +each layer to whether the payload names it. A failed write restores the previous set and raises a +toast; the shared rules behind that shape are +Editing Contract.
+On the brand that carries no config, the read answers not present with an empty layer list and any +write is answered unsuccessfully with “No IBlockSkipConfig on the active runner” — but the node is +not built there, so neither answer is reachable from the tree.
+Subprogram Folders
+Two plain text fields, labelled "Internal folder (M98)" and "External folder (M198)", hinted +“Relative to the host NC file's folder; empty = that folder itself” and “Fanuc external-storage +calls; empty = fall back to the internal folder”. They write +InternalFolder and +ExternalFolder; an empty or +whitespace-only box is stored as null rather than as an empty string.
+Which call statements read these two values
+| Preset | +Call statement | +Field it reads | +File-name chain, first match wins | +
|---|---|---|---|
| Fanuc, Mazak, Syntec | +M98 P_ L_ |
+Internal | +O{P:D4}.NC, O{P}.NC, O{P:D4}, O{P}, {P:D4}.NC, {P}.NC |
+
| Fanuc, Mazak, Syntec | +M198 P_ |
+External, falling back to Internal when it is null | +the same chain | +
| Fanuc | +G65 / G66 macro call |
+Internal | +the same chain | +
| Siemens | +name call (L9810, a named cycle) |
+Internal | +{name}.SPF, {name}.MPF, {name} |
+
| Heidenhain | +CALL PGM name |
+Internal | +{name}.h, {name}.H, {name} |
+
Two consequences follow from the table, and both are invisible from the field labels.
+The External folder is inert on two brands. Only the Fanuc-family inliner,
+SubProgramCallSyntax, ever reads it — the Siemens and
+Heidenhain call syntaxes look up their callee through the Internal folder alone. The field is
+offered on every brand because the storage is brand-neutral, and it is honoured on the three that
+have an M198.
The Internal folder is read by more than the M98 label says. On the Fanuc preset it also anchors
+both Custom Macro B lookups — FanucMacroCallSyntax for
+the G65 one-shot call and FanucModalMacroSyntax for
+the G66 modal — and on Siemens and Heidenhain it is the only subprogram root there is. Emptying
+it to fix an M98 path therefore moves the macro and named-call lookups with it.
Each brand also brings its own file-name chain, and the chains are per-syntax properties rather than
+one shared list — the Fanuc chain is
+FilenamePatterns, a static array every
+Fanuc-family caller shares, while
+FilePatterns and
+FilePatterns are
+instance properties of their own call syntaxes and are re-writable from the runner file. Case
+matching is delegated to the host file system throughout, which is why the Heidenhain chain carries
+both .h and .H.
Where a relative folder is anchored
+The resolver takes the folder as written. An absolute path is used unchanged. A relative path — and +an empty one, which means the root itself — is combined with the base directory supplied by +ProjectFolderDependency, and the machining session wires +that dependency to the project's own root before each play.
+So the anchor is the project folder, not the folder the host NC file happens to sit in. A program
+played from a subfolder of the project resolves NC against the project root, not against its own
+neighbour of that name. The field's hint says otherwise, and so does the API summary on the two
+config properties the panel writes; the resolver is what runs.
Two further conditions end the lookup before any pattern is tried: a relative folder with no base +directory resolves to nothing, and so does a folder that does not exist as a directory.
+A missing file is an error on one family and a warning on the other
+The same empty result is reported at two different severities, and the split follows the call +statement rather than the setting.
+-
+
A resolved call is inlined: the callee is segmented through the runner's own segmenter and its
+blocks are prepended into the program ahead of the host block, each stamped with a record naming the
+call it came from. M98 with an L count above one inlines the same file that many times in
+series, each repetition its own segmentation pass.
The nesting ceiling, and where there is none
+A self- or mutually-recursive callee re-captures its own call statement inside every inlined body, +so a call path needs a rail of its own — no loop watchdog covers it. That rail is declared twice, +independently, and is missing on the third path.
+| Preset | +Ceiling | +Where it is declared | +Exceeded | +
|---|---|---|---|
| Siemens | +32 frames | +DefaultMaxCallDepth | +SiemensCall--DepthLimitExceeded, consumed as a safe skip |
+
| Heidenhain | +32 frames | +DefaultMaxCallDepth | +HeidenhainCall--DepthLimitExceeded, consumed as a safe skip |
+
| Fanuc, Mazak, Syntec | +none | +— | +— | +
The two constants are separate const int declarations on separate classes that happen to hold the
+same number, each surfaced as its own writable MaxCallDepth property and each serialized into the
+runner file as its own element. Raising one raises nothing else. Both compare against the depth of
+the call stack already stamped on the host block, so the count is frames entered, not files listed.
On the Fanuc-family inliner and on both Custom Macro B calls there is no depth comparison at all. +None of them reads the host block's frame count before inlining, so none carries the recursion rail +the Siemens and Heidenhain syntaxes declare. The depth itself is recorded either way — every inlined +block is stamped with a pushed call-stack frame that the matching return statement pops — so what is +missing is the comparison, not the count.
+The Heidenhain call syntax carries a second, unrelated ceiling for its section-repeat form:
+MaxRepetitions caps
+a REP literal at 65534, since the repeat re-scans the file once per repetition.
What an empty Internal folder does not survive
+The config's default internal folder is NC — the layout where the main program sits beside an
+NC/ directory of subprograms — and that default is applied by the property itself, before the XML
+reader runs. The writer omits an element for a null folder, and the reader keeps whatever the
+property already holds when the element is absent. Clearing the Internal folder box therefore
+stores a null, writes no element, and reads back as NC the next time the project is opened.
The External folder box has no such default and round-trips cleanly: cleared, it stores null, +writes no element, and reads back null — which is the state the field's own hint describes, falling +back to the internal folder.
+Editing and Storage
+Both panels open with the branch's two empty layers — the shared "No NC runner — load a project +first." line first, then the panel's own read reporting "No block-skip config on the active +runner." or "No subprogram-folder config on the active runner." The first layer is a live guard +against the snapshot emptying under a panel that is already mounted, which is what closing the +project does. The second is unreachable by navigating: the node's gate and the panel's read probe +the same dependency on the same resolved list, so the leaf exists only where the read is about to +succeed.
+Their commit shapes differ, and both are recorded in +Editing Contract: the block-skip boxes commit on the click +and roll back on failure, while the two folder fields commit as a pair on blur or Enter of +either one, compared against a saved snapshot of both rather than a per-field captured value — so +editing one folder writes both, and a failure restores both.
+Both configs are plain runner-owned entries rather than proxies, so their values serialize with the
+runner and follow it. Installing a controller file replaces them outright, and a brand apply assigns
+a whole fresh preset, so an applied brand change returns both to that preset's defaults — layer 1
+alone, and the NC internal folder with no external one. Selecting the brand already in force is not
+a route to that reset: the apply stays disabled until the selection differs from the runner's own
+brand. Which half of a project each value on this plane lands in is
+Machine and Controller Plane.
Layout
+-
+
Every label above is a translated role string rather than a type name, so a locale change rewrites +what is read without touching an id. Both panels mount against one node and take nothing from it: +each serves a single tree id, so neither reads a role off the node's path.
+Source Code Path
+See HiNC App Anatomy for git repository links.
+Web Application
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
HiAPI Engine
+-
+
See Also
+-
+
Table of Contents
+ +Datum Tables
+ +Datum Tables is the pair of Heidenhain per-case ledgers the Controller branch grows together: the
+preset rows a CYCL DEF 247 cycle selects, and the datum shift rows a CYCL DEF 7 cycle adds on top
+of the selected preset. Both live on the General Setup page at /general-setup under the
+Control-Tree ids equipment/controller/program-data/datum-presets and
+equipment/controller/program-data/datum-shifts — reached as
+?tree=equipment/controller/program-data/datum-presets and
+?tree=equipment/controller/program-data/datum-shifts — and the tree labels them
+Datum Presets (Q339) and Datum Shifts (D). One snapshot flag grows the pair, one component
+serves both nodes, and one of the two tables is also the object the Work Coordinates leaf edits.
Important
+Anatomy makes no claim that any id named here is stable across versions. The migration hop that
+keeps an older ?tree= value resolving is described in
+Tree Ids and Routes.
Two Nodes, One Component
+The branch builder pushes both nodes inside a single test on the snapshot's datum flag, so the two +rows appear and disappear as a pair and never one without the other. That flag probes +HeidenhainDatumTable over the runner's proxy-resolved dependency +list, and only the Heidenhain preset carries it — which brand grows which leaf is +Brand Matrix.
+Both nodes carry the same item type, so the editor row mounts the same component for either
+selection. The component takes its role from the last segment of the node id it is given: an id
+ending in datum-shifts makes it the shift editor, and anything else makes it the preset editor.
+The test is a suffix match with a preset default rather than an equality check over two known ids, so
+the preset role is what an unrecognised id falls to.
Four things follow from that role, and nothing else in the panel differs between the two nodes.
+| Decided by the role | +Datum Presets (Q339) | +Datum Shifts (D) | +
|---|---|---|
| Which array of the one read is rendered | +the preset rows | +the shift rows | +
| The index column's header | +Q339 |
+D |
+
| The description caption above the table | +"Datum presets (CYCL DEF 247, Q339 = row). Rows 1–6 double as G54–G59." | +"Datum shifts (CYCL DEF 7)." | +
| The route segment its writes address | +preset |
+shift |
+
One request serves both nodes. The read returns a presence flag together with both arrays, and +the panel keeps one and discards the other. Selecting the sibling node changes the node id, which +changes the editor row's remount key, so the component remounts and re-fetches the same payload for +the other half. Reading the pair therefore costs two identical requests, and neither node can show a +value the other's request had already returned.
+The preset and shift segments are the only two the panel ever sends; the endpoint's rejection of
+a third is one of the departures recorded in
+Editing Contract.
What Each Table Holds
+Both tables are the same shape — an integer row number against an X / Y / Z translation — and both +are seeded with rows 1 through 20 at zero when the table is constructed. What separates them is which +klartext cycle reads which, and how the value is applied.
+-
+
The two stores sit on one dependency and are two dictionaries of it, which is why one flag grows two +nodes and one request answers for both.
+Neither store is a parameter table. HeidenhainDatumTable does not +derive from the controller parameter base every brand's machine settings hang off, and the Heidenhain +preset carries it as an entry of its own beside the machine-parameter entry. So no datum row has a +native parameter number, and none of these values appears in +Native Parameters — unlike the Fanuc and Syntec work +coordinates, which are parameter addresses and do.
+Where the table is stored
+The Heidenhain preset does not carry a datum table. It carries +HeidenhainDatumTableProxy, a get-or-create placeholder that installs +a bare new table into the project's per-case list when that list holds none of the type, and resolves +to the project's own table when it does. The proxy holds no seed and serializes as an empty element, +so a controller file saved from the branch root carries none of these values, and installing a +Heidenhain runner file over a Heidenhain project leaves the rows already edited exactly where they +are. The plane's storage model in full is Program Data Plane.
+The Columns and the Axis Set
+Each leaf renders one dense markup table: the row-number column, three value columns, and an +unlabelled action column.
+-
+
The axis set is fixed at three and does not follow the machine. A datum row is a three-component
+translation on both sides of the wire, so the columns are X, Y and Z whatever the machining chain
+declares — a fourth or rotary axis has no column here and no stored datum component. That is the
+opposite of the branch's Per-Axis Tables, whose rows are the
+chain's own axis names. The mm in the header is a label: nothing on the path from the stored
+translation to the cell converts anything.
Rows are whatever the read returned, in row-number order. A table built by the proxy opens with +twenty rows of zeros in each node; a table deserialized from a project file carries the rows that +file holds.
+Editing a Row
+Every value cell is the shared numeric field, so it commits on blur or on Enter and never per +keystroke; its parsing, its bounds behaviour and the second commit that follows Enter are +Numeric Input. No cell here passes a minimum or a maximum, so a negative +offset is accepted and stored.
+The write is the whole row. A commit assigns the new number into the local row and then sends +that row's three components as they stand to the row's number; a failure restores the one component +the handler captured and raises the branch's standard toast, though the request carried all three. +That shape, the toast it ends in and what a failed write leaves behind are +Editing Contract.
+A cleared cell parses to null, and Infinity or NaN parse to a non-finite number; the handler
+returns before the request in every one of those cases. Nothing is written and nothing is restored,
+so the cell is left blank — or showing the literal Infinity — while the stored number stands
+unchanged, until the panel is remounted by selecting another node and coming back.
The action button is labelled with the single character 0 and carries the tooltip “Reset to
+zero”. It writes zeros into all three components of that row through a route of its own rather than
+through the row setter, and it is not a delete: the row stays, at zero. It is also the one write on
+these two leaves that is not optimistic — the request resolves first, and only then are the three
+local cells set to zero. No confirmation guards it.
Neither leaf has an add control, a delete control, a draft or a save button, and the component +declares no events at all, so no edit made here rebuilds the branch or marks anything dirty.
+The Preset Table Is Also the Work-Coordinate Table
+This is the pairing with Work Coordinates, and it holds for +exactly one of the two tables.
+HeidenhainDatumTable implements +IIsoCoordinateConfig, and the work-coordinate endpoint reads and +writes through the first implementer of that interface in the runner's proxy-resolved list. On the +Heidenhain preset the datum proxy is the second entry of the dependency list, immediately after the +brand marker, and no other entry that preset carries implements the interface — the Heidenhain +machine-parameter table does not. So on Heidenhain the object behind Work Coordinates is this datum +table, and the snapshot's coordinate-kind field reports the Heidenhain arm for that same reason.
+The alias is narrow, and one-directional in coverage.
+-
+
The two faces are never on screen together — the editor row mounts one panel at a time — and each +panel reads once on mount, so moving the selection between the two nodes is itself the refresh. What +a mounted panel therefore does not see is +Editing Contract.
+What a brand switch does to the pair
+The brand switch's carry option reads the outgoing runner's coordinate provider and writes the values +into the incoming one, for the ids the incoming provider enumerates. On this table that means at most +six values, landing in preset rows 1 through 6; a switch away from Heidenhain reads those same six +rows out. No shift row is ever carried, and neither are preset rows 7 through 20.
+A switch away from Heidenhain then sweeps the datum table itself, because no other brand preset +proxies it — both stores go, and switching back materializes a fresh table of zeros rather than +restoring them. The staged select, its confirmation and the rest of that operation are +Brand Switch.
+What the Rows Feed
+At run time both stores are read and neither is written. No syntax in the parse pipeline writes +either dictionary — every writer is one of the four surfaces named below — so playing a program +cannot change a datum row.
+The preset path. A CYCL DEF 247 block resolves its Q339 number — a literal, or the number a
+Q-expression evaluated to — looks the row up, and writes a synthetic coordinate id naming that row
+alongside the translation. The shared ISO coordinate syntax then keeps the offset alive on following
+blocks by re-resolving that synthetic id against the same table, which is how a mid-program edit to a
+row takes effect on the blocks after the declaration. A CYCL DEF 247 whose Q339 is not a number
+resolves nothing, leaves the active datum unchanged, and reports the validation warning "CYCL DEF
+247 without a literal Q339 preset number; the active datum is kept unchanged."
A successful preset selection clears the active shift. That is the one path in the cycle handler +that does not carry the previous block's shift forward.
+The shift path. A CYCL DEF 7 block carrying a # index reads that shift row and composes it as
+a second, separate transform entry, so preset and shift add rather than replace. On blocks with no
+cycle the shift is carried modally, and a numbered shift re-resolves from the table on every block,
+exactly as the preset does. A CYCL DEF 7 written with direct values carries them forward verbatim
+instead and needs no datum table at all; cancelling is a zero shift. A CYCL DEF 7 carrying neither
+form reports "CYCL DEF 7 carries neither a #-table index nor direct X/Y/Z values; the active datum
+shift is kept unchanged."
A G54 with axis words is not a table read. On the Heidenhain syntax list the datum-shift parser
+claims a G54 that is followed by at least one axis value and routes it into the direct-shift flow,
+which reads no row; a bare G54 is left alone and reaches the ISO path, where it resolves preset row
-
+
A row number that resolves to nothing yields a zero translation rather than an error, on both paths.
+And nothing is active at the first block: the Heidenhain preset's initializer seeds no coordinate
+section at all, unlike the Fanuc family's G54 and the Siemens cancel frame, so a program that
+issues no datum cycle and no G54 machines with no offset applied.
Where Non-Zero Rows Come From
+Four surfaces put a value into these tables, and only the first is on this page.
+-
+
The superseded /controller route also carries datum preset and datum shift surfaces, over the
+separate environment model rather than over the runner — an edit made there does not reach the table
+on this page. That route's own anatomy is Legacy Controller Page.
Layout
+-
+
The panel carries no heading, no toolbar, no add row, no delete button, no toggle, no save button and +no dialog.
+Source Code Path
+See HiNC App Anatomy for git repository links.
+Web Application
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
HiAPI Engine
+-
+
See Also
+-
+
Table of Contents
+ +Frames
+ +Frames is the Controller branch's Sinumerik settable-frame ledger: one row per $P_UIFR entry,
+each carrying the X / Y / Z translation HiNC stores for that frame. It lives on the General Setup
+page at /general-setup under the Control-Tree id equipment/controller/program-data/frames,
+reached as ?tree=equipment/controller/program-data/frames, and it is grown only while the active
+runner resolves a Siemens frame table — among the shipped brand presets, Siemens alone. That same
+table is the offset provider behind the ungated Work Coordinates (G54…) leaf on this brand, so
+the two nodes edit one object.
Important
+Anatomy makes no claim that any id named here is stable across versions. The migration hop that
+keeps an older ?tree= value resolving is described in
+Tree Ids and Routes.
What a Frame Is Here
+On a Sinumerik, $P_UIFR is an array of frames and each entry carries a translation, a rotation, a
+scale and a mirror. HiNC consumes the translation and nothing else, so
+SiemensFrameTable stores one Vec3d per frame
+id in Frames, with a second,
+axis-letter-keyed dictionary beside it for translation components on axes other than X, Y and Z.
The table is a dependency in its own right rather than a section of the machine data, because
+$P_UIFR is not machine data on the control either:
+SiemensMachineDataTable holds the MD-numbered parameters
+— axis type, reference position, stroke limits — and carries no frame. That split is also why the
+frame table is the brand's ISO offset provider while the machine-data table is not.
The array index is not the row key. Rows are keyed by the G-code that selects the frame, and the
+bridge that serves $P_UIFR accesses maps one onto the other:
$P_UIFR index |
+Frame id | +Row on this leaf | +
|---|---|---|
0 |
+G500 |
+none — the cancel frame is never stored | +
1–4 |
+G54–G57 |
+the four base rows | +
5–99 |
+G505–G599 |
+the extended tail | +
An index outside 0–99 maps to no id at all, and the bridge drops the access rather than
+inventing a frame for it.
Settable, and computed
+Four parts of the Sinumerik frame chain show up in this branch's behaviour, and only one of them is +a row here.
+-
+
How the Chain Composes
+A frame word in a program selects one id. The Siemens coordinate syntax resolves that id's offset +through GetEffectiveNcDependencyList(API), taking +the first provider that answers, and writes the result twice: as the block's coordinate-offset +section, and as one named entry in the block's program-to-machine transform chain. Entries in that +chain are multiplied in the order they were written, so slot order is the whole of the composition +rule.
+Two placements in the Siemens syntax list decide what a settable frame ends up doing.
+-
+
Three codes bypass the composed transform for one block rather than changing it: G53, G153 and
+SUPA all reduce to the same one-shot machine-coordinate move, and the values on this leaf take no
+part in such a block.
The word vocabulary is also wider than the table. The ISO syntax's own series adds G58, G59 and
+G59.1–G59.9, none of which the frame table allocates; such a word resolves to no offset from any
+provider and composes a zero translation.
The Rows and the Columns
+The panel is a four-column table and nothing else — no toolbar, no add footer, no action column, no
+dialog. The Frame header is translated; X (mm), Y (mm) and Z (mm) are literals in the
+template and stay English under any locale. The id cell is bold plain text and cannot be edited.
The row set is fixed. The branch exposes exactly two frame routes, a whole-table read and a +per-id write; no third route adds, deletes or resets a row. The write itself is an unguarded +assignment by key rather than an update of an existing row, so an id the table has never held would +be seated as a new one — but no panel has a way to send such an id. A row exists because the table +allocated it, and the ninety-nine allocated ids are the same ninety-nine whatever a project has done +to their values.
+Nothing in the path sorts. The reader projects the dictionary as it enumerates it and applies no
+ordering, unlike the datum, retained-variable, R-parameter and tool-offset readers beside it, and the
+panel adds none of its own. What that yields depends on how the table was built. A table the proxy
+has just created lists G54–G57 first and then the extended tail, in construction order. A table
+read back from a project file lists the ids in the order the file names them — and the file is
+written sorted by id as text, not as a number. Plain string order puts G505 ahead of G54, because
+the third character decides that pair, and G54 ahead of G540, because the shorter of two strings
+that share a prefix comes first. So a saved and reopened table does not push the four base frames to
+either end: it interleaves them into the extended tail, as G505–G539, G54, G540–G549,
+G55, G550–G559, G56, G560–G569, G57, then G570–G599. Work Coordinates enumerates
+the same dictionary, so both leaves reorder together.
Show all
+A Show all toggle sits at the right of the header strip. With it off, a row is listed only when
+its id matches G54 through G57, or when at least one of its three values is non-zero; with it on,
+every allocated id is listed. The test is the panel's own regular expression, evaluated in the
+browser over the rows the read returned — nothing about it reaches the server, and the toggle is a
+plain local flag that returns to off whenever the panel is remounted.
Two details of that rule are worth reading against the sibling leaf. The toggle here is
+unconditional, so it renders even on a table with no extended rows at all. And the always-visible
+test here is narrower than the work-coordinate one, which admits G54 through G59 and any of them
+followed by a dot and one further digit. On the seeded table the two tests keep the same four rows
+visible, because the frame table allocates none of the ids where they differ.
Two Nodes, One Table
+Warning
+On Siemens this leaf and Work Coordinates (G54…) are two faces of one instance, not two tables
+kept in step. The work-coordinate leaf resolves the first
+IIsoCoordinateConfig on the active runner; on this brand that is
+the frame table, because it is the only dependency the Siemens preset carries that implements that
+interface — the machine-data table included. A G54 edit made on either node is the same
+assignment into the same cell.
This is the reciprocal half of the warning Work Coordinates +carries. What the two leaves share, exactly:
+-
+
What is not shared, and what a reader must not infer from the shared face:
+-
+
Nothing reconciles the two views while both are in scope, and nothing needs to: the editor row mounts +one panel at a time and each fetches once on mount, so moving the selection between the two nodes is +itself the refresh. What a panel that stays mounted therefore never sees is described in +Editing Contract.
+Editing a Frame
+Every value cell is the shared numeric field, so it commits on blur or on Enter and never per +keystroke; its parsing, its bounds behaviour and the second commit that follows Enter are +Numeric Input. No cell here passes a minimum or a maximum, and no cell +carries a unit suffix — the millimetre is stated in the column header instead.
+The write is the whole row. A commit assigns the new number into the local row and sends that
+row's x, y and z as they now stand under the row's id. The optimistic shape, the single-cell
+rollback and the toast a failure raises are the branch's own and are described in the
+Editing Contract; what is this panel's is the guard in
+front of them. The numeric field can emit an empty value as null and the literals Infinity,
+-Infinity and NaN as non-finite numbers, and the handler returns before the request on all four.
+So a cleared cell stores nothing and clears nothing: the box shows blank while the stored number
+stands, and the number returns when the panel is remounted by selecting another node and coming back.
No surface here renames a frame: the id is the write's route key and no route accepts a replacement +for it. The panel reports no structural change either, so no edit on this leaf rebuilds the branch.
+What Survives, and What a Brand Change Destroys
+The table is per-case: SiemensFrameTableProxy stands in the
+runner's pipeline list and the real table lives on the project's
+PerCaseNcDependencyList, serialized inside the project file. That
+proxy carries no seed — it installs a freshly constructed table when the project holds none and
+takes the project's own table thereafter — so the runner file records only the placeholder, and a
+.Controller written from this project carries no frame values at all. The ownership rules the
+proxy pattern follows across the branch are Program Data.
Two consequences follow for the values on this leaf.
+-
+
What a Run Writes Back
+A played program does not only read this table; it writes into it, through a bridge with two +halves.
+-
+
The table is deliberately not session-resettable. Settable frames are setting data on a real
+control: a $P_UIFR write survives reset and power-off, so a replayed session must see what the
+previous run left. The runner clears every session-resettable dependency on the fresh-session edge
+and this table is not among them, which means a value a run wrote is what this panel shows
+afterwards, and what the project file keeps.
Which frame a program starts on is not stored in the table either. The Siemens preset's static
+initializer seeds the first block's coordinate id as G500, so an untagged Siemens program begins
+with no frame active and a zero offset until a frame word appears — unlike the Fanuc-family and
+Syntec presets, which begin on G54.
Layout
+-
+
The panel carries no heading, no add or delete control, no reset button, no save button and no +unsaved marker.
+Source Code Path
+See HiNC App Anatomy for git repository links.
+Web Application
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
HiAPI Engine
+-
+
See Also
+-
+
Table of Contents
+ +Program Data Plane
+ +Program Data is the second of the Controller branch's two plane stems: the coordinate, offset and
+variable tables that belong to the workpiece rather than to the control, edited one leaf at a time.
+It lives on the General Setup page at /general-setup under the Control-Tree id
+equipment/controller/program-data, and each of its leaves takes that id plus one segment —
+work-coordinates, tool-offsets, siemens-tool-offsets, tool-names, datum-presets,
+datum-shifts, frames, retained-variables, r-parameters. The stem is not gated: it is part of
+the branch builder's return value rather than a conditional entry, so it appears wherever a runner
+resolves and always carries the two leaves no flag guards.
Important
+Anatomy makes no claim that any id named here is stable across versions. The migration hop that
+keeps an older ?tree= value resolving is described in
+Tree Ids and Routes.
Selecting the stem shows the shared group editor: its own introduction over a clickable list of its +children. The introduction reads "Data that travels with the workpiece / project — work-coordinate +offsets (G54…), tool offsets and the brand-specific per-case tables. Kept on the project even when +the runner file is swapped."
+Every leaf below follows the branch's shared editing rules — one read on mount, per-control commit, +capture-assign-await-restore on failure, a dense markup table with no sort and no pagination, and the +two empty layers. Those rules, and the places this plane departs from them, are +Editing Contract.
+Where the Data Actually Lives
+The stem's introduction names a storage location, and here — unlike the machine stem's own +introduction — it holds for every leaf. What it does not say is that the location is not a file.
+One list, inlined in the project
+NcRunnerSuit carries the runner beside the project's own +PerCaseNcDependencyList, and serializes the two side by side. The +runner half can be written to a side file; the per-case half never is — the suit writes it as a +nested element with no file reference of its own, so it lands inside whatever file the owning project +writes. Every table this plane edits is an entry of that one list.
+No panel and no endpoint reads or writes a table by reaching into that list by name. The one place +that addresses the list directly is the sweep that follows a runner swap, and it removes whole +entries rather than editing any value in one. A brand preset's pipeline list carries an +INcDependencyProxy in place of each of these tables, and every read +and write resolves through +GetEffectiveNcDependencyList(API), which substitutes +the project's table for the placeholder. That is also what the snapshot flags probe, so the tree +grows a leaf for the concrete table and never for the proxy — +Brand Matrix.
+The proxies on this plane carry nothing
+ToolOffsetTableProxy and its siblings — the Siemens $TC_DP,
+frame and R-parameter proxies, the Heidenhain datum proxy and the Fanuc-family retained common
+variable proxy — are get-or-create placeholders with no seed. Each serializes an empty element, and
+on wiring installs a bare new instance into the per-case list when the project holds none of that
+type; an existing table is left untouched.
Two consequences follow. A controller file written from the branch root's Object-Management menu +carries none of the values edited on this plane: the seedless proxies write an empty element, and the +one proxy on this plane that does carry a seed — the brand parameter-table proxy standing behind the +work coordinates on the Fanuc family and Syntec — serializes the fixed seed it was constructed with +rather than the table the panels have been editing. And a table this plane loses is not recoverable +from a preset: a fresh instance is what the proxy makes.
+Which store a work coordinate lands in
+Work Coordinates is the one leaf whose backing object changes with the brand. The panel reads and +writes the first IIsoCoordinateConfig in the effective list, and the +caption above its table names which one answered.
+| Brand | +Backing object | +Ids the table lists | +Caption above the table | +
|---|---|---|---|
| Fanuc, Mazak | +FanucParameterTable | +G54–G59 and G54.1P1–G54.1P48 | +"Stored in the Fanuc parameter table (#5221+ / #7001+)." | +
| Syntec | +SyntecParameterTable | +G54–G59 and G54.1P1–G54.1P48 | +"Stored in the Syntec parameter table (Pr5221+ / Pr7001+)." | +
| Siemens | +SiemensFrameTable | +G54–G57 and G505–G599 | +"Stored as Siemens settable frames ($P_UIFR; G500 cancels and is always zero)." | +
| Heidenhain | +HeidenhainDatumTable | +G54–G59 | +"G54–G59 map onto Heidenhain datum preset rows 1–6." | +
An id appears only where the store actually holds it. A Fanuc-family or Syntec table yields an id +whose parameter address is present, and every one of those default tables seeds all of them at zero; +the Siemens frame table seeds G54–G57 and the extended series, and has no G58 or G59 row at all; the +Heidenhain table yields one id per preset row 1–6 that exists. G500 is never a row — the frame table +refuses to store it and does not seed it.
+That caption is the panel's only statement of where a value went, and it is selected from the +snapshot's work-coordinate kind rather than from the brand marker.
+The one setting that is not in the list
+The Tool Offsets leaf's Set ideal offset dependent on tool house toggle is not a dependency. It +writes IsIdealOffsetDependentOnToolHouse, an +element the project serializes beside the runner suit. It therefore stays with the project like +everything else on this plane, but by a different route — and, being outside the per-case list, it is +untouched by the sweep that follows a runner swap.
+How This Differs From the Machine Plane
+The machine plane's ownership is split. Several of its leaves are plain instances the runner owns +outright — the tool-change position, the block-skip layers, the subprogram folders, and on Siemens +and Heidenhain the peck clearance — and an install replaces them with the incoming runner's; the +rest are rows of the brand parameter table and are stored on the project. One leaf holds both +halves: Controller Parameters, where the macro loop guards are runner-owned fields sitting beside +table rows. Which side of that line a machine-plane field falls on is +Machine and Controller Plane.
+This plane has no such split. Nothing on it is a plain runner-owned instance: every leaf resolves a +per-case table through a proxy, so every value it edits is stored on the project. That is what makes +the two introductions' phrasing load-bearing rather than decorative — and it is why installing a +controller file of the brand already in force changes nothing here at all: each of that brand's +proxies takes the table the project already holds rather than installing a fresh one over it. The +same install does return the machine plane's runner-owned settings to the incoming file's values.
+The one crossing runs the other way. On Fanuc, Mazak and Syntec the work coordinates are rows of the +same brand parameter table whose other rows the machine plane's per-axis leaves and native parameter +form edit. A single object straddles both stems there, which is exactly why that table is per-case at +all: the two planes could not otherwise be separated.
+The Plane's Leaves
+Ordered as the branch builder pushes them: the two ungated leaves first, then each gated leaf or +gated pair behind the snapshot flag that grows it. Which brands satisfy each flag is +Brand Matrix; the column that page does not carry is the last +one here.
+| Node segment | +Label the tree shows | +Gate | +Backing table | +
|---|---|---|---|
work-coordinates |
+Work Coordinates (G54…) | +ungated | +the brand's ISO coordinate provider, per the table above | +
tool-offsets |
+Tool Offsets, read as Tool Offsets (ISO G43 H) where the Siemens table resolves | +ungated; label switched by hasSiemensToolOffsets |
+ToolOffsetTable | +
siemens-tool-offsets |
+Tool Offsets ($TC_DP) | +hasSiemensToolOffsets |
+SiemensToolOffsetTable, its cutting-edge map | +
tool-names |
+Tool Names | +hasSiemensToolOffsets |
+SiemensToolOffsetTable, its name map | +
datum-presets |
+Datum Presets (Q339) | +hasDatums |
+HeidenhainDatumTable, its preset rows | +
datum-shifts |
+Datum Shifts (D) | +hasDatums |
+HeidenhainDatumTable, its shift rows | +
frames |
+Frames (Siemens) | +hasFrames |
+SiemensFrameTable | +
retained-variables |
+Retained Common Variables | +hasRetainedVariables |
+RetainedCommonVariableTable | +
r-parameters |
+R Parameters | +hasRParameters |
+SiemensRParameterTable | +
Every label above is a translated role string rather than a type name, so the tree renders the +translation and a locale change rewrites the rows without touching an id.
+Two leaves stand on every brand: Work Coordinates (G54…) and Tool Offsets. They are the builder's +unconditional entries, and every brand preset satisfies them — each carries an ISO coordinate +provider of its own, and all five proxy the generic tool-offset table. The remaining leaves are +pushed inside a flag test, and each of those flags is satisfied by one brand family only.
+Fewer tables than leaves
+Two flags grow two leaves each, because two leaves edit two halves of one object. Tool Offsets +($TC_DP) and Tool Names are the cutting-edge map and the name map of a single Siemens tool-offset +table, which is why there is no separate tool-name flag and why the two nodes appear and disappear +together. Datum Presets (Q339) and Datum Shifts (D) are the preset rows and the shift rows of a +single Heidenhain datum table, served by one component that reads its role from the node id's last +segment.
+The work coordinates then fold two more leaves together, on the two brands where the ISO provider is +a table this plane already lists. On Siemens, Work Coordinates and Frames (Siemens) are two views of +one frame dictionary — the same ids, the same X/Y/Z translations, edited through two panels. On +Heidenhain, the G54–G59 rows of Work Coordinates are preset rows 1–6 of Datum Presets (Q339), so +editing one changes what the other shows. Datum Shifts has no such alias, and the Siemens frame +table's per-axis translation components beyond X/Y/Z are reachable from neither panel.
+The two tool-offset ledgers disagree on the sign of wear
+Where both are present the difference matters more than the labels suggest. On the generic table an
+effective value is the ideal minus the wear; on the Siemens $TC_DP table it is the geometry plus
+the wear. That is why the Siemens panel spells the addition out in a footnote below its table, and
+says in its own description that a shortened tool is entered as negative wear, while the generic
+panel states no sign rule at all. The relabel of the ungated leaf to Tool Offsets (ISO G43 H)
+exists so that the two are not read as one ledger.
What a Brand Change Does Here
+A brand switch replaces the whole runner with the target brand's preset and then removes every +per-case table the new runner references through no proxy. This plane is where that sweep lands: the +machine plane's project-owned parameter table is one entry, and every other entry the sweep can reach +is a table edited here. The staged select, its confirmation and the rest of the control are +Controller Brand; what follows is the outcome for this plane +alone.
+-
+
The banner shown before the apply says as much in one line: "Switching brand replaces the whole +runner with the {brand} preset. Machine settings reset to that preset's defaults and the old brand's +program-data tables are removed — switching back does not restore them."
+An Object-Management Load, Paste or XML apply runs the same install and the same sweep, so a +controller file of another brand takes this plane through the identical outcome without the carry +option. Both writes are refused while an NC program is playing.
+Layout
+-
+
The stem's own editor holds no field, so nothing on this plane is edited from the group row itself.
+Source Code Path
+See HiNC App Anatomy for git repository links.
+Web Application
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
HiAPI Engine
+-
+
Pages
+Ordered by the first node each page owns, as the plane lists them.
+-
+
See Also
+-
+
Table of Contents
+ +Persistent Variables
+ +Persistent Variables is the pair of program-variable ledgers the Controller branch grows for one
+brand or the other, and no shipped preset carries both: the Fanuc-style retained common variables
+and the Sinumerik R parameters. Both live on the General Setup page at /general-setup under the
+Control-Tree ids equipment/controller/program-data/retained-variables and
+equipment/controller/program-data/r-parameters — reached as
+?tree=equipment/controller/program-data/retained-variables and
+?tree=equipment/controller/program-data/r-parameters — and the tree labels them
+Retained Common Variables and R Parameters. They share a page because no preset shows both:
+each node is grown by a flag that one set of brand presets satisfies and the other set does not.
Important
+Anatomy makes no claim that any id named here is stable across versions. The migration hop that
+keeps an older ?tree= value resolving is described in
+Tree Ids and Routes.
Two Nodes That Exclude Each Other
+The branch builder pushes each of the two nodes inside a test of its own, one snapshot flag each, and +each flag is a single type probe over the runner's proxy-resolved dependency list — +RetainedCommonVariableTable behind one and +SiemensRParameterTable behind the other. Which brand +satisfies which gate is Brand Matrix; the short answer is that +the Fanuc, Syntec and Mazak presets carry a retained-common-variable proxy, the Siemens preset carries +an R-parameter proxy, and the Heidenhain preset carries neither.
+The exclusion is a property of the five preset lists, not a rule in the builder. Nothing in the +tree code forbids the two rows from standing together; no shipped preset simply carries both entries, +and the brand select can produce nothing but a preset. A runner composed by hand or loaded from a +controller file whose pipeline list named both tables would grow both rows, each with its own working +panel — and the Object-Management menu on the branch root, described in +Controller Branch, installs such a file without inspecting what its +pipeline names, so that route is open from inside the branch.
+The two panels are separate components rather than one component discriminating on the node id, so +neither reads the node it was given. What they share is a shell: the same three-state body, the same +dense table, the same footer, and the same commit, rollback and toast rules the whole branch follows +— Editing Contract.
+What Each Ledger Is
+Retained Common Variables
+RetainedCommonVariableTable models the Fanuc-style ISO
+controller's common variables in the retained range #500–#999 — the range a real control keeps in
+non-volatile memory across a power cycle, which is why the model stores it with the project rather
+than with the run. It is the range, not the whole macro-variable surface: the table answers a #nnn
+lookup only for 500 through 999 and returns nothing for any other key, so an out-of-range reference
+falls through to the next lookup in the evaluator's chain.
The neighbouring ranges are deliberately elsewhere, and none of them has a node on this branch.
+#1–#33 are call-frame locals bounded by a macro call rather than by a power cycle. #100–#499
+are the non-retained commons, carried block to block in the per-block dataflow and cleared on the
+block that ends the program. #1000 and above are read-only or computed from runtime state rather
+than stored here: some resolve against other dependencies — the brand parameter table behind the
+work-coordinate addresses, the tool offset table behind #2001–#2200 — and some come from the
+block's own record, which is where the position variables are read and where a system-control write
+is recorded rather than emulated.
The type name and the tree label both say common variable rather than macro variable, because the +model reserves “macro” for Custom Macro B's call frame. The hint above the table is the one string in +the leaf that says Retained macro variables instead.
+R Parameters
+SiemensRParameterTable models the Sinumerik arithmetic
+variable surface R0–R999 — the R63=100.5 and C=R61 of a Siemens program. On the control these
+sit in retentive memory and survive both program end and a power cycle, and the model keeps them the
+same way: stored with the project, not reset with the session.
The table answers an Rn key in the canonical uppercase form the Siemens expression parser produces,
+and a lowercase rn from a raw capture resolves as well. Every other key returns nothing.
Addressing and Ranges
+| + | Retained Common Variables | +R Parameters | +
|---|---|---|
| Node id | +equipment/controller/program-data/retained-variables |
+equipment/controller/program-data/r-parameters |
+
| Snapshot flag | +hasRetainedVariables |
+hasRParameters |
+
| Backing table | +RetainedCommonVariableTable | +SiemensRParameterTable | +
| Brands | +Fanuc, Syntec, Mazak | +Siemens | +
| Key column header | +Variable | +Parameter | +
| Key cell reads | +# then the number |
+R then the number |
+
| Accepted ids | +500–999 | +0–999 | +
| Bounds on the add field | +minimum 500, maximum 999 | +minimum 0, maximum 999 | +
| Absent-table body | +"No retained common variable table on the active runner." | +"No R-parameter table on the active runner." | +
Each range is a pair of constants declared on the table type, and four separate places enforce it. +The panel refuses an add whose id is not a whole number inside the range and says so in a toast of its +own. The numeric field carrying that id rejects a finite value outside its bounds before the panel +ever sees it — the infinity and NaN literals the field also parses skip the bounds test entirely and +are stopped by the panel's whole-number check instead. The write endpoint compares the id against the +same two constants and answers unsuccessfully with the range spelled out in the message. And behind +all three, the table's own writer ignores an out-of-range id — the guard that holds for a caller +reaching the table directly rather than through the endpoint, which is how a run writes.
+The endpoint's guard is not reachable from a project this app wrote. The rows a panel writes to came +from the table, the add path is already filtered, and the serializer drops an out-of-range key on the +way out, so no file the app saves carries one. Loading applies no such filter: a project file edited +by hand can carry an id outside the range, the read lists it and the panel shows it, and editing that +row's value is the one way a reader meets the endpoint's range message.
+No range is narrowed by machine data. The R range in particular is the fixed pair 0–999 on the +table type; the endpoint reads no value from the Siemens machine-data table before accepting an id, +so the parameter count a real control would allocate does not constrain what this leaf accepts.
+Neither table is dense. A row exists for an id that has been written, and for no other, so a +thirty-row table and a four-row table are equally normal and the numbers in the key column need not be +contiguous. A project that has never written one shows an empty table body under the header, because +the proxy that materialises the table installs a bare instance carrying nothing — +Program Data Plane covers the seedless proxies this plane is +built from. The read returns the rows in ascending id order and the panel never re-sorts: an edit +leaves a row where it is, a delete removes it in place, and only the re-read that follows an add +re-orders anything.
+What Persists, and What Clears
+Both tables are per-case entries of the project's own dependency list, reached through a get-or-create +placeholder in the runner's pipeline list. The runner file records only an empty placeholder element, +so the values are written into the project file rather than into a controller asset.
+Four things in the runtime could plausibly discard what these tables hold. Three of them leave both +tables alone; the fourth removes a table outright rather than clearing it.
+-
+
A run writes into the same table
+These are not a snapshot of what a program did — they are the store the program uses. Each brand's
+syntax list carries a reading syntax that consumes a literal assignment out of the parsed block and
+writes it straight into the table: #500 = 1.234 on the three Fanuc-style brands, R63 = 100.5 on
+Siemens. No mirror is kept in the per-block dataflow; the table is the single source of truth. A
+non-literal right-hand side is resolved to a literal earlier in the same block by the expression
+normaliser and then lands by the same route, so #600 = #500 + 1 reaches the table as well.
Reads run the other way through the same object: each table is a variable lookup on the effective +dependency list, so an expression naming an id in range reads whatever the leaf shows.
+Two consequences follow for a reader of this leaf. A played program changes what the table holds, and +because no panel in the branch is pushed at, the change appears only when the panel is remounted by +selecting another node and coming back. And because the table is written into the project file, those +run-time writes are what the next load of that project starts from — once the project is saved. +Nothing in the branch and nothing on the run path saves on its own: a panel edit and a run-time write +alike stand in memory until an explicit save, and closing the project without one discards them.
+Vacant is a value, and it fails loud
+A stored null is a vacant entry, and so is a missing key — the table reads the two identically.
+Vacant is not zero. An expression that evaluates a vacant id does not fall back to a number: the
+evaluator returns a failure carrying the code Variable--Vacant and a message naming the key, which
+is what the R-parameter hint means by reporting an error instead of silently using 0.
Vacant and deleted are different operations even though they read the same at run time. Clearing a +cell keeps the key with a null value; the delete button drops the key. The distinction survives a save, +because a vacant entry is written as an element carrying an id and no value while a deleted one is +written as nothing at all — so a vacant row comes back as a row, and a deleted one does not come back.
+The Editor Body
+Above the table sits a one-line hint, and it is the only prose either panel shows. Both render the
+literal text <vacant> inside it as inline code.
-
+
The table below it has three columns. The first is the key — headed Variable or Parameter — +rendered bold as plain text and not editable, which is the branch's rule for a key column. The second +is headed Value and holds the shared numeric field. The third carries the row's delete button +under a blank header, so the branch's shared Actions heading does not appear on either leaf.
+Editing a value
+A value cell is the shared numeric widget, so it commits on blur or on Enter and never per keystroke; +its parsing, its bound behaviour and the double commit an Enter can cause are +Numeric Input. No value cell is bounded — neither panel passes a minimum +or a maximum to it — so any finite number is accepted and stored, negatives included.
+These two panels are the branch's only pair that commits a null. Every other numeric handler in +the branch treats a cleared cell as no edit and returns before the request; here the null is sent, +because vacating an entry is a real state of both tables and there is no other control that reaches +it. Clearing the cell is therefore how a row is made vacant while keeping its key.
+The handler is otherwise the branch's ordinary optimistic write — assign, send, and on failure put the
+captured value back and raise the toast — and it carries no finiteness test. The widget parses
+Infinity and -Infinity as values, and neither survives JSON encoding: the request body reaches the
+server carrying a null, and the entry is set vacant. The cell keeps showing Infinity until the panel
+is remounted, so that is the one place in the table body where the screen and the store disagree
+after a write the server accepted. A typed NaN takes the same route to vacant, and the cell renders
+it as empty, which is what a vacant entry looks like anyway.
Adding and removing a row
+The footer under the table is the branch's fielded add form: a numeric field for the id — labelled +Variable # (500–999) or Parameter # (0–999) — a second numeric field labelled Value, and a +primary Add / Set button. Both fields are the same numeric widget as the value cells, so what they +hold reaches the panel on blur or Enter rather than per keystroke, and neither input submits on Enter.
+The button label's second word is the accurate one: the write is an upsert. Entering an id the table +already lists overwrites that row's value rather than adding a second row. Leaving Value empty +creates the row vacant. A successful add is followed by a full re-read of the table, which is what +puts the new row in ascending order; the two fields keep what was typed in them.
+Two refusals sit in front of that write, and they read differently. An id outside the field's own +bounds is rejected inside the widget, which shows its message under the box, emits nothing and leaves +the rejected text on screen — so the panel still holds the id the field last committed to it. On a +panel that has committed none, that is nothing at all, and pressing Add / Set reports the panel's +own message: "Variable number must be an integer in 500–999." or "Parameter number must be an +integer in 0–999." The panel's message is also what a fractional id produces, since the panel +requires a whole number and the field does not.
+A field that has already committed a valid id keeps it, and nothing clears either field after an add. +So an out-of-range id typed over one the field already accepted is the second place on these leaves +where the screen and the store part company: the box shows the rejected number, the panel still holds +the earlier id, and Add / Set upserts that earlier id with whatever the value field holds — no +message of any kind, and the re-read that follows leaves the rejected number in the box.
+Removal is one icon button per row, with no label, no tooltip and no confirmation dialog: the click +sends the delete, and the row is filtered out of the panel's list once the request resolves. The branch +guards some of its deletions with a confirmation dialog; neither of these two leaves is among them. +The endpoint removes the key and reports success whether or not the key was there.
+What the Surface Covers
+A third table of the same shape exists in the engine and has no node.
+HeidenhainQParameterTable holds the Heidenhain persistent
+Q parameters — the free range Q0–Q99 and the permanent QR0–QR499, in two stores of its own —
+and the Heidenhain preset carries a proxy for it beside the two documented above. It is materialised
+into the project's per-case list exactly as they are, is serialized into the project file the same way,
+is swept by a brand switch away from Heidenhain the same way, and is read and written during a run by
+the Heidenhain reading syntax that routes Qn and QRn assignments by id range.
What it has no part in is this branch. The snapshot the tree is built from declares no flag that +probes it, the branch's REST surface declares no route that reaches it, and the branch builder mints +no node for it. The Controller branch's editable persistent-variable surface is the two tables named +above; a Heidenhain project's Q parameters are set and read by the program that runs, and are carried +by the project file between runs.
+Layout
+-
+
Neither panel opens a dialog, and neither carries a select, a toggle or a Show all control.
+Source Code Path
+See HiNC App Anatomy for git repository links.
+Web Application
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
HiAPI Engine
+-
+
See Also
+-
+
Table of Contents
+ +Siemens Tool Offsets
+ +Siemens Tool Offsets is the Sinumerik $TC_DP ledger — one offset row per cutting edge of a tool,
+addressed by the tool number and the edge number together — plus the tool-name map that resolves a
+string tool call to a tool number. It occupies two leaves of the Controller branch on the General
+Setup page at /general-setup, under the Control-Tree ids
+equipment/controller/program-data/siemens-tool-offsets and
+equipment/controller/program-data/tool-names, and those two ids are the whole of what this page
+owns. Both are grown by one snapshot flag over one backing object, which is why one page carries
+them.
Important
+Anatomy makes no claim that any id named here is stable across versions. The migration hop that
+keeps an older ?tree= value resolving is described in
+Tree Ids and Routes.
Both panels follow the branch's shared rules — one read on mount, per-control commit, the +capture-assign-await-restore write, the two empty layers, and a dense markup table with no sort and +no pagination. Those rules, and the places these two panels depart from them, are +Editing Contract. What is these leaves' own is everything +below.
+Two Nodes Over One Table
+The tree labels differ, the panels differ, and the object behind them does not.
+| Node id | +Tree label | +Which half of the object | +
|---|---|---|
equipment/controller/program-data/siemens-tool-offsets |
+Tool Offsets ($TC_DP) | +Edges — the offset rows | +
equipment/controller/program-data/tool-names |
+Tool Names | +ToolNames — the name map | +
The branch builder pushes both inside a single test of the snapshot's Siemens tool-offset flag, so +they appear and disappear together and there is no separate tool-name flag anywhere in the snapshot. +The flag itself is a type probe for SiemensToolOffsetTable +over the proxy-resolved dependency list, and the Siemens preset is the only shipped brand list that +carries the proxy for it — Brand Matrix.
+The same flag does one more thing: it relabels the ungated generic offset leaf one row above from +Tool Offsets to Tool Offsets (ISO G43 H), without touching that leaf's id. The two ledgers +are present together on a Siemens project and an unqualified label would blur them into one — see +Tool Offsets.
+Each panel reads only its own half. The $TC_DP panel reads and writes rows; the Tool Names panel
+reads and writes name mappings; neither reports the other's edits, and neither re-reads when the
+other writes. The presence flag both receive is the same one — whether the table object resolved —
+so the Tool Names panel's absent-table line, "No Siemens tool-name table on the active runner.",
+names a table that is not a type of its own.
The Key Has Two Parts
+ISiemensToolOffsetConfig addresses an offset by a tool
+number and a cutting edge number together, where
+IToolOffsetConfig — the contract behind the generic ledger — takes a
+single integer. That is the structural difference between the two ledgers, and it is why one tool can
+carry several rows: a tool with more than one usable edge stores an offset per edge, and the program
+selects among them with the D word after the tool is mounted.
The pair is the dictionary key, and both halves are rendered as bold plain text with a hard-coded
+letter prefix — T on the tool cell and D on the edge cell. Neither is editable and no rename
+route exists on either half of the object, so a key cannot be corrected in place. Neither can a
+chosen one be asked for: the $TC_DP add accepts no target pair — it mints the next tool, or the
+next edge of the tool it is given — so arriving at a particular pair means growing the table up to it
+and deleting what was stepped through. Only the tool-name half takes a typed key. That same
+server-side minting is why nothing on this leaf can create a duplicate.
An unconfigured pair is not an error. The interface contract returns 0 for a pair the table has no +row for, and the try-form is the only reliable miss signal, because 0 is itself a legal stored +offset — a distinction that decides the fallback below.
+The Columns
+Ten labelled columns: the two key columns, then eight value columns in millimetres over the four +geometry components and their four wear counterparts.
+| Column header | +$TC_DP index |
+Stored as | +Read by the pipeline | +
|---|---|---|---|
| Tool T | +— | +the key's tool number | +the tool the offsets belong to | +
| Edge D | +— | +the key's edge number | +the D word |
+
| Length 1 (Z) | +3 | +Length1_mm | +the height path | +
| Length 2 (X) | +4 | +Length2_mm | +nothing | +
| Length 3 (Y) | +5 | +Length3_mm | +nothing | +
| Radius | +6 | +Radius_mm | +nothing | +
| Length 1 Wear | +12 | +WearLength1_mm | +the height path | +
| Length 2 Wear | +13 | +WearLength2_mm | +nothing | +
| Length 3 Wear | +14 | +WearLength3_mm | +nothing | +
| Radius Wear | +15 | +WearRadius_mm | +nothing | +
The table declares accessors for all three length components and for the radius, and the shipped +pipeline calls exactly one of them: the try-form that returns length 1 plus its wear. The radius +accessor and the direction-indexed length accessor are called by nothing in it. Radius compensation +reads the generic ledger's radius by a single offset number, so a radius entered here changes +nothing that plays. Length 2 and Length 3 are stored, serialized and handed back unchanged, and no +consumer reads them.
+The axis letters in the three length headers are part of the header string and not a binding. Length +1 is the component the height path consumes whatever axis the machine calls it; the other two are +indexed positionally by an accessor nothing calls.
+There is no unit column and no conversion — every field is a plain millimetre double, written and +read back as typed. None of the eight numeric cells passes a bound, so a negative value is accepted +and stored, which the sign convention below makes load-bearing rather than incidental.
+The fields the row carries but does not interpret
+A row also holds VerbatimDpFields, a
+bag keyed by $TC_DP index for every index outside the eight above. Nothing interprets those values
+and no cell edits them; they exist so that a full controller dump survives a load and a save
+unchanged. The panel shows only their count, as a small grey +N beside the row's action buttons
+with a tooltip explaining that the fields are stored and saved with the project and not used by the
+simulation. The row write sends the whole row object including that count, and the endpoint assigns
+only the eight doubles, so the bag survives every edit made here.
The two halves stay separate on both sides of serialization. Writing an index into the row routes a
+consumed index onto its typed property and every other index into the bag, and the serializer filters
+the consumed indices back out of the bag before writing, so an index cannot end up stored twice and
+shadow its own typed value on the next load. The element the table writes carries one Edge entry
+per key with the eight components as named attributes and the bag as DP-indexed ones, and one
+ToolName entry per mapping; the reader accepts both spellings of a component, so a DP-indexed
+geometry value lands on the typed property rather than in the bag.
The Sign Convention
+On this table an effective value is the geometry plus the wear. The height the pipeline reads is +length 1 added to length-1 wear, and the radius and per-direction accessors add their wear the same +way. A positive wear therefore lengthens the compensation, and a tool that has worn shorter is +recorded as a negative wear value. The panel states the addition twice: the description above the +table ends "Wear adds onto geometry (enter a shortened tool as negative wear).", and the footnote +below it reads "All values in mm. Effective length 1 = Length 1 (Z) + Length 1 Wear; same for the +other columns."
+Warning
+The generic ledger one node above uses the opposite convention. On +ToolOffsetTable an effective value is the ideal minus +the wear — FullHeight_mm is the ideal height +less the axial wear and FullRadius_mm the +ideal radius less the radial wear — and that panel states no sign rule at all. On a Siemens project +both leaves are present at once, so the same wear number typed into the two ledgers moves the tool +in opposite directions. See Tool Offsets, which carries this +warning from the other side.
+The two conventions are not a defect to reconcile: each matches the control whose vocabulary its +table borrows. What makes the pair sharp is that they meet — the fallback below reads a height out of +the subtracting table and uses it where an adding table's height was expected.
+How a D Word Resolves
+The Siemens height path consumes a standalone D word from the block and writes the same downstream
+state as the ISO G43 path: a tool-height compensation section marked with the term D, and a
+translation of the effective height composed into the program-to-machine transform chain. Both
+brands' paths compose that entry under one source key and replace it in place, so the two are
+mutually exclusive on a file that mixes the dialects.
Resolution runs in this order.
+-
+
A block that carries no D word re-resolves the previous block's edge modally, and only while that
+previous section is still owned by the D term; a section already claimed by an ISO term on the same
+block, or carried modally from one, is left to the ISO path. The modal re-resolve deliberately raises
+no diagnostic — the warnings below are attached to blocks that spell a D word out, so a single
+missing row reports once instead of on every block that inherits it.
What a missing row falls back to
+For a (T, D) pair this table has no row for — and equally when no Siemens offset table resolves at
+all — the height is taken from the generic tool-offset table's height for the tool number,
+through the single-integer contract the ISO G43 H path already reads. Every Siemens runner carries
+that table, so the fallback always has a source. It exists so that an unfilled per-case $TC_DP
+table follows the tool-house-fed ledger instead of resolving 0 and machining the whole program one
+tool length low.
Both arms report. A fallback that finds a usable height raises a configuration warning naming the
+missing pair and the millimetre value it used instead. A fallback that finds NaN — the sentinel the
+generic ledger's tool-house refresh writes for a tool whose tip length does not resolve — raises a
+configuration warning of its own and degrades to 0 rather than composing a non-finite translation.
+Both carry the code SiemensToolOffset--TcdpRowMissing.
The fallback crosses the sign boundary. What it reads out of the generic table is that table's own
+effective height, ideal minus wear; what it substitutes is a $TC_DP height, geometry plus wear. A
+project that keeps wear in both places therefore compensates in one direction while the row exists
+here and in the other once it does not.
An unresolved tool name reaches the same fallback with the tool number left at 0, and the generic +table's own panel cannot create a row 0 — its tool-number cell takes a minimum of 1 and refuses +anything below it, and its add route mints from 1 upward — so the height resolves to 0.
+What the Tool-Name Map Is For
+Siemens programs may name a tool as a string rather than a number, as in the T="D16R3Z6" form the
+panel's own hint quotes. The act chain the simulation builds is keyed by integer tool id, so the
+string has to become a number before anything can use it, and this map is the only surface in the
+engine that performs that translation. Lookup is case-insensitive: the map is constructed with an
+ordinal case-insensitive comparer.
Two consumers read it, and they fail differently.
+-
+
Both consumers reach the map through the concrete Siemens table rather than through an interface, so +a runner that resolves no such table resolves no tool name either, whatever its brand marker says.
+The map's own panel is a two-column table over a name and a tool number. The name is the key and is +rendered as bold plain text; the tool number is a numeric cell with a minimum of 1. Row order is the +server's — the read sorts the keys case-insensitively. The panel never sorts, and its add path +re-reads the whole table rather than appending a row locally.
+Its footer is an Add / Set pair of fields, and the verb is both: the write is an upsert keyed on +the name. Entering a name that already exists overwrites that mapping's number instead of adding a +second row. Because the underlying dictionary keeps the key it already holds when a value is +assigned, a name entered in different casing updates the existing row and the re-read redisplays the +casing that was stored first.
+Row Life Cycle
+The two leaves spell adding and deleting differently, and the difference is which side mints the key.
+The $TC_DP table's adds are fieldless toolbar and row buttons, because the server mints both
+halves of the key. Add Tool in the toolbar sends no tool number and the endpoint takes the
+highest tool present plus one — or 1 on an empty table — with edge 1. The per-row button, tooltipped
+"Add a cutting edge for T{tool}", sends that row's tool number and the endpoint takes that tool's
+highest edge plus one. Neither half of the key fills a gap: a deleted middle tool or edge is stepped
+over rather than reused. Either way the panel appends a locally zeroed row for the returned pair and
+re-sorts by tool then edge; those zeros mirror the fresh row the server actually inserted rather than
+standing in as a placeholder.
The Tool Names table's add is the footer pair described above, since its key is typed rather than +minted. A blank name is refused before any request with "Tool name must not be empty.", and a +number that is not a positive integer with "Tool # must be a positive integer."; a successful write +clears both fields and re-reads the table. The endpoint refuses a blank name a second time.
+Deletion is guarded on one leaf and not the other. A $TC_DP row opens a confirmation dialog first —
+Remove tool offset row over "Remove the offset row for (T{tool}, D{edge})?" — and the row is
+filtered out locally once the request resolves; deleting a pair the table no longer holds answers
+unsuccessfully with a message naming it. A tool name deletes on the click with no dialog and no undo,
+and a name the map does not hold answers unsuccessfully with a message naming it.
Cell edits differ from both. Every numeric cell on the $TC_DP table sends the whole row — all
+eight components — and rolls back only the one cell it captured on failure. The Tool Names number
+cell sends the mapping it belongs to. Neither panel commits a cleared cell: an emptied field parses
+to null and the handler returns before the request, so the box is left blank on screen while the
+stored number stands and returns on the next remount. The tool-number cell additionally drops a
+non-integer silently, while a value below 1 is refused inside the numeric widget itself, which marks
+the box and emits nothing.
What Materializes the Table
+Three of the writes re-wire the runner suit's dependency proxies before resolving the table: the
+$TC_DP row write, the $TC_DP add, and the tool-name write. The proxy is a get-or-create
+placeholder, so that wiring installs a bare table into the project's per-case list when it holds none,
+and the write then lands on a real table instead of answering that one is missing. The reads and the
+two deletes do not re-wire.
Neither panel reports a structural change and neither refreshes the shared snapshot, so a table +created by one of those writes does not re-evaluate the flag or regrow the branch. In practice the +guard never fires from these leaves: the suit wires its proxies when it is deserialized and on every +runner assignment, and the nodes are built only after the flag already read true.
+When the Tables Are Absent
+Both leaves carry the branch's two empty layers. The shared one renders "No NC runner — load a +project first." while the snapshot reports no runner. Each panel's own read then gates on whether the +Siemens table resolved, and renders "No Siemens tool-offset table on the active runner." or "No +Siemens tool-name table on the active runner." when it did not.
+That second layer is not reachable by navigating, because the flag that grows either node is the same
+probe the reads answer with. It is a live guard for a table that goes away under a mounted panel. A
+failed write answers inside a success envelope, and the sentence it carries depends on the route. The
+four that resolve the table through the shared dependency helper — the row upsert, the row delete and
+the two tool-name routes — answer with the type name, No SiemensToolOffsetTable on the active runner; the $TC_DP add builds its own envelope instead and answers word for word with the
+$TC_DP panel's own absent-table line. Either way the panel shows its localized context followed by
+the sentence the server sent.
The table is stored on the project rather than in the runner file. It reaches the pipeline as a +seedless proxy in the Siemens preset's dependency list and resolves to the concrete table on the +project's per-case list, so a controller file saved from the branch root carries none of these rows. +A brand switch away from Siemens removes it along with the two nodes, and a switch back creates an +empty one rather than restoring what was there — +Program Data Plane.
+Layout
+-
+
Source Code Path
+See HiNC App Anatomy for git repository links.
+Web Application
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
HiAPI Engine
+-
+
See Also
+-
+
Table of Contents
+ +Tool Offsets
+ +Tool Offsets is the Controller branch's brand-neutral offset ledger — one row per integer offset
+number, each carrying a height and a radius split into an ideal (geometry) component and a wear
+component. It lives on the General Setup page at /general-setup under the Control-Tree id
+equipment/controller/program-data/tool-offsets, and that one id is the whole of what this page
+owns. The leaf is ungated: every brand preset proxies the table behind it, so the node is grown
+wherever a runner resolves — Brand Matrix.
Important
+Anatomy makes no claim that any id named here is stable across versions. The migration hop that
+keeps an older ?tree= value resolving is described in
+Tree Ids and Routes.
The panel follows the branch's shared rules — one read on mount, per-control commit, the +capture-assign-await-restore write, the two empty layers, and a dense markup table with no sort and +no pagination. Those rules, and the places this panel departs from them, are +Editing Contract. What is this leaf's own is everything +below.
+One Node, Two Labels
+The node id never changes, and the tree label does. The branch builder pushes this leaf +unconditionally, then chooses its label from the snapshot's Siemens tool-offset flag:
+| Condition | +Label the tree row reads | +
|---|---|
| No SiemensToolOffsetTable resolves | +Tool Offsets | +
| One does | +Tool Offsets (ISO G43 H) | +
Among the shipped presets only Siemens carries that table, so only Siemens sees the second label —
+and it sees it beside a second offset leaf, Tool Offsets ($TC_DP), grown from the same flag. The
+qualifier exists so that the two ledgers do not read as one row of the tree; the id under it is
+equipment/controller/program-data/tool-offsets either way, so an existing deep link is unaffected
+by which label is showing. Which flag grows which leaf, and what that flag probes, is
+Brand Matrix.
A second, independent Siemens signal appears inside the panel. While the runner's brand marker
+reads Siemens, an orange caption sits above the toolbar:
++"On Siemens this table serves the ISO
+G43 Hpath only — it is not the Sinumerik tool-offset +ledger.Dtool calls read the Tool Offsets ($TC_DP) table; when a (T, D) row is missing there, +playback falls back to this table's height for the tool number and reports a warning."
The label is chosen from the table flag and the caption from the brand string. Those are computed +from different things and can disagree — one of the departures catalogued in +Editing Contract.
+What a Row Holds
+The table is the Fanuc Memory C layout: the H number and the D number address the same row, so one +row carries both the length and the radius of one offset.
+| Column | +Stores | +Read by | +
|---|---|---|
| Tool # | +the offset number, the row's key | +the H word, the D word, or the Heidenhain tool number | +
| Ideal Height (mm) | +the tool's length before wear | +the height side | +
| Axial Wear (mm) | +accumulated length wear | +the height side | +
| Ideal Radius (mm) | +the cutter's radius before wear | +the radius side | +
| Radial Wear (mm) | +accumulated radius wear | +the radius side | +
There is no unit column and no unit conversion: +ToolOffsetRow stores four plain millimetre doubles, and the +panel writes and reads them unchanged. No cell is bounded except the key, which takes a minimum of
+-
+
The key column is the branch's one renameable key, and the rename is guarded on both sides of the +wire: the panel refuses a value already present in its own rows with the toast "Tool id {id} +already exists." and re-reads the table, and the endpoint refuses the same case with a message +naming the existing id. Below 1 never reaches either — the numeric widget rejects the bound itself, +marks the box and emits nothing. A non-integer that the widget does emit is dropped by the handler +with no request and no message at all.
+The Sign Convention
+On this table an effective value is the ideal minus the wear. +FullHeight_mm is the ideal height less the +axial wear and FullRadius_mm the ideal radius +less the radial wear, and those two are exactly what +IToolOffsetConfig hands every consumer. A positive wear therefore +shortens the compensation and a negative wear lengthens it. Nothing in the panel states this: the +four column headers name the components and no footnote names the operation, which is why the rule +belongs here. An offset number the table holds no row for is not an error either: both getters +answer 0 for it, and neither the ISO height path nor the radius side reports the miss — a mistyped H +is a zero-length compensation that plays to the end of the program without a warning.
+Warning
+The neighbouring Siemens ledger uses the opposite convention. On +SiemensToolOffsetTable an effective value is the geometry +plus the wear, and that panel prints the addition in a footnote under its own table. On a +Siemens project both leaves are present at once, so one wear number entered in the two ledgers +moves the tool in opposite directions. See +Siemens Tool Offsets.
+How the height reaches the machine
+The height side is consumed as a translation, not as a property of the tool. On a G43 block the ISO
+tool-height syntax reads the effective height for the H number and composes it into the
+program-to-machine transform chain as a translation of that many millimetres along the current tool
+orientation; G44 negates the same number and G49 writes zero. The G43.4 RTCP path reads the same
+number for the same H word and re-aims it through the machine kinematics. On Heidenhain there is no
+cancel word: a TOOL CALL block reads the effective height for the tool number and adds the
+block's DL delta to it, and the result composes into the same chain entry.
Nothing on that path reads the tool the project actually carries. The modelled tool assembly's own +spindle-buckle-to-tool-tip length is what decides where its tip sits once the machine has moved by +the compensation, so the stored ideal height and the modelled tool's length are two independent +numbers that have to agree for the programmed contour to land on the workpiece. Making them agree is +the entire purpose of the tool-house dependence below, which writes +SpindleBuckleToToolTipLength straight into the ideal column.
+How the radius reaches the path
+The radius side is read by the G41/G42 radius-compensation syntax against the D number, and it is +signed rather than absolute. A negative effective radius — a radial wear larger than the ideal +radius — is accepted, and it offsets the path to the opposite of the programmed side. On the first +block of a compensation move the parser raises a validation warning for that case on Heidenhain +only, mirroring the look-ahead check a TNC control performs; the other brands take it silently.
+The macro-variable window
+On the Fanuc, Mazak and Syntec syntax lists a variable lookup maps #2001 through #2200 onto the
+effective height of offsets 1 through 200, so a macro program can read the same subtraction the
+G43 path applies. It is a read: the lookup exposes no writer, and neither the Siemens nor the
+Heidenhain syntax list registers it.
Where a blank cell comes from
+A cleared numeric cell is not a stored value — the panel drops a null or a non-finite edit before
+sending anything, so clearing a cell leaves the number on the server and the box repopulates on the
+next remount. A blank cell that arrives from the server is a different thing: it is NaN, which
+the numeric widget renders as empty text. The tool-house refresh is what writes one, for a tool
+whose tip length does not resolve. Only the Siemens $TC_DP fallback tests for it — a NaN height
+there is reported as a configuration warning and degrades to zero — while the ISO G43 path composes
+whatever the table returned.
Tool-House Dependence
+Above the table sits the toggle Set ideal offset dependent on tool house. It is the one setting +this leaf edits that is not a dependency at all: it writes +IsIdealOffsetDependentOnToolHouse, a project-level +element serialized beside the runner suit, so it is untouched by a runner install and by the sweep +that follows a brand switch. Its stored default is on; the reader answers off when no project is +loaded, which is a fallback rather than the model's own default.
+With the dependence on, the panel hands three things over:
+-
+
The table does not otherwise track the tool library
+Offset number and tool id are independent integers, and with the dependence off nothing reconciles +them. A row may name a number the project's tool library has never held, and the library may hold +tools with no row; neither state is reported anywhere on this panel, and both are stored and played +as written. The library itself is edited on Tool House Page.
+The refresh is what imposes a one-to-one mapping, and it does so in both directions.
+The refresh is destructive, and it is not only a button
+One endpoint backs both the button and the toggle, and it runs three steps. For every tool in the
+project's library it writes the ideal height from that tool's spindle-buckle-to-tool-tip length —
+NaN where the length does not resolve — and, where the cutter is a milling cutter, the ideal
+radius from the largest radius on its cutter profile; a row is created for a library tool that had
+none. It then deletes every row whose number is not in the library. The wear columns are never
+touched.
The engine carries the same three steps of its own, as +UpdateIdealByToolHouse(API), +and that copy is what playback calls. The two agree step for step and part company only on a missing +library: the engine method returns without doing anything, while the endpoint answers unsuccessfully +with “No tool house available”.
+Three consequences deserve stating plainly.
+-
+
Row Life Cycle
+The add and the delete are the two flows the branch spells one way here and another way elsewhere.
+Add is a fieldless toolbar button, because the server mints the key: the endpoint takes the +highest existing number plus one — or 1 on an empty table — inserts an all-zero row and returns the +number, and the panel appends that row locally rather than re-reading. All four zeros are the +server's actual new-row values, not a screen placeholder.
+Delete opens a confirmation dialog first, titled Remove tool offset over "Remove the offset +row for tool #{id}?", and filters the row out locally once the request resolves. Deleting a number +the table no longer holds answers unsuccessfully with a message naming it.
+Rename is optimistic and re-sorts the rows ascending once the write returns; a failure restores +the previous number. Every other cell is a per-cell handler that sends the whole row.
+When the Table Is Absent
+The leaf carries both of the branch's empty layers. The shared one renders "No NC runner — load a
+project first." while the snapshot reports no runner. The panel's own read then gates on whether a
+ToolOffsetTable resolved, and renders "No tool-offset table
+on the active runner." when it did not — which hides the dependence toggle along with the table,
+since the whole body sits behind that guard. A failed write answers inside a success envelope. The
+row write, the delete, the rename and the refresh all go through the branch's shared dependency
+helper and carry No ToolOffsetTable on the active runner, which the panel shows as its own context
+followed by that sentence. Add does not use that helper: it tests the table itself and answers
+No tool-offset table on the active runner — the panel's own absent-table line, less its full stop.
+The dependence write reaches neither string, because what it writes is a project-level element
+rather than the table; it is the one route on this leaf that answers a coded payload instead, and it
+reports no project loaded.
That second layer is hard to reach. ToolOffsetTableProxy is +a seedless get-or-create placeholder: on wiring it installs a bare table into the project's per-case +list whenever the project holds none, and the suit wires its proxies when it is deserialized and +again on every runner assignment.
+Layout
+-
+
Source Code Path
+See HiNC App Anatomy for git repository links.
+Web Application
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
HiAPI Engine
+-
+
See Also
+-
+
Table of Contents
+ +Work Coordinates
+ +Work Coordinates is the Controller branch's G54 ledger: one table of coordinate ids against an X / Y
+/ Z machine offset, edited through whichever offset provider the active runner resolves. It lives on
+the General Setup page at /general-setup under the Control-Tree id
+equipment/controller/program-data/work-coordinates, reached as
+?tree=equipment/controller/program-data/work-coordinates, and no snapshot flag gates it — every
+runner grows it. The face is the same on all five brands; what sits behind it is a different object
+on each, and on two of them that object is also the subject of a node of its own.
Important
+Anatomy makes no claim that any id named here is stable across versions. The migration hop that
+keeps an older ?tree= value resolving is described in
+Tree Ids and Routes.
The Discriminator
+The panel does not probe the runner. It reads one field of the shared runner snapshot,
+workCoordinateKind, and uses it for exactly one thing: the grey caption above the table. The field
+is a type switch over the first IIsoCoordinateConfig in the runner's
+proxy-resolved dependency list, and it has six arms.
| Arm | +Provider matched | +Brands that reach it | +Caption the panel shows | +
|---|---|---|---|
fanuc |
+FanucParameterTable | +Fanuc, Mazak | +"Stored in the Fanuc parameter table (#5221+ / #7001+)." | +
syntec |
+SyntecParameterTable | +Syntec | +"Stored in the Syntec parameter table (Pr5221+ / Pr7001+)." | +
siemens |
+SiemensFrameTable | +Siemens | +"Stored as Siemens settable frames ($P_UIFR; G500 cancels and is always zero)." | +
heidenhain |
+HeidenhainDatumTable | +Heidenhain | +"G54–G59 map onto Heidenhain datum preset rows 1–6." | +
generic |
+any other implementer, i.e. IsoCoordinateTable | +none | +"Brand-neutral work-coordinate table." | +
none |
+nothing resolved | +none | +"Brand-neutral work-coordinate table." | +
Two arms answer for no shipped preset, and they answer alike because the caption's switch falls +through to one default for both.
+-
+
The brand marker plays no part here. workCoordinateKind is derived from the resolved object's
+type, while the badge on the branch root reads a free-form string, so a marker edited to say
+something else changes the badge and leaves this caption exactly where it was.
The Row Inventory
+Every row is one id from the provider's own id enumeration, and each provider enumerates a different +set. That is the whole reason the row counts differ: the endpoint asks the resolved provider what ids +it has and fills each row from its offset accessor, substituting a zero triad for a null answer.
+| Brand | +Provider | +Ids enumerated | +Always visible | +Hidden while all-zero | +
|---|---|---|---|---|
| Fanuc, Mazak | +Fanuc parameter table, then the extended table | +G54–G59, G54.1P1–G54.1P48; G59.1–G59.9 | +the six G5x rows and the nine G59.x rows |
+the 48 G54.1P rows |
+
| Syntec | +Syntec parameter table, then the extended table | +G54–G59, G54.1P1–G54.1P48; G59.1–G59.9 | +the six G5x rows and the nine G59.x rows |
+the 48 G54.1P rows |
+
| Siemens | +Siemens frame table | +G54–G57, G505–G599 | +the four G5x rows | +the 95 G5xx rows |
+
| Heidenhain | +Heidenhain datum table | +G54–G59 | +all six | +none | +
| — | +brand-neutral table (standalone) | +G54–G59, G59.1–G59.9 | +all fifteen | +none | +
The extended table on the three ISO presets is the brand-neutral table seeded with the nine
+G59.x ids only; a standalone brand-neutral table (a runner built without a brand table) seeds all
+fifteen.
Three consequences of that column are worth reading twice.
+-
+
G500 is a row on no brand. The frame table treats it as the cancel frame: reads answer a zero
+offset, a write through the coordinate accessor is dropped, and it is deliberately not stored in the
+frame dictionary, so it never reaches the id enumeration — which is why the Siemens caption names a
+code the table below it never shows.
Two Nodes, One Table
+Warning
+On Siemens and on Heidenhain this leaf is a second face on an object that already has a node of +its own. The Siemens frame table is edited here and under Frames (Siemens); the Heidenhain +datum preset table is edited here and under Datum Presets (Q339). These are not two copies +kept in step — they are one instance, and a write through either node lands in the same cell.
+The aliasing is exact, and its two halves are addressed differently.
+-
+
Nothing in the branch reconciles the two views while both are in scope, and nothing needs to: the +editor row mounts one panel at a time and each panel fetches once on mount, so moving the selection +from one node to the other is itself the refresh. What that costs, and what a mounted panel therefore +never sees, is Editing Contract.
+Which Rows Show
+A row is always visible when its id reads G5, one digit in 4–9, and optionally a dot and one more
+digit. Every other row is hidden unless one of its three values is non-zero, or the Show all
+toggle is on. That rule is the panel's own regular expression, evaluated in the browser over the rows
+the read returned; nothing about it reaches the server.
The toggle itself is conditional. It renders only while at least one row fails the always-visible
+test, so it is present on Fanuc, Mazak, Syntec and Siemens and absent on Heidenhain — and absent on
+the brand-neutral table too, whose fifteen ids all pass. The Frames leaf carries the same toggle
+unconditionally and applies a narrower always-visible test of its own, G54 through G57; on the
+Siemens table the two tests keep the same four rows visible, so only the toggle's presence differs.
Editing a Row
+Every value cell is the shared numeric field, so it commits on blur or on Enter and never per +keystroke — its parsing, its bounds behaviour and the double commit that follows Enter are +Numeric Input. No cell here passes a minimum or a maximum, and the id +column is plain bold text that cannot be edited.
+The write is the whole row. A commit assigns the new number into the local row, then sends that
+row's x, y and z as they now stand to the row's id. A failure restores the one component the
+handler captured and raises the branch's standard toast; the request that failed carried all three. A
+cleared cell parses to null, and a cell holding NaN or an infinity parses to a non-finite number;
+the handler returns before the request in both cases, so neither empties a stored offset. The cell
+keeps showing what the field made of the text — blank for a cleared cell and for NaN, the literal
+Infinity or -Infinity for an infinity — until the panel is remounted.
Where that row lands differs by provider, and this is the point at which the uniform face ends.
+-
+
The Fanuc-family and Heidenhain writes silently drop an id they do not map: the Fanuc-family write
+resolves the address first and does nothing without one, and the Heidenhain write matches the id
+against its two recognised forms. The Siemens frame table and the brand-neutral table do the
+opposite — an id they have never held is stored as a new row, the frame table refusing only G500.
+Neither behaviour is reachable from this leaf, because the panel only ever sends an id the same
+provider enumerated.
P0 and M0
+Two flat buttons sit in the Actions column of every row and write the whole triad in one call.
+-
+
Both re-read the whole table on success rather than patching the local row, so a value the provider +stored differently from what was sent is picked up at once. Neither is confirmed. P0 carries a +failure of its own that no other write in the branch produces: with no chain resolving a tool buckle +the position is null, and the endpoint answers unsuccessfully with “Could not get the machine +position at program zero” before it ever reaches the offset provider.
+The Canvas Marker
+Clicking a row also chooses which coordinate the General Setup canvas draws its triad on. That is the +one write in this branch addressed to another surface, and it behaves differently from the rest in +four ways worth naming.
+-
+
The highlight is a tinted row background, and the row carries the native tooltip “Click to mark this +coordinate on the General Setup canvas”.
+What a Brand Switch Carries
+The brand switch is the one operation in the branch that treats work coordinates specially. Its panel +carries a checkbox, Carry work-coordinate XYZ (G54…) into the new brand's table, ticked by +default, and the flag reaches the endpoint with the brand.
+With it set, the offsets are read from the outgoing provider before the preset is swapped in, and +written into the incoming one afterwards — but only for the ids the incoming provider already +enumerates, and only when the two providers are different instances. Everything else is dropped. So +the carry is lossy in a shape that follows directly from the inventory above: Fanuc to Siemens keeps +G54 through G57 and loses G58, G59 and all 48 extended entries; Siemens to Fanuc keeps the same four +and loses the whole G505 series; a switch to Heidenhain keeps at most six.
+The instance guard matters on the pair that shares a table. Mazak and Fanuc proxy the same parameter +table, so a switch between them resolves the same instance on both sides and the carry is skipped as +redundant — the values are already there. What a switch keeps, resets and destroys elsewhere is +Brand Switch.
+What the Rows Feed
+A work-coordinate word in a program is resolved into a translation composed onto the block's +program-to-machine transform. Two details of that path do not match what this panel shows.
+The run-time lookup walks every provider; so does the panel. The resolver iterates all offset
+providers in the effective list and takes the first non-null answer, which is what lets a brand
+table cover its hardware-mapped ids while a second provider covers ids the brand table does not map
+— the shape the Fanuc, Mazak and Syntec presets ship in, with the extended G59.1–G59.9 on a
+brand-neutral table behind the brand table. The panel lists every provider's ids in the same order
+(the first provider that holds an id wins) and writes an edit to the provider that enumerates the
+id; the snapshot's kind field still names the first provider only, which is why the caption reads
+“Fanuc parameter table” above rows the second table holds.
The 48 extended rows are reached by G54.1 Pn — and by G54 Pn. The ISO coordinate syntax
+reads G54–G59 and G59.1–G59.9 from the block's parsed flags, and the additional work coordinate
+systems from the G54.1 P capture the parsing bundle writes as a sub-object; both spellings Fanuc's
+manual gives that chapter (“G54.1 or G54”) land in that capture, with or without a space before the
+P word, so G54 P48 and G54P48 select row 48 exactly as G54.1 P48 does. A G54 with no P word
+in its scope stays the plain G54 flag. The row's id is the un-padded G54.1P48, the same key the
+Fanuc-family table maps to #7001+, so on Fanuc, Mazak and Syntec the extended rows this panel edits
+are the rows the program selects. A row that is selected but was never entered — the brand tables
+seed every extended row with zero, as a fresh-battery controller reads them — reports
+Coord-WorkOffset--AdditionalZero on the selecting block; the program then runs on the machine
+origin, which is almost never what a pallet or fixture offset meant. A zero G54–G59 stays silent: it
+is a common authoring convention. The Fanuc-family table is also the macro variable lookup, so on
+Fanuc and Mazak those rows read back from a macro program as #7001–#7999, alongside G54–G59's
+own #5221–#5328; the Syntec table implements no variable lookup, so on Syntec the rows are
+reached by the coordinate words only.
The extended G59.1–G59.9 ids live on a second table behind the brand table. The Fanuc-family
+tables map G54–G59 and the 48 additional rows and nothing else, so the Fanuc, Mazak and Syntec
+presets mount a brand-neutral coordinate table holding only G59.1–G59.9 right behind their
+brand table. The two id sets are disjoint, the run-time lookup finds each id on the one provider
+that holds it, and this leaf shows the nine rows among the others and writes each edit to the
+provider that carries the id. Those rows are seeded with zero and, like G54–G59 and unlike the
+48 additional rows, a zero one stays silent: leaving a row of the standard series at zero is a
+common authoring convention. A project that kept a G59.x value in its
+legacy coordinate table carries it into this second table when the legacy table migrates (each row
+lands on the provider that carries its id). A controller saved before the second table existed
+gains it on load; one that lost it resolves no offset for a G59.x at all and reports
+Coord-WorkOffset--NoTableEntry instead.
Which row an untagged program starts on is the brand preset's static initializer, and the three +answers differ.
+| Preset | +Initial coordinate id | +Effect | +
|---|---|---|
| Fanuc, Mazak, Syntec | +G54 |
+the G54 row is active from the first block | +
| Siemens | +G500 |
+no frame is active; the offset is zero until a frame word appears | +
| Heidenhain | +none | +no coordinate section is seeded; the datum cycles set one | +
On Heidenhain the program's own route into the same table is the datum cycle rather than a G word: a +preset cycle reads the preset row its number names and writes a synthetic coordinate id that the +table resolves back, which is how rows 7–20 are consumed at run time despite having no row on this +leaf. A word or a cycle that resolves no offset composes a zero translation rather than failing.
+Layout
+-
+
The panel carries no heading, no save button, no unsaved marker and no dialog, and it reports no +structural change, so no edit made on this leaf rebuilds the branch.
+Source Code Path
+See HiNC App Anatomy for git repository links.
+Web Application
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
HiAPI Engine
+-
+
See Also
+-
+
Table of Contents
+ +Fixture
+ +The fixture is what holds the workpiece on the machine table: one geometry, and the two anchor +transformers that place that geometry against the workpiece and against the table. It has two +surfaces:
+-
+
Both edit the one fixture the project owns.
+-
+
The displayee differs by client. The web branch has no fixture-only canvas: it shares the General
+Setup canvas, whose displayee is the equipment-setup displayee (Disp/EquipmentSetupDisplayee.cs) —
+the merged fixture + workpiece scene with the anchor, buckle and controller-coordinate overlays. Its
+option set is the equipment-setup config (Disp/EquipmentSetupDisplayeeConfig.cs), carried on
+UserService.UserConfig as its EquipmentSetupDisplayeeConfig property. The WPF sub-window has a
+canvas of its own, whose displayee is FixtureEditorDisplayee configured by
+FixtureEditorDisplayeeConfig, taken from UserService.UserConfig as its
+FixtureSetupDisplayeeConfig property.
Layout
+Control Tree Branch
+The root panel is rendered inline by the dock's primary editor pane, so it has no title label of its +own — the wrapping expansion header carries the selection breadcrumb. Every item below is a tree +node whose editor is swapped into that same PRIMARY row.
+-
+
WPF Page
+-
+
Both clients put a draggable divider between the editor column and the canvas: a GridSplitter in the +WPF page, nested draggable splitters on the web page (dock / content column / canvas), plus the +dock's own height divider between the tree row and the editor row.
+Behavior
+-
+
Source Code Path
+See HiNC App Anatomy for git repository links.
+Web Application
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
HiAPI Engine
+-
+
See Also
+-
+
Table of Contents
+ +Hidden Controller Branches
+ +Two Control-Tree nodes on the General Setup page are absent from a fresh installation: CSV
+Controller and CL Controller, on /general-setup under the ids equipment/controller-csv and
+equipment/controller-cl. Each is the entire editing face of one resident non-brand runner suit —
+the CSV control-table pipeline and the NX-CL (CLSF) pipeline — and each is minted only when its
+device-local Preference checkbox is on or the current URL targets it. This page is the answer to
+“why is that node not in the tree”, because the equipment tree withholds a node in two different ways
+and only one of them can be talked out of it.
Important
+Anatomy makes no claim that any id named here is stable across versions. The migration hop that
+keeps an older ?tree= value resolving is described in
+Tree Ids and Routes.
Two Kinds of Invisibility
+| + | Preference-gated | +Brand-gated | +
|---|---|---|
| Which nodes | +equipment/controller-csv, equipment/controller-cl |
+every gated leaf under equipment/controller |
+
| What decides | +a checkbox stored in the browser | +the active runner's dependency list | +
| Where the decision lives | +one device | +the project | +
| Reachable by a link anyway | +yes, and deliberately | +no | +
| What a link to it does | +mints the node and selects it | +nothing at all | +
The distinction is not a matter of degree. A preference-gated node exists in the code that builds +the tree and is simply not pushed; the same build reads the URL, so naming the node in a link is +enough to bring it back for that visit. A brand-gated node has no such second door: the branch +builder never creates it, and the selection code refuses an id the built tree does not carry, so +there is nothing for a link to select. Which leaf that branch grows on which brand, and the snapshot +flag behind each one, is Brand Matrix.
+Preference-Gated: The Two Runner-Suit Nodes
+The equipment tree host mints each of the two nodes when a project is open and either the +node's checkbox is on or the route's tree id begins with that node's id. All three parts matter.
+With no project open neither node is built, whatever the checkboxes say — the same guard that
+keeps the Controller branch childless before a project is loaded. The consequence is not a dead end:
+loading a project bumps the layout's project epoch, which destroys and rebuilds the page, and the
+rebuilt host runs the build again and then adopts whatever ?tree= the URL still carries. A link
+opened against an empty application therefore lands on its node once a project is opened, without
+the link being touched.
The checkboxes are device-local. Both are held in one browser-storage record of view +preferences, off by default, written back a moment after each flip, and never sent to the server — +so the setting belongs to a browser rather than to a user account or to a project. They are the +CSV Controller and CL Controller entries of the app menu bar's Preference dropdown, described +in Preference Menu.
+Only the route reveals — not the persisted last selection. The host also remembers the last node
+selected on each tree page, and that memory deliberately mints nothing: the landing selection is
+honoured only when the id it names is already in the built tree, and falls back to the equipment
+group root when it is not. Reveal-by-route has to happen while the tree is being built, because the
+code that adopts a ?tree= id afterwards refuses an id the tree lacks.
Switching a box off while standing on its node
+The host watches both checkboxes and rebuilds the whole equipment tree when either flips. Before the +rebuild it checks whether the current selection is one of the nodes that just lost its box, and when +it is, it does two things in this order:
+-
+
Only then does the tree rebuild. The order is the point: the URL already mirrors the selection, and +the URL is exactly what reveals an unchecked node, so a selection left standing there would leave its +id in the query, and the next build's prefix test would re-mint the node that was just switched off. +The reader lands on the General Setup group panel, whose clickable child list no longer carries the +row. The selection is moved directly rather than through the host's switch request, which would +matter only for a panel registering the before-switch gate; neither of these two does.
+Switching a box off while the selection is elsewhere takes no such detour — the rebuild simply drops +the node from the tree and from the group panel's child list.
+What the two nodes are not
+Both are childless leaves. Neither declares a child builder, so nothing grows beneath them; neither +registers a content view, so the General Setup content column keeps its own empty hint for both; and +neither panel reports a structural change, so nothing either panel does rebuilds any part of the +tree. There is no Object-Management menu on either: a CSV or CL pipeline is not loaded, pasted or +saved as a file of its own the way a brand controller is.
+Brand-Gated: The Node That Was Never Minted
+Under equipment/controller the node set is grown from a snapshot of the active NC runner, and a
+leaf whose backing dependency that runner does not resolve is never created. Nothing reveals it. A
+link naming such an id — equipment/controller/program-data/frames on a project running a Fanuc
+runner, say — reaches the host, finds no such node in the built tree, and is dropped: the selection
+is left as it was, the URL keeps the id it was given, and no redirect and no message follow. On a
+first load that means the editor row shows its own hint, "Select an item in the Control Tree to edit
+it here.", under a tree in which the node the link names is simply not present.
The two behaviours meet at the same line of code from opposite sides. The runner-suit nodes are read +out of the URL while the tree is built, so by the time that check runs they exist; a brand-gated +leaf is not, so the check is the end of the road for it. That is why a checkbox can be overruled by a +link and a runner cannot.
+What the Preference Captions Mean
+Under each checkbox sits a caption reading either “This project plays CSV” / “This project plays +CL” or “Not used by this project”. It is the informed-choice half of the design: the menu answers +whether the loaded project plays that kind, so the decision to show a node is made with evidence +rather than by trial. The caption is read when the Preference dropdown opens rather than on page +load, and both runner snapshots are fetched together; with no project open both captions are blank, +and a snapshot that fails to load leaves its own caption blank rather than asserting either answer.
+The evidence is a scan of the loaded project, per kind:
+-
+
Three properties of that scan are easy to guess wrong. It walks the whole mission, the entries of a
+nested list command included, and it walks through each command's enable wrapper without reading
+it — a Program File command that is switched off still counts as evidence. The script test cuts the
+other way too: a play routed through the generic NC entry point with a .csv path is deliberately
+not matched, because a bare .csv appears in step and shot output templates far more often than in
+play paths. And the answer feeds the caption and nothing else — usage evidence mints no node.
+The tree is built from the checkbox and the route, and from nothing the scan reports.
How Both Panels Edit
+Neither node belongs to the Controller branch, so that branch's +Editing Contract does not govern them. Both panels +nonetheless follow most of its shape, and the places they do not are worth naming.
+Shared with the contract. Each panel fetches its own snapshot once on mount and owns no store. +Each opens with two guards, in order: "No project loaded." from the snapshot's own project flag, +then a line naming the absent config — "No CSV column config on the CSV runner." or "No CLSF +config on the CL runner." Every control commits on its own, with no save button and no dirty +marker. A failed write raises one negative toast, three seconds, composed as the panel's localized +context followed by the server's own English sentence, with a console line beside it, and leaves no +inline error state behind once it expires. Both REST surfaces answer a missing config inside a +success envelope rather than with an error status, and the shared write helper turns that envelope +into a thrown error; the read helper inspects no envelope, and neither endpoint has a non-200 path, +so a read fails only on transport. A failed read therefore leaves the empty snapshot in place and the +panel renders its "No project loaded." line — the same body an absent project produces, with the +toast as the only signal that the two differ.
+Where they part from it. The CSV panel is not optimistic: its text fields are bound to a local +draft, so the screen already shows the new text, and the request is awaited before the panel's +server mirror is updated; a failure restores the field from that mirror. The CL panel is optimistic +in the contract's own shape, assigning first and restoring on failure, and its two numeric fields are +the shared numeric widget, committing on blur or on Enter and never per keystroke; the full widget +contract is Numeric Input.
+Both panels guard on equality, and the guard is the panel's rather than the widget's. Every +commit handler on both — the CSV tag fields, the two CL rates and the tool-house toggle — compares +the incoming value against the fetched server mirror first and returns when the two match, so +committing an untouched field, or blurring after an Enter that already succeeded, sends nothing. +That is the departure from the Controller branch, whose numeric leaves carry no such comparison and +lean on the shared widget alone, and the widget emits on every blur and every Enter. The one control +across the two panels with no such comparison is the CL chip field.
+CSV Controller
+The node is the face of the project's resident CSV runner suit +(CsvRunnerSuit), a whole runner suit constructed alongside +the brand one and never absent from a loaded project. Its runner is a +SoftNcRunner assembled as the CSV pipeline by the factory +GeneralCsvRunner, and it replays a CSV control table. A project file +that stores the column configuration flat, outside any suit, has that element migrated into the +suit's pipeline as it loads, so such a file keeps its tags.
+The whole face is one flat configuration, CsvRunnerConfig — no brand
+presets, no per-case tables and no native parameter form, which is why the node is a leaf rather than
+a branch. The panel's caption says what it edits: "Column tags of the CSV control table (matched
+against the header line). Played by a mission Program File of CSV kind or a script's
+PlayCsvFile("…")." The header line is the file's first row, split on commas; each title then has
+quote characters stripped from its two ends and is trimmed of whitespace, in that order, so a title
+whose opening quote sits behind a space keeps that quote and never matches its tag.
| Field label | +Default tag | +What reads it | +
|---|---|---|
| Machine coordinate prefix | +MC. |
+the machine-coordinate columns, composed as the prefix plus X, Y, Z, A, B, C |
+
| Cutter location prefix | +CL. |
+nothing in the shipped pipeline | +
| Tool id column | +ToolId |
+the tool-change section | +
| Spindle speed column (rpm) | +SpindleSpeed_rpm |
+the spindle section | +
| Spindle direction column | +Spd.Dir. |
+the spindle section, parsed as a direction name; a speed with no direction turns clockwise | +
| Feedrate column (mm/min) | +Feedrate_mmdmin |
+the feedrate section | +
| Step duration column (s) | +StepDuration |
+the recorded-timing section | +
| Actual time column | +ActualTime |
+the recorded-timing section | +
| Coolant column | +Coolant |
+the coolant section | +
| Line-begin C# script column | +LineBeginCsScript |
+the script section, run before the row | +
| Line-end C# script column | +LineEndCsScript |
+the script section, run after the row | +
Four things that table does not show on its own:
+-
+
Each field commits on blur or on Enter — never while typing — and sends only itself. There is no +validation on either side of the wire: no trimming, no bounds, no uniqueness.
+CL Controller
+The node is the face of the project's resident NX-CL suit
+(ClsfRunnerSuit), constructed the same way beside the brand
+suit and the CSV suit, its runner a SoftNcRunner assembled as the CLSF pipeline
+by the factory NxClRunner. Its caption reads "Plays NX
+cutter-location files (CLSF) — a mission Program File of CL kind or a script's PlayClFile("…")."
Everything on the panel is one ClsfRunnerConfig, and every field on +it answers something a cutter-location file cannot say for itself.
+Rapid feedrate (assumed), suffixed mm/min, and Rotary rapid feedrate (assumed), suffixed
+deg/min, default to 20000 and 36000. A CLSF carries no machine axes, so a RAPID move has nothing
+to be timed against — but the two rates are not read on the same path, and the machining chain
+decides which. On a pure-CL chain the CLSF pipeline's own motion semantic reads the linear rate
+straight off this configuration and times every rapid from it; the rotary rate is never consulted.
+On a machine-tool chain, where each CLSF block is re-expressed in machine coordinates and routed to
+the motion semantics reused from the NC pipeline, the configuration doubles as the pipeline's
+rapid-feedrate provider and answers axis-uniformly — every linear axis gets the first rate, every
+rotary axis the second — and that is the only place the rotary rate is read.
Both fields carry a minimum of 1: a smaller value is refused inside the widget itself, which shows +Must be ≥ 1 under the box and emits nothing, so no request is made and no toast appears. The +server's own positivity check sits behind that and is not reachable from this panel. A cleared or +non-finite entry is dropped by the panel before the request, which leaves the box showing what was +typed while the stored rate stands; selecting another node and returning restores the box.
+Prefer Tool House on LOAD/TOOL is a toggle, on by default, committing on the click. Its caption +sits below it permanently rather than as a field hint: "On: a LOAD/TOOL id already configured in the +Tool House keeps that tool (CLSF TLDATA geometry ignored). Off: TLDATA overwrites the tool-house +entry on every load." That is what the code does — a tool change requesting an id the tool house +already holds is left alone while the toggle is on, and the file's own tool geometry rebuilds the +entry when it is off. Either way an id the house does not hold is built from the file's tool data, +and an id with neither is reported as a configuration error.
+Excluded record words is a chip field: a word typed and entered joins the list, and there is no
+dropdown, because the option list is deliberately empty. Its preset list is PAINT, TOOLNO,
+LOADTL, TOOL PATH, TOOLPATH and END-OF-PATH. A CLSF record that reaches the end of the
+pipeline unhandled is otherwise reported as a validation warning naming the record word; a word on
+this list is consumed silently instead, matched whole and without regard to case. Adding or removing
+one chip writes the whole list, the server trims each word and drops the blank ones before replacing
+the stored list, and the panel keeps what was typed — so a word entered with stray whitespace reads
+differently on screen from what was stored, until the panel is remounted.
Layout
+-
+
Source Code Path
+See HiNC App Anatomy for git repository links.
+Web Application
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
HiAPI Engine
+-
+
See Also
+-
+
Table of Contents
+ +General Setup Page
+ +General Setup is the equipment page at /general-setup. It is one of the two Control-Tree pages:
+the left dock names every piece of equipment in the project and edits whatever is selected, and the
+canvas on the right shows all of it at once. Selection rides the URL as ?tree=equipment/…, so any
+branch of it is a link. The tree's one root is a pure Group stem at equipment, selectable in its
+own right: it renders an orientation panel listing the equipment items beneath it.
The page renders three columns in nested splitters — the Control-Tree dock, the content column, and +the equipment canvas. The dock carries two stacked rows: the tree above, and below it the editor +panel of whichever node is selected. The content column holds a second, wider view of the same +selection, mounted only for the item types that register one — on this page the spindle branch and +nothing else — while every other selection leaves it showing its empty hint. Column widths are +device-local, and the dock aligns with the Execution page's so the two tree pages feel like one +application.
+Ordered as the page builds its equipment children.
+Pages
+-
+
The superseded Legacy Controller screen at its own route is a separate +surface, editing a different model from the branch above.
+See Also
+-
+
Table of Contents
+ +Machine Tool
+ +The machine tool is the project's kinematic chain — the linkage between the machine table and the +tool spindle. It has three surfaces:
+-
+
All three edit the one chain the project owns.
+-
+
Layout
+Control Tree Branch
+The root panel is rendered inline by the dock's primary editor pane, so it has no title label of its +own — the wrapping expansion header carries the selection breadcrumb.
+-
+
/machine-tool Route
+Load and show only: no Save As, no ReLoad, no name editing.
+-
+
WPF Page
+-
+
Behavior
+-
+
Source Code Path
+See HiNC App Anatomy for git repository links.
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
See Also
+-
+
Table of Contents
+ +Spindle Capability
+ +The Spindle Capability editor is the equipment/spindle branch of the General Setup page's Control
+Tree (/general-setup?tree=equipment/spindle), the equipment child after Machine Tool. Two older
+paths redirect onto it: /spindle-capability/:tab?, whose tab segment maps straight onto one of the
+branch's five children, and /equipment/spindle. The menu bar carries no entry of its own for it —
+the Page menu reaches General Setup, and the tree reaches the branch.
It edits SpindleCapability on the project's authored equipment face, +SetupEquipment, reached as +SetupEquipment. It exposes the metadata (name, note), +the thermal scalars (energy efficiency, working-temperature ceiling), the gear-shift spindle speed, +the two dry-run coefficients, and the power / torque contour lists.
+-
+
Note
+The WPF desktop app has no spindle-capability surface: no page, no panel, and no handler for the
+.SpindleCapability extension. There, the value is whatever the project XML carries on the
+equipment face, or the class defaults when the XML says nothing. The .SpindleCapability object
+management menu ships on the web only, on this branch's root panel.
Layout
+The branch is one root plus five children, six selectable nodes in all. Each node's editor occupies +the dock's PRIMARY row, and all six — the root included — register the same CONTENT-column view, +the two contour charts, so the charts stay mounted while the operator walks the branch.
+Control Tree Branch
+-
+
Behavior
+-
+
Source Code Path
+See HiNC App Anatomy for git repository links.
+Web Application
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
HiAPI Engine
+-
+
See Also
+-
+
Table of Contents
+ +Workpiece
+ +The Workpiece editor is the equipment/workpiece branch of the General Setup page's Control Tree (/general-setup?tree=equipment/workpiece); the old /workpiece route redirects there. In the WPF client it is a sub-window opened from the Main Panel.
The key model is Workpiece, taken from the Main Panel's +Workpiece. The cached solids it is drawn from belong to +WorkpieceService, which both clients share.
+The display config differs by client. The web branch has none of its own: it shares the General Setup
+canvas, gated by the equipment-setup config (Disp/EquipmentSetupDisplayeeConfig.cs), which carries
+the fixture and workpiece flags together — the fixture and workpiece geometry anchors keep separate
+flags, while the fixture↔workpiece buckle pair, being one attached identity at one location, is a
+single flag. The WPF page takes its own WorkpieceEditorDisplayeeConfig
+from UserService.UserConfig.
Layout
+Control Tree Branch
+-
+
Page Frame
+-
+
WPF Page
+-
+
Default Resource
+The default resources of Workpiece Material and Cutting Parameter exist in Resource folder under application folder (Not project folder). Both clients seed their file pickers at the matching Resource sub folder:
-
+
Behavior
+-
+
Source Code Path
+See HiNC App Anatomy for git repository links.
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
Table of Contents
+ +Box3d Control
+ +The Box3d editor edits an axis-aligned Box3d through four vector rows — Min, Max,
+Dimension and Center — of which an Edit Mode selector decides which two are writable. It is
+reached wherever a Geometry slot admits the Box3d kind: a Control-Tree kind node under a Geometry
+slot, or the geometry switchboard embedded inline.
Layout
+-
+
The mode rules each row read-only or editable rather than showing a different set of rows: Min is +read-only in Center + Dimension, Max in anything but Min / Max, Dimension in Min / Max, and Center +in anything but Center + Dimension.
+Key Model
+Only Min and Max are settable on the type; +Dim and Center are derived and get-only. That is why the +other two modes are arithmetic over the same two stored corners rather than a different storage +shape — and on the web that arithmetic happens in the browser, which then posts the resulting Min +and Max.
+Behavior
+-
+
Source Code Path
+See HiNC App Anatomy for git repository links.
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
See Also
+-
+
Table of Contents
+ +Cylindroid Control
+ +The Cylindroid editor edits a Cylindroid — a solid of revolution defined by a list of
+(Z, R) pairs — as a table of rows, with a minimum of two. It is reached from any Geometry slot that
+admits the Cylindroid kind, and again from the Tool House cylindroid holder, whose Geometry node
+embeds the same editor directly.
Layout
+-
+
The desktop control is shaped differently for the same job: a title, an Add Point and a Clear +All button, and a data grid with Z, R and Actions columns. Clear All has no web counterpart — it +confirms, wipes the list and re-seeds a single pair.
+Behavior
+-
+
Tessellation Resolution Is Not a Property of the Shape
+There is no longitude count, and no per-instance resolution, on a cylindroid. The STL longitude +number is derived internally at generation time from a resolution the caller supplies; the type +carries only a static default. The desktop client once had a Longitude Number field and its handler +was deleted when the backing property went away.
+Where resolution is exposed, it belongs to the holder rather than to the shape: the Tool House +cylindroid holder carries a Resolution surface beside its Geometry one — a tree node on the web, +a tab in the desktop client — offering a linear resolution in millimetres and an angle resolution in +degrees, described in the app as the tessellation resolution used for display and collision meshing.
+Source Code Path
+See HiNC App Anatomy for git repository links.
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
See Also
+-
+
Table of Contents
+ +Extended Cylinder Panel
+ +The Extended Cylinder editor edits one number on an ExtendedCylinder: its full
+length, measured from Z=0 and including the span below the start section. Its only home on
+either client is the Tool House cutter's Upper Beam, where the start section is the flute top.
Layout
+-
+
The desktop panel is a title over the same one field.
+Behavior
+-
+
Reach
+Both clients restrict this kind to the cutter's upper beam, by different mechanisms. In the desktop +client the switchboard's Extended Cylinder entry ships collapsed and is revealed by a property that +exactly one host sets — the milling cutter panel. On the web no Geometry slot offers the kind at +all: it is absent from the container kinds, so it can be neither a transformation geometry's inner +geometry nor a combination child, and it was taken out of the workpiece's raw geometry once a saved +project was found to reload one degenerate. The single surface that offers it is the cutter's Upper +Beam, which mounts the switchboard itself over its own six-kind list rather than being a Geometry +slot.
+Note
+A project saved earlier can still carry one on a workpiece, and the two halves of the tree
+disagree about it. The kind is serialized; only the start-section hookup is not. Such a project
+reloads with the raw geometry in place, and the Geometry slot's child builder tests the type
+against the whole kind map rather than against the slot's whitelist, so the ExtendedCylinder
+kind node and this editor still appear beneath it. The slot's own picker tests the whitelist,
+finds the type outside it and blanks — leaving an empty type dropdown above a child node that
+edits an Extended Cylinder. The bound reads zero there, because nothing is wiring a start
+section.
Source Code Path
+See HiNC App Anatomy for git repository links.
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
See Also
+-
+
Table of Contents
+ +Geometry Combination Control
+ +A GeomCombination is several geometries treated as one, and it has two faces on the +web: an inline editor that renders every child's own editor at once, and a Control-Tree panel that +manages the list only and puts each child on its own node. The desktop client has the inline shape +alone. There is no selection model on any of them — every child carries its own remove button.
+Layout
+The inline editor
+-
+
The Control-Tree panel
+Deliberately shallow: a list of read-only rows with Add, a per-row remove and Clear all, and a
+standing hint that an Item child node is where a child is actually edited. No editor is embedded
+here; each child is its own Item node — .../item-{index} — under this one.
The desktop control
+A title, then an Add and a Clear All button over a scrolling list of bordered cards, each +holding that child's own geometry management panel and a remove button — no index badge and no type +caption. Below the list sits a collapsed Combination Information expander reporting the aggregate +triangle count and bounding box, the latter as a read-only box control.
+Adding a Child
+The two clients differ here, and the web is the one that gained something:
+-
+
Five kinds may be children — Box3d, Cylindroid, StlFile, TransformationGeom and a nested
+GeomCombination, so combinations nest arbitrarily deep. CubeTreeFile and ExtendedCylinder are
+accepted by neither client's switch.
Behavior
+-
+
Source Code Path
+See HiNC App Anatomy for git repository links.
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
See Also
+-
+
Table of Contents
+ +Geometry Management Panel
+ +The geometry switchboard is one control: a kind picker over the kinds its host allows, and — unless +the host asked for the picker alone — the picked kind's editor beneath it. On the web it binds a +key into the shared object store, not an object, and every Control-Tree Geometry slot is this +same control in selector-only mode.
+Layout
+Web: a geometry-type dropdown; a separator, drawn only while a kind is active; then that kind's +editor. Picking a kind commits immediately — there is no Apply button. In selector-only mode +everything below the dropdown is dropped, which is the form each Geometry slot node shows.
+Desktop: a combo box on the left of a head line with a Convert menu on its right, over a bordered +content presenter that hosts the kind's own control.
+What It Binds
+The web panel has no target-geometry property. It takes a modelKey naming the object it edits and
+mutates what sits behind that key; a host that also owns the field holding the geometry passes an
+onCreate hook, so switching the kind rebinds the host's field rather than just the key. The
+desktop panel is the one that exchanges an IStlSource directly, through a getter and
+setter pair its host supplies.
Each host's field type decides which kinds it may offer; whether the kind survives a save decides
+which of those it does. A fixture's geometry and a transformation geometry's inner geometry are
+IGetStl; a workpiece's initial geometry is an IMakeXmlSource, which is why the
+cube-tree file — which is not an IGetStl — reaches that picker and no other.
Type compatibility alone is not sufficient, and ExtendedCylinder is the case that
+shows it. It is an IMakeXmlSource, so the workpiece's raw slot could hold one and once did — but
+its start section is supplied by a host at run time and is not serialized, so a saved project
+reloaded it with a zero-radius start and the shape degenerated. The kind is now offered by the one
+host that wires that source, the cutter's upper beam.
Reach
+The picker's own default is six of the seven kinds: Box3d, +Cylindroid, StlFile, TransformationGeom, +GeomCombination and CubeTreeFile. ExtendedCylinder is +in the kind map but out of that default: a host that can wire its start section names it explicitly. +Nothing is hidden by a flag on the web.
+The default is a guard rail, not a shipped list. Every host passes its own whitelist — the five +Control-Tree Geometry slots from the node context, the four inline hosts as a constant — so no +shipped surface renders the default at all, and the whitelist is what really decides reach.
+| Host | +Kinds offered | +
|---|---|
| Fixture geometry, mechanism anchor, a transformation geometry's inner geometry, a combination child | +the five container kinds — Box3d, Cylindroid, StlFile, TransformationGeom, GeomCombination | +
| Workpiece raw geometry | +six — the five container kinds plus CubeTreeFile | +
| Workpiece target geometry | +four | +
| Cutter upper beam | +six — Cylindroid, ExtendedCylinder, TransformationGeom, StlFile, Box3d, GeomCombination | +
A null geometry is a legal state of the model, but the web picker offers its None (unset) entry
+only when the host asks for it: the three General Setup slots and a transformation geometry's inner
+geometry do, while the mechanism anchor, the cutter upper beam and combination children do not, so
+those pickers cannot clear the slot. Clearing goes through the host's create hook with the literal
+kind None, which the container-aware endpoints map to null. The desktop combo always lists a
+None item.
Converting Is Desktop-Only
+Wrapping an existing geometry into a TransformationGeom — and extracting it back out +— exists only in the desktop client, as items in the Convert menu rather than as buttons. The +Convert to Transformation item re-titles itself Extract from Transformation when the current +geometry already is one; the combination item behaves the same way, and its extract is offered only +when the combination holds exactly one child. Three code-behind properties can hide the menu and each +of its two items, but no shipped host sets any of them.
+The web has no equivalent, and the difference is not cosmetic. Picking TransformationGeom in
+the picker creates a new, empty one and discards the geometry that was there — it does not wrap it.
In the Control Tree
+Geometry is a slot item type. Its child builder probes the slot's key for the type behind it and,
+when that type is a known kind, grows exactly one child node of that kind bound to the same key
+— so the slot node carries the picker and its single child carries the editor. Five kinds resolve to
+the shared sole-editor panel; TransformationGeom and GeomCombination instead map to panels of
+their own, because they grow further slot children rather than hosting one editor. The two
+file-backed kinds compose the referenced file into their node label.
Source Code Path
+See HiNC App Anatomy for git repository links.
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
See Also
+-
+
Table of Contents
+ +Geometry Panels
+ +The Geometry Panels are the editors for Geometry Objects, together +with the switchboard that picks which ITransformer places them. Two switchboards +run the folder: the Geometry Management Panel chooses a geometry kind and +hosts that kind's editor, and the Transformer Select Panel does the same for +a transformer.
+On the shipped SPA a Control Tree is where both switchboards are usually met. A Geometry slot or +a Transformer slot carries the kind picker, and the picked kind becomes the slot's single child +node, whose panel is that kind's editor. The same editors are also embedded inline, without a tree — +by Transformation Geometry Control for its inner geometry and inner +transformer, by Geometry Combination Control for each child, by the +Mechanism Builder page, and by the Tool House cutter's upper beam.
+A page belongs in this folder when it documents one geometry kind's editor, or a switchboard that +picks a kind and hosts that editor. The transformer switchboard lives beside the geometry kinds +rather than under a route because it sits in the same Control-Tree slot pattern and is embedded by +the same editors.
+Ordered the way the folder builds up: the primitive solids first, then the file-backed geometries, +then the operators that wrap and combine them, and last the two switchboards that host all of them.
+Geometry Kinds
+| Page | +What it edits | +
|---|---|
| Box3d Control | +A 3D box defined by min/max coordinates | +
| Cylindroid Control | +A cylindroid with radius values along the Z axis | +
| Extended Cylinder Panel | +An ExtendedCylinder and its full length | +
| STL File Control | +An IStlSource loaded from an STL file | +
| Meshed Geometry Panel | +A CubeTreeFile, the pre-computed voxel cube tree; the kind picker shows it as MeshedGeomFile |
+
| Transformation Geometry Control | +An inner geometry plus the transformer that places it | +
| Geometry Combination Control | +Several geometries combined into one | +
Every kind above is an ordinary entry in the same geometry kind map, and the switchboard resolves each
+one the same way. What separates them in practice is reach, not class: each host names the kinds its
+own model accepts, so CubeTreeFile reaches only the workpiece's raw-geometry picker, and
+ExtendedCylinder only the cutter's upper beam.
Switchboards
+| Page | +What it hosts | +
|---|---|
| Geometry Management Panel | +The geometry kind picker and the picked kind's editor | +
| Transformer Select Panel | +The transformer kind picker and the picked kind's editor, over all seven transformer kinds | +
Source Code Path
+See HiNC App Anatomy for git repository links.
+The editors and the two switchboards:
+-
+
The Control-Tree glue that makes them reachable:
+-
+
The REST controllers, one per kind:
+-
+
Table of Contents
+ +Meshed Geometry Panel
+ +The meshed-geometry editor points a geometry at a pre-computed voxel cube tree in a .wct file. Its
+model is CubeTreeFile, a reference to that file: neither the editor nor the
+controller loads the voxel data, which stays deferred until something actually needs the mesh.
Important
+The kind is CubeTreeFile; the label is MeshedGeomFile. The rename is display-only and lives
+in exactly two registries — the kind picker's option label and the Control-Tree node label — while
+the value sent to the backend stays the type name. The domain type carries the matching display
+name of its own. Any page describing a mesh source has to keep the two apart.
Layout
+-
+
The desktop panel is a read-only path field with Browse and Reload buttons. The web has no +Reload: the controller has no such action.
+Reach
+One slot offers this kind: the workpiece's Raw Geometry. The fixture's geometry and the
+workpiece's target geometry both exclude it, and it is not among the container kinds, so it can be
+neither a transformation geometry's inner geometry nor a combination child. The Control-Tree node
+carries the chosen file in its label, as MeshedGeomFile [<path>].
The desktop client reaches it differently. There, the workpiece page carries a Geometry Source
+combo — Common Geometry or Meshed Geometry — that decides whether this panel or the geometry
+switchboard is visible. The web has no such toggle: picking the MeshedGeomFile kind is choosing
+the meshed source. The classification survives on the server, which still reports a raw geometry of
+this type as a meshed one.
Key Model
+CubeTreeFile is the file reference; CubeTree is the voxel tree itself.
+The reader resolves the stored relative path and opens it with no extension test of its own — .wct
+is what the app writes and what every user-facing string names, not something the loader enforces.
The consumer that reads the data is the equipment-setup scene: it recognises a workpiece whose +initial geometry is a cube-tree file and builds the meshed geometry for the canvas, behind the +canvas's own Meshed Geometry display toggle.
+Source Code Path
+See HiNC App Anatomy for git repository links.
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
See Also
+-
+
Table of Contents
+ +STL File Control
+ +The STL file editor points a geometry at an .stl file rather than describing a shape: the model
+holds a file reference, and the editor is a picker over it plus a read-only summary of what was
+loaded. On the web the file lives on the server, not on the machine running the browser, and
+that difference shapes everything else on the page.
Layout
+-
+
Behavior
+-
+
Reach
+StlFile is the least restricted geometry kind. It is allowed in every Control-Tree geometry slot —
+the fixture's geometry and both of the workpiece's — is one of the container kinds, so it can be a
+transformation geometry's inner geometry or a combination child, and it is offered by the two inline
+hosts as well: a mechanism anchor's geometry and the cutter's upper beam. The Control-Tree node
+carries the referenced file in its own label.
Source Code Path
+See HiNC App Anatomy for git repository links.
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
See Also
+-
+
Table of Contents
+ +Transformation Geometry Control
+ +A TransformationGeom is an inner geometry plus the transformer that places it, and the +editor is those two things side by side. It has two faces on the web: an embedded two-card editor, +and a Control-Tree panel that shows only the status of each half and puts the editing on child +nodes.
+Important
+Choosing a kind here replaces, it does not wrap. Picking an inner geometry calls the +container-aware create endpoint, which constructs a fresh instance and discards whatever was +there. Wrapping an existing geometry into a transformation geometry, and extracting it back out, +exists only in the desktop client's +Geometry Management Panel Convert menu.
+Layout
+The embedded editor
+Two bordered cards, each badged with the live type behind it, over a short explanation of what a +transformer applied to a geometry means:
+-
+
The Control-Tree panel
+Status badges for the two halves and the same explanation, and nothing editable: the tree grows an +Inner Geometry slot and an Inner Transformer slot as children, and each of those grows its +own kind child. The registry deliberately overrides the kind → editor map here, so the two-card +editor never appears inside a tree.
+The desktop control
+A geometry-type combo over the inner geometry's own control, and a transformer group box. Its lists +are narrower on both sides: three inner geometry kinds — Box3d, Cylindroid and StlFile, with no +unset entry — and a transformer list restricted to No Transform, Static Translation, Static Freeform +and General Transform. Static Rotation is unreachable there for a second reason: the desktop +transformer picker never builds it into its base list, so it is absent whatever the restriction says.
+Behavior
+-
+
Source Code Path
+See HiNC App Anatomy for git repository links.
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
See Also
+-
+
Table of Contents
+ +Transformer Select Panel
+ +The Transformer Select Panel is the switchboard that picks which ITransformer
+occupies a transformer-valued slot and edits the one that is there. Both clients carry it under the
+same name: wwwroot-src/src/components/topo/TransformerSelectPanel.vue on the web,
+Mech/Topo/TransformerSelectPanel.xaml on WPF. For the model behind the picker — what each
+transformer does to a frame — see
+Handle Transform Matrix by ITransformer.
Surface
+The panel's principal home is a Transformer slot of the General Setup page's Control Tree
+(/general-setup). Four slots on that tree are Transformer slots:
-
+
A fifth shape appears wherever a TransformationGeom is grown as a tree node: its
+.../inner-transformer child is a Transformer slot too.
Two surfaces embed the panel outside any tree — the Mechanism Builder Page
+(/util/mech-builder), for the selected branch's transformer, and the inner-transformer card of
+Transformation Geometry Control.
Layout
+-
+
The panel takes a selectorOnly prop that drops everything below the dropdown, which gives it two
+forms:
| Form | +Where it is used | +What renders | +
|---|---|---|
| Embedded (default) | +Mechanism Builder page; the inner-transformer card of TransformationGeomEditor.vue |
+dropdown, separator, and the active kind's editor in one column | +
| Selector only | +every Control-Tree Transformer slot, through TransformerSlotPanel.vue |
+the dropdown alone | +
In a Control Tree the picker and the editor sit on two different nodes. buildTransformerChildren()
+grows at most one child under the slot, typed with the current kind, and that child's panel is
+SoleEditorPanel.vue, which resolves the kind through the same TRANSFORMER_EDITORS map and binds
+it to the slot's own key. The builder first probes the slot's key with getIndexType and returns no
+child at all when that probe fails or names a type outside the seven kinds, so a slot holding
+nothing is a picker with nothing beneath it. Selecting the slot shows the picker; selecting the
+child shows the kind's editor.
Transformer Type Dropdown
+The dropdown gets and sets the ITransformer behind the bound key. On mount, and
+again whenever that key changes, it probes the object's type with getIndexType; picking a different
+kind creates the replacement through POST /api/{Kind}/New unless the host supplied an onCreate
+hook, in which case that hook runs instead so the owning domain object is rebound alongside the
+IndexService entry.
Which kinds it offers is the allowedKinds prop, defaulting to all seven. A Control-Tree slot feeds
+it from the node's ctx.allowedKinds; none of the four General Setup slots narrows it, so all seven
+appear there. The WPF panel restricts through four properties on its code-behind instead:
+AllowedTransformerTypes, SelectionFilter, ShowNoTransform and ShowNotSet.
When the probed kind falls outside allowedKinds, both the active and the selected kind reset to
+empty: the dropdown blanks, and the embedded form prints No transformer attached. in place of an
+editor. The out-of-list transformer is not rendered. The WPF client resolves that case the other
+way — UpdateUI() leaves the combo unselected when no item matches, then still calls
+UpdateTransformerPanel() and builds the current transformer's content panel.
Transformer Kinds
+The web client offers seven kinds, one editor each.
+| Kind | +Editor | +What the editor holds | +
|---|---|---|
| StaticTranslation | +StaticTranslationEditor.vue |
+one Vec3 input, the constant offset | +
| StaticRotation | +StaticRotationEditor.vue |
+axis Vec3 input (with Normalize), angle in degrees, pivot Vec3 input | +
| StaticFreeform | +StaticFreeformEditor.vue |
+a 4×4 matrix grid with Identity and Invert (stored column-major, displayed row-major) | +
| DynamicTranslation | +DynamicTranslationEditor.vue |
+axis Vec3 input (with Normalize) and a step in mm | +
| DynamicRotation | +DynamicRotationEditor.vue |
+axis Vec3 input (with Normalize), angle in degrees, pivot Vec3 input | +
| GeneralTransform | +GeneralTransformEditor.vue |
+a scale field over two embedded sub-transformer cards | +
| NoTransform | +NoTransformEditor.vue |
+an identity-transform notice; no controls | +
The WPF client offers six of them — NoTransform, StaticTranslation, StaticFreeform,
+GeneralTransform, DynamicTranslation, DynamicRotation — plus a Not Set entry that leaves the
+slot holding a null transformer. It has no StaticRotation panel, and selecting NoTransform there
+simply empties its content area. The web picker offers no null entry of either sort: identity is
+expressed by choosing NoTransform.
Note
+The GeneralTransform editor is a composition, not a flat form: a scale field,
+then the StaticRotation and StaticTranslation editors
+embedded in their own bordered cards, titled Rotation sub-transformer and Translation
+sub-transformer. It keys the two sub-editors off POST /api/GeneralTransform/IndexRotation and
+/IndexTranslation, edits the scale itself, and owns no vector widget — every
+Vec3d field on it comes from the nested editors. The effective transform is
+T × R × scale × I. Its WPF counterpart Mech/Topo/GeneralTransformPanel.xaml takes the other
+approach: three Vec3dControls of its own (translation, rotation axis, rotation pivot) plus scale
+and angle text boxes, embedding no sub-panel.
Key Model
+-
+
Source Code Path
+See HiNC App Anatomy for git repository links.
+The switchboard and its editors:
+-
+
The Control-Tree glue and the two direct embedders:
+-
+
The transport and the strings:
+-
+
The REST controllers, one per kind:
+-
+
See Also
+-
+
Table of Contents
+ +HiNC App Anatomy
+ +Anatomy is the view ↔ model ↔ source reference for the shipped HiNC applications: one page per +component, each naming the widget tree the user sees, the HiAPI types behind it, and the files that +implement it. It documents how the shipped apps are put together — it is not a build tutorial, and +for that see Getting Started.
+The section is grouped by the surface that owns a page: a folder per live route that hosts a +Control-Tree branch or several pages, a single page where a route needs only one, and — where the +shipped surface owns nothing, as with a reusable control or a cross-screen rule — the source +directory the components actually live in.
+Important
+Anatomy makes no claim that Control-Tree ids are stable across versions, and no page may state
+one. treeRoutes.ts guarantees something narrower: an old id keeps resolving, because each
+regroup adds a migration hop. The ids themselves have moved repeatedly.
Finding the Page From the App
+| What you are looking at | +Where its anatomy is | +
|---|---|
| The window frame, its menu bar and its message bars | +App Shell | +
/execution, and ?tree=execution/… on it |
+Execution Page — its Mission branch is under Mission | +
/general-setup, and ?tree=equipment/… on it |
+General Setup Page — its Controller branch is under Controller | +
/machine-tool |
+Machine Tool — the route is a read-only preview of the same branch | +
/tool-house/:toolId?/:tab?/:subtab?, and ?tree=toolhouse/… |
+Tool House Page | +
/controller/:tab? |
+Legacy Controller | +
/util/file-explorer, /util/mech-builder |
+Utility Pages | +
/login, /preference/log |
+Platform — the two routes that document the platform rather than a machining step | +
| A geometry editor embedded in any of the above | +Geometry Panels | +
| A control that appears on more than one screen | +Widgets | +
| A rule that holds across screens rather than on one | +Conventions | +
Machinery under every screen — the Control-Tree engine, the ?tree= id surface, the locale bundles, the host process |
+Platform | +
| A directory in one of the source trees, rather than a screen | +By Source Directory | +
Sections
+-
+
The Two Codebases
+A page's Source Code Path section lists the files that implement it in the flagship web service. +The desktop client's counterparts are not listed at all: it takes no new feature work, and a +hand-maintained map of its files went stale faster than anyone read it. The two codebases are not +peers.
+HiNC-2025-webservice is the flagship — a Quasar SPA (Vue 3 + TypeScript + Pinia) served by +ASP.NET Core, and the only target for new feature work. Repository: +HiNC-2025-webservice.git
+HiNC-2025-win-desktop is the outgoing WPF client, kept here only so a reader familiar with the +desktop UI can find the equivalent web-service component. Do not start new work against it. +Repository: +HiNC-2025-win-desktop.git
+Tip
+The host process, its configuration and the address it listens on are documented in +Program and Hosting.
+Writing a Page Here
+-
+
See Also
+-
+
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 It Edits, and the One It Does Not
+The project carries both models side by side and serializes both. HardNcEnv is
+loaded from the project's own NcEnv element unconditionally; NcRunnerSuit — the
+runner plus the project's per-case dependency list — is read from its own nested element. The legacy
+element is a fallback source for the suit rather than a shared store: a project file that carries
+no runner element derives one from the legacy element at load time, and from then on the two drift
+apart, because every later edit lands on one of them alone.
The legacy model is still read at play time. The legacy NC runner is constructed over a delegate
+onto the project's NcEnv, so installing a replacement through this page's ⋮ menu propagates without
+rewiring, and a project-level switch selects which of the two pipelines plays. That switch defaults
+to the SoftNc pipeline and no screen in the web application changes it; it is a scripting property,
+EnableSoftNcRunner(API). The legacy NC
+optimisation route is the other live reader: it is taken whenever the switch is off or the session
+holds no played SoftNc layers, and it is handed MachiningProject.NcEnv directly.
What Only This Screen Edits
+Three settings on this screen have no editor anywhere else in the web application — neither on the +branch, nor on any other page.
+Align P0, and its undo history
+The Coordinate Table tab — that is the tab button's label; ISO Coordinate Table is the heading over the panel it opens — carries a third row action beside P0 and M0: Align P0, +titled "Move workpiece+fixture so ProgramZero coincides with this ISO entry (mutates +Fixture.GeomToTableTransformer)". It does not write the coordinate — it writes the fixture's +geometry-to-table transformer, moving the workpiece and fixture so that program zero lands on the +offset the row holds. The write goes to the authored setup equipment and reports the edit, so the +runtime face follows at the next rebuild. The branch's Work Coordinates leaf carries P0 and M0 and +no alignment.
+The endpoint keeps no undo slot. It answers with the assigned translation plus the transformer as it
+stood before and after the write, both serialized as XML, and the tab holds the history
+itself: Undo Align and Redo Align buttons over two component-local stacks, the undo stack
+capped at 32 entries and the oldest dropped past it. A third button sits beside them, Show on
+Display, which belongs to the viewer rather than to the history: it is a bound toggle over the
+IsoCoordinate rendering flag, carrying a visibility / visibility_off icon and writing through
+the same flag endpoint the Scene dropdown writes. The two datum tabs carry the same button over
+HeidenhainCoordinate. Each step posts a stashed snapshot back to a
+stateless revert endpoint, which parses it and assigns the result as the fixture's transformer. Both
+stacks are cleared whenever the tab's has-a-project prop changes, and the snapshot the component
+believes is current is only what it last saw — a transformer changed from another surface is
+overwritten wholesale by the next undo or redo rather than merged.
The engine exposes the same operation as a script call, +AlignWorkpieceProgramZeroToIso(API); the +history is the screen's own.
+Enable Shortest Rotary Path
+The Config tab's single toggle writes EnableShortestRotary, which
+constructs true. Its banner reads "Shortest Rotary Path: optimises rotary axis motion to use
+the shortest angular distance between positions." On the legacy pipeline the flag gates one step:
+each rotary axis of a block is cycled into the ±180° window around the previous block's value. The
+toggle is narrower than it reads, because that same cycle is applied unconditionally on
+Heidenhain — the brand branch runs it before the flag is consulted, so
+clearing the box changes nothing there.
The runner's dependency layer declares no counterpart: no brand parameter table, no generic config
+and no branch leaf carries a shortest-rotary switch. The nearest thing the runner pipeline has is the
+Heidenhain M126 / M127 pair, which is read from the program text rather than from any setting.
Heidenhain master-axis character
+The Brand tab grows a second card while the brand is Heidenhain, holding one select over A,
+B, C labelled Master-axis character. It writes
+HeidenhainMasterAxisChar, a character face over the integer axis
+direction the PLANE … SEQ solution family is resolved against. Reads are normalised: anything that
+is not B or C after trimming and upper-casing becomes A. On the runner pipeline the master
+rotary is derived rather than configured — it is the first declared rotary axis — so the branch
+has nothing to expose and no leaf for it.
What It Shares With the Branch
+Naming what is not exclusive matters as much. The CNC brand, the stroke limits, the rapid feed, +the tool-change time, the tool offset table with its tool-house dependence, the work coordinates +with their P0 and M0 actions, and the Heidenhain datum preset and datum shift tables all have +editors on the branch — see +Brand Matrix for which of those leaves each brand grows. The +Max Speed (rpm) column has a counterpart too: on the runner pipeline the rotary speed ceiling is +read from the rapid-feedrate config, and the legacy import funnels this field into it at rpm × 360 +deg/min, so the branch's Rapid Feedrates leaf edits the runner-side number.
+One control on this screen writes a value the branch also writes. Set ideal offset dependent on +tool house is a project-level configuration flag rather than a member of either NC model, and both +the Offset Table tab and the branch's Tool Offsets leaf read and write that one flag. The two +tables stay separate: this tab's Refresh from Tool House recomputes +MillingToolOffsetTable from MachiningToolHouse, +while the branch's refresh recomputes the runner's own tool-offset table.
+Two Faces of a Work Coordinate
+The work-coordinate marker is one displayee class serving two providers. This page's viewer draws it
+from IsoCoordinateTable — a
+IsoCoordinateTable instance owned by the legacy model, which
+constructs with G54 … G59 and G59.1 … G59.9 all at zero. The General Setup canvas builds the
+same displayee over the active runner's effective
+IIsoCoordinateConfig instead, which on most brands is the brand
+parameter table. Same marker, same code, two stores.
Selecting a row on the ISO Coordinate Table tab writes the marker's id onto the shared Execution +displayee, so it decides which offset the marker draws at. It decides that whether or not the marker +is drawn: all three coordinate flags are off in the shipped rendering-flag set — as are the machine +tool and the cutter, leaving the workpiece, the fixture, the dimension bar and the cutter-location +strip as the four that arrive on — so on an untouched project the selection column moves something +invisible until Show on Display is pressed. The datum tabs also carry a +single-selection column, but nothing is sent when it changes — there the selection is a highlight and +nothing more.
+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:
+-
+
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
+-
+
Source Code Path
+See HiNC App Anatomy for git repository links.
+Web Application
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
HiAPI Engine
+-
+
See Also
+-
+
Table of Contents
+ +Control Tree
+ +The Control Tree is the engine shared by the two tree-shaped pages of the web client: the Execution
+page at /execution and the General Setup page at /general-setup. Each page creates exactly one
+host — useControlTreeHost('execution') or useControlTreeHost('equipment') — provides it to its
+panes under one injection key, and the host owns everything after that: the node graph, the branch
+builders, the selection and the ?tree= query that carries it, the editor panel a selection resolves
+to, and the mission enable ticks. The Tool House page at /tool-house reuses the same node shape and
+the same item-type registry but renders a branch as nested tabs and creates no host.
Important
+Node ids are role paths, not identities that survive a version change. Anatomy makes no claim that
+a ?tree= id is stable across releases; see Tree Ids and Routes
+for the migration hop that keeps an older link resolving.
The Node
+Every branch of every tree is built from one structure, ControlNode, declared in itemTypes.ts:
-
+
The split between id and key is the load-bearing one. A key is re-minted every time the host
+re-indexes the model, so nothing durable may hang off it; an id names a role in the model and
+therefore survives a rebuild, which is why expansion state, the URL and the landing selection are all
+keyed on ids. Two id families are positional rather than role-based: Mission entries are
+${parentId}/${index} and Program files are ${parentId}/${index}, so moving or deleting a mission
+command renumbers its siblings and a link to one names a position in the list rather than that
+command.
The Item-Type Registry
+ITEM_TYPES maps an itemType string onto a TreeItemDef carrying up to three members:
-
+
Two flavours of type share the map. Slot types (Geometry, Transformer) have a kind picker as
+their panel and grow at most one child, the concrete kind, at the kind-independent id
+${slot.id}/type — so switching a slot's kind keeps both the selection and the expansion. A slot
+holding nothing grows no child at all, which for the four geometry slots whose picker offers None is
+an ordinary state rather than a failure. Kind types (Box3d, StaticTranslation, …) register
+SoleEditorPanel, which looks the concrete editor up in the geometry and transformer editor maps and
+binds it to the node's key. The two composite geometry kinds do not embed nested editors:
+TransformationGeom grows an inner-geometry and an
+inner-transformer slot, and GeomCombination grows one slot per item, so the tree owns the structure
+and every panel stays shallow.
The map is one flat namespace assembled in itemTypes.ts: the Group stem type, the geometry,
+transformer, workpiece-leaf, thermal-condition and spindle entries, and the Execution root's own type
+are written inline, and five per-wave registries are spread in beside them. The three roots the panel
+host renders itself — Machine Tool, Fixture and Workpiece — have no entry here at all.
| Wave | +File | +What it contributes | +
|---|---|---|
| Mission | +missionItemTypes.ts |
+MissionRoot, MissionCommand, MissionSection; section children at ${command.id}/${sectionId}; the per-kind bespoke editors, with a generic field editor as the fallback |
+
| Program | +programItemTypes.ts |
+ProgramRoot, ProgramFile, ProgramConversionFile; writeback conversions at ${root.id}/dst-${index} |
+
| Tool House | +toolHouseItemTypes.ts |
+the tool collection and the per-tool editor branches under toolhouse/tool-<id>/… |
+
| SoftNc controller | +softNcItemTypes.ts |
+the controller root and its leaves on two planes — …/machine/<seg> and …/program-data/<seg> — a core every runner grows plus the brand-driven ones |
+
| Runner suits | +runnerSuitItemTypes.ts |
+CsvRunnerRoot and ClRunnerRoot, flat single-panel leaves with no builder |
+
The Program branch is fetched whole: its root builder takes one file-tree response plus one
+conversions response and stashes each file's raw subtree on the node, so the file nodes' builder only
+maps what is already in hand. The SoftNc branch is the opposite — its builder reads the runner
+snapshot once, and with no runner installed grows nothing at all. Where one resolves, the two plane
+stems appear over a fixed core that stands for every brand: six machine leaves, and Work Coordinates
+and Tool Offsets on the program-data plane. The leaves beside that core are the brand-driven ones —
+each appears only where the snapshot reports its backing table, so a brand switch changes which nodes
+exist. One flag does double duty: the Siemens $TC_DP flag adds its own two leaves and renames the
+core Tool Offsets leaf to its ISO G43 H reading, so the two offset ledgers do not read as one.
The two runner-suit nodes are conditional, and the host — not the registry — decides. With a project
+open, a CSV or CL controller node is built when its Preference checkbox is on, or when the current
+?tree= value starts with that node's id. The checkbox is device-local and off by default; the route
+is the only other
+reveal, deliberately not the persisted last selection, so a node switched off while it is selected
+does not resurrect itself. Stepping off comes first: when the selection sits on a node the user has
+just unchecked, the host re-points the URL at the page root before rebuilding.
Building a Branch
+buildSubtree(node, services, depth) is the whole builder. It looks the node's type up, returns
+without touching children when the type declares no buildChildren or when depth has reached
+MAX_DEPTH (12, a guard against a self-referencing model), and otherwise awaits the builder, recurses
+into each returned child, and assigns the result. Building is eager: a branch is materialised in full
+at build time rather than lazily on expansion.
Builders receive one service, registerKey, which registers a freshly minted IndexService key with
+the page's cleanup hub — see Webapi with Hub-Cleanup Assistance.
rebuildBranch(node) is buildSubtree plus one thing: it adds the node's id to the expansion list so
+the fresh children are visible, and reports a failure as a toast named after the node's display label.
The structure-changed event
+A panel that has changed the shape of the model emits structure-changed (or type-changed, wired
+to the same handler). The optional payload is a StructureChangeRequest:
-
+
A mission command's move, duplicate and delete re-scope to the parent list, because those operations +rewrite the parent's children, and they name the post-operation selection: the clone's slot after a +duplicate, the neighbouring index after a delete, the parent list when the delete emptied it. Moving +an entry out of a nested list re-scopes to the grandparent. The controller brand switch and the +runner's Object-Management install re-scope to the controller root. The Tool House type selectors and +a flute add re-scope to their own node; everything that rewrites the tool collection — a new tool, a +duplicate, a delete, an id rename, an Object-Management install — re-scopes to the tool-house root, +and a flute delete re-scopes to its fluting parent.
+Whole-tree rebuilds, and the refreshes that are not rebuilds
+rebuildTree() replaces the root array outright, and runs after an Object-Management swap of the
+machine tool or the fixture, after a blank machining chain is created, and after either runner-suit
+preference flips. On the execution host, an
+execution-status transition re-runs buildSubtree on the Program root directly rather than through
+rebuildBranch, so a run does not force that branch open on every transition.
Two refreshes deliberately mutate existing nodes instead of rebuilding:
+-
+
The mission one exists because a rebuild of the Mission branch re-mints the per-build stamp that
+forms part of the editor's remount key, and remounting an open editor mid-edit would cost the user
+their cursor and any staged autosave. The geometry one is the same principle without the stamp: a
+title change and a source-file change alter no structure, so the label is all that has to move, and
+replacing the node under an open file editor would buy nothing.
The commit chain
+The tree has no component bubbling, so the full post-edit commit chain lives on each node as
+ctx.afterChange, composed level by level as the branch is built. A GeomCombination item's chain
+cleans that combination's aggregated cache before running its parent's chain; the fixture's chain ends
+in a geometry-cache clear; the two workpiece geometry slots re-commit the swap-in before clearing
+their own cache, and skip that re-commit when the slot has been set to none; the fixture's two anchor
+transformers re-commit and then clear the geometry cache, while the workpiece's two re-commit and
+clear nothing, since a placement change invalidates no cached solid. Tool-house nodes all share one
+refresh. Every other family carries a no-op: the Mission, Program, spindle, SoftNc and
+thermal-condition nodes and the workpiece's Mesh and Material leaves have panels that write straight
+to the model, and the pages' canvases draw that model; the group stems and the Machine Tool and
+Workpiece roots have no field editor to commit for at all.
Selection and the Dirty-Switch Gate
+Every selection change funnels through requestSelect(id). It returns immediately when the id is
+already selected; otherwise it awaits the mounted panel's optional tryConsumeBeforeSwitch() and
+abandons the switch when that returns false.
Three entrances use it: a click in the tree, a change to the URL's ?tree= value, and a select-node
+event from the mounted panel — the wire the panel host puts on every registry panel, so a group stem's
+child list, a mission list's entry row and the Program branch's conversion jumps all arrive through
+that one. A fourth path — the selectId of a structure change — assigns the selection directly and
+skips the gate.
There is no “nothing selected” state. Re-clicking the selected row makes the tree yield null, and
+that is ignored; an empty ?tree= lands on the persisted last selection for that page when the built
+tree still contains it, and on the page's root otherwise. The editor row is therefore always alive,
+and reclaiming its height is the row's own collapse toggle rather than a deselection.
The gate itself is registered by the panel host: the mounted registry panel is handed to the host
+through a template ref, and the host asks that instance for tryConsumeBeforeSwitch. Panels that
+write one field per request need no gate and register none. One panel exposes it —
+MissionCommandSlavePanel:
-
+
The same gate is run before duplicating a command, so the clone is made from what has just been typed +rather than from the last saved state.
+Display Labels
+nodeDisplayLabel(n) returns the translation of labelKey — with labelParams interpolated — when
+one is set, and the verbatim label otherwise. labelKey is therefore the form every role label the
+client translates itself takes, while label is the storage for text it cannot: server-composed
+mission titles, file-backed geometry paths and engine type names. The translator function is read
+inside the call rather than captured, so a language change re-renders the whole tree's role labels.
Two labels are composed rather than looked up. A mission command's label is the title the server
+composes for it — the command's kind name in the request language, with the command's own text in
+brackets when there is one, as in Script [Warm-Up]. That title always arrives filled in, so the
+node's labelKey is always dropped and the tree's own kind-name fallback is a safety net that never
+fires in practice. Because titles are composed server-side per request, a language change also
+re-pulls the command entries and rewrites those labels in place. A file-backed
+geometry leaf carries its source file in brackets, so sibling instances read apart:
+StlFile [Geom/x.stl]. The voxel kind is renamed for display, appearing as MeshedGeomFile [...]
+rather than by its type name.
Group stems take the same treatment for their intro text: infoKey wins over info, and a stem with
+neither falls back to a sentence naming the group.
The Panel Host
+PrimarySlavePanel decides what the editor row shows, in this order:
-
+
What decides a remount is the mounted component's key, ${id}|${key}|${stamp} — the node id, the node
+key, and the mission stamp where the node has one. So the editor remounts when the selection moves,
+when the bound object's key is re-minted, and when the Mission branch was rebuilt beneath it. That
+third term is exactly why the mission label refresh above mutates the node instead of rebuilding.
On the Execution page the editor row grows a header: the transport bar is pinned above the scroll area +whenever the Execution root or any of its descendants is selected, so the run controls never scroll +away; that one instance also owns the transport's function-key shortcuts, which are attached for as +long as it is mounted.
+A pure Group stem is not a node without an editor. Group registers GroupInfoPanel, whose body is
+the stem's intro line followed by a clickable list of its children; a row click emits select-node
+and the host moves the selection there. The General Setup root, the two Anchor stems, the workpiece
+Material stem and the controller's two plane stems are all of this kind.
The General Setup page adds a second slave, ContentSlavePanel, which resolves contentPanel from
+the same registry. Three item types declare one — the spindle root and its scalar and contour section
+types — and all three name the same contours view, so the charts stay mounted while the selection
+moves across the spindle branch: the component takes no props and is mounted unkeyed on purpose.
+Every other selection shows the column's empty hint.
NodeTabCascade is the third consumer of the registry. It renders a branch as nested tabs rather than
+tree rows: the node's own panel on top, a single child inlined below it, several children as a dense
+tab strip in which only the active child mounts. The active tab is remembered per role path at module
+scope, with the per-tool segment wildcarded, so switching tools keeps every level's tab. Its panels
+report through an injected cascade host that carries the emitting node, because — unlike the tree's
+one-panel-at-a-time model — several panels are mounted at once.
The Tick Column
+The tree runs a strict tick strategy, and a node hides its checkbox unless it is a mission command or +a mission section that carries its own enable flag. Writing the ticked set walks every command node at +any depth: a command whose state changed is PUT to the Mission API and, on success, has the new state +copied onto its section children; a section whose state changed is dispatched to its kind's writer. A +failed write raises a toast and leaves the model untouched, so the getter re-derives the old value and +the box snaps back.
+A row dims when it is itself disabled — a disabled command, or a section whose own flag is off — or +when an ancestor command is disabled, which is how a disabled command greys its whole subtree while +each descendant keeps its own state. The switch decides only whether the command runs: a disabled +command stays fully editable.
+Layout
+-
+
Both dock rows and both column widths are device-local browser preferences, shared by the two pages +through one preferences module; each page keeps its own visibility record, and both pages share one +expansion list because their id spaces do not overlap.
+Lifecycle
+The host is created while its page's script runs and provided before the dock mounts; the page's
+onMounted then awaits initialize(). That call connects the cleanup hub and builds the tree — on
+the equipment host, after re-indexing the machine tool, the workpiece and the fixture in that order —
+and finishes by adopting whatever the URL's ?tree= names. A loading flag is held for the whole call,
+which is what the tree and editor rows render their spinners from; later rebuilds mutate an
+already-populated tree and never re-enter it. A missing project, or a project without a fixture or
+workpiece, answers 404, and those are treated as empty states rather than errors, so no toast appears.
+Without an open project the Mission, Program and controller branches are still declared, as childless
+stems: their builders run only once a project is loaded.
Because the layout keys its keep-alive wrapper on a project epoch, every page is destroyed and rebuilt +when the project changes, and a host is therefore created once per project per page.
+Source Code Path
+See HiNC App Anatomy for git repository links.
+Web Application
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
See Also
+-
+
Table of Contents
+ +Internationalization
+ +The web client ships three locales — en, zh-Hant and zh-Hans — over one message shape, and it
+owns no route: the mechanism is a vue-i18n instance created in the i18n module, a boot file that
+picks the locale before the application mounts, a single switch function every locale change goes
+through, and two Node scripts that gate the production build. English is not one locale among three;
+it is the schema the other two are written against and the text every unlocalized path falls back to.
+The user-facing gesture that triggers a switch is documented under
+Language Selection SubMenu, and the vocabulary the
+translations are held to under Translation Remarks.
The Three Bundles
+Each locale is a folder under the i18n directory holding one file per namespace plus an index file
+that re-exports them as a single object. The three folders carry identical file lists — the same
+twenty-three namespaces, from apiErrors and auth through tree and widgets — because a
+namespace's keys are added to all three locales in the same change. Keys are named
+namespace.area.element in lowerCamelCase and are split by the UI region they serve rather than by
+the source file that reads them.
English is the schema, mechanically and not by convention. wwwroot-src/src/i18n/schema.ts exports
+MessageSchema = typeof en, and each Chinese locale's index file annotates its own message object
+with that type, so a key missing from a Chinese namespace is a type error at the annotation rather
+than a silent English fallback at runtime. schema.d.ts augments vue-i18n's DefineLocaleMessage with the
+same shape, which registers it as the global message schema for call sites. Type checking is not part
+of the production build, however: wwwroot-src/package.json runs vue-tsc under a separate lint
+script, and build runs the two i18n scripts described below instead.
The instance is created with legacy: false, an initial locale of en and fallbackLocale: 'en'.
+The resolution chain is therefore current locale → English → the key string itself, so a key missing
+from a Chinese bundle renders English with no runtime signal, and a key missing everywhere renders as
+its own dotted name.
Quasar's own component texts are a parallel bundle with different code names, mapped in the same
+module: en to Quasar's en-US pack, zh-Hant to zh-TW, zh-Hans to zh-CN. All three packs are
+static imports rather than a dynamic import built from the locale code, because a template-literal
+import on a bare package specifier is not statically analysable by the bundler.
Applying a Locale
+applyLocale(code) in the i18n module is the only place a locale change happens. It first maps its
+argument through normalizeLocale, falling back to en, and then does six things in one call:
-
+
normalizeLocale maps any language tag — the server's, the browser's, or a stored one — onto a
+supported locale or null. An exact match wins; otherwise a tag beginning en becomes en, and a
+tag beginning zh becomes zh-Hant when it carries the hant script subtag or a TW, HK or MO
+region, and zh-Hans otherwise. Anything else yields null. applyLocale and the boot file's
+browser-language fallback each turn that null into en; the boot file's read of the stored value
+instead treats it as a cache miss and asks the server, so an unsupported stored code takes the
+cold-start path rather than pinning English.
Only two modules call applyLocale: the boot file, and the app-state store's language action.
+The store updates its own languageCode optimistically, POSTs the new value, adopts the current and
+available the server echoes back, and only then applies the locale — so the UI text flips after the
+server has accepted the value, and a failed POST rolls the store reference back without any visible
+language change.
Boot
+wwwroot-src/quasar.config.ts lists three boot files in the order auth, i18n, routine-toast.
+The i18n file is placed after auth because auth patches the global fetch for the 401 login gate,
+and the language request the i18n file makes must go through that patch on builds where login is
+enabled.
The boot file installs the plugin and then reads the hinc.lang value:
-
+
Every locale step is individually guarded, because a boot file that throws does not degrade to +English — the client entry logs the error and never mounts. The one unguarded step is installing the +plugin itself, without which nothing in the application can render a translated string at all. The +worst case the guards preserve is an application that mounts in English.
+The shipped wwwroot-src/index.html is static and carries lang="en", so the very first painted
+frame always reports English on the root element; applyLocale corrects it in the same tick as mount.
What Re-Renders
+A locale change is a write to one reactive ref, so everything that reads a translation inside a +render or a computed re-evaluates on its own. Five things follow it in practice:
+-
+
File Explorer sorting is the one thing that does not follow on its own. The shared collator is a
+live let binding rather than a reactive one: applyLocale rebuilds it for the new locale, but no
+render tracks it and FileExplorer.vue watches no locale, so a listing already on screen keeps its
+pre-switch order until the next folder load or sort-spec change re-runs the comparison. Because the
+binding is live, a caller must invoke collator.compare(...) in place; capturing the instance or
+extracting its compare would pin the pre-switch locale for good. explorerSort.ts is its only
+consumer, and no bare localeCompare — whose undefined locale argument means the browser locale
+— is called anywhere in the client.
Numbers deliberately do not follow the locale. Intl.NumberFormat is used nowhere in the client, and
+the only toLocale* calls are two triangle counts and two timestamps, each passed the app locale
+explicitly rather than defaulting to the browser's. NC and machine-tool quantities go through no
+locale-aware number formatting at all, so a coordinate never picks up a comma decimal separator from
+a locale that uses one.
Control-Tree Node Labels
+A Control-Tree node carries both a label and an optional labelKey, and nodeDisplayLabel(n) in
+itemTypes.ts returns the translation of labelKey — with the node's labelParams interpolated —
+whenever one is set, and the verbatim label otherwise. nodeDisplayInfo applies the same rule to a
+group stem's intro text through infoKey over info. The translator function is read inside the call
+rather than captured at build time, so the computed that maps nodes onto the rendered tree tracks the
+locale ref and the whole tree's role labels change on a switch.
The two fields overlap on purpose. label is required on the node type and labelKey is optional,
+so a fixed role label is written both ways: the key that actually renders, and the verbatim English
+sitting beside it as the fallback nodeDisplayLabel returns when no key is set. label is also the
+only storage for text that can never be a key at all — server-composed mission titles, file-backed
+geometry paths, and engine type names. It is the first of those two jobs, not the second, that makes
+the verbatim English on node labels the largest single class in the census allowlist: fixed role
+labels the census can see but the screen never shows, classified rather than removed because the
+node type requires the field.
Mission command nodes are the one family that never takes the key path. A node's label is the title
+the server composes for the command — the kind name in the request language, with the command's own
+text in brackets when there is one — and Missions/MissionController.cs fills that title in for every
+entry: from the command's own composition where it implements the engine's title contract — whose rule
+keeps the kind name at the front and never drops it — and from the kind's display name, resolved in the
+request culture, where it does not. Because a title is therefore always present, the node's
+labelKey is always dropped and the client's kind-name fallback never fires. Since the composition
+happens on the server per request, the tree host watches the locale and re-reads the command entries,
+rewriting those labels in place; it deliberately does not rebuild the branch, which would remount an
+open command editor mid-edit. The kind keys are still built as tree.mission.kind.${commandType}, and
+they do render — AddCommandDialog.vue prefers one over the server's label for every kind that has
+one — while the key lint resolves the template shape by wildcard rather than reporting an orphan.
The Server Half
+The language preference is server-held. GET /api/preference/language answers
+{ success, current, available }, where current is the persisted UserConfig.LanguageCode —
+default en — and available is the server's own supported list, en, zh-Hans, zh-Hant. POST
+rejects an unlisted code with 400 and otherwise writes the value and saves the user configuration,
+which round-trips through the XML user config file. The localStorage key holds only a paint-time
+hint and loses every disagreement with this value.
Some response text is composed on the server and never passes through a bundle.
+PresentCatalogService resolves the effective language per request in this order: an explicit ?lang=
+query, then the request's Accept-Language header, then the persisted preference, then English.
+Because the header sits above the saved preference, a browser set to a different language than the
+application would win by default — so the client sends ?lang= explicitly, from a currentLang()
+helper returning the app locale, on the endpoints whose text is localized server-side: the
+selected-step info, the execution strip chart, its step-property picker and its colour guide, the
+mission command entries, catalog and field descriptors, and the step-present key list. The mission
+controller resolves the same
+chain into a per-request UI culture and injects it into the command text source, so the culture rides
+on the request rather than on the thread.
Two further server-originated string classes are localized on the client instead:
+-
+
The step-present labels have a third home again: they ship beside the executable as
+catalog.{lang}.json files that overlay translations on top of the live English attribute data.
+English never comes from those files, and a missing file, a missing key or a parse failure simply
+means English.
The Glossary and the Build Gate
+Two Node scripts stand between the bundles and a shipped release. wwwroot-src/package.json defines
+lint:i18n as census.mjs followed by lint-glossary.mjs, and build as lint:i18n followed by
+quasar build — so either script failing stops the production build. The development server runs
+neither.
lint-glossary.mjs bundles each locale's index through esbuild, flattens the three message trees into
+key/value maps, and applies four checks, every one of them a hard failure:
-
+
The tables the third and fourth checks read are written at the top of the script itself.
+glossary.yaml, the large harvested term list that sits beside the bundles, is generated data that
+the application never imports and the lint never reads: it is where a reading is settled before it is
+written into a bundle, not an input to the gate.
census.mjs guards the opposite direction — text that never became a key. It scans the same tree for
+user-visible English string literals reached through display-named template attributes, display-named
+object properties, notification and dialog shorthands, and prose text nodes; subtracts a classified
+allowlist of concrete text-and-file pairs; and compares what is left, per file, against a baseline.
+The gate fails only when a file's residual count rises, so cleanup can land incrementally — but the
+baseline is currently empty, which allows zero residual strings in any file and makes the effective
+rule that a newly hard-coded display string fails the build unless it is added to the allowlist with
+a reason.
+Allowlist entries whose file field is a glob or a brace list are classification notes rather than
+matchers; only concrete paths participate.
Strings Outside the Bundles
+Three classes of user-visible English render in every locale — two because an interpolation hole +hides them from the census, which discards any literal containing one, and one because the allowlist +deliberately keeps it:
+-
+
A fourth class is untranslated by design rather than by escape: the dynamic label text on a
+Control-Tree node — a file-backed path, an engine type name, and the user's own words inside a
+server-composed mission title — and the identity strings the allowlist classifies alongside it, which
+are backend type names rendered inside a translated sentence, axis and unit column headers, and brand
+names. The role-label half of that same allowlist class is a different case again: stored English that
+never reaches the screen at all, because a labelKey always resolves ahead of it.
Source Code Path
+See HiNC App Anatomy for git repository links.
+Web Application
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
See Also
+-
+
Table of Contents
+ +Platform
+ +The machinery every route sits on and no user can point at. A page in this folder documents +something that has no owning screen: the Control-Tree engine two pages share, the id and route +surface three pages hang off, the state that survives a navigation, the locale bundles behind every +label, the gate in front of the whole app, and the host process under all of it.
+The boundary with Conventions is which side of the page the reader +is on. Conventions holds a rule a page author must follow; Platform holds machinery a page +author must understand and never touches.
+Two of these are ordinary routes rather than mechanisms — /login and /preference/log. They sit
+here because what they document is the platform surface, not a step in a machining workflow.
Ordered from the machinery a page sits closest to, outward to the host process beneath it.
+Pages
+-
+
See Also
+-
+
Table of Contents
+ +Log Viewer Page
+ +The Log Viewer is the read-only screen at /preference/log, route name preference-log, that
+renders the web service's own log file for the current day. It is not a Control-Tree page and takes
+no ?tree= query: the whole page is one toolbar over one scrolling text area. Despite the route
+path it is not an entry of the Preference dropdown, and it is not under /util/.
Reaching the Page
+The only navigation into the page is the Show Log button on the menu bar. It sits in the bar's
+right-hand group, after the active-page title and its separator and before the logout button, and it
+carries the article icon with the tooltip View the application log. The button is rendered
+unconditionally — no route, project, licence or authentication state hides it — so it is reachable
+from every page in the shell. The Preference dropdown carries the Language submenu, the CSV and CL
+Controller visibility checkboxes and Show Physics Options, and no log entry; the Page dropdown does
+not list the route either.
The route sits inside the main layout, so the page renders under the same menu bar and footer as
+every other page, and it is behind the login gate whenever that gate is on — the router's navigation
+guard admits only the login route itself while the session is unauthenticated. The route carries an
+i18n title key, so the browser tab reads Log Viewer - HiNC and the menu bar's active-page
+indicator reads Log Viewer.
What It Reads
+Two endpoints, both on the project controller, and both served from the same file:
+| Request | +Answer | +
|---|---|
GET /api/project/logs |
+{ date, content } for today's file; 404 with a message when there is none |
+
GET /api/project/download-log |
+the same file as a text/plain attachment; 404 with the same message when there is none |
+
Both compose logs/log-{yyyy-MM-dd}.txt beneath the process's current working directory and date the
+name from the server's local clock. Neither supports a range or a tail: the whole file is read into
+memory and returned in full on every call, so an auto-refresh at a short interval re-transfers the
+entire log each tick. Any failure inside either handler answers 400 with the exception message.
The two client wrappers part company on that 404. The wrapper over logs normalises it into an
+empty result rather than an error, which is what gives the page a clean “no log yet” state
+instead of a red panel; every other non-OK status there is raised as a typed error carrying the HTTP
+status and the response body. The download wrapper normalises nothing — 404 included, every non-OK
+status becomes that same typed error, a path the disabled-without-content Download button makes
+hard to reach. Because the controller declares no anonymous carve-out, both endpoints fall under the
+authenticated-user fallback policy when the login gate is enabled.
The date badge shows whatever date the last answer reported. On the 404 path that date is not the
+server's — the client fills it from the browser's own clock in UTC — so with no log file present the
+badge can read a different day than the file name the server would have used.
Refresh and the Auto Interval
+The page loads once on mount. After that every reload is driven by one of three things: the
+Refresh button, the Retry button inside the error panel, or the auto-refresh timer.
Auto-refresh is off by default. The Auto toggle arms it and the interval select beside it chooses
+the period from a fixed list of 2 s, 5 s, 10 s and 30 s, defaulting to five seconds; the
+select is disabled while the toggle is off, but it keeps its value. The period is picked from that
+dropdown rather than typed, so it takes effect on selection. A change to either control tears the
+existing timer down, and a fresh one starts only while the toggle is still armed: an interval change
+therefore takes effect immediately but also restarts the countdown, while disarming the toggle
+simply stops the timer. Arming the toggle schedules the first automatic load one full interval
+later; it does not fetch straight away.
Ticks are not coalesced with a request already in flight — each tick starts the same load +unconditionally.
+Neither the toggle nor the interval is persisted. They are page-local state, held in neither the +browser's local storage nor the server-side user preferences, so a browser reload returns both to +their defaults.
+The Page Is Kept Alive
+The shell caches routed pages in a keep-alive keyed on the project epoch. Leaving the Log Viewer
+therefore deactivates it rather than unmounting it, and the page's teardown hook — the one that
+stops the timer — does not run. Two consequences follow. An armed auto-refresh keeps polling the log
+endpoint in the background after navigation away, until the keep-alive is rebuilt by a project change
+or the browser reloads. And returning to the page does not re-run the initial load: the previous
+content, its last loaded stamp, the toggle and the chosen interval are all still there, and the
+view refreshes on the next tick or on Refresh.
Errors and Scroll Position
+A failed load fills the page's error state and also pushes one foreground line onto the shell's +routine-progress footer, so the failure is visible from any page. The error panel takes precedence +over content in the text area, so a failed refresh hides the text that was already loaded — but only +until the next attempt starts, because the error text is cleared before that request goes out. The +content itself is never discarded: it reappears the moment the attempt begins, and the error panel +returns only if that attempt fails as well. With auto-refresh armed against an endpoint that keeps +failing, the text and the panel therefore alternate on every tick.
+After a successful load the view scrolls to the bottom, but only while it is already stuck there — +the scroll handler treats a distance of less than 30 px from the bottom as stuck. Scrolling up +therefore freezes the position through an auto-refresh, and scrolling back to the bottom re-arms the +follow behaviour.
+Level Filtering, and Where the File Comes From
+The page applies no filtering of its own. There is no level selector, no category selector, no +search box and no date picker: it renders the file it is given, verbatim, in the order it was +written. Which entries reach the file at all is decided at write time, by the host's logging filters +rather than by anything on this screen — see Program and Hosting for those rules and how +they differ from the console provider's.
+The sink is a daily file logger provider registered at startup against a logs folder under the
+process's current working directory. It writes one entry per line as
+[yyyy-MM-dd HH:mm:ss.fff] [Level] Category - message, appends the exception on its own lines when
+there is one, serialises appends behind a lock and swallows every I/O failure, on the principle that
+logging must never fault a request. Logging scopes are not rendered into the file. The application
+writes one Information line immediately after the host is built, so the file for the day the service
+starts has content before the first request arrives and a freshly started service shows something
+rather than an empty state. The sink recomposes the file name from the clock on every append, so a
+service left running past midnight has no file for the new day until something is logged, and the
+page shows its empty state until then.
Only the current day's file is reachable. Previous days' files stay on disk and the page offers no +way to open one. A file that exists but is empty renders the same empty state as a missing one; only +the date badge can separate the two, since the empty file's date comes from the server's answer and +the missing file's from the browser's UTC clock.
+The same controller also exposes a POST endpoint that appends a caller-supplied line to the same +daily file. It writes with its own formatting rather than through the logger provider, and nothing +in the shipped SPA calls it.
+Copy, Download, and What the Page Cannot Do
+Copy writes the loaded text to the system clipboard and reports the outcome as a toast, positive
+or negative. Download fetches the file as a blob from the download endpoint and saves it
+client-side under the name log-<date>.txt, repeating the date the last answer reported, and also
+ends in a toast. Both buttons are disabled while there is nothing loaded.
The page cannot clear, truncate, rotate or delete the log — there is no destructive action on it at +all, and no endpoint behind it that would perform one. It cannot reach another day, filter by level +or text, or stream: refreshing is polling over the same whole-file request, with no hub connection +and no server push. It is a viewer.
+Layout
+-
+
Source Code Path
+See HiNC App Anatomy for git repository links.
+Web Application
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
See Also
+-
+
Table of Contents
+ +Login and Authentication
+ +The sign-in screen is the /login route. Whether anyone ever reaches it is decided by one back-end
+setting — the Enabled flag of the Auth configuration section: with the flag off the service is
+open and the route sends every visitor straight back out, and with it on a cookie-authentication
+scheme plus a global authorization fallback policy lock every hub and every controller but the
+authentication one — the whole api/ surface apart from api/auth — until a visitor signs in. The
+route hosts no Control-Tree branch and takes no tree query argument.
The Enable Switch, and Its Two Defaults
+AuthConfig binds the Auth section of configuration and is registered as one instance, so the
+authentication controller and the startup decision read the same object. Two different values answer
+the question “is the gate on by default”, and conflating them gets the answer wrong on a real
+instance:
-
+
Three further settings live in the same section. SessionHours defaults to 8 and is raised to 1 at
+registration if a smaller number is configured. SlidingExpiration defaults to true.
+Users is a list of username and password pairs and defaults to empty.
That list is the whole credential store. Passwords are held in the configuration file in clear text
+and compared verbatim — a first-match scan for an exact username and password — so there is no
+hashing, no lockout, no rate limit and no user database behind it. The class documents the clear-text
+form as deliberate, because the project also ships as sample code. Every entry grants the same
+access; there are no roles. Enabled set to true over an empty list therefore admits nobody.
What the Gate Registers
+The flag guards exactly two registrations at startup: a cookie authentication scheme, which becomes +the application's default and only scheme, and a global authorization fallback policy requiring an +authenticated user.
+The fallback policy is what does the locking. It applies to every endpoint carrying no authorization
+metadata of its own — every controller in the host assembly except the authentication one, and all
+eight SignalR hub routes. UseAuthentication and UseAuthorization are added to the request pipeline
+unconditionally and are no-ops when neither registration happened. See
+Program and Hosting for the pipeline order this sits in.
Three places in the host carry AllowAnonymous, and they are the only ones that do:
-
+
Two pipeline stages answer before the authorization stage is reached and are unaffected by the +policy: static-file serving and the Swagger middleware. The first of those is what lets the login +screen render before anyone has signed in — the built bundle and the brand image it draws are +physical files under the web root.
+With the gate off, none of this is registered: every controller, every hub and both fallbacks are +open, the login endpoint answers success without inspecting anything, the client's navigation guard +short-circuits, and the login route bounces the visitor away as it mounts.
+The Cookie
+The scheme issues a cookie named HiNC.Auth. It is HttpOnly, its SameSite mode is Lax, and its
+secure policy is same as request — marked Secure when the request itself arrived over HTTPS and
+issued plainly otherwise, so a plain-HTTP run on a local network still works. That decision is one of
+the reasons forwarded headers are applied first in the pipeline, the HTTPS-redirection stage being
+the other: behind a TLS-terminating reverse proxy the scheme must be read from X-Forwarded-Proto
+rather than from the plain hop between proxy and service.
The ticket carries exactly one claim, the name of the matched user. Its lifetime is the configured
+session length, refreshed on activity when sliding expiration is left on. Sign-in passes no
+authentication properties, so the ticket is not marked persistent and the cookie carries no Expires
+attribute: the browser holds it for the browser session, and the ticket's own expiry bounds a session
+left open.
One event pair is overridden, and it is what makes a single-page client possible at all. A challenge +answers 401 and a refusal answers 403, in place of the framework's default redirect to a +server-rendered login page. Every protected call therefore fails as data the client can read rather +than as a 302 that would arrive at the fetch layer as an HTML page.
+The Auth Endpoints
+The controller is routed at api/auth and holds three endpoints.
| Endpoint | +Behaviour | +
|---|---|
GET status |
+Reports whether the gate is enabled, whether this caller is authenticated, the caller's name, and a version string. It is anonymous, so it answers before sign-in and whether or not the gate is on. | +
POST login |
+With the gate off, returns success without inspecting the body. With it on, scans the configured users for an exact match and either answers 401 with an English message plus the stable code InvalidCredentials, or signs the caller in. |
+
POST logout |
+Signs the caller out when the gate is on, and reports “not authenticated” either way. | +
The version the status endpoint reports is the HiNc assembly version, +ApiVersion(API). Because the endpoint is +anonymous, that value is readable before sign-in, which is what lets the login screen and the menu bar +show the same version mark.
+Nothing else in the host reads the signed-in identity — the status endpoint is its only consumer. Every +application service is registered as a process-wide singleton — the host adds no scoped or transient +registration of its own — so authentication decides admission rather than identity: two signed-in +browsers drive the same project and the same session. See Session State for what that +shared state consists of.
+The Client Side
+The Store
+A Pinia store holds the whole client-side picture: whether the gate is enabled, whether this session is
+authenticated, the user name, the version string, and a ready flag set once the first status probe has
+resolved. refresh reads the status endpoint; login and logout call their endpoints and update the
+same fields; a fourth action flips the store to logged-out with no round trip, for the interceptor
+below. Every transition re-drives the hub gate.
The Navigation Guard
+One global beforeEach guard runs the whole client-side decision, in order:
-
+
What the guard does not do is protect anything. It is a navigation redirect: it runs on router
+navigations only, so it has no bearing on a direct api/ request, on a hub negotiate, or on a static
+asset, all of which are the fallback policy's business. It reads no per-route metadata, so there is no
+list of public routes beyond the login route itself and no notion of a role. And it fails open by
+construction — when the status probe throws, the store keeps the gate disabled and the navigation is
+allowed, so a back-end hiccup cannot lock a user out of an installation that has no gate.
The 401 Interceptor
+A boot file wraps the global fetch once for the whole application. Every API module, and SignalR's
+own negotiate, call that global, so one wrapper covers a session that expires mid-use: on any 401
+response, and only while the store says the gate is enabled, it marks the store logged out and pushes
+the login route with the current full path in redirect, unless the router is already there. The
+response is handed back unchanged, so the calling code still sees its own failure and handles it.
Boot order is load-bearing here. The interceptor's boot file is declared ahead of the i18n one, and
+boot files are awaited in declaration order, so the locale probe made during startup goes through the
+patched fetch.
The Hub Gate
+The shared-hub registry carries one tri-state gate that the store drives on every transition: undecided
+while the status probe is still resolving, closed while the gate is on and the session is signed out,
+and open for a signed-in session or a service with no gate. Undecided holds, so a first-paint race
+never fires a negotiate that is certain to fail; closed keeps every hub idle rather than letting the
+never-give-up reconnect schedule hammer a /negotiate that answers 401 by design. The store releases
+the gate even when the status probe fails, so a probe hiccup never strands the hubs of a service that
+has no login. Opening the gate resumes the connections a caller had already asked for, without the
+caller asking again.
After a Successful Sign-In
+The login screen navigates with a full page load rather than a router navigation. The application's +one-shot wiring — the project hub subscription and the first project-status fetch — sits behind a +watcher on the auth predicate and runs once per page load, so reloading is what re-runs it with the +cookie present.
+Signing Out
+The logout control sits at the right of the menu bar and renders only while the gate is enabled and
+the session is authenticated. It is labelled with the signed-in user name, falling back to Logout
+when the status carries no name, and its tooltip reads Log out. It posts the logout endpoint and
+then, in a finally, hard-navigates to /login — so a failed request still tears the page down and
+rebuilds the client from scratch, rather than leaving the singletons of an ended session wired up.
Layout
+The route renders outside the shell layout, in a Quasar layout of its own — the same arrangement the
+catch-all not-found route uses. Nothing of the application frame is present: no menu bar, and therefore
+no Project, Page or Preference menu and no language submenu; no cached page panel; no
+routine-progress footer. The browser tab reads Login - HiNC.
-
+
Pressing Enter in either field submits the same form. Neither field carries a validation rule, so an
+empty pair is submitted and refused by the server like any other wrong pair. The error caption shows
+the localized “Incorrect username or password” whenever the server pairs its refusal with the
+InvalidCredentials code, and the server's own English message for any other refusal that carries
+one.
Two cases send the visitor away again as soon as the screen mounts. The mount hook hydrates the auth
+status if the first probe has not resolved yet, and then replaces the route with the parked redirect
+target — or with /, which redirects on to the Execution page — when the gate reports disabled or when
+the session is already authenticated.
The locale the screen paints in is whatever the boot sequence resolved, because the stored language +preference is served by an endpoint the fallback policy locks. With a browser-local cached locale the +screen paints in that; without one the awaited probe fails and the locale falls back to the browser's +own language, and to English after that. The screen's own strings ship in all three locales.
+Source Code Path
+See HiNC App Anatomy for git repository links.
+Web Application
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
HiAPI Engine
+-
+
See Also
+-
+
Table of Contents
+ +Program and Hosting
+ +Program.cs is the entire host of the HiNC web service: one class whose Main builds an ASP.NET
+Core application and runs it. It presents no screen of its own — it is the process that starts and
+stops HiAPI, registers every service the controllers and hubs resolve, maps those controllers and
+the eight SignalR hubs, serves the built Quasar SPA out of the web root, and falls back to the SPA
+shell for every URL the Vue router owns. BuildApp is public and returns the built application
+without starting it, so the build phase and the run phase are separable; Main calls it, writes one
+startup line to the log, and runs.
HiAPI Lifetime
+The host brackets its whole life in one pair of HiAPI calls.
+AppBegin(API) runs once during BuildApp, after the
+service container is built and before the request pipeline is configured. It logs in every licence
+License holds, initialises the display engine (DispEngine), and
+opens the SQLite step-cache database at the path it is given, publishing it as
+SqliteStepStorage's default instance. The overload the webservice calls takes a
+logger and a cache-database path.
AppEnd(API) unwinds the same set in reverse: it waits for +queued background CubeTree frees to drain so no native delete runs against a half-torn-down runtime, +disposes the step and identity storages, shuts the display engine down, and logs the licences out.
+Three separate events can reach AppEnd, and a static latch guarantees it runs at most once:
-
+
The cache-database path is Cache/{CacheDbId}.db beneath the admin directory.
+CacheDbId is the HiNC:CacheDbId configuration value when one is set and
+the hosting environment name otherwise. Supplying a path matters:
+SqliteStepStorage given none falls back to a single per-user file, so several
+instances on one account would otherwise share it. Two instances launched under different
+--environment names get different cache files with no further configuration.
Startup Order
+Three registrations run before the builder exists, because each seeds a table that is read — or +frozen — the moment anything else touches it:
+-
+
After the container is built and before the pipeline is assembled, the host wires the native core's
+log output into the application logger through CppLogUtil, sets the cache
+identifier, calls AppBegin, and then runs
+Seed(API) over the admin Resource root. The
+seeder copies in only the marked .default items, leaves unmarked items alone as user property, and
+skips the whole pass when its version stamp already matches the shipped resource assembly.
Registered Services
+Every application service is registered as a singleton. The repository contains no AddScoped
+and no AddTransient call, so no service this host registers has a per-request lifetime and any
+state one of them holds is process-wide. Any shorter lifetime in the container is one of the
+framework's own registrations rather than an application service.
Four registrations are worth reading closely:
+-
+
Six singletons are resolved eagerly at the end of BuildApp so they subscribe to their engine events
+at startup rather than when the first client happens to connect: the CL-strip broadcast service, the
+four per-sink message broadcast services, and the NC-program registry.
Kestrel is configured with AllowSynchronousIO = true, which permits handlers to write to the
+response body synchronously.
Controllers
+Controllers are added with AddControllers(), with the host's own assembly registered as an explicit
+application part, and reached by MapControllers() at the end of the pipeline. Every controller in
+the host assembly is attribute-routed, and every route template begins with api/ — most as
+api/[controller], the rest as an explicit kebab-case path such as api/mech/machine-tool or
+api/execution/cl-strip. No controller route sits outside api/, which is what makes the
+“everything else is the SPA” fallback rule below safe.
One controller is exempt from the login gate: the authentication controller carries
+[AllowAnonymous] so it stays reachable when the fallback policy locks everything else.
SignalR Hubs
+AddSignalR() is called without protocol configuration, and eight hub routes are mapped:
| Route | +Hub type | +What it carries | +
|---|---|---|
/renderingHub |
+RenderingHub |
+server-rendered canvas frames and the input that drives them, one display engine per connection | +
/shellMessageHub |
+ShellMessageHub |
+session-level routine and lifecycle messages | +
/ncDiagnosticHub |
+NcDiagnosticHub |
+NC-pipeline diagnostics | +
/stepDiagnosticHub |
+StepDiagnosticHub |
+step-anchored diagnostics | +
/ncManipulationDiagnosticHub |
+NcManipulationDiagnosticHub |
+NC-manipulation diagnostics — writeback conversion and optimisation | +
/executionStatusHub |
+ExecutionStatusHub |
+ExecutionStatusUpdated, SessionCursor and SessionStatusMessage, broadcast to all clients; the client-callable GetExecutionStatus() answers the caller alone, on the same ExecutionStatusUpdated name |
+
/clStripHub |
+ClStripHub |
+strip-chart display range, selected and entered step, and throttled data-update hints | +
/cleanupHub |
+CleanupHub |
+index keys, and optional follow-up action keys, that a client registers against its connection with Add; the disconnect sweep finds none of them — see below |
+
The four message hubs share one base class and one contract — the client calls GetMessages(limit)
+and the server answers on MessagesUpdate — but they are deliberately separate routes rather than
+one hub with a discriminator, so a client subscribes to exactly the sink it wants and an idle panel
+means an idle hub rather than a lost connection.
The cleanup hub is the one whose registry does not outlive the call that fills it. SignalR builds a
+fresh hub instance for every invocation, and the key dictionary Add writes to is an instance
+member rather than shared state, so the dictionary the disconnect handler walks is a different,
+empty one and releases nothing. Only IndexService — a singleton — is shared across those
+instances. The client does not depend on the hub for the release: useCleanupHub.ts keeps its own
+set of registered keys and posts each one to /api/Index/Remove before the component unmounts. That
+is the path that actually frees an entry.
JSON Serialisation
+The controllers' serializer options carry two settings, both applied through AddJsonOptions.
JsonNumberHandling.AllowNamedFloatingPointLiterals. Several engine values use
+double.PositiveInfinity as their “no limit” state — the optimizer's Max Feed Per Tooth and
+Preferred Force among them — and the snapshots the SPA reads carry those doubles straight into the
+response object. System.Text.Json refuses to write an infinite or NaN double as a JSON number, so
+without this setting every such response would fault during serialisation instead of returning. With
+it, those values travel as the JSON strings "Infinity", "-Infinity" and "NaN". The reading side
+is not uniform: the numeric input widgets take all three spellings case-insensitively, while the
+option-snapshot reader in the mission API recognises the two infinity spellings only and resolves
+anything else non-numeric — "NaN" included — to the fallback its caller passed, which is positive
+infinity for Max Feed Per Tooth and Preferred Force.
+Numeric Input/Output covers the client half of that boundary, and
+NC Optimization Option Panel (NC Optimization Config) the panel that leans hardest on it — including the two write
+endpoints that take a string body so the same spelling survives the round trip.
JsonStringEnumConverter. Enums are read and written as their names rather than as integers,
+which is what the API wrappers expect and what keeps the OpenAPI schema self-describing.
Both settings belong to the MVC controllers only. Hub payloads are serialised by SignalR's own +protocol, which is left at its defaults, so neither the named floating-point literals nor the string +enums extend to hub messages.
+The Request Pipeline
+The order the middleware is added in is load-bearing, and the host sets it explicitly rather than +accepting the default arrangement:
+-
+
A request answered before step 7 never reaches the authorization stage at all: the static-file
+middleware and the Swagger middleware both sit ahead of it and short-circuit the request when they
+match. The two SPA fallbacks are endpoints rather than middleware, so they do reach it, and both are
+marked AllowAnonymous — which is what lets the SPA shell, carrying no data of its own, load and
+show its own login view when the gate is on.
The SPA Fallback
+Two fallback registrations serve index.html:
-
+
The bare form's implicit route pattern carries a nonfile constraint that rejects any URL whose last
+segment contains a dot. File Explorer deep links mirror real folder and file names into the URL, and
+those names routinely contain dots, so reloading or pasting such a location needs the explicit
+pattern. Genuine static assets cannot be shadowed by it, because the static-file middleware has
+already run by the time either fallback is reached.
Serving the SPA
+The Quasar build writes its output directly into the host's web root — the front end's build
+configuration sets its distribution directory to the web service's wwwroot — so there is no copy
+step between building the SPA and serving it. The router runs in history mode, which is what makes
+the SPA fallback necessary: every client-side route is an extension-less URL the server has no
+endpoint for.
The front-end build script runs the i18n census and glossary lint before invoking the Quasar build, +so a build that fails those checks produces no new bundle.
+For front-end work the Quasar dev server runs on its own port and proxies the routes the backend owns
+to the running service. That proxy list holds /api, /swagger, /renderingHub,
+/executionStatusHub, /clStripHub, /cleanupHub, and /sessionMessageHub — a path the host maps
+no hub on. The four per-sink message hubs are absent from it, so the Session Messages panels receive
+no live messages through the dev server.
OpenAPI and Swagger
+A single private constant holds the REST contract version. It is simultaneously the Swashbuckle +document group name — so it appears in the document's route — and the version the UI displays, which +is why it is defined in one place. It is raised by hand on a breaking REST change only, and is +deliberately not tied to the assembly version or to the project-file format version.
+Both OpenAPI surfaces are exposed in every environment rather than behind a development gate, so an
+automated caller always has a machine-readable contract. The Swashbuckle UI is served at /swagger
+and its document at /swagger/{version}/swagger.json; the framework's own document is published
+under /openapi/.
Schema ids are keyed on the full type name with the nested-type separator normalised. Without that,
+two controllers' nested request types that share a simple name collide and the document generator
+throws — which surfaces as an HTTP 500 from the document route rather than as a startup failure. XML
+documentation comments are folded in from the host assembly and from every referenced assembly whose
+name starts with Hi. Every read is skipped when its file is missing, and each referenced-assembly
+read carries a catch-all of its own on top of that, so one whose file is present but unreadable is
+ignored; the host assembly's read has no such catch-all.
CORS
+One policy, named AllowAll, permits any origin, any method and any header, and it is applied
+globally. It does not allow credentials, so the response carries a wildcard origin and a cross-origin
+browser call cannot use the session cookie. The SPA is served from the same origin as the API and the
+hubs, so this constrains only callers hosted elsewhere.
Logging
+The default providers are cleared and two are added: a console provider, and a daily file provider
+rooted at a logs folder beneath the process's current working directory. The file provider writes
+one line per entry to log-{yyyy-MM-dd}.txt and swallows every I/O failure, on the principle that
+logging must not fault a request or a startup.
The two providers are filtered independently. The console follows the Logging:LogLevel section of
+configuration, which sets everything to Warning. The file provider carries its own code-set rules —
+Information for the application's own categories, Warning for Microsoft and System — and
+provider-specific rules take precedence over configuration's provider-neutral ones, so the file stays
+useful while the console stays quiet.
Main writes one Information line immediately after the application is built, so the file for the day
+the service starts has content before the first request arrives. That guarantee stops at midnight: the
+provider recomposes the file name from the clock on every append, and creates nothing ahead of time, so
+a host left running into a new day has no file for it until the next entry is written.
+GET /api/project/logs reads the same directory.
Configuration
+Configuration is the standard chain: appsettings.json, then appsettings.{Environment}.json, then
+environment variables, then command-line arguments. The keys this host reads are:
| Key | +What it controls | +
|---|---|
Logging:LogLevel |
+the console provider's levels | +
AllowedHosts |
+host filtering; shipped as a wildcard | +
Kestrel:Endpoints |
+the addresses the server binds | +
ProxyConfig:AdminDirectory |
+the admin working root, under which Resource and Cache live |
+
Auth |
+the optional login gate | +
HiNC:CacheDbId |
+an explicit step-cache database id, overriding the environment name | +
The Development overlay repeats the logging, admin-directory and authentication sections and declares
+no Kestrel section, so the listening address is the same under either environment.
Kestrel Endpoints Win
+Kestrel:Endpoints is the authoritative source of the listening address. When it declares endpoints,
+Kestrel binds those and ignores addresses supplied any other way — ASPNETCORE_URLS, --urls,
+and the launch profile's applicationUrl alike — logging that it is overriding them. The shipped
+configuration declares one HTTP endpoint on loopback port 5000. The http launch profile names the
+same address, so the override is invisible there; the https profile also names an HTTPS address on
+a second port, and that address is one of the ones Kestrel discards, so launching under it still
+yields plain HTTP on 5000 and nothing else.
To move a local instance to another port, set the Kestrel__Endpoints__Http__Url environment
+variable, which the environment-variable provider maps onto the same key. Setting ASPNETCORE_URLS
+alone changes nothing.
The endpoint binds localhost, so the shipped configuration answers only on the machine it runs on.
+Reaching it from elsewhere means either overriding that key or placing a reverse proxy in front,
+which is the arrangement the forwarded-headers configuration handles.
Environment and Working Directory
+The environment name reaches further here than usual. Both launch profiles set it to Development,
+which selects the Development overlay, skips HTTPS redirection, and — unless HiNC:CacheDbId
+overrides it — names the step-cache database file.
Two paths follow the process's current working directory rather than the content root: the logs
+folder the file logger writes and the log endpoint reads, and the per-user preference file. An
+instance launched from the project directory finds both where they are expected; one launched from
+elsewhere puts them beside wherever it was started.
Source Code Path
+See HiNC App Anatomy for git repository links.
+Web Application
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
HiAPI Engine
+-
+
See Also
+-
+
Table of Contents
+ +Session State
+ +Session state is what the web client still holds after the user moves from one screen to the next. It
+owns no route: the mechanism lives in the shell that wraps every route under / — a router outlet
+inside a <keep-alive> keyed on a project epoch — together with four Pinia stores, a family of
+module-level composables that deliberately sit outside the page lifecycle, and three browser storage
+keys. Every value in the client answers the same three questions: does it survive a navigation, does
+it survive a project change, and does it survive a browser reload.
The Project Epoch
+MainLayout.vue renders the routed page inside a <keep-alive> whose :key is projectEpoch. That
+epoch is a plain integer ref declared in the layout component itself — it is not a store value, it
+is not provided to anything, and nothing outside the file reads it. Two watchers in the same file are
+the only writers, and each adds one to it: a watcher on the project store's projectPath, and a
+watcher on the project store's projectVersion.
The key sits on the <keep-alive> element rather than on the page component inside it. Changing it
+therefore discards the cache itself along with every page instance held in it and builds a fresh one,
+which is why a page may rely on its own mount hook and never watch the project: both Control-Tree
+pages call their host's initialize() from onMounted, and that runs once per project rather than
+once per visit.
The two inputs move for different reasons.
+-
+
Save on the current path moves neither, so it does not remount. The epoch only counts up, and because +it lives in the layout it starts again at zero on a browser reload — nothing reads its value, only its +transitions.
+What a Navigation Costs
+The <keep-alive> carries no include, exclude or max, so every page reached under / is cached
+until the epoch changes. Leaving a route deactivates its component instead of unmounting it, and
+returning re-activates the same instance with its in-flight edits and its Control-Tree host intact.
+Scroll offsets are not part of that: the router's scrollBehavior resolves every navigation to the
+top of the page, and nothing saves an inner scroll position across a deactivation. The two routes
+declared outside the layout — the login screen and the not-found catch-all — are not cached at all.
Three consequences the pages are written around:
+-
+
The File Explorer page adds a route-leave guard that settles its editor buffer — flushing a pending +auto-save, or prompting before discarding manual edits — and can refuse the navigation.
+The Stores
+Four Pinia stores are created against the single Pinia instance built at boot. None of them is inside
+the <keep-alive>, so a project change resets none of them; only a browser reload rebuilds them.
-
+
State Outside the Stores
+A second family of state lives at module scope in composables rather than in a store. The module is
+evaluated once per page load and its refs are created there, so every caller of the composable
+receives the same objects and nothing in a component's lifecycle creates or destroys them.
Three of these hold project data and share one lifecycle idiom — useSpindleCapability,
+useSoftNcRunner and useToolHouse. Each exposes an idempotent ensureInstalled() that, on its first
+call only, opens a detached effect scope and watches hasProject: the state reloads when a project
+appears and is cleared when one closes. Every panel that reads the singleton calls ensureInstalled()
+in its own setup, and every call after the first does nothing.
The consequence is the one that matters when reading a panel on screen: a mounted panel is not
+refreshed by a change made elsewhere. A project change unmounts and rebuilds every page, but it does
+not re-read a singleton — the installer is already installed, and the watch it installed is on a
+boolean that the load and new actions leave true. What each singleton holds is replaced only by an
+action on the composable itself, or by a consumer that asks for it explicitly: the Tool House page's
+mount hook calls the composable's reload(), while the spindle and controller panels call
+ensureInstalled() alone. The controller branch is the sharpest case. Its leaf panels fetch their own
+tables on mount and the tree builder re-fetches the runner snapshot it grows children from, but the
+shared snapshot the panels gate on — the brand, the chain axes, the per-group presence flags and the
+object key — is replaced only by a brand switch, an Object-Management install, or a close.
The remaining module singletons carry no project data and exist to join components that are not in one +another's tree: the Execution runtime flags the run page publishes for the nav bar, the sentence cursor +shared by the Program file panel and the syntax view, the strip-chart group's reload tick and hovered +x label, the two cycle-chart cursor marks the sim and sensor chart groups share, the parked cross-panel +line jump, the transport's shared reset flag, and the shared-hub registry with its auth gate.
+Device-Local State
+Three localStorage keys hold state belonging to one browser profile on one machine. None of them is
+written into the project file and none is sent to the server.
-
+
What is not device-local is easy to mistake for it. The physics-options switch, the interface
+language and the Execution division-visibility flags live in the server's UserConfig, held by a
+singleton service and written to an XML file in the service's working directory. They are per install,
+not per browser: two browsers pointed at the same service read and write the same values. The
+machining project is server-side in the same way — one project is loaded at a time and every connected
+browser sees it, which is why the status-hub broadcast is what keeps a second tab honest.
The URL carries the rest: the route, the ?tree= selection on the two Control-Tree pages, the browsed
+path on the File Explorer route, and the tab segments on the Tool House and Controller routes. Those
+come back on a reload from the address bar, and when a Control-Tree page is opened with no ?tree= the
+host lands on the stored last selection for that page, falling back to the page's root when the stored
+id is not in the current project's tree.
| What | +Navigation | +Project change | +Browser reload | +
|---|---|---|---|
| A page's own component state, including its Control-Tree host | +survives | +rebuilt | +rebuilt | +
| The four Pinia stores | +survives | +survives | +rebuilt | +
| Module-singleton composables | +survives | +survives | +rebuilt | +
The localStorage preferences |
+survives | +survives | +survives | +
Route, ?tree=, browsed path and tab segments |
+this is what changes | +survives | +survives | +
| Server preferences and the loaded project | +survives | +server-side | +survives | +
SignalR Connections
+Most hubs are shared singletons built by one factory: the execution-status hub, the CL strip hub, and +the four session-message sinks for shell, NC diagnostic, step diagnostic and NC-manipulation +diagnostic messages. There is one connection per hub for the whole application, reference-counted by +consumer. A component registers as a consumer when it calls the hub's composable and releases on its +own unmount; when the count reaches zero the teardown waits out a short grace window, so the +remount a project change forces — every consumer dropping and re-adding within the same render flush — +keeps its connections rather than renegotiating them.
+The project store takes out a consumer registration on the execution-status hub when the store is +created and never releases it itself, so that hub keeps a consumer no matter which page is showing.
+A global gate sits in front of all of them. While the optional login gate is enabled and the session is +not authenticated — and while auth status is still resolving — hubs stay idle rather than negotiating +into a 401. A logout or an intercepted 401 closes every connection; permitting again reconnects the +ones a caller had asked for, without the caller asking twice. Reconnection is a dense burst of +attempts, then a fixed cadence, then a slow tail that never gives up, and tab visibility or window +focus starts a fresh burst immediately.
+Two kinds of connection are per instance rather than shared:
+-
+
A shared connection therefore closes when its last consumer has been gone longer than the grace window, +or when the auth gate shuts. A per-instance connection closes when the component owning it unmounts — +which is what a project change does to every page at once, while the shared hubs ride that same remount +out inside their grace window.
+Boot Order
+App.vue renders nothing but the router outlet and holds the wiring that must run exactly once per
+page load: subscribe the project hub, then fetch project status. Both sit behind a watcher on the auth
+predicate with a latch rather than behind a mount hook, because auth status may still be resolving at
+first paint. The router's own guard hydrates auth status before the first navigation and fails open, so
+a status hiccup cannot lock out an installation that has no login gate. The layout's mount hook then
+hydrates the server preferences. A successful sign-in navigates with a full page load rather than a
+router push, so the whole sequence runs again with the authentication cookie present.
Source Code Path
+See HiNC App Anatomy for git repository links.
+Web Application
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
See Also
+-
+
Table of Contents
+ +Tree Ids and Routes
+ +The web client addresses itself with two things: a vue-router path, and — on the two Control-Tree
+pages — a tree query argument naming the selected tree node, as in
+/general-setup?tree=equipment/fixture/geometry. This page is the reference for both surfaces: the
+route table, the redirect-only paths that keep older bookmarks resolving, the id-migration chain,
+and the resolver that decides which page owns a given ?tree= id. The router runs in HTML5 history
+mode, so every one of these paths is a real URL the server has to answer.
Important
+Nothing here promises that a Control-Tree id is stable. The guarantee is narrower and exact: an
+id keeps resolving, because every regroup adds a migration hop to migrateLegacyTreeId
+rather than rewriting the previous one. The ids themselves have moved repeatedly, and a page that
+quotes one is quoting a value that can move again.
The Route Table
+/ is the shell-layout record itself; every route below it except the login and catch-all records
+is one of its children, so each renders inside the Main Panel's frame and menu bar.
| Path | +Route name | +What it owns | +
|---|---|---|
/ |
+— | +The shell layout. It carries no page of its own; its empty child redirects to the Execution route. | +
/execution |
+execution |
+The run cockpit and the Execution Control Tree, whose selection rides ?tree=execution/…. |
+
/general-setup |
+general-setup |
+The equipment Control Tree, whose selection rides ?tree=equipment/…. |
+
/machine-tool |
+machine-tool |
+The machining chain on a canvas of its own, beside a left column that shows either a read-only identity panel or the chain's XML source — two states of one column, chosen with a GUI/XML toggle. A load button installs a chain file into the project. Carries no Page-menu entry and is reached by URL. | +
/controller/:tab? |
+controller |
+The superseded HardNcEnv controller surface. The optional segment mirrors the active tab. | +
/tool-house/:toolId(\d+)?/:tab?/:subtab? |
+tool-house |
+The tool library and the per-tool editor. The three optional segments carry the selected tool and the two nested tab levels. | +
/preference/log |
+preference-log |
+The Log Viewer, reached from the menu bar's Show Log button. It is not under /util/. |
+
/util/file-explorer/:location(.*)* |
+util-file-explorer |
+The File Explorer. Its catch-all mirrors the browsed location — root display name followed by the relative segments — into the path. | +
/util/mech-builder |
+util-mech-builder |
+The Mechanism Builder. | +
/login |
+login |
+The sign-in form. It sits outside the shell layout and carries its own Quasar layout. | +
/:catchAll(.*)* |
+— | +The 404 page, likewise outside the shell layout: it prints the unmatched full path and offers one button back to the Execution route. | +
The menu bar's Page dropdown holds every one of these that is menu-reachable, in setup order: Tool
+House, General Setup and Execution, then File Explorer and Mechanism Builder below a separator,
+then the legacy Controller below a second one. The Log Viewer has a button of its own rather than a
+menu entry, and /machine-tool has neither — it is reached by URL.
The Tool House route's tool segment is constrained to digits, but all three of its segments are
+optional, so a non-numeric segment in the tool position is not rejected: the router skips the tool
+parameter and reads that segment as the tab, which is what makes /tool-house/cutter a working tab
+deep link with no tool named. The consequence is that the record absorbs three segments when the
+first is a number and only two when it is not, and a URL carrying more than the record can absorb —
+/tool-house/abc/def/ghi — falls through to the catch-all. An unrecognised tab name is not an error
+either: the page canonicalises it to that level's default, as
+The Tool House Translation sets out.
Redirect-Only Paths
+These records carry no component. They exist so that a link minted before the corresponding editor +moved into a Control Tree still lands on the editor.
+| Path | +Lands on | +
|---|---|
/spindle-capability/:tab? |
+/general-setup?tree=equipment/spindle, with /<tab> appended when the segment is one of thermal, gear-shift, dry-run, power, torque. An unrecognised segment is dropped and the branch root is selected. |
+
/equipment/spindle |
+/general-setup?tree=equipment/spindle |
+
/fixture/:rest(.*)* |
+/general-setup?tree=equipment/fixture — any trailing path is discarded. |
+
/workpiece/:rest(.*)* |
+/general-setup?tree=equipment/workpiece — any trailing path is discarded. |
+
/equipment/background-coolant |
+/general-setup?tree=equipment/background. Background and Coolant are two separate tree nodes; the redirect picks Background. |
+
/mission |
+/execution?tree=execution/mission |
+
The five spindle tab names are declared in treeRoutes.ts as SPINDLE_TABS, and imported by
+wwwroot-src/src/router/routes.ts — which is the list the redirect tests its :tab? segment
+against. The equipment tree does not import that constant: it spells the same five segments as
+literals while building the equipment/spindle/<seg> children, so redirect and branch agree by
+convention rather than through a shared list — renaming a spindle section is therefore an edit in
+two places. The Tool House name lists below are the other case, genuinely shared.
The ?tree= Query
+Selection and URL are synced two ways by the Control-Tree host, one instance per tree page.
+URL to selection. A watcher on the query drives applyRouteSelection(), and it is registered
+immediate, so an id belonging to another page redirects on the first tick rather than after this
+page's first tree build. The host also calls the same function once the tree has actually been
+built, which is when a deep link can finally be honoured.
Selection to URL. A watcher on the selected id replaces the tree argument, preserving the
+rest of the query. It uses router.replace, so browsing the tree does not fill the browser's
+history with one entry per node. The comparison that breaks the loop is made against the raw
+query rather than the migrated form, which is what makes an older id canonicalise: opening
+?tree=controller selects the node and then rewrites the URL to ?tree=equipment/controller.
Neither watcher touches the URL unless the current route name is the page's own. The shell layout +keeps every visited page alive, so a host whose page is not showing keeps receiving route changes, +and without the guard it would write another page's URL. The selection watcher does one thing +before that guard: it records the id as this page's last selection. That ordering is load-bearing, +because a selection can move while its page is off screen — unticking a CSV or CL Controller +checkbox steps the equipment host off that node onto the equipment root, and the Preference menu +that carries those checkboxes is open on every page — and the landing selection has to have +followed it.
+That same checkbox flip is a third writer of the tree argument, and the one place the guard is
+absent. The equipment host watches both checkboxes, and when the standing selection is a node the
+flip removes, it replaces tree with equipment — the rest of the query preserved — before
+stepping the selection off. The route name is not consulted there, so the replacement goes to
+whichever route is showing.
What applyRouteSelection Does
+-
+
The Id the URL Names, Before the Tree Exists
+treePathOfRoute() — the raw query put through migrateLegacyTreeId — is not read only by the
+selection logic. The equipment tree consults it while it is being built, to decide whether to
+materialise the CSV Controller and CL Controller nodes: each is normally shown only when its
+Preference checkbox is ticked, and a URL naming one reveals it regardless. That reveal has to
+happen at build time precisely because step 4 above refuses an id the built tree lacks, so a
+bookmark to a switched-off node would otherwise dead-end. Only the URL reveals a node this way; the
+persisted last selection does not, so a node the user has just unticked while standing on it does
+not resurrect itself.
Id Migration
+migrateLegacyTreeId is one pass of ordered guards over the whole id; the first that matches
+returns, and an id matching none is returned unchanged.
| An id shaped like | +Becomes | +
|---|---|
controller, controller/… |
+equipment/controller, equipment/controller/… |
+
spindle, spindle/… |
+equipment/spindle, equipment/spindle/… |
+
mission, mission/… |
+execution/mission, execution/mission/… |
+
equipment/mission, equipment/mission/… |
+execution/mission, execution/mission/… |
+
any id ending in /contours/tray, or containing /contours/tray/ |
+the same id with that segment spelled fluting |
+
The last rule renames one segment in place and leaves its children — baseline, flute-<i>,
+side, bottom — untouched, so a deep link into a flute contour survives the engine's Fluting
+type naming. The two mission rules cover the two id shapes separately rather than chaining, because
+the first matching guard returns.
The function is applied at four points, which is what makes the guarantee hold in practice:
+-
+
Note what the first rule implies: a bare ?tree=controller selects the equipment/controller
+branch — the current controller editor on the General Setup page — and has nothing to do with the
+/controller route, which is the separate legacy HardNcEnv surface.
Landing an Id on Its Page
+TREE_PAGE_ROOTS lists the first segments that name a page: execution, equipment and
+toolhouse. spindle is absent on purpose — migration folds it into equipment/… before any root
+check runs, so no root check ever sees it.
routeForTreeId migrates the id, then dispatches on its first segment:
-
+
The returned location replaces the whole current location. Only the tree argument survives the
+hop; any other query argument on the URL being redirected away from is dropped, and the Tool House
+form carries no query at all.
The Tool House Translation
+The Tool House page keeps its state in path segments rather than a query, so an id arriving from
+another page has to be translated. routeForTreeId walks the segments of
+toolhouse/tool-<n>/<tab>/<subtab>:
-
+
Only the sub-tab step is nested inside the tab step: an unrecognised tab discards the sub-tab with
+it, whereas the tool step is tested on its own, so a second segment that is not tool-<n> costs the
+toolId param alone and the tab still translates. Everything deeper than the last segment the URL
+can carry is discarded either way — an id pointing at a single flute contour under
+…/cutter/contours/fluting/… lands on the nearest tab the URL can express. The same three name
+lists are imported by the page itself, so the tab set and the translation cannot drift apart.
Once on the page, the tab segments are kept in step with the tabs by useRouteTabs, the composable
+the Controller route uses as well. It gives each tab level one route param, a valid-name set and a
+default; a nested level may declare which parent values make it apply, and while its parent is
+something else the level is dormant — its segment is dropped from the URL while its reference
+quietly remembers the last value, so returning to that parent tab returns to the sub-tab it had.
+The Tool House sub-tab level applies under cutter and holder only, and both its valid set and
+its default depend on which of the two is showing. A segment the level does not recognise resolves
+to that default rather than failing. Canonicalisation runs on the first mount and on every
+keep-alive re-entry, which is why a bare /controller becomes /controller/coordinate-table and a
+bare /tool-house acquires its tab segments. The tool segment is not one of the composable's
+levels: the page fills it in itself, adopting the URL's tool when the project holds it and the first
+tool in the list otherwise, then replacing the URL with what it adopted. That resolver reacts to the
+toolId param and to the tool list rather than to page activation, so it also re-points the URL
+when the list changes underneath it.
The Tool House page never reads or writes ?tree=. A toolhouse/… id reaches it only by way of
+the redirect above, which is issued by one of the two tree pages' hosts.
Reading an Id
+An id's first segment names the page that owns it, and therefore the folder that documents it:
+?tree= root |
+Route | +Documented under | +
|---|---|---|
execution/… |
+/execution |
+Execution Page | +
equipment/… |
+/general-setup |
+General Setup Page | +
toolhouse/… |
+/tool-house/… |
+Tool House Page | +
The remaining segments are the branch path, one segment per level of the tree, and the branch's own
+page sits in that folder — equipment/workpiece/material/cutting-parameter is the Cutting Parameter
+item of the Workpiece branch on the General Setup page. Most segments are kebab-case role names;
+the two list-backed branches, Mission entries and Program files, use a positional index instead.
Every segment comes from the node id, never from the label the user reads: the General Setup
+page's root node is labelled General Setup and carries the id equipment. A label is a display
+string — the translation of the node's labelKey where it has one, and otherwise raw data such as
+an NC file name or a mission command's user-typed title, as Control Tree
+sets out — while an id is neither translated nor taken from data. Two consequences follow. A
+?tree= link is locale-independent, and an id can never be read off what is on screen.
Titles
+A route's meta.title holds an i18n key, such as routes.execution, not a title. The router
+resolves it through the active locale in an afterEach hook and sets the document title to the
+translated name followed by - HiNC; a route with no key — the catch-all — leaves the bare
+product name. The sibling name: is the route id used by every programmatic navigation and is
+never translated; the two must not be confused, which is why the route table above lists them side
+by side.
The same resolver is handed to the i18n module through registerRetitle, so that switching
+language re-titles a parked tab without a navigation. The registration is indirect because the
+router already imports the i18n module for its translation function, and importing back would close
+a cycle.
Serving These URLs
+History mode means the browser sends the whole path to the server on a refresh or a pasted link, so +the back end maps two SPA fallbacks. The bare fallback carries an implicit constraint that rejects +any URL whose last segment contains a dot, which is exactly the shape a File Explorer deep link +takes, since those mirror real file names. An explicit fallback for the explorer's path is +therefore mapped ahead of it with no such constraint. Static assets cannot be shadowed by either, +because static-file serving runs earlier in the pipeline. Both fallbacks allow anonymous access, so +the SPA itself always loads and the sign-in decision is made in the client.
+That decision is a navigation guard: it hydrates the authentication status once, lets every
+navigation through when the back end reports the feature disabled, and otherwise sends an
+unauthenticated visitor to the login route with the original full path — ?tree= included — parked
+in a redirect query. A successful sign-in replays that path as a full page load rather than a
+router navigation, so the application re-initialises with the cookie present. A visitor who is
+already signed in, or who arrives while the feature is off, is sent straight back out of the login
+route to the same parked path.
Source Code Path
+See HiNC App Anatomy for git repository links.
+Web Application
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
See Also
+-
+
Table of Contents
+ +Bottom Message Bar
+ +The Bottom Message Bar displays application-level notifications at the bottom of the Main Panel. This is the UI Error Notification channel — see Message Management for how it fits into the overall message architecture.
+Message Handling
+The Bottom Message Bar is connected to MessageBoardUtil (or ILogger with level-filtered treatment). When a notification is triggered:
-
+
The second step belongs to the logging path rather than to the bar, and only one client shares it. +The WPF brief field is fed by a logger provider, so a message that reaches the field has already +gone to the file sink. The web footer is fed by the routine-progress store instead: what lands there +is client-side, and it reaches the server's daily log only when the server logged it too — no part +of the shell posts a message to the log-append endpoint.
+Message Types
+The MessageFlag determines the display behavior:
+| Flag | +Display Behavior | +
|---|---|
| Exception | +Alert style, shown in Message Bar | +
| Warning and above | +Shown in Message Bar | +
| Info and below | +Logged only, not shown in Message Bar | +
Note
+When the message is an Exception, the brief message shows Message while the full exception details are logged to file.
+Platform-Specific Layouts
+WPF Application
+The WPF version uses a fixed bottom bar:
+-
+
Web Application
+The web version docks a single dense bar along the bottom of the layout — AppFooter.vue, not a
+stack of toasts:
-
+
Transient notifications are a second channel beside the bar rather than the bar itself: toasts are +Quasar notifications, anchored bottom-right and auto-hiding on a timeout that does not vary with +severity. A boot patch mirrors every one of them into the footer's foreground history, which is what +keeps a toast reviewable after it fades.
+Show Log Button
+The Show Log button is the only entrance to the log screen, and the two clients answer it
+differently.
In the web application it is a router link in the menu bar's right-hand group — not on this bar — +and it opens the Log Viewer as a full page inside the shell. See +Log Viewer Page for that screen's toolbar, its auto-refresh interval, its +copy and download actions and the states of its text area.
+In the WPF client the button sits at the right end of this bar. It looks for the current day's file
+under the client's own logs folder and, when it is there, opens it with whatever application the
+operating system associates with the file type; when it is not, it reports that in a message box
+instead of opening anything.
See Also
+-
+
Table of Contents
+ +App Shell
+ +The frame every route renders inside. MainLayout.vue is mounted once and stays mounted: the menu
+bar across the top, the notification bar along the bottom, and a router outlet between them that
+each page fills. Nothing here belongs to one screen, and nothing here disappears when the route
+changes.
Ordered outward from the window frame: the panel that hosts every route, then the bars it docks, +then the menus it drops.
+Pages
+-
+
See Also
+-
+
Table of Contents
+ +Language Selection SubMenu
+ +The submenu locates on the Preference Menu Dropdown. It is the only +place either client offers for changing the interface language. What that choice sets in motion in +the web client — the message bundles, the locale applied before the first frame, the single switch +function and everything that re-renders behind it — is +Internationalization; this page covers the gesture.
+The web submenu's model is the application-state store rather than a service handed down by the +parent component: it reads the current code and the available-code list from that store and calls +the store's language action. Both values are hydrated by the store's server-preferences load, which +the shell layout runs on mount — a separate path from the one that decides which locale the first +painted frame uses. The WPF client resolves its language manager during start-up, and the manager +reads the persisted code out of the user configuration as it is constructed.
+Layout
+The two clients draw the submenu differently.
+-
+
Choosing a row in the web client closes the popup and makes one call, the store's language action. +The store writes the new code optimistically, POSTs it, adopts the current and available lists the +server echoes back, and only then switches the live catalogue — so the interface text flips after +the server has accepted the value, and the confirmation toast already reads in the language just +picked. A rejected write rolls the store back and changes no interface text at all. In the WPF +client picking a radio swaps the merged string dictionary in place and saves the code to the user +configuration, and the labels follow without a restart.
+Source Code Path
+See HiNC App Anatomy for git repository links.
+Web Application
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
See Also
+-
+
Table of Contents
+ +Main Panel
+ +The Main Panel is the primary window of the HiNC application, providing navigation and access to all major features.
+Key Models
+-
+
Layout Structure
+-
+
In the WPF application the run tool bars belong to the menu shell and appear only while the central panel is the player. The web application does not carry them on the menu bar at all: the transport controls ride the Execution Page‘s primary panel header and the view controls ride its canvas panel header. What the menu bar keeps instead is the page's set of column quick-toggles, the connection badge, the active page's title, the Show Log button and — when authentication is on — the logout button. The toggles follow whichever tree page is current: four on the Execution page (control dock, canvas, strip charts, step info) and three on General Setup (control dock, content, canvas); the Tool House page lays itself out with plain splitters and gets none. The connection badge is Execution-only — the other pages’ canvases carry their own in-panel badges.
Project Menu Behavior
+The Project Path Text Field displays the current project path when a project is loaded. It is implemented as a pure text field (not a button) that allows users to select and copy the path.
The Project Menu manages MachiningProject with the following operations:
| Operation | +Description | +Example | +
|---|---|---|
| New | +Creates a new project | +See DemoBuildGeomOnlyMachiningProject |
+
| Load | +Opens an existing project | +See DemoUseMachiningProject |
+
| ReLoad | +Re-reads the current project from disk, same path | +— | +
| Save | +Saves the current project | +See DemoBuildGeomOnlyMachiningProject |
+
| Save As | +Saves the project to a new location | +See DemoBuildGeomOnlyMachiningProject |
+
| Close | +Closes the current project and clears the shell | +— | +
In the web application every Project action reports through two channels: a background progress line on the footer while it runs, then a toast when it lands — positive on success, a “busy” warning when a concurrent action has already claimed the service (HTTP 409), negative otherwise. In the WPF client the operations log through ILogger and, after a load, set the player panel's DispEngine to the isometric view. What a project change drives in the web shell is the layout's keep-alive epoch, not a canvas view: every cached page is discarded and rebuilt.
Note
+The web implementation reports through the toast helper and the routine-progress footer store; the WPF client reports through ILogger. Neither routes project messages through MessageUtil. Project I/O is asynchronous on both clients, so the shell stays responsive during file I/O.
Platform-Specific Differences
+WPF Application
+-
+
Web Application
+-
+
Source Code Path
+See HiNC App Anatomy for git repository links.
+Web Application
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
See Also
+-
+
Table of Contents
+ +Preference Menu Dropdown
+ +The Preference dropdown holds the application's display settings, and it is on the Main Panel. The server-backed ones are service-wide rather than per-account: one UserService singleton holds one UserConfig, saved to a single UserConfig.xml resolved against the process working directory, so every browser signed in to that service reads and writes the same values. The sign-in cookie carries a user-name claim that nothing outside the authentication endpoints reads, and no preference endpoint is keyed on an account.
In the WPF application the menu binds UserService, whose UserConfig holds the persisted values. In the web application the dropdown has two models: Language and Show Physics Options bind the appState store, hydrated from and written back through /api/preference/*, where the server keeps them on UserConfig; the CSV / CL Controller checkboxes bind the device-local useViewPrefs singleton, persisted in the browser's localStorage and never sent to the server.
Layout
+-
+
So the web application's Preference dropdown carries the Language submenu, the CSV Controller and CL Controller visibility checkboxes, and the Show Physics Options checkbox.
+A server write that fails raises a negative toast and rolls the item back in appState, so the dropdown never shows a language or a physics flag the server did not take.
Source Code Path
+See HiNC App Anatomy for git repository links.
+-
+
See Also
+-
+
Session Message Panel
-Session messages are partitioned by kind into three sinks on LocalProjectService (obtained via dependency injection):
+Session messages are partitioned by kind into four sinks on LocalProjectService (obtained via dependency injection):
The panel surfaces each sink in its own tab, so one tab holds one message kind.
+The panel surfaces each sink in its own tab, so one tab holds one message kind. ResetRuntime — which a project change runs — empties the first three, so each of them holds the current project only; the manipulation sink is outside that sweep, cleared only at the start of the conversion or optimization run that fills it, and its rows therefore survive a project change.
Layout
-
-
Message Table (per tab)
-Each tab renders its sink's message list — Messages, Diagnostics, or Messages — as rows of:
+Each tab renders its sink's message list — Messages, Messages, or Diagnostics for the two NC-diagnostic sinks — as rows of:
Only take the last filtered elements (e.g. 500–1000) for user experience. Find the usage example in the code:
internal static void DemoUseSessionMessageHost(LocalProjectService localProjectService)
@@ -153,7 +164,7 @@
$"[{m.GetSeverity()}] {m.GetId()}: {m.GetNotification()}"));
}
-Add the update-table event per sink: MessageAdded / MessageAdded, and for the session-scoped shell sink the app-lifetime bridge OnShellMessageAdded (with the matching Cleared events). The updating process has to be called by Loose Manner for user experience.
Add the update-table event per sink: MessageAdded / MessageAdded, and for the session-scoped shell sink the app-lifetime bridge OnShellMessageAdded (with the matching Cleared events). The updating process has to be called by Loose Manner for user experience.
Tip
On window desktop application (WPF), consider use textarea instead of datagrid to MessageTable for better performance. Use padding to show the different columns. And use the font in the textarea that with consistent width.
@@ -163,23 +174,23 @@The message display should be real-time.
Behavior of Export Button
-Export ALL filtered elements of the active tab's sink.
+Every tab has its own Export, and it carries that tab's sink alone. It writes the rows the tab is holding — the recent window pulled from that sink, narrowed by that tab's severity, category and text filters — as a CSV of Index,Count,Severity,Category,Id,Anchor,Notification,Detail, named after the sink it came from. It is disabled while the filters match nothing.
SignalR Implementation (Webapi Only)
-One hub per sink — /shellMessageHub, /ncDiagnosticHub, /stepDiagnosticHub — each with a GetMessages(int limit) pull returning MessagesUpdate { messages, totalCount }. A per-sink broadcast service subscribes its sink's MessageAdded/Cleared and raises a coalesced MessagesChanged notification via LooseRunner; the client re-pulls the recent window on each notification (loss-free regardless of how many appends coalesced). The JavaScript components connect to the three hubs to receive real-time updates.
One hub per sink — /shellMessageHub, /ncDiagnosticHub, /stepDiagnosticHub, /ncManipulationDiagnosticHub — each with a GetMessages(int limit) pull returning MessagesUpdate { messages, totalCount }. A per-sink broadcast service subscribes its sink's MessageAdded/Cleared and raises a coalesced MessagesChanged notification via LooseRunner; the client re-pulls the recent window on each notification (loss-free regardless of how many appends coalesced). The client components connect to the hubs they display to receive real-time updates.
Source Code Path
-See this page for git repository.
-WPF Application Source Code Path
+See HiNC App Anatomy for git repository links.
-
-
Web Page Application Source Code Path
+See Also
-
-
Table of Contents
+ +APT Profile Panel
+ +The main model is AptProfile and its property AptProfile.Apt.
+See Cutter Geometry. GeneralApt is the generalization of the other IAptBased types.
+On the web client these fields have no panel of their own: they are rendered inline by the Flute Profile section of the cutter, the Control-Tree node whose role path is toolhouse/tool-<id>/cutter/profile, reached at /tool-house/:toolId/cutter/profile under the page route /tool-house/:toolId?/:tab?/:subtab?. That same section carries the Profile Type selector above the fields. In the WPF client the APT fields are a component of their own, hosted by the Milling Cutter panel's Flute-Profile tab.
Layout
+Web Layout
+-
+
Which of the five type-dependent fields appear is decided by a per-APT-type table held in the panel itself, not by testing the APT object's interfaces:
+| Profile Type | +Fields shown | +
|---|---|
| General APT | +Rc, Rr, Rz, Alpha, Beta | +
| Ball APT | +none — Diameter and Length of Cut only | +
| Column APT | +Rc | +
| Cone APT | +Alpha | +
| Taper APT | +Rc, Rz, Alpha, Beta | +
The interface casts live one layer down, on the read side: the cutter DTO builder fills rc_mm, rr_mm, rz_mm, alpha_deg and beta_deg by casting the APT to IAptRc, IAptRr, IAptRz, IAptAlpha and IAptBeta, and emits null for a cast that fails.
Taper APT is where the panel's table and the engine's types disagree. TaperApt is declared over IAptBased, IAptRz, IAptAlpha and IAptBeta and carries no round radius, so the table's Rc entry gives a Taper profile a Round Radius box that the DTO fills with null — the field reads 0 — and the value the panel sends back is not read by the server's Taper branch, which builds the APT from Diameter, Rz, Alpha, Beta and Flute Height alone. Rc on a Taper APT is therefore inert in both directions.
WPF Layout
+-
+
Tip
+Keep field format G4 on this client. The web fields apply no format: they show the value as stored and commit on blur or Enter.
Features
+Every edit re-sends the whole profile. Changing the type, or any one field, builds a fresh shaper-profile request carrying the type, Diameter, Length of Cut and only those of Rc / Rr / Rz / Alpha / Beta that the chosen type's table lists, and the server replaces the cutter's AptProfile with a newly constructed APT of that type. The panel then clears the tool cache so the canvas redraws.
+Clearing a field is ignored. The shared numeric input emits null for an empty box, and the panel returns on null instead of committing, so a blanked box keeps the previous value rather than writing 0.
+Because the profile changes the flute geometry, the server does more than drop the cutter's cached solids: it also re-aligns the tool's holder-to-cutter anchor transformer, so the cutter is re-placed under the holder at its new height instead of rendering with a wrong exposed length.
+Source Code Path
+See HiNC App Anatomy for git repository links.
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
Table of Contents
+ +Cutter Panel
+ +Overview
+The key component is ICutter. A tool carries at most one cutter, and two concrete types answer to that interface: MillingCutter and FreeformRemover. This panel is where the type is chosen, and it holds the cutter's own General fields.
+On the web client it is the Cutter tab of the Tool House Page, reached at /tool-house/:toolId/cutter under the page route /tool-house/:toolId?/:tab?/:subtab?. It is the panel of the Control-Tree Cutter node under the selected tool — the node whose role path is toolhouse/tool-<id>/cutter. In the WPF client the same surface is the Cutter Management Panel embedded in the tool's editor.
Layout
+Web Layout
+-
+
The three numeric fields are the cutter's General fields, and they sit here under the Metadata-on-container convention — a container's own General fields are edited on the container's panel instead of in a General child. They are plain, always-editable inputs: one label each, no label switching and no auto-computed mode, and all three commit together through the general endpoint, which accepts exactly shankMass_g, honeRadius_um and reliefAngle_deg. A blanked box commits nothing, so clearing a field never writes a zero.
The type also decides what grows below the node. A Milling Cutter grows the cutter's section tabs — Material, Flute Profile, Flute Contours, Upper Beam, Optimization — as the nodes whose role paths end in .../cutter/material, .../cutter/profile, .../cutter/contours, .../cutter/upper-beam and .../cutter/opt; Material is the one gated behind the Advanced Physics preference. Selecting one replaces this panel with that section's own panel; see Milling Cutter Panel. A Freeform Remover or no cutter grows nothing, so the Cutter node is a leaf and the caption or hint is the whole surface; see Freeform Remover Panel.
This panel carries no object management of its own. Object management for the tool library sits on the tool-house root node, where the Object Management Menu Button is bound to the whole .MachiningToolHouse file rather than to a per-cutter file.
WPF Layout
+-
+
The per-cutter .Cutter file is this client's, and so is the third option's Unset wording; the web selector's third option reads None.
Features
+Committing a type on the web is one server call plus a cache clear. Milling Cutter calls EnsureCutter, which keeps the tool's existing MillingCutter and only creates one when the tool has none; None calls ClearCutter, which drops the tool's cutter altogether. The panel then clears the tool cache, re-reads the tool and emits a structure change for its own node, which is how the section tabs appear or disappear with the type.
Cache clearing is not a step the user performs. ICutter implements IClearCache, and every cutter endpoint that changes a field or a geometry clears the cache server-side before it answers: the field endpoints call ClearCache() on the milling cutter directly, while the ones that change flute or beam geometry go through the controller's resync hook, which clears the cache and re-aligns the tool's holder-to-cutter anchor transformer so the cutter is re-placed under the holder at its new height. On top of that the panels call clearToolCache(toolId), which clears the tool's cached solids and the holder's, so the tool canvas redraws.
Source Code Path
+See HiNC App Anatomy for git repository links.
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
Freeform Remover Panel
-The key model is FreeformRemover.
+The key model is FreeformRemover: a cutter whose removal volume comes from two referenced geometries — a non-cutting strut and a cutting shaper — anchored to the holder buckle.
+The two clients divide this cutter between them. The WPF client owns the editing surface: both geometries, the anchor, the spinning flag and the identity fields. The web client offers the type in the Cutter Type selector and nothing more; there is no Freeform Remover editor on the web, and no freeform-remover endpoint in the backend, so a freeform remover is built and edited on the WPF client or through HiNcRcl.
Layout
+WPF Layout
Cutter Panel
+Cutter Tab
-Overview
-The key component is ICutter.
-The Cutter Panel is used to manage cutting tool definitions in HiNC. It supports two main types of cutting tools: MillingCutter and FreeformRemover.
-Layout
+The Cutter tab of the Tool House page, reached as /tool-house/:toolId/cutter and, for its inner
+editors, /tool-house/:toolId/cutter/:subtab. The tab's own panel chooses the cutter type and holds
+the identity fields every cutter has; the type chosen decides which editor grows below it.
Ordered with the tab's own panel first, then one page per cutter type, then the profile editor the +types share.
+Pages
-
-
Features
-Since ICutter implements IClearCache, remember to call the ClearCache method when the cutter geometry or properties change to ensure proper updates in simulation.
-Source Code Path
-See this page for git repository.
-WPF Application Source Code Path
--
-
Web Page Application Source Code Path
--
-
Table of Contents
+ +Milling Cutter Panel
+ +The key model is MillingCutter, the cutter type both clients edit in full.
+On the web client this cutter is not one panel but the section tabs the Cutter Panel grows once a Milling Cutter is assigned: the Control-Tree nodes under toolhouse/tool-<id>/cutter, reached at /tool-house/:toolId/cutter/:subtab under the page route /tool-house/:toolId?/:tab?/:subtab?. The five :subtab values are material, profile, contours, upper-beam and opt. The cutter's General fields — Shank Mass, Hone Radius, Relief Angle — are not among them: they sit on the Cutter node's own panel. In the WPF client the same cutter is one tab control, and it carries several groups the web does not offer.
Layout
+Web Layout
+-
+
Material is the only physics-gated section. Flute Profile, Flute Contours, Upper Beam and Optimization grow for every Milling Cutter. The preference behind the gate is the SPA's Advanced Physics option, held as isShowPhysicsOptions in the app-state store and read from the server's preference endpoint, which reports it as on only while the physics feature is licensed. UserService.EnablePhysics is the WPF client's name for the same preference.
Integral mode is read-only on the web. The cutter DTO reports it, and the Material section reads it to decide whether the Shank Material picker applies, but no endpoint sets it; it is chosen on the WPF client's Property tab.
+Material Section
+-
+
All three pickers are resource-only, which is what leaves the dropdown with a single browse entry: the absolute Browse item is hidden and only Browse Resource… remains. The picked material's name and note are rendered as a caption line under the picker rather than as a tooltip on a readonly name box.
+The mirroring for a solid-end cutter is enforced server-side: loading a flute material onto a solid-end cutter assigns the same material as the shank material, and unsetting it clears both. For an insert-end cutter, unsetting the shank material resets it to the built-in AlloySteel42CrMo rather than to nothing.
+Every coating endpoint answers with the whole cutter DTO, so the row list re-renders from the server's own ordering after an add, a move or a delete.
+Default Resource
+The default resources of Material live in the built-in Resource folder the server exposes as a named root of its own — not in the project folder. Each picker opens Browse Resource… into its own subfolder of that root:
-
+
Flute Profile Section
+-
+
The five types are all wrapped by AptProfile, with a different Apt assigned; which fields appear depends on the type. See APT Profile Panel for the field list and the per-type table that drives it.
+See the DemoBuildMachiningProject sample in the Hi.Sample repository for creating the APT profile and setting it on the cutter.
CustomSpinningProfile is the WPF client's sixth profile type; the web offers the five APT types only.
Flute Contours Section
+This part manages Fluting. It is a stem, not a leaf: the section node carries only the type selector, and the fluting itself is a sub-tree of nodes below it.
+-
+
Both freeform forms share one span-position table: four numeric columns — R (mm), A (deg), Z (mm), R.Ang (deg) — one row per 4D span position, with an insert-next button that clones the row and a double-click to remove it. The header wording mirrors the CSV field order the wire format uses, and the R.Ang column carries a tooltip explaining that the radial angle feeds the bottom-surface loft.
+A flute contour is always committed as a whole. Editing a Setup Angle, a side contour or a bottom contour re-sends the entire contour: a uniform fluting re-sends the fluting with its current flute number, and a free fluting updates the slot at its index. Free flute node ids are positional, so adding or deleting a flute rebuilds the branch to re-point them.
+Picking — unset — in the Fluting Type selector is inert — the panel returns without a call, because the backend exposes no way to null a fluting. Picking — unset — on a Side or Bottom Contour, in contrast, does commit: it clears that contour on the flute.
Upper Beam Section
+The upper beam is the cutter's shank / body above the flute.
+-
+
The banner is persistent rather than a transient toast: the same unreasonable settings fail thermal physics at run time as configuration errors, so they are surfaced at edit time. Creating a geometry here is container-aware — the server builds it, sets it as the cutter's upper-beam geometry, auto-links an ExtendedCylinder to the flute with a valid seed length, and indexes it under the same key so the per-kind editor edits it in place. Each later edit resyncs the cutter and refreshes the warning list.
+Optimization Section
+This part manages MillingCutterOptOption.
+-
+
Both checkboxes are plain labelled checkboxes — neither displays a computed value. The server's defaults when a limit set is written without them are a Yielding Safety Factor of 3 (a utilization factor of about 0.33), a Max Feed Per Tooth of 999, and both limit flags on.
+The minimum uncut chip thickness itself is not on this surface: no cutter endpoint returns it, and no field displays it. It is the WPF client's Optimization panel that computes it from the cutter and the project workpiece's cutting parameter and shows it beside the checkbox.
+WPF Layout
+-
+
UserService.EnablePhysics gates the Property, Material, Flute-Contours, Flute-Inner-Beam and Optimization tabs on this client; the Insert-Cutter tab additionally requires Insert End. Flute-Profile, Upper-Beam and Info are always visible. On the web only the Material section is gated, and the same preference is named isShowPhysicsOptions.
Numeric fields on this client are formatted G4. The web's numeric field applies no format: it shows the value as stored and commits on blur or Enter.
The cutter has no identity fields on the web. Tool ID, Note and the auto-derived Abstract Note belong to the tool, one level up, and are edited on the tool's own General tab; see Tool House Page.
+Features
+Every web edit is a whole-object commit followed by a cache clear. The Flute Profile panel re-sends the entire shaper profile on any field change — only the fields the selected APT type carries are included — then clears the tool cache. The contour panels re-send the whole flute contour. The optimization panel re-sends the whole limit set. The material and coating endpoints each answer with the full cutter DTO, which the panels re-render from.
+A blanked numeric box commits nothing. The shared numeric field emits null for an empty entry, and every cutter panel returns on null rather than writing a zero, so clearing a field leaves the stored value alone.
+Source Code Path
+See HiNC App Anatomy for git repository links.
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
See Also
+-
+
Table of Contents
+ +Cylindroid Holder Panel
+ +The key model is CylindroidHolder: a holder revolved from a Z-R profile, carrying its own name and note and its own STL tessellation resolution.
+On the web client the holder occupies two child sections of the Holder node under the selected tool — Geometry and Resolution, the nodes whose role paths end in .../holder/geometry and .../holder/resolution, reached at /tool-house/:toolId/holder/geometry and /tool-house/:toolId/holder/resolution. Its Name, readonly Abstract Note and Note are edited one level up, on the Holder Panel itself, under the Metadata-on-container convention. Every field ships on both clients; what differs is the surface hosting them — the WPF client gives the same three fields a tab of their own.
Layout
+Web Layout
+-
+
WPF Layout
+-
+
Remember to call UpdateByCylindroid() after geometry reference or content changed.
+Feature
+The two web sections reach the model by different routes, and that difference is why the holder resync belongs to one of them and not the other.
+Geometry. The section mounts the reusable Cylindroid editor on a key. Get publishes the holder's own Cylindroid in the index service under a per-session -HolderCylindroid key and hands that key back — but only when the caller supplies its session key; with no session key the response carries a null key and the section shows its initializing caption in the editor's place. The key names the same object the holder holds, so an edit lands on the holder's geometry directly. Because that generic editor mutates the bare geometry and holds no reference back to the holder, the section issues the holder resync itself after each geometry edit: UpdateGeometryContent runs UpdateByCylindroid() and then ClearCache(), recomputing the holder topology before regenerating the solid — clearing the cache alone would skip the recompute. The slave column mounts only the selected node's panel, so at most one holder-geometry panel is alive at a time and the shared key never collides.
Resolution. The section writes the polar resolution straight onto the holder: SetPolarResolution assigns a fresh PolarResolution2d built from the two values and calls ClearCache(), so the server regenerates the solid cache at the new tessellation. No topology resync is involved on this path. Get also returns the current pair, which is how the two fields are seeded; a null resolution on the model is reported as 0 mm / 0 deg.
Guards sit on both sides. Every action on the controller answers Not Found for an unknown tool id and Bad Request when the tool's holder is not a CylindroidHolder; SetPolarResolution additionally rejects a linear or angle value that is not greater than zero, and the Resolution section refuses to send a non-positive value at all. Both fields commit on blur or Enter.
Source Code Path
+See HiNC App Anatomy for git repository links.
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
See Also
+-
+
Table of Contents
+ +Freeform Holder Panel
+ +The key model is FreeformHolder: a holder whose shape comes from a referenced geometry, typically an STL, positioned by two anchor transformers — one to the spindle, one to the cutter.
+On the web client the holder occupies four child sections of the Holder node under the selected tool — Geometry, Geom To Spindle, Geom To Cutter and Resolution, the nodes whose role paths end in .../holder/geometry, .../holder/geom-to-spindle, .../holder/geom-to-cutter and .../holder/resolution, reached at /tool-house/:toolId/holder/:subtab. Its Name, readonly Abstract Note and Note are edited one level up, on the Holder Panel itself, under the Metadata-on-container convention. Every field ships on both clients; what differs is the surface hosting them — the WPF client gives the same fields a tab strip of its own.
The three shape sections are not holder-specific editors. They are the same generic Geometry and Transformer slots the Fixture branch of the General Setup page is built from: a kind picker on the section node, and the chosen kind's own editor on the child node below it. So an STL holder is picked as StlFile and its source file set through the shared StlFile editor, a TransformationGeom holder grows Inner Geometry and Inner Transformer nodes, a GeomCombination holder grows one node per item, and each placement grows the chosen transformer kind's editor — Static Translation for the usual (0, 0, h) offset.
Layout
+Web Layout
+-
+
WPF Layout
+-
+
Remember to call UpdateByGeom() after geometry reference or content changed.
+Feature
+The generic editors reach the holder's own members, and that is what makes the split work. When the Holder node's children are built, the page calls FreeformHolderController's Get with its session key; the server publishes the holder's Geom, GeomToSpindleTransformer and GeomToCutterTransformer in the index service under three per-session keys (-HolderGeom, -HolderGeomToSpindle, -HolderGeomToCutter) and hands the keys back. The keys name the very objects the holder holds, so an edit through a kind editor lands on the holder directly. The keys are returned even when a member is empty — nothing is published then, the section's picker reads an empty slot, and picking a kind fills that same key — which is how a freshly switched-to Freeform holder, whose geometry starts empty, gets its first shape.
Picking a kind is a container-aware create, not a bare New. The Geometry picker calls CreateGeometry, which builds the geometry, sets it as the holder's Geom and publishes it under the section's key (None clears the geometry). A placement picker first creates the transformer through the kind's own New endpoint and then attaches it with UpdateGeomToSpindleTransformer or UpdateGeomToCutterTransformer. Without the attach, only the index-service entry would change and the holder would keep pointing at its old transformer.
Because the generic editors hold no reference back to the holder, every edit at or below a section ends in the holder resync the page wires onto the section: a geometry edit calls UpdateGeometryContent, a placement edit re-attaches the transformer under its key. Both run UpdateByGeom() to regenerate the solid, ClearCache(), and AlignAnchorByExposedCutterHeight() to re-place the cutter under the holder — clearing the cache alone would leave the cutter where it was. The page then refreshes the tool list and the canvas.
The Resolution section writes the polar resolution straight onto the holder: SetPolarResolution assigns a fresh PolarResolution2d and the holder swaps its solid for one born with the new value, keeping the geometry. Get also returns the current pair, which is how the two fields are seeded.
Guards sit on both sides. Every action on the controller answers Not Found for an unknown tool id and Bad Request when the tool's holder is not a FreeformHolder; SetPolarResolution rejects a linear or angle value that is not greater than zero, and the Resolution section refuses to send one. Fields commit on blur or Enter.
Switching a tool to Freeform is a committing choice. SetHolderType assigns a new FreeformHolder only when the tool does not already carry one, so a holder authored on the WPF client or through HiNcRcl survives a round trip through the web page with its geometry, anchors and resolution intact.
Source Code Path
+See HiNC App Anatomy for git repository links.
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
See Also
+-
+
Table of Contents
+ +Holder Panel
+ +This section describes the user interface and behavior for managing different types of tool holders in the application. Tool holders are crucial components in defining a complete tool assembly.
+Note
+While tool holders are essential components in real-world machining operations, some users may choose not to define them in simulation environments for convenience, particularly when collision detection is not a primary concern. The system allows for this flexibility, though it's recommended to include holders for accurate representation and comprehensive collision analysis.
+The primary models involved are subclasses of IHolder. Two common types are:
+-
+
Each holder type has its own user interface elements for defining its geometry and properties.
+On the web client this panel is the Holder tab of the Tool House Page, reached at /tool-house/:toolId/holder. It is the panel of the Control-Tree Holder node under the selected tool — the node whose role path ends in .../holder.
Layout
+Web Layout
+-
+
The three identity fields belong to whichever holder the tool carries. They sit on this panel under the Metadata-on-container convention — a container's own General fields are edited on the container's panel instead of in a General child — and they commit through the per-type holder controller's SetName and SetNote (CylindroidHolderController or FreeformHolderController), not through ToolHouseController. No holder leaves the panel without them.
The type also decides what grows below the node. A Cylindroid holder grows two child sections, Geometry and Resolution — the nodes whose role paths end in .../holder/geometry and .../holder/resolution; see Cylindroid Holder Panel. A Freeform holder grows four — Geometry, Geom To Spindle, Geom To Cutter and Resolution; see Freeform Holder Panel. Selecting a section replaces this panel in the slave column with that section's own panel. None grows no children, so the Holder node is a leaf and the hint is the whole surface.
This panel carries no object management of its own. Object management for the tool library sits on the tool-house root node, where the Object Management Menu Button is bound to the whole .MachiningToolHouse file rather than to a per-holder file.
WPF Layout
+-
+
Feature
+Committing a type on the web is one server call plus a cache clear. SetHolderType maps the selector value onto the holder classes: none clears the tool's holder, while cylindroid and freeform each assign a new holder only when the tool does not already carry one of that class — so re-picking the type a tool already has keeps the holder it has, and the edits on it, rather than replacing it. When the caller passes its session key the server re-indexes the resulting holder under that session's holder key, and removes that entry for None, so the session key keeps pointing at the tool's current holder. The response carries the holder's class name and its abstract note.
The panel then calls ClearToolCache, which clears the tool's cached solids and, when the holder implements IClearCache, the holder's own cache as well, so the tool canvas redraws against the new holder. Finally it emits a structure change for its own node, which is how the Geometry and Resolution sections appear or disappear with the type.
GetHolder reports the same pair — class name and abstract note — and is what the panel reads on mount to seed the selector; an unknown tool id answers Not Found. The panel then reads the per-type controller's Get (CylindroidHolderController or FreeformHolderController) to fill the three identity fields; for a Freeform holder that same call also publishes the holder's geometry and two placements under the session's keys for the sections below.
Source Code Path
+See HiNC App Anatomy for git repository links.
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
Mat4dControl Component
+Holder Tab
-Mat4dControl is a user control for Mat4d editing and display.
-Main Features
--
-
Source Code Path
-See this page for git repository.
-Web Service Application Source Code Path
--
-
Table of Contents
+ +Tool House Page
+ +The Tool House is a page of its own at /tool-house/:toolId?/:tab?/:subtab?, reached from the menu bar's Page ▾ dropdown. In the WPF client it is a sub-window opened from the Main Panel.
The key model is MachiningToolHouse.
+The model UserService is per-client. On the web it is the server-side Environments/UserService.cs, which Mech/ToolHouseDisplayController.cs reads EnablePhysics from; the WPF client resolves its own UserService from the application's service provider.
Layout
+Three columns, left to right, in two nested resizable splitters — the tool house and its tool list, the selected tool's editor tabs, and the tool canvas. Creating a tool opens no window of its own: New Tool adds a default tool and the user sets it up in the editor tabs.
+-
+
The three anchor-and-buckle flags of HolderEditorDisplayee and the Holder Rendering Mode radios are all disabled while Show Holder is off, so a child option is reachable only when its parent is on. The menu is one flat list of labelled groups rather than nested submenus, which is what keeps it one click deep.
+The cutter's shape mode is not a control here. The server picks it when the canvas binds: InitializeDisplay sets ShapeMode to Solid Bounding Shape whenever UserService.EnablePhysics is false, and the web Display Options dropdown offers no switch for it. The WPF page is the client whose Editor Displayee Options menu carries the Solid Bounding Shape / Detail Physics Shape radio submenu.
The left column is the other place the two clients differ. The WPF Tool House page lists its tools in a DataGrid with a checkbox column, an editable Tool ID column and a read-only Note column, and puts a Batch Actions menu above it — Select All, De-Select All, then Batch Duplicate and Batch Delete — with Duplicate and Delete also on the grid's context menu. On the web page Tool ID and Note are edited on the selected tool's General tab, where Duplicate and Delete are per-tool icon buttons, so the list itself stays a plain navigation column of router links.
+The Tool ID can not be repeated. Create Tool takes the largest existing ID plus 1 (or 1 in an empty house), Duplicate Tool inserts the clone at the first free ID past both the source and the largest existing ID, and renaming a tool onto an ID the house already holds is refused as a conflict.
+When the canvas first binds, InitializeDisplay attaches the MillingToolEditorDisplayee and snaps the camera to the isometric view. Each later tool selection re-points that displayee's MillingToolGetter at the chosen tool and calls the renderingCanvas.DispEngine.SetViewToHomeView().
Duplication Button
+Use Duplicate(params object[]) to duplicate the tool; the clone joins the house under a fresh ID.
+Tool Row Note
+A tool's row shows its note when the note exists and is not an empty string; otherwise it shows the AbstractNote, set in an italic caption so the auto-derived text reads as a fallback.
+Source Code Path
+See HiNC App Anatomy for git repository links.
+HiNC-2025-webservice (Quasar CLI SPA). The page is built on the shared Control Tree: the tool nodes on the left, the selected node's tab cascade in the middle, the canvas on the right.
+-
+
Note
+The fluting type is named loosely on the wire, and every spelling resolves. PUT /{id}/fluting accepts UniformFluting / uniform and FreeFluting / free, and equally UniformContourTray / FreeContourTray. A Control-Tree id whose cutter branch reads …/contours/tray… resolves to the …/contours/fluting… node.
One editor lives on the WPF client only, and the app says so where the type is chosen: the FreeformRemover cutter. Selecting that type on the web page keeps the existing model intact and shows a note pointing at the WPF client or HiNcRcl.
+Three smaller surfaces are likewise the WPF client's: the InsertCutter and FluteInnerBeam physics groups, and the CustomSpinningProfile shaper-profile type — the web profile tab offers the five APT types (General / Ball / Column / Cone / Taper).
See Also
+-
+
Table of Contents
+ +Stick Tool Panel
+ +The term stick is for not only milling, but other remover like electric discharge machining tool.
+On the shipped web client the Stick Tool Panel is the toolhouse/tool-<n> node of the
+Tool House Page: picking a tool in that page's left list mounts it, and
+its editor is the tab strip filling the page's middle column.
The key model is MillingTool.
+Other model: UserService — the WPF client reads its EnablePhysics to decide whether its
+Intelligent Holder tab is shown.
Layout
+-
+
All five tabs ship unconditionally on the web page: the Int. Holder tab is one of the five
+TOOL_TABS, and the tool node always grows its …/intelligent child. The WPF Stick Tool Panel is
+the client that hides its Intelligent Holder tab when UserService.EnablePhysics is false, from
+UpdateIntelligentHolderTabVisibility in Mech/ToolHouse/StickToolPanel.xaml.cs.
Object Management on the web page is house-level rather than per-tool: the
+Object Management Menu Button sits on the Tool House root
+panel with file extension .MachiningToolHouse (load type Hi.Machining.MachiningToolHouse, HiMech)
+and covers the whole house, while the tool node offers Duplicate and Delete for the single tool. The
+WPF Stick Tool Panel is the client that carries a per-tool Object Management Menu Button on its head
+line, with file extension .MillingTool and the Stick Tool Management Panel as its pointed editor
+panel. That client also gives the identity fields a tab of their own — Info, a read-only Abstract
+Note TextBox over an editable Note TextBox; on the web page those two fields are the General tab,
+alongside Tool ID and the Duplicate / Delete buttons.
Tabs and the URL
+The tabs are TOOL_TABS — General, Cutter, Holder, Clamping, Int. Holder —
+declared in wwwroot-src/src/router/treeRoutes.ts, and those same five names are the :tab? segment
+of the page route /tool-house/:toolId?/:tab?/:subtab?. General is the tool node's own panel; the
+other four are the tool node's children (…/cutter, …/holder, …/clamping, …/intelligent),
+registered in wwwroot-src/src/components/controlTree/toolHouseItemTypes.ts.
Cutter and Holder are type-selector stems, so each grows a second, URL-synced sub-tab strip — the
+:subtab? segment, from CUTTER_SECTIONS (Material / Flute Profile / Flute Contours / Upper Beam /
+Optimization — Material only while the shell's Show Physics Options preference is on) and
+HOLDER_SECTIONS (Geometry / Resolution). The levels below that — Flute Contours
+to fluting to flutes to side and bottom contour — nest inside
+wwwroot-src/src/components/controlTree/NodeTabCascade.vue, which the page mounts inside the active
+tab. wwwroot-src/src/composables/useRouteTabs.ts keeps both segments in the URL, defaulting to
+general, and to profile under Cutter / geometry under Holder.
Selecting another tool keeps the tab segments: only the tool part of the URL moves, so the reader
+stays on the tab they were reading. A Control-Tree id addressing any of these levels is translated
+into the page's route params by routeForTreeId, and an id deeper than the URL carries lands on the
+nearest tab.
Note
+The Exposed-Cutter-Height and Preserved-Distance-Between-Flute-and-Spindle-Nose are directly
+related: ExposedCutterHeight = PreservedDistance + FluteHeight, so each value changes when
+the other is changed. Editing Preserved Distance commits
+PUT /api/ToolHouse/{id}/preserved-distance, whose response carries the recomputed pair and fills
+both fields; editing Exposed Height commits PUT /api/ToolHouse/{id}/exposed-height, which
+answers without a body, so the panel re-reads the tool to pick up the new preserved distance.
Source Code Path
+See HiNC App Anatomy for git repository links.
+HiNC-2025-webservice (Quasar CLI SPA). The tool node is a set of tabs on the Tool House page rather +than a panel of its own:
+-
+
See Also
+-
+
Table of Contents
+ +File Explorer Page
+ +The File Explorer page is a server-side filesystem browser covering three named roots:
+-
+
wwwroot-src/src/components/FileExplorer.vue holds the whole browser — toolbar, tree, editor panel
+and preview. wwwroot-src/src/pages/FileExplorerPage.vue is a thin route wrapper around it, and
+wwwroot-src/src/components/widgets/FileExplorerDialog.vue mounts the same component in a modal as
+the app's file picker (see Picker Mode).
Note
+Absolute filesystem paths never leave the server through the File Explorer's own endpoints. The /roots endpoint returns only { name, displayName }, and Common/FileExplorerController.cs routes every exception message through a scrub that replaces root prefixes with the tokens <Admin> / <Project> / <Resource> before returning it. That scrub is private to that controller, so it does not cover the STL preview endpoints the editor column also calls — see STL Preview Pane.
Layout
+-
+
Behavior
+-
+
Picker Mode
+The same component runs inside wwwroot-src/src/components/widgets/FileExplorerDialog.vue, which
+adds an apply bar and emits each pick as "{rootName}:{relativePath}". Props steer it:
+pickable (file / folder / any / none) turns the pick column on, multi swaps radios for
+checkboxes, filters supplies the file-type dropdown (“All Files” is appended when the caller's
+list omits it), lockRoot and allowedRoots narrow the root switcher, and mode: 'save' turns the
+path input into a save target with a forcedExtension appended on confirm.
That dialog is how the app opens and saves files: the menu bar's Project ▾ entries, the
+Object Management Menu Button, FilePathInput, the
+Mission command panels, the Machine Tool page, and the Mechanism Builder's Load and both Save As
+actions.
Syntax Highlighting
+wwwroot-src/src/components/widgets/TextEditor.vue wraps CodeMirror 6 so the editor gets real syntax highlighting for the common project-file formats listed below.
Stack:
+-
+
Props: modelValue (two-way), language: 'xml' | 'json' | 'markdown' | 'csharp' | 'mission-script' | 'text' (default text), readonly, and completionSource — an optional CodeMirror completion source that overrides the language-provided completions, read once at mount. Language and readonly are swapped via Compartment.reconfigure, so the view never has to tear down on mode change.
Extension → language mapping lives in wwwroot-src/src/components/widgets/editorLanguage.ts (single source of truth imported by both TextEditor.vue and FileExplorer.vue):
| Language | +Extensions | +
|---|---|
xml |
+xml, hincproj, CoatingMaterial, CutterMaterial, Holder, WorkpieceMaterial, mp, MillingPara, SpindleCapability, StickMachiningTool, general-mech, mt |
+
markdown |
+md, markdown |
+
json |
+json |
+
csharp |
+cs, csx |
+
mission-script |
+none — the mode is picked from the editor's language select, and wwwroot-src/src/components/mission/ScriptCommandPanel.vue sets it directly on its own editor |
+
text (fallback) |
+everything else, including .nc / .ptp / .mpf / .h / .csv |
+
The language select in the editor bar offers all six modes, so an auto-detected mode can be overridden per file.
+Source Code Path
+See HiNC App Anatomy for git repository links.
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
| Method | +Path | +Notes | +
|---|---|---|
| GET | +/roots |
+List available roots; Project is omitted when no project is loaded. Returns display names only. | +
| GET | +/list?rootName=&relativePath= |
+Directories-first, name-ascending listing; the client re-sorts it per the toolbar's sort control. | +
| GET | +/read-text?… |
+UTF-8 text; when the file is detected as binary returns content=null, isBinary=true. |
+
| POST | +/write-text |
+Body {rootName, relativePath, content}; creates parents as needed. |
+
| POST | +/create-file |
+Fails if target exists. | +
| POST | +/create-directory |
+Recursive create; no-op if already a directory. | +
| POST | +/rename |
+Body {rootName, relativePath, newRelativePath}; refuses cross-root moves. |
+
| POST | +/copy |
+{name}-Copy-00..-Copy-19; uses Hi.Common.FileLines.FileUtil.CopyDirectory for directories. |
+
| DELETE | +/delete?… |
+Files → delete, directories → recursive; refuses to delete the root itself. | +
| GET | +/download?… |
+Streams a file as application/octet-stream. |
+
| GET | +/download-zip?… |
+Zips a directory in-memory and streams application/zip. |
+
| POST | +/upload?… |
+Multipart single-file upload; overwrites target. | +
| POST | +/extract-zip |
+Body {rootName, relativePath}; extracts next to the archive into <name>/. |
+
Addressing
+The browsed location is mirrored into the URL by an optional catch-all: /util/file-explorer/{RootTitle}/{relative/path}. The bare /util/file-explorer still resolves through the named route the menu uses, so both a deep link and a plain menu click land correctly. Browsing rewrites the URL with router.replace, and an external URL change — a paste, a bookmark, browser back — drives the explorer the other way.
The named roots (Admin / Project / Resource) are the only addressing the client sees, and the absolute path is formed and kept on the server — subject to the error-message caveat noted at the head of this page.
+See Also
+-
+
Mission Page
+Util Pages
-The Mission Page manages machining mission commands and execution settings.
-Key Models
+The utility pages are the ones not tied to the main machining workflow. They support side-tasks —
+browsing the server filesystem and building a generic mechanism XML — and are reachable from the
+menu bar's Page ▾ dropdown, below the separator that follows the three workflow pages (Tool House
+/ General Setup / Execution). Their routes are /util/file-explorer and /util/mech-builder; the
+dropdown itself is built in wwwroot-src/src/components/AppMenuBar.vue over the menu.page.*
+strings in wwwroot-src/src/i18n/en/menu.ts, and both routes are declared in
+wwwroot-src/src/router/routes.ts.
The two pages share a component, not just a menu section. The File Explorer's browser
+(wwwroot-src/src/components/FileExplorer.vue) also runs inside a modal wrapper,
+wwwroot-src/src/components/widgets/FileExplorerDialog.vue, and that wrapper is the app's file
+picker: it is what the Mechanism Builder's Load and both Save As actions open, and what the menu
+bar's Project ▾ entries open. One page documents the browser, the other a page built on it.
Pages
-
-
Layout
+Where Neighbouring Editors Live
+Three editors a reader might expect here are shipped surfaces of other pages:
-
-
Mission Type Selection ComboBox
-The options:
--
-
Source Code Locations
-See HiNC GUI Architecture for git repository links.
-Tip
-Implementation Order: When building a new Mission Page, create the page window/panel first, then implement the command panels (List Command Panel, Script Command Panel).
-WPF Application
--
-
Web Application
-Current (Quasar CLI SPA):
--
-
Table of Contents
+ +Mechanism Builder Page
+ +The Mechanism Builder page edits a standalone GeneralMechanism — its anchor topology, per-branch ITransformer, and per-anchor optional geometry inside a Solid. Unlike the project-scoped editors (Fixture / Workpiece / ToolHouse / Spindle Capability), this page is user-scoped — no project needs to be loaded.
-
+
Layout
+-
+
Behavior
+-
+
Source Code Path
+See HiNC App Anatomy for git repository links.
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
IndexService Keys
+The mechanism state is persistent across pages (unlike Fixture which re-initialises on mount), so mechanism keys are not registered with useCleanupHub. Stale keys are harmless; they are re-pointed on the next index-transformer / index-geom call.
-
+
File IO
+Load and both Save As actions go through one server file picker — the File Explorer's dialog wrapper — over the three named roots AdminDirectory / ProjectDirectory / ResourceDir. The XML is read and written by the backend at the picked path; nothing travels through the browser. The picker opens on the root and folder of the last successful operation, starting at ResourceDir, where the shipped mechanism templates live, and a save offers the current file's name with the .default ownership marker stripped and the target extension forced (.GeneralMechanism or .MachineTool).
The File menu has New / Load… / ReLoad plus the two Save As entries; there is no plain “Save” that overwrites the last path. Saving a General Mechanism retargets ReLoad at the new file; saving a Machine Tool deliberately does not, because it is a separate file type that does not replace the open mechanism pointer.
+See Also
+-
+
Table of Contents
+ +STL Preview Pane
+ +The STL Preview Pane is the server-rendered 3D view that takes over the File Explorer's editor
+column while an .stl file is open. It is neither a route nor a dialog of its own: it renders
+inside the right-hand slave panel of /util/file-explorer, and of the modal file picker built on
+the same browser component, replacing the text editor's bar and body for as long as an STL is
+selected. It carries its own rendering connection, its own copy of the file's triangles, and a
+Transform section that can bake a pose back into the file on disk.
Where It Renders
+The File Explorer mounts the pane on the root-relative path of the STL being previewed; the named
+root and that path are both pinned when the preview opens, so switching the explorer's root closes
+the preview rather than silently re-targeting it. Three gestures open one, and all key on the stl
+extension alone:
-
+
Panel visibility starts from the device-local File Explorer preferences on the page and defaults to +shown. Inside the modal picker it always starts hidden and is never written back, so there the +double-click is the only gesture that reaches a preview until the toolbar's editor-panel toggle is +pressed — and in a save-mode picker, where the path field names the save target rather than a place +to navigate to, submitting it never opens a preview at all.
+Four gestures close one: the bar's own close button, opening a text file in the same panel, a root +switch, and the toolbar's editor-panel toggle. The first three leave the panel showing the text +editor; the last settles the text buffer, hides the whole panel and drops the preview together.
+Layout
+-
+
Loading a Preview
+One request path serves every trigger: the canvas reporting a connection id, and any change to the
+root or the relative path. Each request clears the banner and the triangle badge, raises the
+spinner, and posts the named root and the relative path to show.
Server side, the controller resolves the root name, re-combines it with the relative path, refuses +a result that is not a descendant of that root, and refuses a path that does not exist. The read +itself runs on a thread-pool task rather than the request thread, and the format is sniffed from +the file's first two lines rather than from its extension — ASCII and binary STL both load. The +file is opened for shared read and write, so a load does not lock it against other readers.
+A successful read builds the native topology, commits it into the connection's slot, and only then +touches the engine: the displayee is re-pointed at the new composition, the camera is reset to the +engine's home view and the render cache cleared, and the geometry the commit replaced is disposed +afterwards, so the render thread never spends a frame on a dead reference.
+The response carries the triangle count, the bounding box, the connection's origin-axes flag and +the transform key. The pane reads all of those except the bounding box, which it ignores — the +bounding-box readout a reader sees belongs to the STL File Control's info dialog, not to this pane.
+A rejected load — unknown or unavailable root, a path escaping the root, a missing file, an +unreadable file — answers a non-2xx status carrying the server's message, and the shared response +helper folds the status into the text; that composite is what the banner shows. The message itself +is passed through as the server wrote it: unlike the File Explorer's own endpoints, this controller +runs no root-prefix scrub over an exception message, so a read or write that fails down in the +filesystem layer can surface a server path in the banner.
+Cancelling a Load
+Cancellation is the pane's defining behaviour: clicking down a folder of large STLs does not stack +multi-second reads behind each other, and the tree stays fully interactive throughout.
+Three mechanisms compose:
+-
+
The read is cooperatively cancellable rather than interruptible: the STL reader checks the token +once every 4096 triangles, and once every 4096 lines of the pre-scan an ASCII file needs, which +bounds the latency to a few thousand parses rather than a whole file.
+The commit is gated on the ticket. A load that finishes after a newer one superseded it is refused,
+disposes the geometry it has just built, and answers HTTP 200 with a canceled flag; the pane drops
+that response without touching the badge or the banner, and lowers the spinner only when no newer
+request has taken the pane over in the meantime.
The Rendering Connection
+The pane embeds one rendering canvas, so it opens its own SignalR connection to /renderingHub and
+the server gives that connection its own display engine — a second live engine beside the host
+page's canvas whenever the preview is running inside the modal picker. See
+Rendering Canvas on Web Service Application for the
+transport itself.
The connection id is the key for everything on the server side: the display engine, and the preview +service's per-connection slot. The canvas re-emits it after every automatic reconnect, and the pane +answers by re-issuing the show — which is what restores the preview after a connection drop. The +reconnected slot starts empty, so the pane re-pushes its own Origin axes preference whenever the +server's answer disagrees with it. The pose is not restored: every commit mints a fresh identity +transform.
+Release runs off the connection's death rather than off an explicit teardown call. Closing the +preview unmounts the pane, which aborts the in-flight fetch and disconnects the canvas; the hub's +disconnect handler disposes the engine and raises its engine-removed event; the preview service's +subscriber then cancels any load still running, disposes the STL topology and the origin-axes +drawing, and withdraws the transform key. That subscriber exists because a display engine disposes +only itself — never the displayee it was pointed at.
+The clear endpoint performs most of that detach on demand — it cancels the load in flight, empties
+the canvas, disposes the topology and withdraws the transform key — but it keeps the connection's
+slot registered, with its origin-axes drawing and flag intact for the next preview; only the
+connection's death frees those. It is also the one entry in this surface that ships without a caller
+in the SPA.
Server-Side Composition
+The slot a connection owns holds a small display graph rather than a bare mesh:
+-
+
Stl is not among them. The reader's output is a local of the show request, alive only +long enough for the native topology to copy its triangles into its own buffer and for the response +to report the triangle count and the bounding box; the save path then mints a second one out of the +topology's own snapshot, and it is that second object the save transforms and writes.
+The committed topology always holds the file's as-loaded triangles. The editor's pose lives only in +the wrapper, which is why it can be re-applied, reset or saved repeatedly without drift.
+Transform and Save
+Each committed load registers a fresh GeneralTransform — identity, and a new
+object rather than a reset one — in the server's keyed object store, and returns its key. The pane
+hands that key to the standard general-transform editor, the same component and the same REST
+surface the Transformer panels use; there is no preview-specific transform schema. After every
+committed edit the pane posts apply-transform, which re-reads the transform's matrix into the
+display wrapper and clears the render cache. That is display only — the file is untouched.
Save asks for confirmation naming the root-relative path, then bakes. The service snapshots the +native triangles under the topology's own dispose lock, multiplies them by the current matrix and +writes the result over the previewed file as a binary STL, so an ASCII source is rewritten in +binary form. Because the shown topology keeps the as-loaded triangles and the editor keeps its +values, screen and disk agree afterwards — both are the original geometry times the current matrix — +and saving twice with the same values rewrites the same file rather than compounding the pose. +Resetting the editor's fields returns to the as-loaded pose.
+A save that finds the topology already disposed by a newer selection reports that it was superseded +instead of writing. On success the pane raises a toast and the File Explorer re-lists the containing +folder, so the row's size and modified columns catch up. Nothing else is refreshed: a +StlFile geometry elsewhere in the project that references the same path keeps the +triangles it already loaded.
+Switching files re-mints the transform — a new key, an identity pose, and an editor that re-reads +cleanly because the key changed. The old key is withdrawn from the keyed store on replacement, on +clear and on connection death; the two derived keys the editor mints beneath it for the rotation and +translation sub-transformers are not withdrawn with it.
+Endpoints
+Every entry is a POST under /api/stl-preview, addressed by the rendering connection id. The four
+that touch the canvas resolve the display engine through the non-creating lookup, so a disconnected
+id cannot orphan a fresh engine; save needs no engine at all and writes straight from the slot.
| Path | +Body | +Purpose | +
|---|---|---|
show/{renderingConnectionId} |
+{ rootName, relFile } |
+Supersede, read, commit, re-point the engine. Answers the triangle count, the bounding box, the origin-axes flag and the transform key — or a canceled flag. |
+
set-coordinate/{renderingConnectionId} |
+a bare boolean | +Show or hide the origin axes. With nothing on the canvas yet it stores the flag for the next commit. | +
apply-transform/{renderingConnectionId} |
+none | +Re-read the transform's matrix into the display wrapper. | +
save/{renderingConnectionId} |
+none | +Bake the matrix into the geometry and overwrite the file. | +
clear/{renderingConnectionId} |
+none | +Cancel the load in flight, empty the canvas, free the STL geometry; the slot and its origin-axes drawing survive. | +
rootName is one of the three named roots — the admin directory, the loaded project directory and
+the shared resource directory — resolved server-side; the project root is unavailable until a
+project is loaded. Addressing is root-relative in both directions and the absolute path is formed
+and kept on the server; the unscrubbed error text noted above is the one place it can escape.
The Text Buffer Underneath
+Opening a preview does not disturb the text editor beneath it. There is no flush, no discard prompt +and no reload: the loaded path, the buffer, its saved snapshot and its dirty mark are all left as +they were, and the bar's close button brings them straight back, dirty star included. Clicking a +text file while a preview is open closes the preview first and then loads that file through the +editor's normal open path, which settles the outgoing buffer the way any file switch does.
+Two consequences follow from the buffer staying live. The toolbar's editor-panel toggle remains +reachable while the preview is up, and it settles the buffer before hiding the panel — so a dirty +buffer in manual mode raises its discard confirm at that point, even though the visible pane is a 3D +canvas. And an auto-save debounce armed just before the preview opened still fires underneath it.
+Source Code Path
+See HiNC App Anatomy for git repository links.
+Web Application
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
HiAPI Engine
+-
+
See Also
+-
+
Table of Contents
+ +Widgets
+ +The reusable controls that pages embed rather than own. Each page here documents one control — most
+of them under the SPA's components/widgets/ directory — together with its WPF counterpart where one
+exists, so a reader who meets the same control on four screens reads about it once.
A control earns a page here when more than one screen embeds it. A control that only ever appears +on one screen is documented on that screen's page instead.
+Ordered by how widely each control is embedded across the two clients, most-embedded first.
+Pages
+-
+
See Also
+-
+
Table of Contents
+ +Mat4dControl Component
+ +Mat4dControl edits and displays a 4x4 transform matrix. It is an embedded widget with no route and +no Control-Tree node of its own, reached only through the editor that hosts it.
+Key Model
+The persisted model is Mat4d, exposed over /api/Mat4d by Widget/Mat4dController.cs.
The widget's own model is a plain array of sixteen numbers, exported as type Mat4 from
+wwwroot-src/src/components/widgets/Mat4Input.vue. The layout is column-major, matching the backend
+Mat4d: cell (row, col) resolves to index col * 4 + row, so indices 0 to 3 are the first column and
+12 to 15 the translation column. wwwroot-src/src/api/transformer.ts states the same column-major
+contract on the wire. A model shorter than sixteen numbers, or not an array at all, is padded out
+with zeros before display, and a longer one is truncated to sixteen.
The widget carries no key and calls no endpoint. Its host,
+wwwroot-src/src/components/topo/StaticFreeformEditor.vue, binds it with v-model and persists
+each change through updateStaticFreeformMat from wwwroot-src/src/api/transformer.ts, which posts
+the whole sixteen-number array to /api/StaticFreeform/Update; Mech/Topo/StaticFreeformController.cs
+rejects a payload that is not exactly sixteen elements.
The props are modelValue, disable, readonly, identity (on by default) and invert (off by
+default), and the emits are update:modelValue, identity and invert. disable both disables
+the cells and dims the whole block.
Layout
+-
+
The cells and both buttons are inactive while the control is disabled or readonly.
+Feature
+Single input mode
+The grid of sixteen cells is the only editing surface, and each cell shows the full precision of its +number with no rounding for display.
+Matrix operations
+-
+
Commit and special values
+Each cell commits on blur or Enter, and the widget emits the entire sixteen-number array rather than +a single index. An unparseable cell reverts to its previous value, an empty cell becomes 0, and a +cell whose parsed value is unchanged emits nothing. Formatting and parsing are local to the +component: Infinity and -Infinity are shown and parsed as literal text, while NaN and a missing cell +both display as 0, so NaN is not round-tripped.
+Source Code Path
+See HiNC App Anatomy for git repository links.
+-
+
See Also
+-
+
Table of Contents
+ +Numeric Input
+ +NumericInput is the single-value numeric field the rest of the web application embeds. It has no
+route and no Control-Tree node of its own: it is reached only through the editors that host it,
+and those span the equipment and mission trees, the Tool House tool editors, the Legacy Controller
+tabs, the transformer and geometry editors and the Preference menu. The rule for carrying NaN and
+the infinities across the JSON boundary is stated once in
+Numeric Input/Output; this page is the widget that implements
+the client end of it.
The Binding Contract
+The widget owns no value. It holds the raw text the user is typing and nothing else, and every +committed number is handed straight back to the host, which decides what to persist and when.
+| Prop | +Meaning | +
|---|---|
modelValue |
+number, null or undefined — the value to show |
+
label |
+the field's label, inside the outline; floats to the top of it once the field has content | +
hint |
+text below the field | +
unit |
+a display-only suffix inside the field | +
readonly / disable |
+passed through to the underlying field | +
min / max |
+inclusive bounds, checked on commit | +
allowEmpty |
+whether a blank field commits null; defaults to on |
+
hideBottomSpace |
+stop holding space below the field; a hint or message still renders when there is one | +
rules |
+extra validation functions over the raw text | +
Two events leave the widget.
+-
+
Commit Semantics
+Typing changes nothing but the text in the box. The commit runs on blur and on Enter, and on +nothing else — there is no per-keystroke emit, no debounce and no timer. The underlying control is +a plain text field rather than a browser number input, so there is no spinner, no step and no +keystroke-level filtering: any text at all may sit in the box until the field is committed.
+A commit parses the text once and then takes one of three exits. On success the error strip is
+cleared, update:modelValue is emitted, and the box is rewritten from the parsed number. On text
+that does not parse, parseError is emitted and a message appears. On a number outside the bounds,
+a message appears. Neither failing exit emits update:modelValue, and neither rewrites the box.
Enter commits without moving focus, so the value is committed again when the field is finally left.
+The widget carries no equality guard — it emits whenever the text parses and passes the bounds,
+whether or not the result differs from modelValue — so that second commit reaches the host as a
+second, identical write. Here the widget parts company with the vector and matrix editors, both of
+which compare against the model before emitting.
Bounds and Special Values
+min and max are inclusive and are enforced inside the widget, before anything is emitted: a
+number below min or above max is refused and the host never sees it.
The bounds apply to finite numbers only. Infinity, -Infinity, NaN and the null a blank
+field produces all skip the range check, so a field declared with a min of 0 still commits
+-Infinity and NaN. Where that matters, the host filters what it receives.
| Typed | +Committed | +Shown afterwards | +
|---|---|---|
Infinity in any case, or ∞ |
+Infinity |
+Infinity |
+
-Infinity in any case, or -∞ |
+-Infinity |
+-Infinity |
+
NaN in any case |
+NaN |
+(empty) | +
blank, allowEmpty on |
+null |
+(empty) | +
blank, allowEmpty off |
+nothing — parse error | +the blank stays | +
anything JavaScript's Number() reads as a finite number |
+that number | +its default string form | +
| anything else | +nothing — parse error | +the rejected text stays | +
Number() is the whole numeric parser, so exponent notation and the 0x, 0b and 0o integer
+literals are accepted alongside ordinary decimals, while a thousands separator is not. Its result
+must also come back finite to be accepted, so a literal that overflows the double — 1e400 — is a
+parse error rather than a second route to Infinity. The two spellings at the head of the table
+are the only way to reach one.
Two consequences follow from NaN and null sharing the empty box. A NaN arriving from the
+server is indistinguishable from a blank field; and because a blank field commits null under the
+default, merely focusing such a field and leaving it replaces the NaN with null. With
+allowEmpty turned off the same field instead fails to parse on every blur until something is
+typed into it.
The infinity spellings the widget writes and reads are the same strings the API layer puts on the
+wire for a non-finite number — see Numeric Input/Output. The
+widget itself never touches the wire: it emits a JavaScript number, and converting a non-finite one
+into its string form belongs to the host's API module, as wwwroot-src/src/api/mission.ts does for
+the mission commands. That conversion is not symmetric for NaN: the mission module writes all
+three strings but recognises only the two infinity spellings when reading, so a NaN returned by
+the endpoint becomes the caller's supplied default instead.
Unit Suffix and Precision
+unit is rendered as a suffix inside the field, to the right of the text. It is decoration only:
+it is not part of the editable text, it is not parsed, and no unit conversion happens anywhere in
+the widget — the number committed is in whatever unit the host's model already uses. Hosts pass
+plain unit text, mm and deg being the common ones, alongside mm/min, rpm, N, °C and
+MB.
There is no precision, decimals or step prop. A value is displayed through JavaScript's default +number-to-string conversion, so it shows at full precision and switches to exponent notation at the +magnitudes where that conversion does. Rounding, where a host wants it, is the host's own: the +graphic-cache current-size field rounds in its commit handler, after the widget has handed the +number over, while the two limit fields in the same menu send what they were given.
+Validation Messages
+Three messages can appear under the field, and all three are built inside the component in English:
+Invalid number: "…" quoting the rejected text, Must be ≥ … and Must be ≤ … quoting the bound.
+They are not keys and appear in no i18n bundle, so they do not follow the application's language
+setting — unlike label and hint, which hosts pass in already translated.
rules is a separate mechanism and differs in three ways: its functions receive the raw display
+text rather than the parsed number, they run on the underlying field's own validation pass —
+as the text changes, and again when focus leaves — rather than inside the commit, and they do
+not gate the commit: a value failing a caller rule is still parsed, still bounds-checked and
+still emitted. While the widget's own message is showing, it also masks the rule's.
No shipped host passes rules, and none fills the append slot, so both are available rather
+than exercised. hideBottomSpace is the opposite — it is among the most-passed props on the
+widget, set on nearly every field the SoftNc controller panels, the spindle contour editor and the
+fluting span list drop into a table cell.
When the Box and the Model Disagree
+A rejected commit leaves the typed text in the box. That text is replaced only when modelValue
+changes to something whose formatted form differs from what is showing; the widget does not revert
+on its own. That is the second thing the vector and matrix editors do that this one does not: both
+reinstate the last valid value the moment a cell fails to parse. Two situations follow, and both
+put a number on screen that was never stored:
-
+
The message is equally persistent: it is cleared only by the next successful commit, so it can +outlive the value that caused it and sit under a field the host has since repopulated.
+Layout
+-
+
Source Code Path
+See HiNC App Anatomy for git repository links.
+Web Application
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
See Also
+-
+
Object Management Menu Button
The menu button represent the target object with getter function and setter function. The target object generally is IMakeXmlSource.
@@ -97,7 +97,7 @@ Action<TargetObject> TargetObjectSetter{get;set;}Show message when paste action success or failed. See Handle Message and Exception.
+Show message when paste action success or failed. See Handle Message and Exception.
If the data type is not matched, show the un-matched message.
Editor Panel Mode Ratio Button
The Editor Panel switched by the Editor Panel Mode Ratio Button.
@@ -178,18 +178,18 @@ The last argument should also be delivered by the host. So there must exist an pShows error message if the xml-parsing or object creation failed on XML Editor Panel Apply Button applied.
-WPF Application Source Code Path
--
-
see this page for git repository.
-Web Page Application Source Code Path
+Source Code Path
+See HiNC App Anatomy for git repository links.
HiNC-2025-webservice (Quasar CLI SPA):
See Also
+-
+
Table of Contents
+ +Polar Resolution 2D Panel
+ +The model is PolarResolution2d: a linear step in millimetres plus an angular step, carried in radians and edited in degrees, that together tessellate a revolved shape into an STL mesh. Both clients edit it on a tool holder and nowhere else — the WPF client through this widget, the web client through the Cylindroid holder's Resolution section.
+Layout
+WPF Layout
+-
+
Web Layout
+-
+
There is no enable checkbox and no null state on the web: both fields are always editable, and each commits on blur or Enter, and only when both values are greater than zero.
+Feature
+The Enable CheckBox is always shown. The host drives the WPF widget only through GetterFunc / SetterFunc; it calls UpdateUI() after its own model changes and subscribes to OnModelChanged to react to an edit.
An edit republishes a whole new carrier through the setter instead of mutating the current one, in either client. A holder's PolarResolution2d setter swaps the holder's solid for one born with the new value and disposes the old one, and skips a value-equal reassignment — which is what makes the WPF widget's per-keystroke setting affordable. The web client performs the same swap server-side inside SetPolarResolution, then clears the holder's cache.
If the host model is null, it may mean the resolution applied the default value: PolarResolution2d is a snapshot of the solid's identity, and a null carrier lets a parametric geometry apply its own default. Only the WPF client can reach that state. The service reports a null resolution as 0 mm / 0 deg, the two web fields display those zeros, and the commit guard then blocks any write until real numbers are typed.
+Source Code Path
+See HiNC App Anatomy for git repository links.
+HiNC-2025-webservice (Quasar CLI SPA): no standalone widget. The resolution is edited on the Cylindroid holder's Resolution section, at the Control-Tree id toolhouse/tool-{id}/holder/resolution and the route /tool-house/:toolId/holder/resolution. A Freeform holder grows no sections in the web client; its Holder panel shows a hint pointing at the WPF client instead.
-
+
See Also
+-
+
Table of Contents
+ +RenderingCanvas Tool Bar
+ +The RenderingCanvas Tool Bar is the camera-preset menu that every 3D canvas in the app carries. In the WPF client the control holds a DispEngine and drives it directly from each menu click. On the web it holds no engine: it takes a canvas prop and calls that component's exposed setView(name), which invokes the SetView hub method, and the hub is what drives the per-connection DispEngine.
View Menu
+The bar is a single View ▾ menu. Its seven entries, in shipped order, are Isometric, Front, Back, Right, Left, Top and Bottom. The engine also understands a home view and RenderingCanvas.vue exposes setViewToHomeView, but this menu does not offer it.
| Entry | +API Method | +
|---|---|
| Isometric | +SetViewToIsometricView() | +
| Front | +SetViewToFrontView() | +
| Back | +SetViewToFrontView() + TurnBackView() | +
| Right | +SetViewToRightView() | +
| Left | +SetViewToRightView() + TurnBackView() | +
| Top | +SetViewToTopView() | +
| Bottom | +SetViewToTopView() + TurnBackView() | +
Back View Implementation
+Back / Left / Bottom views are composed by first calling the corresponding forward-view method (SetViewToFrontView / SetViewToRightView / SetViewToTopView) and then invoking TurnBackView() to flip the camera about the view plane. Both clients compose them the same way — the web in the hub's SetView switch, WPF in the tool bar's click handlers.
Canvas Binding
+On the web the tool bar declares exactly one prop, a nullable canvas, typed structurally as an object exposing setView(v: string): Promise<void> rather than as the RenderingCanvas component type. Every entry calls it optionally, so a tool bar with no canvas bound still opens and each entry is a no-op. The preset names travel to the hub untranslated; only the row labels are localized, from the widgets.canvas.* keys.
Scene Menu
+Pages that sit next to a RenderingCanvas surface a per-page Scene ▾ menu button — the DisplayOptionsMenu component, labelled from widgets.canvas.scene because no caller overrides the label. It chooses what the 3D scene draws (solids, coordinates, display aids), as distinct from the camera-oriented View ▾ menu beside it. The layout (header + checkboxes + radio rows) is shared across four callers — the Execution page's extended tool bar, the General Setup equipment panel, the Tool House setup panel and the STL preview pane — so it is implemented once as a generic, schema-driven component.
Schema
+Each dropdown consumes a DisplayGroup[] array. A group has an optional header and a flat list of items:
-
+
The component is generic over the radio value type, so RenderingMode / HolderRenderingMode / etc. stay fully typed at the call site. Besides groups it accepts label, disable, title, minWidthPx (default 220), size (default sm), contentClass and compact. compact is a tighter-than-dense row mode, used by the General Setup and Tool House panels for their long option lists; the General Setup, Tool House and Execution callers pass content-class="bg-white" to opt out of Quasar's transparent menu.
HiNC-2025-webservice (Quasar CLI SPA)
+-
+
WPF
+-
+
Source Code Path
+See HiNC App Anatomy for git repository links.
+HiNC-2025-webservice (Quasar CLI SPA):
+-
+
See Also
+-
+
Table of Contents
+ ++Class SetupEquipment +
+ +The authored (setup) face of the machining equipment: the machine chain, +workpiece, fixture, environment data and the Setup-page tool selection, +with the project XML IO. This is the ONLY face that persists — the runtime +face (MachiningEquipment) is materialised from it and never +serialized, so a mid-run save can no longer write live poses or the +runner-equipped tool into the project file.
+
+The XML wire name stays "MachiningEquipment" (see XName):
+shipped project files keep their layout, only the deserialized object changed.
+
public class SetupEquipment : IMachiningEquipment, IDisplayee, IExpandToBox3d, IGetAnchoredDisplayeeList, IGetProgramCl, IGetAsmb, IGetAnchor, IGetTopoIndex, IGetMachiningChain, IMakeXmlSource
+ -
+
-
+
-
+
-
+
Constructors +
+ + + + ++ SetupEquipment() + +
+ +Ctor.
+public SetupEquipment()
+ + SetupEquipment(XElement, string, string, IProgress<IMessage>) + +
+ +Initializes a new instance of the SetupEquipment class from XML data.
+public SetupEquipment(XElement src, string baseDirectory, string relFile, IProgress<IMessage> progress)
+ Parameters
+-
+
Properties +
+ + + + ++ Asmb + +
+ +Asmb of the entire authored equipment topology.
+public Asmb Asmb { get; }
+ Property Value
+-
+
+ BackgroundTemperature_C + +
+ +Gets or sets the background temperature in Celsius. +This is a convenience property that converts between Celsius and Kelvin.
+public double BackgroundTemperature_C { get; set; }
+ Property Value
+-
+
+ BackgroundTemperature_K + +
+ +Gets or sets the background temperature in Kelvin. +This represents the ambient temperature of the machining environment.
+public double BackgroundTemperature_K { get; set; }
+ Property Value
+-
+
+ CoolantHeatCondition + +
+ +Gets or sets the coolant heat condition settings. +This includes coolant temperature and heat transfer coefficients.
+public CoolantHeatCondition CoolantHeatCondition { get; set; }
+ Property Value
+-
+
+ CoolantHeatConditionFile + +
+ +Gets or sets the file path of the coolant heat condition +(WorkpieceMaterial pattern: when set, XML IO externalizes +CoolantHeatCondition to this side-file; when null the +condition serializes inline).
+public string CoolantHeatConditionFile { get; set; }
+ Property Value
+-
+
+ Fixture + +
+ +Fixture.
+public Fixture Fixture { get; set; }
+ Property Value
+-
+
+ MachiningChain + +
+ +Body of the equipment — the authored machine topology instance. The +runtime face gets its own instance from +MaterialiseMachiningEquipment(string, IProgress<IMessage>); the two never share graph +objects (only leaf Solid geometry is shared).
+public IMachiningChain MachiningChain { get; set; }
+ Property Value
+-
+
+ MachiningChainFile + +
+ +Gets or sets the file path of the solid machining chain. +This is used for XML serialization and file management.
+public string MachiningChainFile { get; set; }
+ Property Value
+-
+
+ MachiningTool + +
+ +The Setup-page selected tool (persisted as the project's
+<MillingTool>). Never the tool-house instance itself — see
+Tooling(int, MachiningToolHouse).
public IMachiningTool MachiningTool { get; set; }
+ Property Value
+-
+
+ SpindleCapability + +
+ +Gets or sets the spindle capability configuration. +This defines the operational capabilities and limits of the spindle.
+public SpindleCapability SpindleCapability { get; set; }
+ Property Value
+-
+
+ SpindleCapabilityFile + +
+ +Gets or sets the file path of the spindle capability configuration. +This is used for XML serialization and file management.
+public string SpindleCapabilityFile { get; set; }
+ Property Value
+-
+
+ TableToComp + +
+ +Transformer of the branch from table buckle to workpiece side buckle. +Note that the workpiece side buckle links to Fixture if fixture exist; +otherwise, it links to Workpiece if workpiece exist. +If Workpiece either Fixture do not exist, this property makes no effect.
+public ITransformer TableToComp { get; set; }
+ Property Value
+-
+
+ Workpiece + +
+ +Workpiece.
+public Workpiece Workpiece { get; set; }
+ Property Value
+-
+
+ WorkpieceDisplayee + +
+ +Displayee for workpiece rendering. Optional on the authored face — +setup views typically draw the workpiece through their own display +composition instead.
+public IDisplayee WorkpieceDisplayee { get; set; }
+ Property Value
+-
+
+ XName + +
+ +Name for XML IO. Pinned to the shipped wire name
+“MachiningEquipment” — the type was split off the runtime face,
+the file layout was not.
public static string XName { get; }
+ Property Value
+-
+
Methods +
+ + + + ++ Display(Bind) + +
+ +Display function called in DispEngine rendering loop.
+public void Display(Bind bind)
+ Parameters
+-
+
+ ExpandToBox3d(Box3d) + +
+ +Expands the destination box. +This function is usually used to compute the bounding box of elements.
+public void ExpandToBox3d(Box3d dst)
+ Parameters
+-
+
+ GetAnchor() + +
+ +Get key anchor. (i.e. root anchor)
+public Anchor GetAnchor()
+ Returns
+-
+
+ GetAnchoredDisplayeeList() + +
+ +Gets a list of anchored displayable objects.
+public List<IAnchoredDisplayee> GetAnchoredDisplayeeList()
+ Returns
+-
+
+ GetAsmb() + +
+ +Gets the key asmb.
+public Asmb GetAsmb()
+ Returns
+-
+
+ GetMachiningChain() + +
+ +Gets the machining chain instance.
+public IMachiningChain GetMachiningChain()
+ Returns
+-
+
+ GetProgramCl() + +
+ + + + +public DVec3d GetProgramCl()
+ Returns
+-
+
+ GetToolTipXyzOnProgramZero() + +
+ +Get tool tip xyz from workpiece geom anchor.
+public Vec3d GetToolTipXyzOnProgramZero()
+ Returns
+-
+
+ GetTransformFromRootToProgramZero() + +
+ +Gets the transform matrix from the root coordinate system to the program zero coordinate system.
+public Mat4d GetTransformFromRootToProgramZero()
+ Returns
+-
+
+ MakeXmlSource(string, string, bool) + +
+ +Creates an XML representation of the object. +This method may also generate additional resources such as related files.
+public XElement MakeXmlSource(string baseDirectory, string relFile, bool exhibitionOnly)
+ Parameters
+-
+
Returns
+-
+
Remarks
+For the demand of easy moving source folder (especially project folder) without configuration file path corruption, the relative file path is applied.
+The baseDirectory is typically the folder at the nearest configuration file folder.
+Since the folder can be moving with the configuration file.
+ MaterialiseMachiningEquipment(string, IProgress<IMessage>) + +
+ +Builds the runtime face from this authored face: a fresh +MachiningEquipment whose topology is an XML round-trip +copy of this one (chain, fixture, workpiece, table transformer, tool), +with the heavy machine-body and fixture Solid instances +SHARED (this face keeps their lifecycle), axis poses copied, and the +environment data stamped by reference. Information flows one way: +the returned instance never writes back into this one.
+public MachiningEquipment MaterialiseMachiningEquipment(string baseDirectory, IProgress<IMessage> progress)
+ Parameters
+-
+
Returns
+-
+
+ Reg(XFactory) + +
+ +Registers this type's deserializer with the given XFactory
+and chains Reg(factory) on dependents. Idempotent.
public static void Reg(XFactory factory = null)
+ Parameters
+-
+
+ Tooling(int, MachiningToolHouse) + +
+ +Selects the Setup-page tool by toolId from
+toolHouse. The authored face never hosts the
+tool-house instance itself: the runtime face equips house instances
+(and mutates their tessellation mid-run), and one tool's Asmb cannot
+live in two topologies — so this hosts a private duplicate.
+Clear the selection (“do not show”) by setting
+MachiningTool to null.
public bool Tooling(int toolId, MachiningToolHouse toolHouse)
+ Parameters
+-
+
Returns
+-
+
Exceptions
+-
+
-
@@ -114,6 +118,20 @@ The adjacent solids will not be added to the
-
+
@@ -126,7 +144,10 @@ Interfaces
diff --git a/App/wwwroot/HiAPI-docsite/api/Hi.Machining.MachiningVolumeRemovalProc.StepMotionSnapshot.html b/App/wwwroot/HiAPI-docsite/api/Hi.Machining.MachiningVolumeRemovalProc.StepMotionSnapshot.html
index ca0e6f88..c87a59d6 100644
--- a/App/wwwroot/HiAPI-docsite/api/Hi.Machining.MachiningVolumeRemovalProc.StepMotionSnapshot.html
+++ b/App/wwwroot/HiAPI-docsite/api/Hi.Machining.MachiningVolumeRemovalProc.StepMotionSnapshot.html
@@ -186,8 +186,8 @@ Class MachiningVolumeRemovalProc.StepMotionSnapshot
-
- StepMotionSnapshot(DVec3d, DVec3d, SeqPair<Mat4d>, Dictionary<Anchor, Mat4d>, double[], bool, IMachiningTool, WorkpieceService, double, CoolantHeatCondition, SortedList<double, double>)
+
+ StepMotionSnapshot(DVec3d, DVec3d, SeqPair<Mat4d>, Dictionary<Anchor, Mat4d>, double[], bool, IMachiningTool, WorkpieceService, double, CoolantHeatCondition, SortedList<double, double>, DVec3d)
@@ -196,7 +196,7 @@ Class MachiningVolumeRemovalProc.StepMotionSnapshot
- public StepMotionSnapshot(DVec3d GeomCl, DVec3d ProgramCl, SeqPair<Mat4d> Seq, Dictionary<Anchor, Mat4d> AnchorTransformDictionary, double[] McValues, bool EnableSweeping, IMachiningTool MachiningTool, WorkpieceService WorkpieceService, double BackgroundTemperature_K, CoolantHeatCondition CoolantHeatCondition, SortedList<double, double> FluteZToDzList)
+ public StepMotionSnapshot(DVec3d GeomCl, DVec3d ProgramCl, SeqPair<Mat4d> Seq, Dictionary<Anchor, Mat4d> AnchorTransformDictionary, double[] McValues, bool EnableSweeping, IMachiningTool MachiningTool, WorkpieceService WorkpieceService, double BackgroundTemperature_K, CoolantHeatCondition CoolantHeatCondition, SortedList<double, double> FluteZToDzList, DVec3d PreTipPose = null)
Parameters
@@ -233,6 +233,13 @@ Class MachiningVolumeRemovalProc.StepMotionSnapshot
+ PreTipPose
+
+
+
+ The tip pose the previous StepMotion(bool, double, Mat4d) call fed to the
+valve, on the workpiece-geometry coordinate – whether or not that call produced a step
+(the valve skips a collinear return over a segment it already covered). Null on the first
+call after a reset. The one-step reference for the real tip feedrate; Seq.pre is not,
+it may span two steps back.
+
+
+
+
+ public DVec3d PreTipPose { get; init; }
+
+
+
+
+
+
+ Property Value
+
+
+
+
+
+
+
+
+
+
diff --git a/App/wwwroot/HiAPI-docsite/api/Hi.MachiningProcs.LocalProjectService.html b/App/wwwroot/HiAPI-docsite/api/Hi.MachiningProcs.LocalProjectService.html
index 6ab60d29..528bd712 100644
--- a/App/wwwroot/HiAPI-docsite/api/Hi.MachiningProcs.LocalProjectService.html
+++ b/App/wwwroot/HiAPI-docsite/api/Hi.MachiningProcs.LocalProjectService.html
@@ -566,7 +566,9 @@ Will be removed when Gets or sets the fixture.
+ Gets or sets the authored fixture (setup face). The runtime face
+follows by rebuild; runtime readers use
+MachiningEquipment.Fixture.
@@ -831,7 +833,9 @@ post-run reset.
- StepMotionSnapshot(DVec3d, DVec3d, SeqPair<Mat4d>, Dictionary<Anchor, Mat4d>, double[], bool, IMachiningTool, WorkpieceService, double, CoolantHeatCondition, SortedList<double, double>)
+
+ StepMotionSnapshot(DVec3d, DVec3d, SeqPair<Mat4d>, Dictionary<Anchor, Mat4d>, double[], bool, IMachiningTool, WorkpieceService, double, CoolantHeatCondition, SortedList<double, double>, DVec3d)
@@ -196,7 +196,7 @@ Class MachiningVolumeRemovalProc.StepMotionSnapshot
- public StepMotionSnapshot(DVec3d GeomCl, DVec3d ProgramCl, SeqPair<Mat4d> Seq, Dictionary<Anchor, Mat4d> AnchorTransformDictionary, double[] McValues, bool EnableSweeping, IMachiningTool MachiningTool, WorkpieceService WorkpieceService, double BackgroundTemperature_K, CoolantHeatCondition CoolantHeatCondition, SortedList<double, double> FluteZToDzList)
+ public StepMotionSnapshot(DVec3d GeomCl, DVec3d ProgramCl, SeqPair<Mat4d> Seq, Dictionary<Anchor, Mat4d> AnchorTransformDictionary, double[] McValues, bool EnableSweeping, IMachiningTool MachiningTool, WorkpieceService WorkpieceService, double BackgroundTemperature_K, CoolantHeatCondition CoolantHeatCondition, SortedList<double, double> FluteZToDzList, DVec3d PreTipPose = null)
Parameters
@@ -233,6 +233,13 @@ Class MachiningVolumeRemovalProc.StepMotionSnapshot
+ PreTipPose
+
+
+
+ The tip pose the previous StepMotion(bool, double, Mat4d) call fed to the
+valve, on the workpiece-geometry coordinate – whether or not that call produced a step
+(the valve skips a collinear return over a segment it already covered). Null on the first
+call after a reset. The one-step reference for the real tip feedrate; Seq.pre is not,
+it may span two steps back.
+
+
+
+
+ public DVec3d PreTipPose { get; init; }
+
+
+
+
+
+
+ Property Value
+
+
+
+
+
+
+
+
+
+
diff --git a/App/wwwroot/HiAPI-docsite/api/Hi.MachiningProcs.LocalProjectService.html b/App/wwwroot/HiAPI-docsite/api/Hi.MachiningProcs.LocalProjectService.html
index 6ab60d29..528bd712 100644
--- a/App/wwwroot/HiAPI-docsite/api/Hi.MachiningProcs.LocalProjectService.html
+++ b/App/wwwroot/HiAPI-docsite/api/Hi.MachiningProcs.LocalProjectService.html
@@ -566,7 +566,9 @@ Will be removed when Gets or sets the fixture.
+ Gets or sets the authored fixture (setup face). The runtime face
+follows by rebuild; runtime readers use
+MachiningEquipment.Fixture.
@@ -831,7 +833,9 @@ post-run reset.
public StepMotionSnapshot(DVec3d GeomCl, DVec3d ProgramCl, SeqPair<Mat4d> Seq, Dictionary<Anchor, Mat4d> AnchorTransformDictionary, double[] McValues, bool EnableSweeping, IMachiningTool MachiningTool, WorkpieceService WorkpieceService, double BackgroundTemperature_K, CoolantHeatCondition CoolantHeatCondition, SortedList<double, double> FluteZToDzList)
+ public StepMotionSnapshot(DVec3d GeomCl, DVec3d ProgramCl, SeqPair<Mat4d> Seq, Dictionary<Anchor, Mat4d> AnchorTransformDictionary, double[] McValues, bool EnableSweeping, IMachiningTool MachiningTool, WorkpieceService WorkpieceService, double BackgroundTemperature_K, CoolantHeatCondition CoolantHeatCondition, SortedList<double, double> FluteZToDzList, DVec3d PreTipPose = null)
The tip pose the previous StepMotion(bool, double, Mat4d) call fed to the
+valve, on the workpiece-geometry coordinate – whether or not that call produced a step
+(the valve skips a collinear return over a segment it already covered). Null on the first
+call after a reset. The one-step reference for the real tip feedrate; Seq.pre is not,
+it may span two steps back.
public DVec3d PreTipPose { get; init; }
+ Gets or sets the fixture.
+Gets or sets the authored fixture (setup face). The runtime face +follows by rebuild; runtime readers use +MachiningEquipment.Fixture.