This commit is contained in:
2026-09-09 17:35:23 +08:00
parent 2a98ae7556
commit 161af68fe3
1271 changed files with 58251 additions and 98639 deletions
@@ -0,0 +1,136 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Widgets | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Widgets | 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 &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<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-Widgets">
<h1 id="widgets">Widgets</h1>
<p>The reusable controls that pages embed rather than own. Each page here documents one control — most
of them under the SPA's <code>components/widgets/</code> directory — together with its WPF counterpart where one
exists, so a reader who meets the same control on four screens reads about it once.</p>
<p>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.</p>
<p>Ordered by how widely each control is embedded across the two clients, most-embedded first.</p>
<h2 id="pages">Pages</h2>
<ul>
<li><a href="numeric-input.html">Numeric Input</a> — 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</li>
<li><a href="object-management-menu-button.html">Object Management Menu Button</a> — Load / Save / Paste / XML for any object that can produce an XML source</li>
<li><a href="renderingcanvas-tool-bar.html">RenderingCanvas Tool Bar</a> — The <code>View ▾</code> camera-preset menu every 3D canvas docks</li>
<li><a href="vec3d-control.html">Vec3dControl</a> — The three-component vector editor: per-axis and single-field text modes, with local special-value handling</li>
<li><a href="polar-resolution-2d-panel.html">Polar Resolution 2D Panel</a> — The editor for <a class="xref" href="../../api/Hi.Geom.Resolution.PolarResolution2d.html">PolarResolution2d</a>, embedded by the two holder editors that tessellate a revolved profile; the one control here with no SPA widget of its own</li>
<li><a href="mat4d-control.html">Mat4dControl</a> — The sixteen-cell 4x4 matrix grid, with its Identity and Invert events</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a href="numeric-input.html">Numeric Input</a> — the single-value numeric field the other editors on this shelf are built beside</li>
<li><a href="../conventions/index.html">Conventions</a> — the file-path and numeric-value contracts two of these controls implement</li>
<li><a href="../shell/index.html">App Shell</a> — the frame these controls are embedded inside</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>
@@ -0,0 +1,182 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Mat4dControl Component | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Mat4dControl Component | 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 &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<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-Mat4dControl">
<h1 id="mat4dcontrol-component">Mat4dControl Component</h1>
<p>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.</p>
<h2 id="key-model">Key Model</h2>
<p>The persisted model is <a class="xref" href="../../api/Hi.Geom.Mat4d.html">Mat4d</a>, exposed over <code>/api/Mat4d</code> by <code>Widget/Mat4dController.cs</code>.</p>
<p>The widget's own model is a plain array of sixteen numbers, exported as <code>type Mat4</code> from
<code>wwwroot-src/src/components/widgets/Mat4Input.vue</code>. The layout is column-major, matching the backend
Mat4d: cell (row, col) resolves to index <code>col * 4 + row</code>, so indices 0 to 3 are the first column and
12 to 15 the translation column. <code>wwwroot-src/src/api/transformer.ts</code> 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.</p>
<p>The widget carries no key and calls no endpoint. Its host,
<code>wwwroot-src/src/components/topo/StaticFreeformEditor.vue</code>, binds it with <code>v-model</code> and persists
each change through <code>updateStaticFreeformMat</code> from <code>wwwroot-src/src/api/transformer.ts</code>, which posts
the whole sixteen-number array to <code>/api/StaticFreeform/Update</code>; <code>Mech/Topo/StaticFreeformController.cs</code>
rejects a payload that is not exactly sixteen elements.</p>
<p>The props are <code>modelValue</code>, <code>disable</code>, <code>readonly</code>, <code>identity</code> (on by default) and <code>invert</code> (off by
default), and the emits are <code>update:modelValue</code>, <code>identity</code> and <code>invert</code>. <code>disable</code> both disables
the cells and dims the whole block.</p>
<h2 id="layout">Layout</h2>
<ul>
<li>Mat4dControl
<ul>
<li><code>Matrix Grid</code> — four rows of four numeric cells, each cell right-aligned</li>
<li><code>Identity Button</code> — present when the identity flag is set, which is the default</li>
<li><code>Invert Button</code> — present only when the invert flag is set</li>
</ul>
</li>
</ul>
<p>The cells and both buttons are inactive while the control is disabled or readonly.</p>
<h2 id="feature">Feature</h2>
<h3 id="single-input-mode">Single input mode</h3>
<p>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.</p>
<h3 id="matrix-operations">Matrix operations</h3>
<ul>
<li>Identity — the button raises an <code>identity</code> event and writes nothing itself; the host supplies the
matrix. StaticFreeformEditor pushes its own identity constant through <code>updateStaticFreeformMat</code>.</li>
<li>Invert — the button raises an <code>invert</code> event and computes nothing itself. StaticFreeformEditor
calls <code>invertStaticFreeformMat</code>, which posts to <code>/api/StaticFreeform/InvertMat</code> and feeds the
sixteen numbers that come back into the model.</li>
</ul>
<h3 id="commit-and-special-values">Commit and special values</h3>
<p>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.</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>
<ul>
<li><code>wwwroot-src/src/components/widgets/Mat4Input.vue</code> — the widget itself: the column-major
sixteen-cell grid, the Identity and Invert buttons, and the local format, parse and commit helpers</li>
<li><code>wwwroot-src/src/components/topo/StaticFreeformEditor.vue</code> — the host, and the full binding
contract in one place: <code>v-model</code>, the two button flags, <code>@update:model-value</code>, <code>@identity</code>, <code>@invert</code></li>
<li><code>wwwroot-src/src/api/transformer.ts</code> — <code>updateStaticFreeformMat</code> and <code>invertStaticFreeformMat</code></li>
<li><code>Mech/Topo/StaticFreeformController.cs</code> — the backend behind those two endpoints: Update takes a
sixteen-element array, InvertMat returns the inverted sixteen numbers</li>
<li><code>Widget/Mat4dController.cs</code> — the <code>/api/Mat4d</code> REST 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.</li>
<li><code>wwwroot-src/src/i18n/en/widgets.ts</code> — the Invert label and both button tooltips under <code>widgets.matInput</code></li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="vec3d-control.html">Vec3dControl Component</a> — the sibling numeric-geometry editor, and the one a reader usually wants next</li>
<li><a class="xref" href="../conventions/numeric-io-utilities.html">Numeric Input/Output</a> — why NaN and the infinities are text on the wire, and why NaN does not survive this grid</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>
@@ -0,0 +1,347 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Numeric Input | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Numeric Input | 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 &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<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-NumericInput">
<h1 id="numeric-input">Numeric Input</h1>
<p>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 <code>NaN</code> and
the infinities across the JSON boundary is stated once in
<a class="xref" href="../conventions/numeric-io-utilities.html">Numeric Input/Output</a>; this page is the widget that implements
the client end of it.</p>
<h2 id="the-binding-contract">The Binding Contract</h2>
<p>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.</p>
<table>
<thead>
<tr>
<th>Prop</th>
<th>Meaning</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>modelValue</code></td>
<td><code>number</code>, <code>null</code> or <code>undefined</code> — the value to show</td>
</tr>
<tr>
<td><code>label</code></td>
<td>the field's label, inside the outline; floats to the top of it once the field has content</td>
</tr>
<tr>
<td><code>hint</code></td>
<td>text below the field</td>
</tr>
<tr>
<td><code>unit</code></td>
<td>a display-only suffix inside the field</td>
</tr>
<tr>
<td><code>readonly</code> / <code>disable</code></td>
<td>passed through to the underlying field</td>
</tr>
<tr>
<td><code>min</code> / <code>max</code></td>
<td>inclusive bounds, checked on commit</td>
</tr>
<tr>
<td><code>allowEmpty</code></td>
<td>whether a blank field commits <code>null</code>; defaults to on</td>
</tr>
<tr>
<td><code>hideBottomSpace</code></td>
<td>stop holding space below the field; a hint or message still renders when there is one</td>
</tr>
<tr>
<td><code>rules</code></td>
<td>extra validation functions over the raw <strong>text</strong></td>
</tr>
</tbody>
</table>
<p>Two events leave the widget.</p>
<ul>
<li><code>update:modelValue</code> carries a number or <code>null</code>. <code>null</code> is a real outcome rather than an error
signal, so a host that must not receive one either turns <code>allowEmpty</code> off or filters what it
gets: the cutter section panel returns early on <code>null</code> so that clearing a field cannot write a
zero into the profile, and the graphic-cache menu rejects <code>null</code> and every non-finite value
before it calls the server.</li>
<li><code>parseError</code> carries 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.</li>
</ul>
<h2 id="commit-semantics">Commit Semantics</h2>
<p>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.</p>
<p>A commit parses the text once and then takes one of three exits. On success the error strip is
cleared, <code>update:modelValue</code> is emitted, and the box is rewritten from the parsed number. On text
that does not parse, <code>parseError</code> is emitted and a message appears. On a number outside the bounds,
a message appears. Neither failing exit emits <code>update:modelValue</code>, and neither rewrites the box.</p>
<p>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 <code>modelValue</code> — 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.</p>
<h2 id="bounds-and-special-values">Bounds and Special Values</h2>
<p><code>min</code> and <code>max</code> are inclusive and are enforced inside the widget, before anything is emitted: a
number below <code>min</code> or above <code>max</code> is refused and the host never sees it.</p>
<p><strong>The bounds apply to finite numbers only.</strong> <code>Infinity</code>, <code>-Infinity</code>, <code>NaN</code> and the <code>null</code> a blank
field produces all skip the range check, so a field declared with a <code>min</code> of 0 still commits
<code>-Infinity</code> and <code>NaN</code>. Where that matters, the host filters what it receives.</p>
<table>
<thead>
<tr>
<th>Typed</th>
<th>Committed</th>
<th>Shown afterwards</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>Infinity</code> in any case, or <code>∞</code></td>
<td><code>Infinity</code></td>
<td><code>Infinity</code></td>
</tr>
<tr>
<td><code>-Infinity</code> in any case, or <code>-∞</code></td>
<td><code>-Infinity</code></td>
<td><code>-Infinity</code></td>
</tr>
<tr>
<td><code>NaN</code> in any case</td>
<td><code>NaN</code></td>
<td><em>(empty)</em></td>
</tr>
<tr>
<td>blank, <code>allowEmpty</code> on</td>
<td><code>null</code></td>
<td><em>(empty)</em></td>
</tr>
<tr>
<td>blank, <code>allowEmpty</code> off</td>
<td>nothing — parse error</td>
<td>the blank stays</td>
</tr>
<tr>
<td>anything JavaScript's <code>Number()</code> reads as a finite number</td>
<td>that number</td>
<td>its default string form</td>
</tr>
<tr>
<td>anything else</td>
<td>nothing — parse error</td>
<td>the rejected text stays</td>
</tr>
</tbody>
</table>
<p><code>Number()</code> is the whole numeric parser, so exponent notation and the <code>0x</code>, <code>0b</code> and <code>0o</code> 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 — <code>1e400</code> — is a
parse error rather than a second route to <code>Infinity</code>. The two spellings at the head of the table
are the only way to reach one.</p>
<p>Two consequences follow from <code>NaN</code> and <code>null</code> sharing the empty box. A <code>NaN</code> arriving from the
server is indistinguishable from a blank field; and because a blank field commits <code>null</code> under the
default, merely focusing such a field and leaving it replaces the <code>NaN</code> with <code>null</code>. With
<code>allowEmpty</code> turned off the same field instead fails to parse on every blur until something is
typed into it.</p>
<p>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 <a class="xref" href="../conventions/numeric-io-utilities.html">Numeric Input/Output</a>. 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 <code>wwwroot-src/src/api/mission.ts</code> does for
the mission commands. That conversion is not symmetric for <code>NaN</code>: the mission module writes all
three strings but recognises only the two infinity spellings when reading, so a <code>NaN</code> returned by
the endpoint becomes the caller's supplied default instead.</p>
<h2 id="unit-suffix-and-precision">Unit Suffix and Precision</h2>
<p><code>unit</code> 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, <code>mm</code> and <code>deg</code> being the common ones, alongside <code>mm/min</code>, <code>rpm</code>, <code>N</code>, <code>°C</code> and
<code>MB</code>.</p>
<p>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.</p>
<h2 id="validation-messages">Validation Messages</h2>
<p>Three messages can appear under the field, and all three are built inside the component in English:
<code>Invalid number: &quot;…&quot;</code> quoting the rejected text, <code>Must be ≥ …</code> and <code>Must be ≤ …</code> quoting the bound.
They are not keys and appear in no i18n bundle, so they do not follow the application's language
setting — unlike <code>label</code> and <code>hint</code>, which hosts pass in already translated.</p>
<p><code>rules</code> is a separate mechanism and differs in three ways: its functions receive the raw display
<strong>text</strong> 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 <strong>they do
not gate the commit</strong>: 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.</p>
<p>No shipped host passes <code>rules</code>, and none fills the <code>append</code> slot, so both are available rather
than exercised. <code>hideBottomSpace</code> 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.</p>
<h2 id="when-the-box-and-the-model-disagree">When the Box and the Model Disagree</h2>
<p>A rejected commit leaves the typed text in the box. That text is replaced only when <code>modelValue</code>
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:</p>
<ul>
<li>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.</li>
<li>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
<code>modelValue</code> is not a change and moves nothing.</li>
</ul>
<p>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.</p>
<h2 id="layout">Layout</h2>
<ul>
<li>Numeric Input Field
<ul>
<li>Label — the host's <code>label</code>, 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</li>
<li>Text Box — one line, free text until committed</li>
<li>Unit Suffix — the host's <code>unit</code>, inside the field at the right; display only</li>
<li>Append Slot — an optional trailing slot for a host-supplied control</li>
<li>Bottom Strip — the host's <code>hint</code>, replaced by the validation message while the field is in
error. <code>hideBottomSpace</code> stops 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.</li>
</ul>
</li>
</ul>
<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/widgets/NumericInput.vue</code> — 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.</li>
<li><code>wwwroot-src/src/components/preference/GraphicCacheMenu.vue</code> — a compact host of three instances,
one of them bounded by the values of the other two; every handler rejects <code>null</code> and non-finite
numbers, and the current-size handler is the one that rounds before sending.</li>
<li><code>wwwroot-src/src/components/controlTree/toolhouse/CutterSectionPanel.vue</code> — a host that returns
early on the <code>null</code> a cleared field emits, so blanking a cutter dimension cannot write a zero.</li>
<li><code>wwwroot-src/src/components/spindle/SpindleContourEditor.vue</code> — a host that turns <code>allowEmpty</code>
off and keeps its own draft copy of the contour points around the blur commit.</li>
<li><code>wwwroot-src/src/api/mission.ts</code> — the module that turns a committed non-finite number into the
<code>Infinity</code>, <code>-Infinity</code> or <code>NaN</code> string the endpoint takes, and reads the two infinity spellings
back; a <code>NaN</code> arriving from the endpoint falls through to the caller's default instead.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="../conventions/numeric-io-utilities.html">Numeric Input/Output</a> — the cross-boundary rule this widget implements the client half of, and how the three numeric inputs differ from one another</li>
<li><a class="xref" href="index.html">Widgets</a> — the other controls that pages embed rather than own</li>
<li><a class="xref" href="../general-setup/controller/editing-contract.html">Editing Contract</a> — the branch that embeds this field most heavily, and the
commit rules its panels inherit from it</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>
@@ -0,0 +1,218 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Object Management Menu Button | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Object Management Menu Button | 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 &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<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-ObjectManagementMenuButton">
<h1 id="object-management-menu-button">Object Management Menu Button</h1>
<p>The menu button represent the target object with getter function and setter function. The target object generally is <a class="xref" href="../../api/Hi.Common.XmlUtils.IMakeXmlSource.html">IMakeXmlSource</a>.</p>
<p>the target object with getter function and setter function is such like:</p>
<pre><code class="lang-csharp">object TargetObject{get=&gt;TargetObjectGetter?.Invoke();set=&gt;TargetObjectSetter?.Invoke(value);}
Func&lt;TargetObject&gt; TargetObjectGetter{get;set;}
Action&lt;TargetObject&gt; TargetObjectSetter{get;set;}
</code></pre>
<p>The target object has the following functions:</p>
<ul>
<li>File Save/Load
<ul>
<li>See <a class="xref" href="../conventions/gui-file-path-assignment.html">GUI File Path Assignment</a></li>
</ul>
</li>
<li>Object Copy/Paste</li>
<li>Editor Panel Mode Selection
<ul>
<li>GUI (user-friendly)</li>
<li>XML
<ul>
<li>Get XML by <a class="xref" href="../../api/Hi.Common.XmlUtils.IMakeXmlSource.html">IMakeXmlSource</a> with <code>exhibitionOnly</code> true.</li>
<li>Set XML by <a class="xref" href="../../api/Hi.Common.XmlUtils.XFactory.html">XFactory</a>.</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>If the target object is not <a class="xref" href="../../api/Hi.Common.XmlUtils.IMakeXmlSource.html">IMakeXmlSource</a>, then the XML Editor Panel Mode should not appear. The other functions are still buildable.</p>
<p>On <a class="xref" href="../../api/Hi.Common.XmlUtils.XFactory.html">XFactory</a> functions, set <code>enableRebase</code> to <code>true</code> (which is the default). The rebase mode resolves relative file paths from the file's own directory, preventing silent inner exceptions.</p>
<h2 id="layout">Layout</h2>
<ul>
<li>Object Management Menu Button
<ul>
<li>Load Button</li>
<li>Load Resource Button</li>
<li>Save As Button</li>
<li>(splitter)</li>
<li>Copy Button (with hotkey support if the Object Management Menu Button is focused)</li>
<li>Paste Button (with hotkey support if the Object Management Menu Button is focused)</li>
<li>(splitter)</li>
<li>GUI Ratio Button</li>
<li>XML Ratio Button</li>
</ul>
</li>
</ul>
<div class="TIP">
<h5>Tip</h5>
<ul>
<li>Since the Object Management Menu Button has special meaning, use icon instead of text label.</li>
<li>Do not use icon on the other child buttons. The icons gains nothing but hard to keep style to the full application.</li>
<li>If the model is selected, show a different style (may be color) on the menu button.</li>
<li>The model should contain a ResourceDirectory property.</li>
<li>Do not show Load Resource Button if Resource directory not explicitly gave.</li>
</ul>
</div>
<h2 id="copy--paste">Copy &amp; Paste</h2>
<ul>
<li>Object Copy/Paste (i.e. Select/Set or Duplicated-Set)
<ul>
<li>Copy (i.e. Select)
Set the model to <code>UserService.SelectedItem</code>.</li>
<li>Paste
Set <code>UserService.SelectedItem</code> to the model.
Set by reference is default. Apply Duplicated-Set if explicitly required.</li>
</ul>
</li>
</ul>
<p>While a object is copied (selected) here, it can also be paste (drag) to:</p>
<ul>
<li>Text editor
Use <a class="xref" href="../../api/Hi.Common.XmlUtils.IMakeXmlSource.html">IMakeXmlSource</a>.<a class="xref" href="../../api/Hi.Common.XmlUtils.IMakeXmlSource.html#Hi_Common_XmlUtils_IMakeXmlSource_MakeXmlSource_System_String_System_String_System_Boolean_">MakeXmlSource(string, string, bool)</a> with <code>exhibitionOnly</code> false and the argument (<code>baseDirectory</code> and <code>relFile</code>) from object's host to paste the text, it should be the same text content by the host XML output.</li>
<li>File browser
Use <a class="xref" href="../../api/Hi.Common.XmlUtils.IMakeXmlSource.html">IMakeXmlSource</a>.<a class="xref" href="../../api/Hi.Common.XmlUtils.IMakeXmlSource.html#Hi_Common_XmlUtils_IMakeXmlSource_MakeXmlSource_System_String_System_String_System_Boolean_">MakeXmlSource(string, string, bool)</a> with <code>exhibitionOnly</code> true, <code>baseDirectory</code> destination folder, <code>relFile</code> destination file name (maybe xxx.class-name) to paste the file.</li>
</ul>
<p>Show message when paste action success or failed. See <a class="xref" href="../conventions/index.html">Handle Message and Exception</a>.</p>
<p>If the data type is not matched, show the un-matched message.</p>
<h2 id="editor-panel-mode-ratio-button">Editor Panel Mode Ratio Button</h2>
<p>The Editor Panel switched by the Editor Panel Mode Ratio Button.</p>
<p>The BaseDirectory and RelFile properties should exist. Use the RelFile property if the object's host has the corresponding property like XXXFile.</p>
<p>The Apply action should be well-set. Include <a class="xref" href="../../api/Hi.Common.XmlUtils.SetFileDelegate.html">SetFileDelegate</a> from <a class="xref" href="../../api/Hi.Common.XmlUtils.XFactory.html#Hi_Common_XmlUtils_XFactory_Gen_">Gen</a> function.
The last argument should also be delivered by the host. So there must exist an property to pass the argument.</p>
<h3 id="xml-editor-panel-layout">XML Editor Panel Layout</h3>
<ul>
<li>XML Editor Panel
<ul>
<li>Cancel Button</li>
<li>Apply Button</li>
<li>XML TextArea</li>
</ul>
</li>
</ul>
<p>Shows error message if the xml-parsing or object creation failed on XML Editor Panel Apply Button applied.</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>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>wwwroot-src/src/components/widgets/ObjectManagementMenuButton.vue</code> — Quasar dropdown + XML editor dialog. Emits <code>update:modelKey</code>, <code>objectLoaded</code>, <code>objectPasted</code>, <code>xmlApplied</code>, <code>error</code>. Supports Ctrl+C / Ctrl+V.</li>
<li><code>wwwroot-src/src/api/objectManagement.ts</code> — typed wrapper over <code>/api/ObjectManagement/*</code>.</li>
<li><code>Widget/ObjectManagementController.cs</code> — REST endpoints: <code>Load</code>, <code>LoadResource</code>, <code>SaveAs</code>, <code>Copy</code>, <code>Paste</code>, <code>GetXml</code>, <code>ApplyXml</code>, <code>SupportsXml</code>, <code>GetSelectedItem</code>.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a href="../util/file-explorer.html">File Explorer</a> — reaches the same project / resource folders through a different endpoint family</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>
@@ -0,0 +1,172 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Polar Resolution 2D Panel | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Polar Resolution 2D Panel | 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 &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<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-PolarResolution2dPanel">
<h1 id="polar-resolution-2d-panel">Polar Resolution 2D Panel</h1>
<p>The model is <a class="xref" href="../../api/Hi.Geom.Resolution.PolarResolution2d.html">PolarResolution2d</a>: 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.</p>
<h2 id="layout">Layout</h2>
<h3 id="wpf-layout">WPF Layout</h3>
<ul>
<li>Polar Resolution 2D Panel
<ul>
<li>Enable Custom Resolution CheckBox
<ul>
<li>checked if the host model is not null; the checkbox itself is always clickable.</li>
<li>unchecking sets the host model to null; checking a null model publishes a fresh <a class="xref" href="../../api/Hi.Geom.Resolution.PolarResolution2d.html">PolarResolution2d</a> of 0.5 mm / 5 deg through the setter.</li>
</ul>
</li>
<li>Linear Resolution Input Field, with an <code>mm</code> suffix label
<ul>
<li>enabled if model not null</li>
</ul>
</li>
<li>Angle Resolution Input Field, with a degree-sign suffix label
<ul>
<li>enabled if model not null</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="web-layout">Web Layout</h3>
<ul>
<li>Holder Resolution Section
<ul>
<li>Resolution Hint Caption — names the STL tessellation used for display and collision meshing</li>
<li>Linear Resolution (mm) Input Field</li>
<li>Angle Resolution (deg) Input Field</li>
</ul>
</li>
</ul>
<p>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.</p>
<h2 id="feature">Feature</h2>
<p>The Enable CheckBox is always shown. The host drives the WPF widget only through <code>GetterFunc</code> / <code>SetterFunc</code>; it calls <code>UpdateUI()</code> after its own model changes and subscribes to <code>OnModelChanged</code> to react to an edit.</p>
<p>An edit republishes a whole new carrier through the setter instead of mutating the current one, in either client. A holder's <a class="xref" href="../../api/Hi.NcMech.Holders.CylindroidHolder.html#Hi_NcMech_Holders_CylindroidHolder_PolarResolution2d">PolarResolution2d</a> 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 <code>SetPolarResolution</code>, then clears the holder's cache.</p>
<p>If the host model is null, it may mean the resolution applied the default value: <a class="xref" href="../../api/Hi.NcMech.Solids.Solid.html#Hi_NcMech_Solids_Solid_PolarResolution2d">PolarResolution2d</a> 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.</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>
<p>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 <code>toolhouse/tool-{id}/holder/resolution</code> and the route <code>/tool-house/:toolId/holder/resolution</code>. A Freeform holder grows no sections in the web client; its Holder panel shows a hint pointing at the WPF client instead.</p>
<ul>
<li><code>wwwroot-src/src/components/controlTree/toolhouse/HolderSectionPanel.vue</code> — the <code>section === 'resolution'</code> branch: the two number fields, seeded from <code>Get</code> and committed on blur / Enter.</li>
<li><code>wwwroot-src/src/components/controlTree/toolHouseItemTypes.ts</code> — registers that panel as the <code>HolderSection</code> item type and grows the Resolution node only for a <code>CylindroidHolder</code>.</li>
<li><code>wwwroot-src/src/components/controlTree/toolhouse/ToolHolderPanel.vue</code> — the Holder stem above it, whose holder-type selector decides which sections appear.</li>
<li><code>wwwroot-src/src/api/cylindroidHolder.ts</code> — <code>PolarResolutionDto</code> (<code>linear_mm</code> / <code>angle_deg</code>) and <code>setCylindroidHolderResolution</code>.</li>
<li><code>wwwroot-src/src/router/treeRoutes.ts</code> — maps the holder's section ids onto the Tool House route params.</li>
<li><code>wwwroot-src/src/i18n/en/toolhouse.ts</code> — the two field labels and the tessellation hint caption.</li>
<li><code>Mech/CylindroidHolderController.cs</code> — <code>POST /api/CylindroidHolder/SetPolarResolution</code>, which rejects a non-positive value, assigns a fresh <code>PolarResolution2d</code> and calls <code>ClearCache()</code>; <code>GET Get</code> reports a null resolution as 0 / 0.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="../tool-house/holder/cylindroid-holder-panel.html">Cylindroid Holder Panel</a> — the holder editor that embeds this panel to tessellate its profile</li>
<li><a class="xref" href="../tool-house/holder/freeform-holder-panel.html">Freeform Holder Panel</a> — the other holder editor that embeds it</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>
@@ -0,0 +1,210 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>RenderingCanvas Tool Bar | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="RenderingCanvas Tool Bar | 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 &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<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-RenderingCanvasToolBar">
<h1 id="renderingcanvas-tool-bar">RenderingCanvas Tool Bar</h1>
<p>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 <a class="xref" href="../../api/Hi.Disp.DispEngine.html">DispEngine</a> and drives it directly from each menu click. On the web it holds no engine: it takes a <code>canvas</code> prop and calls that component's exposed <code>setView(name)</code>, which invokes the <code>SetView</code> hub method, and the hub is what drives the per-connection <a class="xref" href="../../api/Hi.Disp.DispEngine.html">DispEngine</a>.</p>
<h2 id="view-menu">View Menu</h2>
<p>The bar is a single <code>View ▾</code> menu. Its seven entries, in shipped order, are Isometric, Front, Back, Right, Left, Top and Bottom. The engine also understands a <code>home</code> view and <code>RenderingCanvas.vue</code> exposes <code>setViewToHomeView</code>, but this menu does not offer it.</p>
<table>
<thead>
<tr>
<th>Entry</th>
<th>API Method</th>
</tr>
</thead>
<tbody>
<tr>
<td>Isometric</td>
<td><a class="xref" href="../../api/Hi.Disp.DispEngine.html#Hi_Disp_DispEngine_SetViewToIsometricView">SetViewToIsometricView()</a></td>
</tr>
<tr>
<td>Front</td>
<td><a class="xref" href="../../api/Hi.Disp.DispEngine.html#Hi_Disp_DispEngine_SetViewToFrontView">SetViewToFrontView()</a></td>
</tr>
<tr>
<td>Back</td>
<td><a class="xref" href="../../api/Hi.Disp.DispEngine.html#Hi_Disp_DispEngine_SetViewToFrontView">SetViewToFrontView()</a> + <a class="xref" href="../../api/Hi.Disp.DispEngine.html#Hi_Disp_DispEngine_TurnBackView">TurnBackView()</a></td>
</tr>
<tr>
<td>Right</td>
<td><a class="xref" href="../../api/Hi.Disp.DispEngine.html#Hi_Disp_DispEngine_SetViewToRightView">SetViewToRightView()</a></td>
</tr>
<tr>
<td>Left</td>
<td><a class="xref" href="../../api/Hi.Disp.DispEngine.html#Hi_Disp_DispEngine_SetViewToRightView">SetViewToRightView()</a> + <a class="xref" href="../../api/Hi.Disp.DispEngine.html#Hi_Disp_DispEngine_TurnBackView">TurnBackView()</a></td>
</tr>
<tr>
<td>Top</td>
<td><a class="xref" href="../../api/Hi.Disp.DispEngine.html#Hi_Disp_DispEngine_SetViewToTopView">SetViewToTopView()</a></td>
</tr>
<tr>
<td>Bottom</td>
<td><a class="xref" href="../../api/Hi.Disp.DispEngine.html#Hi_Disp_DispEngine_SetViewToTopView">SetViewToTopView()</a> + <a class="xref" href="../../api/Hi.Disp.DispEngine.html#Hi_Disp_DispEngine_TurnBackView">TurnBackView()</a></td>
</tr>
</tbody>
</table>
<h3 id="back-view-implementation">Back View Implementation</h3>
<p>Back / Left / Bottom views are composed by first calling the corresponding forward-view method (<code>SetViewToFrontView</code> / <code>SetViewToRightView</code> / <code>SetViewToTopView</code>) and then invoking <a class="xref" href="../../api/Hi.Disp.DispEngine.html#Hi_Disp_DispEngine_TurnBackView">TurnBackView()</a> to flip the camera about the view plane. Both clients compose them the same way — the web in the hub's <code>SetView</code> switch, WPF in the tool bar's click handlers.</p>
<h3 id="canvas-binding">Canvas Binding</h3>
<p>On the web the tool bar declares exactly one prop, a nullable <code>canvas</code>, typed structurally as an object exposing <code>setView(v: string): Promise&lt;void&gt;</code> rather than as the <code>RenderingCanvas</code> 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 <code>widgets.canvas.*</code> keys.</p>
<h2 id="scene-menu">Scene Menu</h2>
<p>Pages that sit next to a <code>RenderingCanvas</code> surface a per-page <code>Scene ▾</code> menu button — the <code>DisplayOptionsMenu</code> component, labelled from <code>widgets.canvas.scene</code> because no caller overrides the label. It chooses what the 3D scene draws (solids, coordinates, display aids), as distinct from the camera-oriented <code>View ▾</code> 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.</p>
<h3 id="schema">Schema</h3>
<p>Each dropdown consumes a <code>DisplayGroup[]</code> array. A group has an optional header and a flat list of items:</p>
<ul>
<li><code>{ kind: 'check', label, modelValue, disable?, onUpdate }</code> — a checkbox row.</li>
<li><code>{ kind: 'radio', label, groupValue, value, disable?, onUpdate }</code> — a radio row; rows sharing a <code>groupValue</code> behave as one group, each row bound to its own <code>value</code>.</li>
</ul>
<p>The component is generic over the radio <code>value</code> type, so <code>RenderingMode</code> / <code>HolderRenderingMode</code> / etc. stay fully typed at the call site. Besides <code>groups</code> it accepts <code>label</code>, <code>disable</code>, <code>title</code>, <code>minWidthPx</code> (default 220), <code>size</code> (default <code>sm</code>), <code>contentClass</code> and <code>compact</code>. <code>compact</code> 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 <code>content-class=&quot;bg-white&quot;</code> to opt out of Quasar's transparent menu.</p>
<h3 id="hinc-2025-webservice-quasar-cli-spa">HiNC-2025-webservice (Quasar CLI SPA)</h3>
<ul>
<li><code>wwwroot-src/src/components/widgets/DisplayOptionsMenu.vue</code> — the shared <code>&lt;q-btn-dropdown&gt;</code> + <code>&lt;q-list&gt;</code> implementation behind four of the app's five Scene menus. The Controller viewer is the exception: <code>wwwroot-src/src/components/controller/ControllerExtendedToolBar.vue</code> builds its Scene dropdown inline against the same <code>/api/rendering-flags</code> surface.</li>
<li>Callers (each reads a <code>DisplayGroup[]</code> computed from its page-local state and forwards <code>onUpdate</code> to its existing handlers):
<ul>
<li><code>wwwroot-src/src/components/execution/ExecutionExtendedToolBar.vue</code></li>
<li><code>wwwroot-src/src/components/mech/EquipmentSetupPanel.vue</code></li>
<li><code>wwwroot-src/src/components/toolhouse/ToolHouseSetupPanel.vue</code></li>
<li><code>wwwroot-src/src/components/StlPreviewPane.vue</code></li>
</ul>
</li>
<li>Backends — one per caller, each a different surface: <code>ExecutionExtendedToolBar.vue</code> reaches <code>/api/rendering-flags</code> (<code>Common/RenderingFlagsController.cs</code>), which flips bits in <code>ExecutionDisplayee.RenderingFlagBitArray</code>; <code>EquipmentSetupPanel.vue</code> reaches <code>/api/mech/equipment-setup-display/*</code> (<code>Mech/EquipmentSetupDisplayController.cs</code>); <code>ToolHouseSetupPanel.vue</code> reaches <code>/api/mech/tool-house-display/*</code> (<code>Mech/ToolHouseDisplayController.cs</code>); <code>StlPreviewPane.vue</code> reaches <code>/api/stl-preview/set-coordinate/*</code> (<code>Disp/StlPreviewController.cs</code>). Those last three route each POST by the caller's <code>renderingConnectionId</code>, so the toggle lands on that canvas's own engine; the rendering-flags surface carries no connection id and acts on the project-wide <code>ExecutionDisplayee</code> instead. <code>DisplayOptionsMenu.vue</code> is UI-only; it does no REST work itself — the caller owns the <code>onUpdate</code> handlers, so optimistic update and error reversion stay page-local.</li>
</ul>
<h3 id="wpf">WPF</h3>
<ul>
<li><code>Play/RenderingFlagSubmenu.xaml</code> — the shared flag submenu, wrapped per surface by <code>Numerical/Controller/ControllerExtendedRenderingCanvasToolBar.xaml</code> and <code>Play/PlayerExtendedRenderingCanvasToolBar.xaml</code>. Behaviour parity with the web <code>Scene ▾</code> menu; no schema-driven composition.</li>
</ul>
<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>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>wwwroot-src/src/components/RenderingCanvasToolBar.vue</code> — the <code>View ▾</code> menu. Eight callers embed it, one per canvas: <code>wwwroot-src/src/pages/ExecutionPage.vue</code> (teleported into the canvas panel's header, beside <code>ExecutionExtendedToolBar</code>), <code>wwwroot-src/src/pages/ControllerPage.vue</code> (beside <code>ControllerExtendedToolBar</code>), <code>wwwroot-src/src/pages/MachineToolPage.vue</code>, <code>wwwroot-src/src/pages/MechBuilderPage.vue</code>, <code>wwwroot-src/src/components/mech/EquipmentSetupPanel.vue</code> (the General Setup canvas column, mounted by <code>wwwroot-src/src/pages/GeneralSetupPage.vue</code>), <code>wwwroot-src/src/components/toolhouse/ToolHouseSetupPanel.vue</code> (mounted by <code>wwwroot-src/src/pages/ToolHousePage.vue</code>), <code>wwwroot-src/src/components/StlPreviewPane.vue</code> (the File Explorer preview pane, mounted by <code>wwwroot-src/src/components/FileExplorer.vue</code>) and <code>wwwroot-src/src/components/execution/StepVolumePanel.vue</code> (the CWE footprint canvas, teleported into its host expansion's header). Four of those sit beside a <code>Scene ▾</code> menu — the Execution, General Setup, Tool House and STL preview canvases; the Machine Tool, Mechanism Builder and CWE canvases carry the <code>View ▾</code> menu alone, and the Controller viewer pairs it with an inlined Scene dropdown.</li>
<li><code>wwwroot-src/src/components/widgets/DisplayOptionsMenu.vue</code> — schema-driven dropdown described above.</li>
<li><code>wwwroot-src/src/components/RenderingCanvas.vue</code> — SignalR-hosted WebSocket canvas whose exposed <code>setView</code> / <code>setViewTo*View</code> methods the tool bar calls (see <a class="xref" href="../conventions/rendering-canvas-web-service.html">Rendering Canvas on Web Service</a>).</li>
<li><code>wwwroot-src/src/i18n/en/widgets.ts</code> — the <code>widgets.canvas.*</code> keys: <code>view</code>, the seven preset labels, and <code>scene</code>.</li>
<li>Backends:
<ul>
<li><code>Disp/RenderingHub.cs</code> — the <code>SetView(string)</code> hub method that maps each preset name onto engine calls.</li>
<li><code>Disp/RenderingService.cs</code> — <code>GetOrCreateEngine(connectionId)</code>, the per-connection <code>DispEngine</code> store the hub resolves against.</li>
<li><code>Common/RenderingFlagsController.cs</code> — the <code>/api/rendering-flags</code> surface (<code>GET</code>, <code>POST update</code>, <code>POST batch</code>) behind the Execution page's Scene menu and the Controller viewer's inlined one, wrapped by <code>wwwroot-src/src/api/renderingFlags.ts</code>. It reads and writes <code>ExecutionDisplayee.RenderingFlagBitArray</code> through <code>ProjectDisplayeeService</code>.</li>
<li><code>wwwroot-src/src/api/equipmentSetup.ts</code>, <code>wwwroot-src/src/api/toolHouse.ts</code> and <code>wwwroot-src/src/api/stlPreview.ts</code> — the typed wrappers the other three Scene menus post through.</li>
</ul>
</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="../execution/index.html">Execution Page</a> — the page whose canvas header adopts this tool bar</li>
<li><a class="xref" href="../execution/execution-extended-renderingcanvas-tool-bar.html">Execution Extended RenderingCanvas Tool Bar</a> — the run-specific controls that sit beside it</li>
<li><a class="xref" href="../conventions/rendering-canvas-web-service.html">Rendering Canvas on Web Service Application</a> — the SignalR transport whose hub actually moves the camera this menu asks for</li>
<li><a class="xref" href="../util/stl-preview-pane.html">STL Preview Pane</a> — the one caller whose canvas is a single file rather than a project scene</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>
@@ -0,0 +1,40 @@
<div id="sidetoggle">
<div>
<div class="sidefilter">
<form class="toc-filter">
<span class="glyphicon glyphicon-filter filter-icon"></span>
<span class="glyphicon glyphicon-remove clear-icon" id="toc_filter_clear"></span>
<input type="text" id="toc_filter_input" placeholder="Filter by title" onkeypress="if(event.keyCode==13) {return false;}">
</form>
</div>
<div class="sidetoc">
<div class="toc" id="toc">
<ul class="nav level1">
<li>
<a href="index.html" name="" title="Overview">Overview</a>
</li>
<li>
<a href="object-management-menu-button.html" name="" title="Object Management Menu Button">Object Management Menu Button</a>
</li>
<li>
<a href="renderingcanvas-tool-bar.html" name="" title="RenderingCanvas Tool Bar">RenderingCanvas Tool Bar</a>
</li>
<li>
<a href="numeric-input.html" name="" title="Numeric Input">Numeric Input</a>
</li>
<li>
<a href="polar-resolution-2d-panel.html" name="" title="Polar Resolution 2D Panel">Polar Resolution 2D Panel</a>
</li>
<li>
<a href="vec3d-control.html" name="" title="Vec3dControl">Vec3dControl</a>
</li>
<li>
<a href="mat4d-control.html" name="" title="Mat4dControl">Mat4dControl</a>
</li>
</ul>
</div>
</div>
</div>
</div>
@@ -0,0 +1,2 @@
{"items":[{"name":"Overview","href":"index.html","topicHref":"index.html"},{"name":"Object Management Menu Button","href":"object-management-menu-button.html","topicHref":"object-management-menu-button.html"},{"name":"RenderingCanvas Tool Bar","href":"renderingcanvas-tool-bar.html","topicHref":"renderingcanvas-tool-bar.html"},{"name":"Numeric Input","href":"numeric-input.html","topicHref":"numeric-input.html"},{"name":"Polar Resolution 2D Panel","href":"polar-resolution-2d-panel.html","topicHref":"polar-resolution-2d-panel.html"},{"name":"Vec3dControl","href":"vec3d-control.html","topicHref":"vec3d-control.html"},{"name":"Mat4dControl","href":"mat4d-control.html","topicHref":"mat4d-control.html"}]}
@@ -0,0 +1,660 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Vec3dControl Component | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Vec3dControl Component | 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 &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<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-Vec3dControl">
<h1 id="vec3dcontrol-component">Vec3dControl Component</h1>
<p>Vec3dControl edits and displays a three-component vector. It is an embedded widget with no route
and no Control-Tree node of its own, reached only through the editors that host it.</p>
<h2 id="key-model">Key Model</h2>
<p>The persisted model is <a class="xref" href="../../api/Hi.Geom.Vec3d.html">Vec3d</a>, exposed over <code>/api/Vec3d</code> by <code>Widget/Vec3dController.cs</code>.</p>
<p>In the web application the widget's own model is the plain <code>{ x, y, z }</code> number triple exported as
<code>interface Vec3</code> from <code>wwwroot-src/src/components/widgets/Vec3Input.vue</code>. The widget carries no key
and calls no endpoint: the host binds it with <code>v-model</code> plus an <code>@update:model-value</code> handler, and
that handler does the persisting, each host on its own terms. The four transformer editors call
<code>updateVec3d</code> from <code>wwwroot-src/src/api/geometry.ts</code>, which posts all three axes together to
<code>/api/Vec3d/Update</code>, while <code>wwwroot-src/src/components/geom/Box3dEditor.vue</code> folds the edited triple
back into a min and max pair and posts that to <code>/api/Box3d/Update</code>.</p>
<p>The WPF control likewise holds no value of its own. <code>GetterFunc</code> returns the live
<a class="xref" href="../../api/Hi.Geom.Vec3d.html">Vec3d</a> instance, the text handlers write the parsed numbers straight onto it, and
<code>UpdateByContentFunc</code> is awaited afterwards so the host can push the change on.</p>
<p>The web props are <code>modelValue</code>, <code>disable</code>, <code>readonly</code>, <code>normalize</code>, <code>labels</code> and <code>textMode</code>, and the
emits are <code>update:modelValue</code> and <code>normalize</code>. <code>labels</code> is a three-tuple defaulting to
<code>['X', 'Y', 'Z']</code>, and carries units where the host has them, as in <code>['X (mm)', 'Y (mm)', 'Z (mm)']</code>.
<code>textMode</code> sets the initial mode only and defaults to false. The corresponding WPF properties are
<code>ShowNormalizeButton</code>, <code>IsTextMode</code> and <code>IsReadOnly</code>.</p>
<h2 id="layout">Layout</h2>
<ul>
<li>Vec3dControl
<ul>
<li><code>Mode Toggle Button</code> — always present; switches between the per-axis columns and the
single-field text form, and is highlighted while the text form is showing</li>
<li><code>X</code>, <code>Y</code> and <code>Z</code> Input Fields — the per-axis form, three fields side by side</li>
<li><code>Vector Text Field</code> — the single-field form, standing in place of the three axis fields</li>
<li><code>Normalize Button</code> — present only when the normalize flag is set, and disabled while the
control is disabled or readonly</li>
</ul>
</li>
</ul>
<p>In the WPF client the two forms are two panels, <code>StandardModePanel</code> and <code>TextModePanel</code>, of which
exactly one is visible; each panel carries its own mode-toggle and normalize buttons.</p>
<h2 id="feature">Feature</h2>
<h3 id="per-axis-form">Per-axis form</h3>
<p>X, Y and Z in three separate fields. The web widget renders every finite value at full precision;
the WPF control renders each axis with a fixed three decimal places.</p>
<h3 id="single-field-text-form">Single-field text form</h3>
<p>The web form is <code>(x, y, z)</code>. Its parser strips surrounding brackets and splits on comma, semicolon
or whitespace, requiring at least three parts, and entering the text form re-syncs the field from
the current value so a stale in-progress edit is discarded. The WPF text box holds comma-separated
<code>x,y,z</code> without brackets and accepts two further shapes: a pasted 4x4 matrix, from which it takes
the translation column, and loose text, from which it takes the first three numbers it can read.</p>
<h3 id="vector-normalization">Vector normalization</h3>
<p>The normalize button is opt-in and off by default in both clients: the <code>normalize</code> prop in the web
widget, the <code>ShowNormalizeButton</code> property in the WPF control.</p>
<p>In the web widget the button only raises a <code>normalize</code> event, and the embedding editor performs the
normalization on its own owning object. <code>wwwroot-src/src/components/topo/StaticRotationEditor.vue</code>
posts to <code>/api/StaticRotation/NormalizeAxis</code>, while
<code>wwwroot-src/src/components/topo/DynamicRotationEditor.vue</code> and
<code>wwwroot-src/src/components/topo/DynamicTranslationEditor.vue</code> call <code>normalizeDynamicRotationAxis</code>
and <code>normalizeDynamicTranslationAxis</code> from <code>wwwroot-src/src/api/transformer.ts</code>. The WPF control
normalizes the vector in place and writes the result back at three decimal places.</p>
<h3 id="commit-and-special-values">Commit and special values</h3>
<p>Web edits commit on blur or Enter, never per keystroke, and a value is emitted only when it differs
from the current model. An empty field parses to 0; any other unparseable text reverts the field to
the last valid value. Formatting and parsing are local to the component: NaN renders as <code>NaN</code> and
the infinities as <code>Infinity</code> and <code>-Infinity</code>, and the parser accepts those spellings
case-insensitively as well as the <code>∞</code> and <code>-∞</code> glyphs. The WPF control commits on every keystroke
instead, leaving the model untouched while the text is unparseable so that a half-typed number is
not treated as an error.</p>
<h2 id="refer-sample-code">Refer Sample Code</h2>
<pre><code class="lang-csharp" name="SampleCode-xaml">&lt;UserControl x:Class=&quot;HiNC_2025_win_desktop.Geom.Vec3dControl&quot;
xmlns=&quot;http://schemas.microsoft.com/winfx/2006/xaml/presentation&quot;
xmlns:x=&quot;http://schemas.microsoft.com/winfx/2006/xaml&quot;
xmlns:mc=&quot;http://schemas.openxmlformats.org/markup-compatibility/2006&quot;
xmlns:d=&quot;http://schemas.microsoft.com/expression/blend/2008&quot;
mc:Ignorable=&quot;d&quot;
d:DesignHeight=&quot;30&quot; d:DesignWidth=&quot;200&quot;&gt;
&lt;Grid&gt;
&lt;Grid.ColumnDefinitions&gt;
&lt;ColumnDefinition Width=&quot;*&quot;/&gt;
&lt;ColumnDefinition Width=&quot;*&quot;/&gt;
&lt;ColumnDefinition Width=&quot;*&quot;/&gt;
&lt;ColumnDefinition Width=&quot;Auto&quot;/&gt;
&lt;ColumnDefinition Width=&quot;Auto&quot;/&gt;
&lt;/Grid.ColumnDefinitions&gt;
&lt;!-- Standard Mode Panel --&gt;
&lt;StackPanel x:Name=&quot;StandardModePanel&quot; Orientation=&quot;Horizontal&quot; Grid.ColumnSpan=&quot;5&quot;&gt;
&lt;TextBox x:Name=&quot;XTextBox&quot; Width=&quot;60&quot; Margin=&quot;0,0,2,0&quot; TextChanged=&quot;XTextBox_TextChanged&quot; IsReadOnly=&quot;{Binding IsReadOnly}&quot;/&gt;
&lt;TextBox x:Name=&quot;YTextBox&quot; Width=&quot;60&quot; Margin=&quot;0,0,2,0&quot; TextChanged=&quot;YTextBox_TextChanged&quot; IsReadOnly=&quot;{Binding IsReadOnly}&quot;/&gt;
&lt;TextBox x:Name=&quot;ZTextBox&quot; Width=&quot;60&quot; Margin=&quot;0,0,2,0&quot; TextChanged=&quot;ZTextBox_TextChanged&quot; IsReadOnly=&quot;{Binding IsReadOnly}&quot;/&gt;
&lt;Button Content=&quot;{DynamicResource Vec3d_TextMode_Toggle}&quot; Width=&quot;20&quot; Click=&quot;TextModeToggle_Click&quot; Margin=&quot;0,0,2,0&quot; ToolTip=&quot;{DynamicResource Vec3d_TextMode_Tooltip}&quot;/&gt;
&lt;Button Content=&quot;{DynamicResource Vec3d_Normalize}&quot; Width=&quot;20&quot; Click=&quot;NormalizeButton_Click&quot; Visibility=&quot;{Binding ShowNormalizeButton, Converter={StaticResource BooleanToVisibilityConverter}}&quot; ToolTip=&quot;{DynamicResource Vec3d_Normalize_Tooltip}&quot;/&gt;
&lt;/StackPanel&gt;
&lt;!-- Text Mode Panel --&gt;
&lt;StackPanel x:Name=&quot;TextModePanel&quot; Orientation=&quot;Horizontal&quot; Grid.ColumnSpan=&quot;5&quot; Visibility=&quot;Collapsed&quot;&gt;
&lt;TextBox x:Name=&quot;VectorTextBox&quot; Width=&quot;180&quot; Margin=&quot;0,0,2,0&quot; TextChanged=&quot;VectorTextBox_TextChanged&quot; IsReadOnly=&quot;{Binding IsReadOnly}&quot;
ToolTip=&quot;{DynamicResource Vec3d_TextMode_Format_Tooltip}&quot;/&gt;
&lt;Button Content=&quot;{DynamicResource Vec3d_StandardMode_Toggle}&quot; Width=&quot;20&quot; Click=&quot;StandardModeToggle_Click&quot; Margin=&quot;0,0,2,0&quot; ToolTip=&quot;{DynamicResource Vec3d_StandardMode_Tooltip}&quot;/&gt;
&lt;Button Content=&quot;{DynamicResource Vec3d_Normalize}&quot; Width=&quot;20&quot; Click=&quot;NormalizeButton_Click&quot; Visibility=&quot;{Binding ShowNormalizeButton, Converter={StaticResource BooleanToVisibilityConverter}}&quot; ToolTip=&quot;{DynamicResource Vec3d_Normalize_Tooltip}&quot;/&gt;
&lt;/StackPanel&gt;
&lt;/Grid&gt;
&lt;/UserControl&gt;
</code></pre><pre><code class="lang-csharp" name="SampleCode-xaml.cs">using System;
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows;
using Hi.Geom;
using System.Text.RegularExpressions;
using System.ComponentModel;
using Hi.Common;
using Hi.Common.Messages;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace HiNC_2025_win_desktop.Geom
{
/// &lt;summary&gt;
/// Vec3dControl.xaml 的交互逻辑
/// &lt;/summary&gt;
public partial class Vec3dControl : UserControl, INotifyPropertyChanged
{
private bool _isUpdating = false;
private Func&lt;Vec3d&gt; _getterFunc;
private Func&lt;Task&gt; _updateByContentFunc;
private bool _showNormalizeButton = false;
private bool _isTextMode = false;
private bool _isReadOnly = false;
private readonly ILogger&lt;Vec3dControl&gt; _logger = App.ServiceProvider.GetRequiredService&lt;ILogger&lt;Vec3dControl&gt;&gt;();
private static readonly Regex VectorRegex = new Regex(@&quot;^\s*[\(\[\{]?\s*(-?\d*\.?\d+)\s*,\s*(-?\d*\.?\d+)\s*,\s*(-?\d*\.?\d+)\s*[\)\]\}]?\s*$&quot;, RegexOptions.Compiled);
private static readonly Regex MatrixRegex = new Regex(@&quot;\{(?:\s*\{?\s*(-?\d*\.?\d+)\s*,\s*(-?\d*\.?\d+)\s*,\s*(-?\d*\.?\d+)\s*,\s*(-?\d*\.?\d+)\s*\}?\s*,){3}\s*\{?\s*(-?\d*\.?\d+)\s*,\s*(-?\d*\.?\d+)\s*,\s*(-?\d*\.?\d+)\s*,\s*(-?\d*\.?\d+)\s*\}?\s*\}&quot;, RegexOptions.Compiled);
public event PropertyChangedEventHandler PropertyChanged;
public string ControlId { get; set; } = Guid.NewGuid().ToString();
public Func&lt;Vec3d&gt; GetterFunc
{
get =&gt; _getterFunc;
set
{
_getterFunc = value;
UpdateUI();
}
}
public Func&lt;Task&gt; UpdateByContentFunc
{
get =&gt; _updateByContentFunc;
set =&gt; _updateByContentFunc = value;
}
public bool ShowNormalizeButton
{
get =&gt; _showNormalizeButton;
set
{
if (_showNormalizeButton != value)
{
_showNormalizeButton = value;
OnPropertyChanged(nameof(ShowNormalizeButton));
}
}
}
public bool IsTextMode
{
get =&gt; _isTextMode;
set
{
if (_isTextMode != value)
{
_isTextMode = value;
UpdateModeVisibility();
OnPropertyChanged(nameof(IsTextMode));
}
}
}
public bool IsReadOnly
{
get =&gt; _isReadOnly;
set
{
if (_isReadOnly != value)
{
_isReadOnly = value;
OnPropertyChanged(nameof(IsReadOnly));
}
}
}
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public Vec3dControl()
{
InitializeComponent();
DataContext = this;
}
private void UpdateModeVisibility()
{
if (IsTextMode)
{
StandardModePanel.Visibility = Visibility.Collapsed;
TextModePanel.Visibility = Visibility.Visible;
UpdateVectorTextFromXYZ();
}
else
{
StandardModePanel.Visibility = Visibility.Visible;
TextModePanel.Visibility = Visibility.Collapsed;
}
}
private void UpdateVectorTextFromXYZ()
{
if (_isUpdating) return;
if (double.TryParse(XTextBox.Text, out double x) &amp;&amp;
double.TryParse(YTextBox.Text, out double y) &amp;&amp;
double.TryParse(ZTextBox.Text, out double z))
{
VectorTextBox.Text = $&quot;{x},{y},{z}&quot;;
}
}
private void TextModeToggle_Click(object sender, RoutedEventArgs e)
{
IsTextMode = true;
}
private void StandardModeToggle_Click(object sender, RoutedEventArgs e)
{
IsTextMode = false;
}
private async void NormalizeButton_Click(object sender, RoutedEventArgs e)
{
if (_isUpdating || _getterFunc == null || IsReadOnly)
return;
try
{
_isUpdating = true;
var vec = _getterFunc();
if (vec != null)
{
vec.Normalize();
XTextBox.Text = vec.X.ToString(&quot;F3&quot;);
YTextBox.Text = vec.Y.ToString(&quot;F3&quot;);
ZTextBox.Text = vec.Z.ToString(&quot;F3&quot;);
if (IsTextMode)
{
VectorTextBox.Text = $&quot;{vec.X:F3},{vec.Y:F3},{vec.Z:F3}&quot;;
}
if(_updateByContentFunc != null)
await _updateByContentFunc();
}
}
catch (Exception ex)
{
_logger.LogWarning(string.Format(Application.Current.FindResource(&quot;Vec3d_Update_Error&quot;).ToString(), ex.Message));
_logger.LogError(ex, &quot;{Message}&quot;, ex.Message);
}
finally
{
_isUpdating = false;
}
}
public void UpdateUI()
{
if (_isUpdating || _getterFunc == null)
return;
try
{
_isUpdating = true;
var vec = _getterFunc();
if (vec != null)
{
XTextBox.Text = vec.X.ToString(&quot;F3&quot;);
YTextBox.Text = vec.Y.ToString(&quot;F3&quot;);
ZTextBox.Text = vec.Z.ToString(&quot;F3&quot;);
if (IsTextMode)
{
VectorTextBox.Text = $&quot;{vec.X},{vec.Y},{vec.Z}&quot;;
}
}
else
{
XTextBox.Text = &quot;&quot;;
YTextBox.Text = &quot;&quot;;
ZTextBox.Text = &quot;&quot;;
VectorTextBox.Text = &quot;&quot;;
}
}
finally
{
_isUpdating = false;
}
}
private async void XTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
await HandleTextChanged();
}
private async void YTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
await HandleTextChanged();
}
private async void ZTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
await HandleTextChanged();
}
private async void VectorTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
if (_isUpdating || _getterFunc == null || IsReadOnly)
return;
try
{
string text = VectorTextBox.Text.Trim();
// 如果文本为空或太短,不做处理
if (string.IsNullOrWhiteSpace(text) || text.Length &lt; 3)
return;
_isUpdating = true;
// 尝试解析为向量格式
if (TryParseVector(text, out double x, out double y, out double z))
{
await UpdateVectorValues(x, y, z);
}
// 尝试解析为变换矩阵格式(仅提取位移分量)
else if (TryParseTransformMatrix(text, out double tx, out double ty, out double tz))
{
await UpdateVectorValues(tx, ty, tz);
}
// 尝试作为单值解析每个字段(宽松模式)
else if (TryParseLooseVector(text, out double lx, out double ly, out double lz))
{
await UpdateVectorValues(lx, ly, lz);
}
}
catch (Exception ex)
{
_logger.LogWarning(string.Format(Application.Current.FindResource(&quot;Vec3d_Update_Error&quot;).ToString(), ex.Message));
_logger.LogError(ex, &quot;{Message}&quot;, ex.Message);
}
finally
{
_isUpdating = false;
}
}
private bool TryParseVector(string text, out double x, out double y, out double z)
{
x = y = z = 0;
// 使用正则表达式匹配向量格式
Match match = VectorRegex.Match(text);
if (match.Success &amp;&amp; match.Groups.Count &gt;= 4)
{
if (double.TryParse(match.Groups[1].Value, out x) &amp;&amp;
double.TryParse(match.Groups[2].Value, out y) &amp;&amp;
double.TryParse(match.Groups[3].Value, out z))
{
return true;
}
}
return false;
}
private bool TryParseTransformMatrix(string text, out double tx, out double ty, out double tz)
{
tx = ty = tz = 0;
// 移除所有换行和多余空格,便于匹配
text = Regex.Replace(text, @&quot;\s+&quot;, &quot; &quot;);
// 尝试匹配变换矩阵格式
Match match = MatrixRegex.Match(text);
if (match.Success &amp;&amp; match.Groups.Count &gt;= 8)
{
// 变换矩阵的第4列通常是位移分量
if (double.TryParse(match.Groups[4].Value, out tx) &amp;&amp;
double.TryParse(match.Groups[8].Value, out ty) &amp;&amp;
double.TryParse(match.Groups[12].Value, out tz))
{
return true;
}
}
// 尝试匹配更宽松的变换矩阵表示(例如从界面复制的数据)
var numbers = Regex.Matches(text, @&quot;(-?\d*\.?\d+)&quot;);
if (numbers.Count &gt;= 16)
{
// 假设这是一个4x4矩阵,提取位移分量(第4、8、12个数字)
if (double.TryParse(numbers[3].Value, out tx) &amp;&amp;
double.TryParse(numbers[7].Value, out ty) &amp;&amp;
double.TryParse(numbers[11].Value, out tz))
{
return true;
}
}
return false;
}
private bool TryParseLooseVector(string text, out double x, out double y, out double z)
{
x = y = z = 0;
// 移除所有非数字和小数点以外的字符,然后按空白分割
string cleanText = Regex.Replace(text, @&quot;[^\d\.\-\s,;]+&quot;, &quot; &quot;);
string[] parts = Regex.Split(cleanText, @&quot;[\s,;]+&quot;);
// 尝试从分割后的部分获取三个数字
var numbers = new System.Collections.Generic.List&lt;double&gt;();
foreach (var part in parts)
{
if (!string.IsNullOrWhiteSpace(part) &amp;&amp; double.TryParse(part, out double value))
{
numbers.Add(value);
if (numbers.Count &gt;= 3) break; // 最多取3个数字
}
}
// 如果获取到了三个数字,就认为解析成功
if (numbers.Count &gt;= 3)
{
x = numbers[0];
y = numbers[1];
z = numbers[2];
return true;
}
return false;
}
private async Task UpdateVectorValues(double x, double y, double z)
{
XTextBox.Text = x.ToString(&quot;F3&quot;);
YTextBox.Text = y.ToString(&quot;F3&quot;);
ZTextBox.Text = z.ToString(&quot;F3&quot;);
var vec = _getterFunc?.Invoke();
if (vec != null)
{
vec.X = x;
vec.Y = y;
vec.Z = z;
if (_updateByContentFunc != null)
{
await _updateByContentFunc();
}
}
}
private async Task HandleTextChanged()
{
if (_isUpdating || _getterFunc == null || IsReadOnly)
return;
try
{
_isUpdating = true;
// 尝试解析每个文本框的值
bool allValid = true;
allValid &amp;= double.TryParse(XTextBox.Text, out double x);
allValid &amp;= double.TryParse(YTextBox.Text, out double y);
allValid &amp;= double.TryParse(ZTextBox.Text, out double z);
if (allValid)
{
if (IsTextMode)
{
VectorTextBox.Text = $&quot;{x},{y},{z}&quot;;
}
var vec = _getterFunc();
if (vec != null)
{
vec.X = x;
vec.Y = y;
vec.Z = z;
if(_updateByContentFunc != null)
await _updateByContentFunc();
}
}
else
{
// 如果有无效输入,不进行更新但也不显示错误
// 这允许用户在输入过程中有不完整的状态
}
}
catch (Exception ex)
{
// 记录异常但不中断用户操作
_logger.LogWarning(string.Format(Application.Current.FindResource(&quot;Vec3d_Update_Error&quot;).ToString(), ex.Message));
_logger.LogError(ex, &quot;{Message}&quot;, ex.Message);
}
finally
{
_isUpdating = false;
}
}
}
}
</code></pre><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>
<ul>
<li><code>wwwroot-src/src/components/widgets/Vec3Input.vue</code> — the widget itself: mode toggle, per-axis and
single-field inputs, normalize button, and the local format and parse helpers</li>
<li><code>wwwroot-src/src/api/geometry.ts</code> — the <code>/api/Vec3d</code> client the transformer editors use, and the <code>Vec3dDto</code> shape</li>
<li><code>Widget/Vec3dController.cs</code> — the REST surface behind <code>/api/Vec3d</code>: New, NewWithValue, Get, Update,
UpdateAt, ParseAndUpdate and Normalize. The SPA reaches Update.</li>
<li><code>wwwroot-src/src/components/topo/StaticRotationEditor.vue</code> — the full binding contract in one
place: <code>v-model</code>, <code>@update:model-value</code>, the normalize opt-in and its <code>@normalize</code> handler</li>
<li><code>wwwroot-src/src/components/topo/StaticTranslationEditor.vue</code> — one instance, no normalize button</li>
<li><code>wwwroot-src/src/components/topo/DynamicRotationEditor.vue</code> — axis with normalize, plus pivot</li>
<li><code>wwwroot-src/src/components/topo/DynamicTranslationEditor.vue</code> — axis with normalize</li>
<li><code>wwwroot-src/src/components/geom/Box3dEditor.vue</code> — four instances (min, max, dimension, center)
whose <code>readonly</code> state follows the current edit mode, persisted through the Box3d endpoint</li>
<li><code>wwwroot-src/src/components/geom/StlFileEditor.vue</code> — four display-only instances for the STL
bounding-box pad</li>
<li><code>wwwroot-src/src/i18n/en/widgets.ts</code> — the tooltip strings under <code>widgets.vecInput</code></li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="mat4d-control.html">Mat4dControl Component</a> — the sibling numeric-geometry editor, and the one a reader usually wants next</li>
<li><a class="xref" href="../conventions/numeric-io-utilities.html">Numeric Input/Output</a> — why NaN and the infinities are text on the wire, and how the three inputs differ</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>