Archived
595 lines
44 KiB
HTML
595 lines
44 KiB
HTML
<!DOCTYPE html>
|
||
<html>
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<title>Control Tree | HiAPI-C# 2025 </title>
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<meta name="title" content="Control Tree | HiAPI-C# 2025 ">
|
||
|
||
|
||
<link rel="icon" href="../../img/HiAPI.favicon.ico">
|
||
<link rel="stylesheet" href="../../public/docfx.min.css">
|
||
<link rel="stylesheet" href="../../public/main.css">
|
||
<meta name="docfx:navrel" content="../../toc.html">
|
||
<meta name="docfx:tocrel" content="toc.html">
|
||
|
||
<meta name="docfx:rel" content="../../">
|
||
|
||
|
||
|
||
<meta name="loc:inThisArticle" content="In this article">
|
||
<meta name="loc:searchResultsCount" content="{count} results for "{query}"">
|
||
<meta name="loc:searchNoResults" content="No results for "{query}"">
|
||
<meta name="loc:tocFilter" content="Filter by title">
|
||
<meta name="loc:nextArticle" content="Next">
|
||
<meta name="loc:prevArticle" content="Previous">
|
||
<meta name="loc:themeLight" content="Light">
|
||
<meta name="loc:themeDark" content="Dark">
|
||
<meta name="loc:themeAuto" content="Auto">
|
||
<meta name="loc:changeTheme" content="Change theme">
|
||
<meta name="loc:copy" content="Copy">
|
||
<meta name="loc:downloadPdf" content="Download PDF">
|
||
|
||
<script type="module" src="./../../public/docfx.min.js"></script>
|
||
|
||
<script>
|
||
const theme = localStorage.getItem('theme') || 'auto'
|
||
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
|
||
</script>
|
||
|
||
</head>
|
||
|
||
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
|
||
<header class="bg-body border-bottom">
|
||
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
|
||
<div class="container-xxl flex-nowrap">
|
||
<a class="navbar-brand" href="../../index.html">
|
||
<img id="logo" class="svg" src="../../img/HiAPI.logo.png" alt="">
|
||
|
||
</a>
|
||
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
|
||
<i class="bi bi-three-dots"></i>
|
||
</button>
|
||
<div class="collapse navbar-collapse" id="navpanel">
|
||
<div id="navbar">
|
||
<form class="search" role="search" id="search">
|
||
<i class="bi bi-search"></i>
|
||
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
|
||
</form>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</nav>
|
||
</header>
|
||
|
||
<main class="container-xxl">
|
||
<div class="toc-offcanvas">
|
||
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
|
||
<div class="offcanvas-header">
|
||
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
|
||
</div>
|
||
<div class="offcanvas-body">
|
||
<nav class="toc" id="toc"></nav>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="content">
|
||
<div class="actionbar">
|
||
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
|
||
<i class="bi bi-list"></i>
|
||
</button>
|
||
|
||
<nav id="breadcrumb"></nav>
|
||
</div>
|
||
|
||
<article data-uid="Anatomy-ControlTree">
|
||
<h1 id="control-tree">Control Tree</h1>
|
||
|
||
<p>The Control Tree is the engine shared by the two tree-shaped pages of the web client: the Execution
|
||
page at <code>/execution</code> and the General Setup page at <code>/general-setup</code>. Each page creates exactly one
|
||
host — <code>useControlTreeHost('execution')</code> or <code>useControlTreeHost('equipment')</code> — 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 <code>?tree=</code> query that carries it, the editor panel a selection resolves
|
||
to, the mission enable ticks, and every structural operation on the Mission branch. The Tool House
|
||
page at <code>/tool-house</code> reuses the same node shape and the same item-type registry but renders a branch
|
||
as nested tabs and creates no host.</p>
|
||
<div class="IMPORTANT">
|
||
<h5>Important</h5>
|
||
<p>Node ids are role paths, not identities that survive a version change. Anatomy makes no claim that
|
||
a <code>?tree=</code> id is stable across releases; see <a class="xref" href="tree-ids-and-routes.html">Tree Ids and Routes</a>
|
||
for the migration hop that keeps an older link resolving.</p>
|
||
</div>
|
||
<h2 id="the-node">The Node</h2>
|
||
<p>Every branch of every tree is built from one structure, <code>ControlNode</code>, declared in <code>itemTypes.ts</code>:</p>
|
||
<ul>
|
||
<li><code>id</code> — the node's role path, slash-separated, whose first segment is the owning page's scope
|
||
(<code>execution/…</code>, <code>equipment/…</code>, <code>toolhouse/…</code>). This is the addressing surface: the <code>?tree=</code> query,
|
||
the persisted expansion list and the persisted last selection all hold ids. The General Setup
|
||
page's root carries its scope as its own id, <code>equipment</code>; the Execution page has no node with the
|
||
bare <code>execution</code> id — its two roots are <code>execution/mission</code> and <code>execution/program</code>, and the
|
||
segment is a scope key only.</li>
|
||
<li><code>label</code>, <code>labelKey</code>, <code>labelParams</code> — the display text; see <a href="#display-labels">Display Labels</a>.</li>
|
||
<li><code>itemType</code> — the registry key that decides the node's editor and its child builder.</li>
|
||
<li><code>key</code> — 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 (<code>0</code>, <code>1</code>,
|
||
the dotted <code>0.2</code> of 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.</li>
|
||
<li><code>ctx</code> — the parent-provided <code>SlotCtx</code>: the <code>afterChange</code> commit chain, a slot's <code>onCreate</code>
|
||
create-and-rebind hook, and a slot picker's <code>allowedKinds</code> / <code>allowNone</code> constraints.</li>
|
||
<li><code>children</code> — grown by the builders, not declared by the tree column.</li>
|
||
<li><code>selectable</code>, <code>info</code> / <code>infoKey</code>, and the <code>mission</code> / <code>program</code> bookkeeping records the Mission and
|
||
Program waves stamp on their own nodes.</li>
|
||
</ul>
|
||
<p>The split between <code>id</code> and <code>key</code> 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 <em>role</em> 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
|
||
<code>${parentId}/${index}</code> and Program files are <code>${parentId}/${index}</code>, 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 host keeps three id sets of its own that must follow the rows rather than the numbers —
|
||
the persisted expansion list, the Add Command focus and the selection — and translates all three
|
||
through every mission row operation it runs; see <a href="#the-structure-changed-event">the row operations</a>.</p>
|
||
<h2 id="the-item-type-registry">The Item-Type Registry</h2>
|
||
<p><code>ITEM_TYPES</code> maps an <code>itemType</code> string onto a <code>TreeItemDef</code> carrying up to three members:</p>
|
||
<ul>
|
||
<li><code>panel</code> — the editor mounted in the dock's editor row for a node of this type.</li>
|
||
<li><code>contentPanel</code> — a large view for the General Setup page's content column.</li>
|
||
<li><code>buildChildren</code> — an async builder that returns this node's children.</li>
|
||
</ul>
|
||
<p>Two flavours of type share the map. <strong>Slot</strong> types (<code>Geometry</code>, <code>Transformer</code>) have a kind picker as
|
||
their panel and grow at most one child, the concrete kind, at the kind-independent id
|
||
<code>${slot.id}/type</code> — 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 <em>None</em> is
|
||
an ordinary state rather than a failure. <strong>Kind</strong> types (<code>Box3d</code>, <code>StaticTranslation</code>, …) register
|
||
<code>SoleEditorPanel</code>, 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:
|
||
<code>TransformationGeom</code> grows an inner-geometry and an
|
||
inner-transformer slot, and <code>GeomCombination</code> grows one slot per item, so the tree owns the structure
|
||
and every panel stays shallow.</p>
|
||
<p>The map is one flat namespace assembled in <code>itemTypes.ts</code>: the <code>Group</code> stem type and the geometry,
|
||
transformer, workpiece-leaf, thermal-condition and spindle entries are written inline, and five
|
||
per-wave registries are spread in beside them. The Execution page's two roots are registry types —
|
||
<code>MissionRoot</code> and <code>ProgramRoot</code>, from their waves — and no wrapper type stands over them. The three
|
||
roots the panel host renders itself — Machine Tool, Fixture and Workpiece — have no entry here at all.</p>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>Wave</th>
|
||
<th>File</th>
|
||
<th>What it contributes</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td>Mission</td>
|
||
<td><code>missionItemTypes.ts</code></td>
|
||
<td><code>MissionRoot</code>, whose panel is informational — the command count and the run-order hint that names the row affordances; <code>MissionCommand</code>, whose panel is the kind's editor, or only a Title field for a List; <code>MissionSection</code>; section children at <code>${command.id}/${sectionId}</code>; the per-kind bespoke editors, with a generic field editor as the fallback</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Program</td>
|
||
<td><code>programItemTypes.ts</code></td>
|
||
<td><code>ProgramRoot</code>, <code>ProgramFile</code>, <code>ProgramConversionFile</code>; writeback conversions at <code>${root.id}/dst-${index}</code></td>
|
||
</tr>
|
||
<tr>
|
||
<td>Tool House</td>
|
||
<td><code>toolHouseItemTypes.ts</code></td>
|
||
<td>the tool collection and the per-tool editor branches under <code>toolhouse/tool-<id>/…</code></td>
|
||
</tr>
|
||
<tr>
|
||
<td>SoftNc controller</td>
|
||
<td><code>softNcItemTypes.ts</code></td>
|
||
<td>the controller root and its leaves on two planes — <code>…/machine/<seg></code> and <code>…/program-data/<seg></code> — a core every runner grows plus the brand-driven ones</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Runner suits</td>
|
||
<td><code>runnerSuitItemTypes.ts</code></td>
|
||
<td><code>CsvRunnerRoot</code> and <code>ClRunnerRoot</code>, flat single-panel leaves with no builder</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
<p>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 <code>$TC_DP</code> flag adds its own two leaves <em>and</em> renames the
|
||
core Tool Offsets leaf to its ISO G43 H reading, so the two offset ledgers do not read as one.</p>
|
||
<p>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
|
||
<code>?tree=</code> 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.</p>
|
||
<h2 id="building-a-branch">Building a Branch</h2>
|
||
<p><code>buildSubtree(node, services, depth)</code> is the whole builder. It looks the node's type up, returns
|
||
without touching <code>children</code> when the type declares no <code>buildChildren</code> or when <code>depth</code> has reached
|
||
<code>MAX_DEPTH</code> (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.</p>
|
||
<p>Builders receive one service, <code>registerKey</code>, which registers a freshly minted IndexService key with
|
||
the page's cleanup hub — see <a class="xref" href="../conventions/webapi-hub-cleanup-pattern.html">Webapi with Hub-Cleanup Assistance</a>.</p>
|
||
<p><code>rebuildBranch(node)</code> is <code>buildSubtree</code> 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.</p>
|
||
<h3 id="the-structure-changed-event">The structure-changed event</h3>
|
||
<p>A panel that has changed the <em>shape</em> of the model emits <code>structure-changed</code> (or <code>type-changed</code>, wired
|
||
to the same handler). The optional payload is a <code>StructureChangeRequest</code>:</p>
|
||
<ul>
|
||
<li><code>scopeId</code> — 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.</li>
|
||
<li><code>selectId</code> — the selection to adopt afterwards. It is assigned directly, <strong>bypassing the
|
||
dirty-switch gate</strong>, because the emitting panel is being replaced on purpose. A <code>null</code> clears the
|
||
selection.</li>
|
||
<li><code>inserted</code> — entries a panel inserted into a mission list itself: the list node's id, the child
|
||
index the run starts at and its count. The host then treats the rebuild as a mission operation —
|
||
it holds the busy gate and slides its index-based ids past the new siblings — rather than as a
|
||
plain branch refresh.</li>
|
||
</ul>
|
||
<p>One command editor emits the event: the Program File editor, whose multi-pick turns every extra file
|
||
into a new Program File command inserted right after it, in pick order; the command panel hosting it
|
||
re-scopes the event to the parent list, keeps that command selected and passes the insert range
|
||
along. Two panels emit it with no payload and so take the default scope, their own node: the Program
|
||
root panel's refresh button, which re-reads the file tree from the current session and rebuilds the
|
||
Program branch through <code>rebuildBranch</code>, so the branch opens under it; and the <code>GeomCombination</code>
|
||
item panel after an item add, an item remove or a clear, since its item slots must be regrown. 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.</p>
|
||
<p>Every other structural change to the Mission branch is an operation of the host itself, not an
|
||
event, because the tree rows are the only surface that offers it: Add Command, a drag between rows,
|
||
Move up / Move down, Duplicate and Delete. A drag is HTML5 drag-and-drop between mission rows; every
|
||
command row drags, the Mission root row only receives, and where the pointer sits in the target row
|
||
decides the operation. A plain command row has no edge band: it splits at its midline, the upper
|
||
half putting the dragged command before that row and the lower half after it — a reorder when both
|
||
share a list, a relocation across lists otherwise. A List row keeps that before / after reading only
|
||
in its top and bottom quarters; its middle half, and the whole of the Mission root row, take the
|
||
command into that list, at the tail. A drop onto the dragged row's own subtree is
|
||
refused, and a drop aborts when a rebuild mid-drag re-stamped the dragged node, since its id may then
|
||
name another command. All five run through one helper that holds a busy gate
|
||
(one operation at a time), runs the open panel's dirty-switch gate, calls the Mission API, translates
|
||
the expansion list and the Add Command focus through the operation, rebuilds the branch of the list
|
||
or lists it rewrote — the one list, or the nearest list above both ends of a cross-list drag — and
|
||
only then translates the selection and names any post-operation selection, since the ids it names
|
||
exist only after the rebuild. A refused gate drops the operation and says so in a notification
|
||
("Cancelled — the open editor keeps its unsaved edits."); the delete's own confirm dialog runs
|
||
before the gate, so a cancelled delete forces no save prompt on the open editor.</p>
|
||
<p>The selection moves only when it rides on the acted-on row — that row itself, or a section or
|
||
nested entry under it. Then it follows a stepped or dragged command, lands on the clone after a
|
||
duplicate, and after a delete lands on the nearest remaining entry of that list, or on the list
|
||
node itself once the list is empty. Otherwise the editor the user is in stays on its own,
|
||
re-indexed row. Add Command never moves the selection: it reveals the new row and makes it the
|
||
focus, so a run of adds lands in order. The focus is the command most recently selected — a section
|
||
row counts for its owning command — or the last one added, and an Add Command from a list row's <code>+</code>
|
||
inserts right after that focus when the focus is a direct entry of that list, and at the list's
|
||
tail otherwise; the Mission root row's <code>+</code> therefore never chases a focus inside a nested list.</p>
|
||
<h3 id="whole-tree-rebuilds-and-the-refreshes-that-are-not-rebuilds">Whole-tree rebuilds, and the refreshes that are not rebuilds</h3>
|
||
<p><code>rebuildTree()</code> 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 <code>buildSubtree</code> on the Program root <strong>directly</strong> rather than through
|
||
<code>rebuildBranch</code>, so a run does not force that branch open on every transition.</p>
|
||
<p>Two refreshes deliberately mutate existing nodes instead of rebuilding:</p>
|
||
<ul>
|
||
<li>A mission command's title is re-read from the entry list and written onto the node's label.</li>
|
||
<li>A file-backed geometry leaf's label is re-read when its source file changes.</li>
|
||
</ul>
|
||
<p>The mission one exists because a rebuild of the Mission branch re-mints the per-build <code>stamp</code> 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.</p>
|
||
<h3 id="the-commit-chain">The commit chain</h3>
|
||
<p>The tree has no component bubbling, so the full post-edit commit chain lives on each node as
|
||
<code>ctx.afterChange</code>, composed level by level as the branch is built. A <code>GeomCombination</code> 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.</p>
|
||
<h2 id="selection-and-the-dirty-switch-gate">Selection and the Dirty-Switch Gate</h2>
|
||
<p>Every selection change funnels through <code>requestSelect(id)</code>. It returns immediately when the id is
|
||
already selected; it refuses outright while a mission row operation, or a tick's own writes, are in
|
||
flight — the operation ends by naming the post-operation selection, which would silently undo a
|
||
change landed during its rebuild; otherwise it awaits the mounted panel's optional
|
||
<code>tryConsumeBeforeSwitch()</code> and abandons the switch when that returns false.</p>
|
||
<p>Three entrances use it: a click in the tree, a change to the URL's <code>?tree=</code> value, and a <code>select-node</code>
|
||
event from the mounted panel — the wire the panel host puts on every registry panel, so a group stem's
|
||
child list and the Program branch's conversion jumps both arrive through that one. Two paths assign
|
||
the selection directly and skip the gate: the <code>selectId</code> of a structure change, and the
|
||
post-operation selection of a mission row operation, which has already run the gate itself.</p>
|
||
<p>There is no “nothing selected” state. Re-clicking the selected row makes the tree yield <code>null</code>, and
|
||
that is ignored; an empty <code>?tree=</code> lands on the persisted last selection for that page when the built
|
||
tree still contains it, and otherwise on the page's root — General Setup on the equipment host, and
|
||
Mission, the first of its two roots, on the execution host. The editor row is therefore always alive,
|
||
and reclaiming its height is the row's own collapse toggle rather than a deselection.</p>
|
||
<p>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 <code>tryConsumeBeforeSwitch</code>. Panels that
|
||
write one field per request need no gate and register none. One panel exposes it —
|
||
<code>MissionCommandSlavePanel</code>:</p>
|
||
<ul>
|
||
<li>on a <code>list</code> entry it waits for a title save already on the wire, then flushes the debounced title
|
||
save while the command path is still live, and allows the switch only when that save landed;</li>
|
||
<li>on any other entry it forwards its inner editor's gate. Two editors have one. The Script editor
|
||
first lets a save already on the wire land, then with unsaved text asks save, discard or cancel:
|
||
cancel returns false, discard reverts to the last confirmed text and puts the autosave to sleep
|
||
until the next keystroke, and save proceeds only when the flush landed — a conflict answered with
|
||
Cancel leaves the edit staged and the switch refused. The NC Code editor waits for a save on the
|
||
wire, drains its debounced field save, and returns false when that save failed.</li>
|
||
</ul>
|
||
<p>The same gate runs before every mission row operation, not only before a selection change, because
|
||
the operation's rebuild remounts the open editor and mission paths are index-based: a debounced save
|
||
that outlived the remount would land on whichever command slid into the path it captured at mount.
|
||
For the same reason both the NC Code editor and the list title cancel, never flush, on unmount.</p>
|
||
<h2 id="display-labels">Display Labels</h2>
|
||
<p><code>nodeDisplayLabel(n)</code> returns the translation of <code>labelKey</code> — with <code>labelParams</code> interpolated — when
|
||
one is set, and the verbatim <code>label</code> otherwise. <code>labelKey</code> is therefore the form every role label the
|
||
client translates itself takes, while <code>label</code> 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.</p>
|
||
<p>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 <code>Script [Warm-Up]</code>. That title always arrives filled in, so the
|
||
node's <code>labelKey</code> 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:
|
||
<code>StlFile [Geom/x.stl]</code>. The voxel kind is renamed for display, appearing as <code>MeshedGeomFile [...]</code>
|
||
rather than by its type name.</p>
|
||
<p>Group stems take the same treatment for their intro text: <code>infoKey</code> wins over <code>info</code>, and a stem with
|
||
neither falls back to a sentence naming the group.</p>
|
||
<h2 id="the-panel-host">The Panel Host</h2>
|
||
<p><code>PrimarySlavePanel</code> decides what the editor row shows, in this order:</p>
|
||
<ol>
|
||
<li><strong>The three inline root types</strong> — <code>MachineToolRoot</code>, <code>FixtureRoot</code>, <code>WorkpieceRoot</code> — 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.</li>
|
||
<li><strong>Otherwise the registry's <code>panel</code></strong> for the selection's item type, mounted with the node as its
|
||
only prop, with <code>changed</code>, <code>type-changed</code>, <code>structure-changed</code>, <code>select-node</code> and <code>error</code> wired to
|
||
the host.</li>
|
||
<li><strong>A spinner</strong> while the first build is still running, and the select-an-item hint after that when
|
||
the selection's type registers no panel.</li>
|
||
</ol>
|
||
<p>What decides a remount is the mounted component's key, <code>${id}|${key}|${stamp}</code> — 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.</p>
|
||
<p>The editor row carries nothing but the editor. The run controls sit in the tree pane instead: on the
|
||
Execution page the Control Tree pane pins a run header above its tree scroller — the transport bar
|
||
and the live execution-status badge — whatever the selection is. It rides the pane's content rather
|
||
than the dock's expansion header because that header is the same component on General Setup, which
|
||
renders neither piece. This is the page's sole <code>ExecutionToolBar</code>, so it owns the F5–F8 shortcuts,
|
||
and because the dock keeps the tree pane mounted while its row is collapsed, a collapsed tree row
|
||
does not drop them. When the tree column narrows, the six-button strip scrolls horizontally and the
|
||
badge never shrinks, so the run state is the last thing a resize can hide.</p>
|
||
<p>A pure <code>Group</code> stem is not a node without an editor. <code>Group</code> registers <code>GroupInfoPanel</code>, whose body is
|
||
the stem's intro line followed by a clickable list of its children; a row click emits <code>select-node</code>
|
||
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.</p>
|
||
<p>The General Setup page adds a second slave, <code>ContentSlavePanel</code>, which resolves <code>contentPanel</code> 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 <strong>unkeyed</strong> on purpose.
|
||
Every other selection shows the column's empty hint.</p>
|
||
<p><code>NodeTabCascade</code> 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.</p>
|
||
<h2 id="the-tick-column">The Tick Column</h2>
|
||
<p>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. The ticks share the mission operations' busy gate in both directions: a tick
|
||
during a row operation is ignored — it would write to the node's pre-operation path — and the box
|
||
snaps back the same way, while a tick's own writes hold the gate so no row operation can overtake
|
||
them on the server and land the enable flag on whichever command slid into the ticked path.</p>
|
||
<p>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.</p>
|
||
<h2 id="layout">Layout</h2>
|
||
<ul>
|
||
<li>Left Dock — both tree pages, one nav-bar button toggling the whole column
|
||
<ul>
|
||
<li><strong>Control Tree</strong> Expansion Row — collapses in place, keeping its header, which is the same
|
||
page-neutral header on both pages; stays mounted while collapsed, so the tree keeps its scroll
|
||
position and the run header keeps its shortcuts
|
||
<ul>
|
||
<li>Run Header — Execution page only, pinned above the tree scroller and separated from it by a
|
||
rule
|
||
<ul>
|
||
<li>Execution Transport Bar — the six transport buttons; the strip scrolls horizontally when the
|
||
column is narrower than it</li>
|
||
<li>Execution Status Badge — the status label in its status colour; never shrinks</li>
|
||
</ul>
|
||
</li>
|
||
<li>Tree — the Execution page has two root rows, <strong>Mission</strong> above <strong>Program</strong>; the General Setup
|
||
page has one, <strong>General Setup</strong>
|
||
<ul>
|
||
<li>Drag Handle — on every mission command row, List rows included, immediately before the
|
||
label. It is the first thing the row's own header renders, but the tree puts the expand
|
||
arrow, on a row with children, and then the enable checkbox ahead of that header, so the
|
||
handle sits to the checkbox's right rather than at the row's left edge. The Mission root row
|
||
has none. While a drag is under way the dragged row dims, a line along the target row's top
|
||
or bottom edge marks a before / after drop, and a tinted target row marks a drop into that
|
||
list</li>
|
||
<li>Node Row — the label is a real link to this node's <code>?tree=</code> 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 node icons are rendered.</li>
|
||
<li>Node CheckBox — mission command rows, and mission section rows carrying an enable flag</li>
|
||
<li>Row Operations — on every mission command row, right-aligned: <strong>Move up</strong>, <strong>Move down</strong>,
|
||
<strong>Duplicate</strong> and <strong>Delete</strong>. Absent from the layout at rest, so a resting label keeps the
|
||
column's full width, and shown on the hovered row and on the selected row; on a device with
|
||
no hover they are always shown. Move up is disabled on a list's first entry, Move down on its
|
||
last, and all four while an operation is in flight</li>
|
||
<li>Add Command — the <code>+</code> at the right edge of the Mission root row and of every List row,
|
||
always visible, scoped to that row's list; disabled without an open project and while an
|
||
operation is in flight. It opens the Add Command dialog — a search box over the command
|
||
catalog, grouped by category, which fetches the catalog on every opening and accepts one
|
||
pick per opening</li>
|
||
</ul>
|
||
</li>
|
||
<li>Spinner and “Loading project…” — shown instead of the tree until the first build lands</li>
|
||
</ul>
|
||
</li>
|
||
<li>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</li>
|
||
<li><strong>Editor</strong> Expansion Row — its header carries the selection breadcrumb, the node path joined with
|
||
slashes, and falls back to <strong>Editor</strong>; stays mounted while collapsed
|
||
<ul>
|
||
<li>The selected node's editor panel, or “Select an item in the Control Tree to edit it here.”</li>
|
||
</ul>
|
||
</li>
|
||
</ul>
|
||
</li>
|
||
<li>Content Column — General Setup page only, one nav-bar button
|
||
<ul>
|
||
<li>The selected node's large content view, or “The selected item has no expanded content.”</li>
|
||
</ul>
|
||
</li>
|
||
</ul>
|
||
<p>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.</p>
|
||
<h2 id="lifecycle">Lifecycle</h2>
|
||
<p>The host is created while its page's script runs and provided before the dock mounts; the page's
|
||
<code>onMounted</code> then awaits <code>initialize()</code>. 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 <code>?tree=</code> 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.</p>
|
||
<p>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.</p>
|
||
<h2 id="source-code-path">Source Code Path</h2>
|
||
<p>See <a class="xref" href="../index.html">HiNC App Anatomy</a> for git repository links.</p>
|
||
<h3 id="web-application">Web Application</h3>
|
||
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
|
||
<ul>
|
||
<li><code>wwwroot-src/src/components/controlTree/useControlTreeHost.ts</code> — the host: the two scopes' root
|
||
builders, the selection gate, the <code>?tree=</code> sync, the mission ticks, the five mission row
|
||
operations with their shared busy gate and id translation, the rebuild entry points, the re-index
|
||
and Object-Management handlers, and the reactive surface the panes consume.</li>
|
||
<li><code>wwwroot-src/src/components/controlTree/useMissionTreeDrag.ts</code> — the drag-and-drop bookkeeping
|
||
between mission rows: which rows drag, the drop mode from the pointer's position in the target
|
||
row, and the row classes behind the drop indicators.</li>
|
||
<li><code>wwwroot-src/src/components/controlTree/itemTypes.ts</code> — the <code>ControlNode</code> and <code>TreeItemDef</code> shapes,
|
||
the <code>SlotCtx</code> hooks, the structure-change payload, <code>nodeDisplayLabel</code> / <code>nodeDisplayInfo</code>, the
|
||
geometry and transformer builders, the <code>ITEM_TYPES</code> map and <code>buildSubtree</code>.</li>
|
||
<li><code>wwwroot-src/src/components/controlTree/ControlTreePanel.vue</code> — the tree column: the Execution
|
||
page's run header with the transport bar and the execution-status badge, the node anchors, the
|
||
click handling that keeps a plain click in-app, the drag handles, the row operations, the Add
|
||
Command <code>+</code> and the dialog it opens.</li>
|
||
<li><code>wwwroot-src/src/components/execution/ExecutionToolBar.vue</code> — the six transport buttons, and the
|
||
F5–F8 shortcuts the sole instance registers.</li>
|
||
<li><code>wwwroot-src/src/utils/executionStatusPresent.ts</code> — the badge's status label and colour.</li>
|
||
<li><code>wwwroot-src/src/components/controlTree/AddCommandDialog.vue</code> — the search-first command picker
|
||
over the server-served catalog.</li>
|
||
<li><code>wwwroot-src/src/components/controlTree/ControlTreeDock.vue</code> — the two-row dock and the height
|
||
divider that sets the editor row's stored pixel height.</li>
|
||
<li><code>wwwroot-src/src/components/controlTree/PrimarySlavePanel.vue</code> — the panel host: the three inline
|
||
root types, the registry lookup, the remount key and the gate registration.</li>
|
||
<li><code>wwwroot-src/src/components/controlTree/ContentSlavePanel.vue</code> — the General Setup content column
|
||
and its unkeyed content panel.</li>
|
||
<li><code>wwwroot-src/src/components/controlTree/GroupInfoPanel.vue</code> — a group stem's intro and its
|
||
navigating child list.</li>
|
||
<li><code>wwwroot-src/src/components/controlTree/SoleEditorPanel.vue</code> — a kind node's editor, resolved from
|
||
the geometry and transformer editor maps.</li>
|
||
<li><code>wwwroot-src/src/components/controlTree/NodeTabCascade.vue</code> — the same branches rendered as nested
|
||
tabs, with the remembered per-role tab and the cascade host injection.</li>
|
||
<li><code>wwwroot-src/src/components/controlTree/missionItemTypes.ts</code> — the Mission wave: node bookkeeping,
|
||
the section table and its enable-flag readers and writers, the per-kind editors and display names.</li>
|
||
<li><code>wwwroot-src/src/components/controlTree/programItemTypes.ts</code> — the Program wave: the file tree from
|
||
one response, and the writeback conversion nodes.</li>
|
||
<li><code>wwwroot-src/src/components/controlTree/softNcItemTypes.ts</code> — the controller wave: the two planes,
|
||
the core leaves every runner grows, and the snapshot flags that decide which brand leaves join them.</li>
|
||
<li><code>wwwroot-src/src/components/controlTree/toolHouseItemTypes.ts</code> — the Tool House wave and its
|
||
<code>tool-<id></code> role paths.</li>
|
||
<li><code>wwwroot-src/src/components/controlTree/runnerSuitItemTypes.ts</code> — the two runner-suit leaves.</li>
|
||
<li><code>wwwroot-src/src/components/controlTree/MissionRootPanel.vue</code> — the Mission root's informational
|
||
panel: the command count and the run-order hint.</li>
|
||
<li><code>wwwroot-src/src/components/controlTree/ProgramRootPanel.vue</code> — the Program root's summary panel
|
||
and the refresh button that emits a payload-less <code>structure-changed</code>.</li>
|
||
<li><code>wwwroot-src/src/components/controlTree/GeomCombinationTreePanel.vue</code> — the combination's item list,
|
||
whose add, remove and clear emit the same payload-less event.</li>
|
||
<li><code>wwwroot-src/src/components/controlTree/MissionCommandSlavePanel.vue</code> — a command's editor host: a
|
||
List's Title field with its debounced save, and the only panel that exposes a dirty-switch gate.</li>
|
||
<li><code>wwwroot-src/src/components/mission/ScriptCommandPanel.vue</code> — the autosaving editor whose gate is
|
||
forwarded, with its save / discard / cancel prompt.</li>
|
||
<li><code>wwwroot-src/src/composables/useAutoSave.ts</code> — the Script editor's autosave: the flush, the wait
|
||
for a save on the wire, and the discard that sleeps the instance until the next edit.</li>
|
||
<li><code>wwwroot-src/src/components/mission/NcCodeCommandPanel.vue</code> — the second forwarded gate: the
|
||
debounced field save it drains, and the unmount that cancels instead of flushing.</li>
|
||
<li><code>wwwroot-src/src/components/mission/NcFileCommandPanel.vue</code> — the one command editor that emits
|
||
<code>structure-changed</code>, with the <code>inserted</code> range of its multi-pick.</li>
|
||
<li><code>wwwroot-src/src/components/panels/PanelExpansion.vue</code> — the expansion row both dock rows are built
|
||
from.</li>
|
||
<li><code>wwwroot-src/src/composables/useViewPrefs.ts</code> — the device-local expansion list, last selections,
|
||
dock sizes and the two runner-suit checkboxes.</li>
|
||
<li><code>wwwroot-src/src/composables/useCleanupHub.ts</code> — the key registration the builders call.</li>
|
||
<li><code>wwwroot-src/src/router/treeRoutes.ts</code> — the id migration hop and the page a given id belongs to.</li>
|
||
<li><code>wwwroot-src/src/pages/ExecutionPage.vue</code> — creates and provides the <code>execution</code> host.</li>
|
||
<li><code>wwwroot-src/src/pages/GeneralSetupPage.vue</code> — creates and provides the <code>equipment</code> host, and adds
|
||
the content column.</li>
|
||
<li><code>wwwroot-src/src/pages/ToolHousePage.vue</code> — the registry consumer that renders tabs instead of a
|
||
tree.</li>
|
||
<li><code>wwwroot-src/src/layouts/MainLayout.vue</code> — the project epoch that decides when a host is rebuilt.</li>
|
||
<li><code>wwwroot-src/src/i18n/en/tree.ts</code> — the dock labels, the group intros and the node role labels.</li>
|
||
</ul>
|
||
<h2 id="see-also">See Also</h2>
|
||
<ul>
|
||
<li><a class="xref" href="tree-ids-and-routes.html">Tree Ids and Routes</a> — how a node id becomes a URL, which page owns a given id, and how
|
||
an older id keeps resolving</li>
|
||
<li><a class="xref" href="session-state.html">Session State</a> — the project epoch and the stores a host is rebuilt around</li>
|
||
<li><a class="xref" href="../execution/index.html">Execution Page</a> — the first live host: the run cockpit's Mission and Program branches</li>
|
||
<li><a class="xref" href="../general-setup/index.html">General Setup Page</a> — the second live host: the equipment branches and the content column</li>
|
||
<li><a class="xref" href="../general-setup/controller/index.html">Controller Branch</a> — 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</li>
|
||
</ul>
|
||
|
||
</article>
|
||
|
||
<div class="contribution d-print-none">
|
||
</div>
|
||
|
||
<div class="next-article d-print-none border-top" id="nextArticle"></div>
|
||
|
||
</div>
|
||
|
||
<div class="affix">
|
||
<nav id="affix"></nav>
|
||
</div>
|
||
</main>
|
||
|
||
<div class="container-xxl search-results" id="search-results"></div>
|
||
|
||
<footer class="border-top text-secondary">
|
||
<div class="container-xxl">
|
||
<div class="flex-fill">
|
||
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
|
||
</div>
|
||
</div>
|
||
</footer>
|
||
</body>
|
||
</html>
|