@things-factory/figure-ui 10.1.210 → 10.1.211

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,12 +5,14 @@ import { live } from 'lit/directives/live.js';
5
5
  import { i18next, localize } from '@operato/i18n';
6
6
  import { SidePanelStyles } from '@things-factory/figure-viewer/dist-client/side-panel-styles.js';
7
7
  import { AXES, followOf, followableAxes, measureNow } from './links.js';
8
+ import { scrubLabel } from './live.js';
8
9
  import { measureFactor, sameSizeSource, sizeInputOf, sizeRelationOf, sizeRelationSteps, sourceMeasureOf, suggestedSizeSource } from './size-response.js';
9
10
  /**
10
11
  * A part's measures and what each follows, one row an axis (ADR-0159 3). Made to follow, a measure takes what the part
11
12
  * hangs from, along the axis its own axis lies on as it stands -- the author turns it on, nothing more. Another source
12
13
  * is there when asked for, or when there is no such axis (the part lies at an angle) or the measure already follows
13
- * something else; each way of following says what it will do, in millimetres.
14
+ * something else; each way of following says what it will do, in millimetres. A measure of the part's own is a value in
15
+ * hand as on any field (user 2026-09-24): drawn while it is typed or its label is dragged, written once on the release.
14
16
  */
15
17
  let FigureSizeEditor = class FigureSizeEditor extends localize(i18next)(LitElement) {
16
18
  constructor() {
@@ -39,6 +41,9 @@ let FigureSizeEditor = class FigureSizeEditor extends localize(i18next)(LitEleme
39
41
  .result { margin: 0 0 6px 30px; }
40
42
  [role=alert] { color: var(--md-sys-color-error); }
41
43
  `]; }
44
+ tell(name, detail) {
45
+ this.dispatchEvent(new CustomEvent(name, { detail, bubbles: true, composed: true }));
46
+ }
42
47
  apply(steps) {
43
48
  if (this.locked)
44
49
  return;
@@ -115,6 +120,42 @@ let FigureSizeEditor = class FigureSizeEditor extends localize(i18next)(LitEleme
115
120
  const choosing = relation.variable && (this.choosing.has(key) || !sameSizeSource(source, suggested));
116
121
  const selectedSource = source ? (source.of === 'instance' ? '@figure' : source.part) : '@custom';
117
122
  const sourceAxes = source?.of === 'part' ? followableAxes(asset, source.part) : AXES;
123
+ const factor = measureFactor(asset, this.partId, axis);
124
+ /* Its own measure, no rule over it: in hand while typed or scrubbed, as the input it is (a diameter, its radius). */
125
+ const own = !relation.variable && !!input && !this.locked;
126
+ const typingSize = (e) => {
127
+ const value = e.target.valueAsNumber;
128
+ if (own && Number.isFinite(value) && value > 0)
129
+ this.tell('size-typing', { input, value: value / factor });
130
+ };
131
+ const scrubSize = (down) => {
132
+ if (!own || mm === undefined)
133
+ return;
134
+ const field = down.currentTarget.closest('.fields')?.querySelector('input[type=number]');
135
+ if (!field)
136
+ return;
137
+ scrubLabel(down, { value: mm, unit: 'mm' }, {
138
+ move: value => {
139
+ field.value = String(value);
140
+ if (value > 0)
141
+ this.tell('size-typing', { input, value: value / factor });
142
+ },
143
+ release: value => {
144
+ if (!(value > 0)) {
145
+ field.value = String(mm);
146
+ this.tell('size-let-go');
147
+ return;
148
+ }
149
+ this.error = '';
150
+ this.tell('size-input', { input, value: value / factor, part: this.partId });
151
+ this.requestUpdate();
152
+ },
153
+ cancel: () => {
154
+ field.value = String(mm);
155
+ this.tell('size-let-go');
156
+ }
157
+ });
158
+ };
118
159
  const changeSize = (e) => {
119
160
  const value = e.target.valueAsNumber;
120
161
  if (!(Number.isFinite(value) && value > 0)) {
@@ -126,7 +167,7 @@ let FigureSizeEditor = class FigureSizeEditor extends localize(i18next)(LitEleme
126
167
  this.configure(axis, source, mode, value);
127
168
  else if (!relation.variable && input) {
128
169
  this.error = '';
129
- this.dispatchEvent(new CustomEvent('size-input', { detail: { input, value: value / measureFactor(asset, this.partId, axis), part: this.partId }, bubbles: true, composed: true }));
170
+ this.tell('size-input', { input, value: value / factor, part: this.partId });
130
171
  this.requestUpdate();
131
172
  }
132
173
  };
@@ -147,9 +188,9 @@ let FigureSizeEditor = class FigureSizeEditor extends localize(i18next)(LitEleme
147
188
  };
148
189
  return html `<div class="dimension" data-axis=${axis}>
149
190
  <div class="fields">
150
- <span>${label}</span>
191
+ <span ?scrub=${own} @pointerdown=${scrubSize}>${label}</span>
151
192
  <input type="number" step="any" min="0" aria-label=${`${this.partId} ${label} ${i18next.t('figure.label.size-measure')}`} .value=${live(mm === undefined ? '' : String(Number(mm.toFixed(6))))}
152
- ?disabled=${this.locked} ?readonly=${relation.variable ? mode === 'custom' || uniform : !input} @change=${changeSize}>
193
+ ?disabled=${this.locked} ?readonly=${relation.variable ? mode === 'custom' || uniform : !input} @input=${typingSize} @change=${changeSize}>
153
194
  <label class="variable"><input type="checkbox" aria-label=${`${this.partId} ${label} ${i18next.t('figure.label.size-follows')}`} .checked=${live(relation.variable || this.choosing.has(key))} ?disabled=${this.locked || uniform}
154
195
  @change=${follows}></label>
155
196
  ${choosing || (this.choosing.has(key) && !relation.variable)
@@ -1 +1 @@
1
- {"version":3,"file":"figure-size-editor.js","sourceRoot":"","sources":["../../client/modeller/figure-size-editor.ts"],"names":[],"mappings":"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","sourcesContent":["import { LitElement, html, css, nothing } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { live } from 'lit/directives/live.js'\nimport { i18next, localize } from '@operato/i18n'\nimport type { FigureAuthoringAction } from '@hatiolab/figure-model'\nimport { SidePanelStyles } from '@things-factory/figure-viewer/dist-client/side-panel-styles.js'\nimport type { FigureRevision } from './revision.js'\nimport { AXES, followOf, followableAxes, measureNow, type Axis } from './links.js'\nimport {\n measureFactor,\n sameSizeSource,\n sizeInputOf,\n sizeRelationOf,\n sizeRelationSteps,\n sourceMeasureOf,\n suggestedSizeSource,\n type SizeMode,\n type SizeSource\n} from './size-response.js'\n\n/**\n * A part's measures and what each follows, one row an axis (ADR-0159 3). Made to follow, a measure takes what the part\n * hangs from, along the axis its own axis lies on as it stands -- the author turns it on, nothing more. Another source\n * is there when asked for, or when there is no such axis (the part lies at an angle) or the measure already follows\n * something else; each way of following says what it will do, in millimetres.\n */\n@customElement('figure-size-editor')\nexport class FigureSizeEditor extends localize(i18next)(LitElement) {\n @property({ attribute: false }) revision?: FigureRevision\n @property() partId = ''\n @property({ type: Boolean }) locked = false\n @state() private error = ''\n /* Rows whose source the author asked to choose: `<part>:<axis>`. */\n @state() private choosing = new Set<string>()\n private equivalentModes = new Map<string, 'ratio' | 'difference'>()\n static styles = [SidePanelStyles, css`\n :host { display: block; min-width: 0; }\n .heading, .fields {\n display: grid;\n grid-template-columns: 24px minmax(55px, .85fr) 32px minmax(65px, 1fr) 42px;\n gap: 6px;\n align-items: center;\n }\n .heading { color: var(--md-sys-color-on-surface-variant); padding-bottom: 6px; }\n .dimension { padding: 3px 0; }\n .variable { display: flex; align-items: center; justify-content: center; min-height: var(--figure-control-height); }\n .source { grid-column: 4 / -1; display: flex; align-items: center; gap: 6px; min-width: 0; }\n .source span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n .modes { margin: 4px 0 2px 30px; }\n .result { margin: 0 0 6px 30px; }\n [role=alert] { color: var(--md-sys-color-error); }\n `]\n private apply(steps: FigureAuthoringAction[]) {\n if (this.locked) return\n this.error = ''\n this.dispatchEvent(new CustomEvent('size-recipe', { detail: { steps }, bubbles: true, composed: true }))\n this.requestUpdate()\n }\n private configure(axis: Axis, source: SizeSource, mode: 'ratio' | 'difference', mm?: number) {\n if (this.locked || this.revision?.asset.sizing === 'uniform') return\n try {\n this.apply(sizeRelationSteps(this.revision!, this.partId, axis, source, mode, mm))\n this.equivalentModes.set(`${this.partId}:${axis}`, mode)\n } catch (e) {\n this.error = i18next.t((e as Error).message === 'SIZE_VALUE' ? 'figure.text.size-invalid' : 'figure.text.size-unavailable-source')\n this.requestUpdate()\n }\n }\n private choose(key: string, on: boolean) {\n const next = new Set(this.choosing)\n if (on) next.add(key)\n else next.delete(key)\n this.choosing = next\n }\n /** What a source is called on the panel: the figure, or the part, with the axis it is measured along. */\n private sourceWords(source: SizeSource) {\n return `${source.of === 'instance' ? i18next.t('figure.label.size-figure') : source.part} ${source.axis.toUpperCase()}`\n }\n /** What following this way does, in millimetres: the share it is of its source, or the difference it keeps. */\n private resultWords(revision: FigureRevision, source: SizeSource, mode: 'ratio' | 'difference', mm: number | undefined) {\n const theirs = sourceMeasureOf(revision, source)\n if (mm === undefined || theirs === undefined || !(theirs > 0)) return nothing\n const words = { source: this.sourceWords(source), size: Number(theirs.toFixed(1)) }\n if (mode === 'ratio') return i18next.t('figure.text.size-ratio-result', { ...words, pct: Number(((mm / theirs) * 100).toFixed(1)) })\n const gap = Number((theirs - mm).toFixed(1))\n if (Math.abs(gap) < 0.05) return i18next.t('figure.text.size-difference-same', words)\n return i18next.t(gap > 0 ? 'figure.text.size-difference-less' : 'figure.text.size-difference-more', { ...words, gap: Math.abs(gap) })\n }\n render() {\n const revision = this.revision\n if (!revision) return nothing\n const asset = revision.asset\n const axes = followableAxes(asset, this.partId)\n const targets = asset.appearance.map(a => a.target).filter(p => p !== this.partId && followableAxes(asset, p).length)\n const uniform = asset.sizing === 'uniform'\n return html`\n <div class=\"heading\" aria-hidden=\"true\">\n <span>${i18next.t('figure.label.size-axis')}</span><span>${i18next.t('figure.label.size-measure')}</span><span>${i18next.t('figure.label.size-follows')}</span><span>${i18next.t('figure.label.size-source')}</span><span></span>\n </div>\n ${axes.map(axis => {\n const key = `${this.partId}:${axis}`\n const mm = measureNow(revision, this.partId, axis)\n const relation = sizeRelationOf(revision, this.partId, axis)\n const f = followOf(asset, this.partId, axis)\n const source = relation.source\n const suggested = suggestedSizeSource(asset, this.partId, axis)\n let mode: SizeMode = relation.mode\n if (source && (f.of === 'instance' || f.of === 'part') && !f.plus) {\n const same = (source.of === 'part' ? measureFactor(asset, source.part, source.axis) : 1) / measureFactor(asset, this.partId, axis)\n if (Math.abs((f.times ?? 1) - same) < 1e-6) mode = this.equivalentModes.get(key) ?? mode\n }\n const currentMode = mode === 'custom' ? 'ratio' : mode\n const input = sizeInputOf(asset, this.partId, axis)\n const label = `${axis.toUpperCase()}${measureFactor(asset, this.partId, axis) === 2 ? ' · ' + i18next.t('figure.label.size-diameter') : ''}`\n /* The selects show when asked for, or when what it follows is not what its fastening gives -- or nothing is. */\n const choosing = relation.variable && (this.choosing.has(key) || !sameSizeSource(source, suggested))\n const selectedSource = source ? (source.of === 'instance' ? '@figure' : source.part) : '@custom'\n const sourceAxes = source?.of === 'part' ? followableAxes(asset, source.part) : AXES\n const changeSize = (e: Event) => {\n const value = (e.target as HTMLInputElement).valueAsNumber\n if (!(Number.isFinite(value) && value > 0)) {\n this.error = i18next.t('figure.text.size-invalid')\n this.requestUpdate()\n return\n }\n if (relation.variable && source && mode !== 'custom') this.configure(axis, source, mode, value)\n else if (!relation.variable && input) {\n this.error = ''\n this.dispatchEvent(new CustomEvent('size-input', { detail: { input, value: value / measureFactor(asset, this.partId, axis), part: this.partId }, bubbles: true, composed: true }))\n this.requestUpdate()\n }\n }\n const follows = (e: Event) => {\n if ((e.target as HTMLInputElement).checked) {\n if (suggested) this.configure(axis, suggested, 'ratio')\n else {\n /* At an angle to what it hangs from: which of its measures it follows is the author's to pick. */\n this.choose(key, true)\n this.error = i18next.t('figure.text.size-unpaired')\n }\n } else if (mm !== undefined) {\n this.choose(key, false)\n this.apply([{ kind: 'unlink-dimension', part: this.partId, axis, mm: mm / measureFactor(asset, this.partId, axis) }])\n }\n }\n return html`<div class=\"dimension\" data-axis=${axis}>\n <div class=\"fields\">\n <span>${label}</span>\n <input type=\"number\" step=\"any\" min=\"0\" aria-label=${`${this.partId} ${label} ${i18next.t('figure.label.size-measure')}`} .value=${live(mm === undefined ? '' : String(Number(mm.toFixed(6))))}\n ?disabled=${this.locked} ?readonly=${relation.variable ? mode === 'custom' || uniform : !input} @change=${changeSize}>\n <label class=\"variable\"><input type=\"checkbox\" aria-label=${`${this.partId} ${label} ${i18next.t('figure.label.size-follows')}`} .checked=${live(relation.variable || this.choosing.has(key))} ?disabled=${this.locked || uniform}\n @change=${follows}></label>\n ${choosing || (this.choosing.has(key) && !relation.variable)\n ? html`\n <select aria-label=${`${this.partId} ${label} ${i18next.t('figure.label.size-source')}`} .value=${live(selectedSource)} ?disabled=${this.locked || uniform}\n @change=${(e: Event) => {\n const target = (e.target as HTMLSelectElement).value\n const possible = target === '@figure' ? AXES : followableAxes(asset, target)\n const targetAxis = possible.includes(source?.axis ?? axis) ? (source?.axis ?? axis) : possible[0]!\n this.configure(axis, target === '@figure' ? { of: 'instance', axis: targetAxis } : { of: 'part', part: target, axis: targetAxis }, currentMode)\n }}>\n ${!source ? html`<option value=\"@custom\" disabled>${i18next.t(relation.variable ? 'figure.label.size-existing-rule' : 'figure.label.size-pick-source')}</option>` : nothing}\n <option value=\"@figure\" ?selected=${selectedSource === '@figure'}>${i18next.t('figure.label.size-figure')}</option>\n ${targets.map(p => html`<option value=${p} ?selected=${selectedSource === p}>${p}</option>`)}\n </select>\n <select aria-label=${`${this.partId} ${label} ${i18next.t('figure.label.size-source-axis')}`} .value=${live(source?.axis ?? axis)} ?disabled=${this.locked || uniform || !source}\n @change=${(e: Event) => source && this.configure(axis, { ...source, axis: (e.target as HTMLSelectElement).value as Axis }, currentMode)}>\n ${sourceAxes.map(a => html`<option value=${a} ?selected=${a === (source?.axis ?? axis)}>${a.toUpperCase()}</option>`)}\n </select>`\n : relation.variable && source\n ? html`<div class=\"source\">\n <span>${this.sourceWords(source)}</span>\n <button quiet ?disabled=${this.locked || uniform} @click=${() => this.choose(key, true)}>${i18next.t('figure.button.size-other-source')}</button>\n </div>`\n : html`<span></span><span></span>`}\n </div>\n ${relation.variable\n ? html`<div class=\"modes\" choice-row>\n ${(['ratio', 'difference'] as const).map(\n m => html`<label toggle><input type=\"radio\" name=${`mode-${this.partId}-${axis}`} .checked=${live(mode === m)} ?disabled=${this.locked || uniform || !source}\n @change=${() => source && this.configure(axis, source, m)}>${i18next.t(`figure.label.size-${m}`)}</label>`\n )}\n </div>\n ${mode === 'custom'\n ? html`<p hint>${i18next.t('figure.text.size-custom-rule')}</p>`\n : source\n ? html`<p hint class=\"result\">${this.resultWords(revision, source, currentMode, mm)}</p>`\n : nothing}`\n : nothing}\n </div>`\n })}\n ${uniform ? html`<p hint>${i18next.t('figure.text.size-uniform')}</p>` : nothing}\n ${this.error ? html`<p hint role=\"alert\">${this.error}</p>` : nothing}\n `\n }\n}\n"]}
1
+ {"version":3,"file":"figure-size-editor.js","sourceRoot":"","sources":["../../client/modeller/figure-size-editor.ts"],"names":[],"mappings":"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","sourcesContent":["import { LitElement, html, css, nothing } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { live } from 'lit/directives/live.js'\nimport { i18next, localize } from '@operato/i18n'\nimport type { FigureAuthoringAction } from '@hatiolab/figure-model'\nimport { SidePanelStyles } from '@things-factory/figure-viewer/dist-client/side-panel-styles.js'\nimport type { FigureRevision } from './revision.js'\nimport { AXES, followOf, followableAxes, measureNow, type Axis } from './links.js'\nimport { scrubLabel } from './live.js'\nimport {\n measureFactor,\n sameSizeSource,\n sizeInputOf,\n sizeRelationOf,\n sizeRelationSteps,\n sourceMeasureOf,\n suggestedSizeSource,\n type SizeMode,\n type SizeSource\n} from './size-response.js'\n\n/**\n * A part's measures and what each follows, one row an axis (ADR-0159 3). Made to follow, a measure takes what the part\n * hangs from, along the axis its own axis lies on as it stands -- the author turns it on, nothing more. Another source\n * is there when asked for, or when there is no such axis (the part lies at an angle) or the measure already follows\n * something else; each way of following says what it will do, in millimetres. A measure of the part's own is a value in\n * hand as on any field (user 2026-09-24): drawn while it is typed or its label is dragged, written once on the release.\n */\n@customElement('figure-size-editor')\nexport class FigureSizeEditor extends localize(i18next)(LitElement) {\n @property({ attribute: false }) revision?: FigureRevision\n @property() partId = ''\n @property({ type: Boolean }) locked = false\n @state() private error = ''\n /* Rows whose source the author asked to choose: `<part>:<axis>`. */\n @state() private choosing = new Set<string>()\n private equivalentModes = new Map<string, 'ratio' | 'difference'>()\n static styles = [SidePanelStyles, css`\n :host { display: block; min-width: 0; }\n .heading, .fields {\n display: grid;\n grid-template-columns: 24px minmax(55px, .85fr) 32px minmax(65px, 1fr) 42px;\n gap: 6px;\n align-items: center;\n }\n .heading { color: var(--md-sys-color-on-surface-variant); padding-bottom: 6px; }\n .dimension { padding: 3px 0; }\n .variable { display: flex; align-items: center; justify-content: center; min-height: var(--figure-control-height); }\n .source { grid-column: 4 / -1; display: flex; align-items: center; gap: 6px; min-width: 0; }\n .source span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n .modes { margin: 4px 0 2px 30px; }\n .result { margin: 0 0 6px 30px; }\n [role=alert] { color: var(--md-sys-color-error); }\n `]\n private tell(name: 'size-input' | 'size-typing' | 'size-let-go', detail?: object) {\n this.dispatchEvent(new CustomEvent(name, { detail, bubbles: true, composed: true }))\n }\n private apply(steps: FigureAuthoringAction[]) {\n if (this.locked) return\n this.error = ''\n this.dispatchEvent(new CustomEvent('size-recipe', { detail: { steps }, bubbles: true, composed: true }))\n this.requestUpdate()\n }\n private configure(axis: Axis, source: SizeSource, mode: 'ratio' | 'difference', mm?: number) {\n if (this.locked || this.revision?.asset.sizing === 'uniform') return\n try {\n this.apply(sizeRelationSteps(this.revision!, this.partId, axis, source, mode, mm))\n this.equivalentModes.set(`${this.partId}:${axis}`, mode)\n } catch (e) {\n this.error = i18next.t((e as Error).message === 'SIZE_VALUE' ? 'figure.text.size-invalid' : 'figure.text.size-unavailable-source')\n this.requestUpdate()\n }\n }\n private choose(key: string, on: boolean) {\n const next = new Set(this.choosing)\n if (on) next.add(key)\n else next.delete(key)\n this.choosing = next\n }\n /** What a source is called on the panel: the figure, or the part, with the axis it is measured along. */\n private sourceWords(source: SizeSource) {\n return `${source.of === 'instance' ? i18next.t('figure.label.size-figure') : source.part} ${source.axis.toUpperCase()}`\n }\n /** What following this way does, in millimetres: the share it is of its source, or the difference it keeps. */\n private resultWords(revision: FigureRevision, source: SizeSource, mode: 'ratio' | 'difference', mm: number | undefined) {\n const theirs = sourceMeasureOf(revision, source)\n if (mm === undefined || theirs === undefined || !(theirs > 0)) return nothing\n const words = { source: this.sourceWords(source), size: Number(theirs.toFixed(1)) }\n if (mode === 'ratio') return i18next.t('figure.text.size-ratio-result', { ...words, pct: Number(((mm / theirs) * 100).toFixed(1)) })\n const gap = Number((theirs - mm).toFixed(1))\n if (Math.abs(gap) < 0.05) return i18next.t('figure.text.size-difference-same', words)\n return i18next.t(gap > 0 ? 'figure.text.size-difference-less' : 'figure.text.size-difference-more', { ...words, gap: Math.abs(gap) })\n }\n render() {\n const revision = this.revision\n if (!revision) return nothing\n const asset = revision.asset\n const axes = followableAxes(asset, this.partId)\n const targets = asset.appearance.map(a => a.target).filter(p => p !== this.partId && followableAxes(asset, p).length)\n const uniform = asset.sizing === 'uniform'\n return html`\n <div class=\"heading\" aria-hidden=\"true\">\n <span>${i18next.t('figure.label.size-axis')}</span><span>${i18next.t('figure.label.size-measure')}</span><span>${i18next.t('figure.label.size-follows')}</span><span>${i18next.t('figure.label.size-source')}</span><span></span>\n </div>\n ${axes.map(axis => {\n const key = `${this.partId}:${axis}`\n const mm = measureNow(revision, this.partId, axis)\n const relation = sizeRelationOf(revision, this.partId, axis)\n const f = followOf(asset, this.partId, axis)\n const source = relation.source\n const suggested = suggestedSizeSource(asset, this.partId, axis)\n let mode: SizeMode = relation.mode\n if (source && (f.of === 'instance' || f.of === 'part') && !f.plus) {\n const same = (source.of === 'part' ? measureFactor(asset, source.part, source.axis) : 1) / measureFactor(asset, this.partId, axis)\n if (Math.abs((f.times ?? 1) - same) < 1e-6) mode = this.equivalentModes.get(key) ?? mode\n }\n const currentMode = mode === 'custom' ? 'ratio' : mode\n const input = sizeInputOf(asset, this.partId, axis)\n const label = `${axis.toUpperCase()}${measureFactor(asset, this.partId, axis) === 2 ? ' · ' + i18next.t('figure.label.size-diameter') : ''}`\n /* The selects show when asked for, or when what it follows is not what its fastening gives -- or nothing is. */\n const choosing = relation.variable && (this.choosing.has(key) || !sameSizeSource(source, suggested))\n const selectedSource = source ? (source.of === 'instance' ? '@figure' : source.part) : '@custom'\n const sourceAxes = source?.of === 'part' ? followableAxes(asset, source.part) : AXES\n const factor = measureFactor(asset, this.partId, axis)\n /* Its own measure, no rule over it: in hand while typed or scrubbed, as the input it is (a diameter, its radius). */\n const own = !relation.variable && !!input && !this.locked\n const typingSize = (e: Event) => {\n const value = (e.target as HTMLInputElement).valueAsNumber\n if (own && Number.isFinite(value) && value > 0) this.tell('size-typing', { input, value: value / factor })\n }\n const scrubSize = (down: PointerEvent) => {\n if (!own || mm === undefined) return\n const field = (down.currentTarget as HTMLElement).closest('.fields')?.querySelector<HTMLInputElement>('input[type=number]')\n if (!field) return\n scrubLabel(down, { value: mm, unit: 'mm' }, {\n move: value => {\n field.value = String(value)\n if (value > 0) this.tell('size-typing', { input, value: value / factor })\n },\n release: value => {\n if (!(value > 0)) {\n field.value = String(mm)\n this.tell('size-let-go')\n return\n }\n this.error = ''\n this.tell('size-input', { input, value: value / factor, part: this.partId })\n this.requestUpdate()\n },\n cancel: () => {\n field.value = String(mm)\n this.tell('size-let-go')\n }\n })\n }\n const changeSize = (e: Event) => {\n const value = (e.target as HTMLInputElement).valueAsNumber\n if (!(Number.isFinite(value) && value > 0)) {\n this.error = i18next.t('figure.text.size-invalid')\n this.requestUpdate()\n return\n }\n if (relation.variable && source && mode !== 'custom') this.configure(axis, source, mode, value)\n else if (!relation.variable && input) {\n this.error = ''\n this.tell('size-input', { input, value: value / factor, part: this.partId })\n this.requestUpdate()\n }\n }\n const follows = (e: Event) => {\n if ((e.target as HTMLInputElement).checked) {\n if (suggested) this.configure(axis, suggested, 'ratio')\n else {\n /* At an angle to what it hangs from: which of its measures it follows is the author's to pick. */\n this.choose(key, true)\n this.error = i18next.t('figure.text.size-unpaired')\n }\n } else if (mm !== undefined) {\n this.choose(key, false)\n this.apply([{ kind: 'unlink-dimension', part: this.partId, axis, mm: mm / measureFactor(asset, this.partId, axis) }])\n }\n }\n return html`<div class=\"dimension\" data-axis=${axis}>\n <div class=\"fields\">\n <span ?scrub=${own} @pointerdown=${scrubSize}>${label}</span>\n <input type=\"number\" step=\"any\" min=\"0\" aria-label=${`${this.partId} ${label} ${i18next.t('figure.label.size-measure')}`} .value=${live(mm === undefined ? '' : String(Number(mm.toFixed(6))))}\n ?disabled=${this.locked} ?readonly=${relation.variable ? mode === 'custom' || uniform : !input} @input=${typingSize} @change=${changeSize}>\n <label class=\"variable\"><input type=\"checkbox\" aria-label=${`${this.partId} ${label} ${i18next.t('figure.label.size-follows')}`} .checked=${live(relation.variable || this.choosing.has(key))} ?disabled=${this.locked || uniform}\n @change=${follows}></label>\n ${choosing || (this.choosing.has(key) && !relation.variable)\n ? html`\n <select aria-label=${`${this.partId} ${label} ${i18next.t('figure.label.size-source')}`} .value=${live(selectedSource)} ?disabled=${this.locked || uniform}\n @change=${(e: Event) => {\n const target = (e.target as HTMLSelectElement).value\n const possible = target === '@figure' ? AXES : followableAxes(asset, target)\n const targetAxis = possible.includes(source?.axis ?? axis) ? (source?.axis ?? axis) : possible[0]!\n this.configure(axis, target === '@figure' ? { of: 'instance', axis: targetAxis } : { of: 'part', part: target, axis: targetAxis }, currentMode)\n }}>\n ${!source ? html`<option value=\"@custom\" disabled>${i18next.t(relation.variable ? 'figure.label.size-existing-rule' : 'figure.label.size-pick-source')}</option>` : nothing}\n <option value=\"@figure\" ?selected=${selectedSource === '@figure'}>${i18next.t('figure.label.size-figure')}</option>\n ${targets.map(p => html`<option value=${p} ?selected=${selectedSource === p}>${p}</option>`)}\n </select>\n <select aria-label=${`${this.partId} ${label} ${i18next.t('figure.label.size-source-axis')}`} .value=${live(source?.axis ?? axis)} ?disabled=${this.locked || uniform || !source}\n @change=${(e: Event) => source && this.configure(axis, { ...source, axis: (e.target as HTMLSelectElement).value as Axis }, currentMode)}>\n ${sourceAxes.map(a => html`<option value=${a} ?selected=${a === (source?.axis ?? axis)}>${a.toUpperCase()}</option>`)}\n </select>`\n : relation.variable && source\n ? html`<div class=\"source\">\n <span>${this.sourceWords(source)}</span>\n <button quiet ?disabled=${this.locked || uniform} @click=${() => this.choose(key, true)}>${i18next.t('figure.button.size-other-source')}</button>\n </div>`\n : html`<span></span><span></span>`}\n </div>\n ${relation.variable\n ? html`<div class=\"modes\" choice-row>\n ${(['ratio', 'difference'] as const).map(\n m => html`<label toggle><input type=\"radio\" name=${`mode-${this.partId}-${axis}`} .checked=${live(mode === m)} ?disabled=${this.locked || uniform || !source}\n @change=${() => source && this.configure(axis, source, m)}>${i18next.t(`figure.label.size-${m}`)}</label>`\n )}\n </div>\n ${mode === 'custom'\n ? html`<p hint>${i18next.t('figure.text.size-custom-rule')}</p>`\n : source\n ? html`<p hint class=\"result\">${this.resultWords(revision, source, currentMode, mm)}</p>`\n : nothing}`\n : nothing}\n </div>`\n })}\n ${uniform ? html`<p hint>${i18next.t('figure.text.size-uniform')}</p>` : nothing}\n ${this.error ? html`<p hint role=\"alert\">${this.error}</p>` : nothing}\n `\n }\n}\n"]}
@@ -278,7 +278,7 @@ export interface Motion {
278
278
  plus?: number;
279
279
  };
280
280
  }
281
- export declare function motionOf(asset: FigureAsset, part: string): Motion | undefined;
281
+ export declare function motionOf(asset: FigureAsset, part: string, k?: number): Motion | undefined;
282
282
  /** What the motion form holds: how the part moves, and either its own control or the joint it moves with. */
283
283
  export interface MotionForm {
284
284
  kind: 'slide' | 'turn' | 'continuous';
@@ -315,14 +315,15 @@ export interface MotionForm {
315
315
  * The motion form filled from a joint as it is, so a motion can be changed where it is -- its range, the length it
316
316
  * travels by, what it follows -- rather than taken away and made again. Taking it away is refused while another joint
317
317
  * follows it, which left a leader's stroke fixed for good (the telescopic conveyor, 2026-09-25). The form is the
318
- * joint's first axis (ADR-0159); the axes after it are kept as they are when it is written (`motionJoint`).
318
+ * joint's axis `k` (ADR-0159), the first unless said; the other axes are kept as they are when it is written
319
+ * (`motionJoint`). An axis past the last is a new one: a turn about the first of X, Y, Z the joint's axes do not use.
319
320
  */
320
- export declare function motionFormOf(joint: FigureJoint): MotionForm;
321
+ export declare function motionFormOf(joint: FigureJoint, k?: number): MotionForm;
321
322
  /**
322
323
  * The axes a part's joint can move with (URDF's mimic): one of another part's that moves the same way — a slide follows
323
324
  * a slide, a turn a turn — and has a control of its own, since a follower follows the axis that is driven (ADR-0159).
324
325
  */
325
- export declare function leadersFor(asset: FigureAsset, part: string, kind: MotionForm['kind']): Array<FigureNamedMotion & {
326
+ export declare function leadersFor(asset: FigureAsset, part: string, kind: MotionForm['kind'], self?: string): Array<FigureNamedMotion & {
326
327
  part: string;
327
328
  }>;
328
329
  /**
@@ -330,7 +331,15 @@ export declare function leadersFor(asset: FigureAsset, part: string, kind: Motio
330
331
  * a turn without end, as in USD) and a control; a follower gets the leader, the multiplier and the offset, and no
331
332
  * control or limits of its own — its range is the leader's times the multiplier.
332
333
  */
333
- export declare function motionJoint(joint: FigureJoint, m: MotionForm, part: string): FigureJoint;
334
+ export declare function motionJoint(joint: FigureJoint, m: MotionForm, part: string, k?: number): FigureJoint;
335
+ /** The control an axis gets when the author names none: `<part>-<kind>`, and `<part>-<kind>-<n>` for the n-th axis. */
336
+ export declare function controlIdFor(part: string, kind: MotionForm['kind'], k: number): string;
337
+ /**
338
+ * The joint without its axis `k`. The axes after it keep the names they went by: one named by its place
339
+ * (`<joint>.<k>`, figure-model `motionIdOf`) is given that name before it moves up, so its control is not renamed under
340
+ * a board that writes to it.
341
+ */
342
+ export declare function withoutAxis(joint: FigureJoint, k: number): FigureJoint;
334
343
  /**
335
344
  * Why the motion form cannot be saved as it stands, as the kernel would refuse it: a follower's multiplier that is not
336
345
  * a number or is 0, or a range whose most is not above its least. A range need not hold 0 -- the control starts at the
@@ -1,4 +1,4 @@
1
- import { figureShapeFaces, figurePartsExtentOf, figureFollowOf, proposeFigureProportion, figureAttachmentsOf, figureBindingsOf, figureInstanceSizeRangeOf, figureFollowableAxesOf, figureShapeProviderOf, figureMimicTermsOf, figureFastenKeptPlaceOf, figureFastenAxesOf, figureJointAsFixed, figureJointWithMotion, figureSurfaceOf, figureAxesOf, figureMotionsOf } from '@hatiolab/figure-model';
1
+ import { figureShapeFaces, figurePartsExtentOf, figureFollowOf, proposeFigureProportion, figureAttachmentsOf, figureBindingsOf, figureInstanceSizeRangeOf, figureFollowableAxesOf, figureShapeProviderOf, figureMimicTermsOf, figureFastenKeptPlaceOf, figureFastenAxesOf, figureJointAsFixed, figureJointWithMotion, figureSurfaceOf, figureAxesOf, figureMotionsOf, motionIdOf, motionStateIdOf } from '@hatiolab/figure-model';
2
2
  import { startingShapeKindOf } from './starting-shape.js';
3
3
  import { boxOf } from './align.js';
4
4
  import { graphValuesOf } from './revision.js';
@@ -455,17 +455,34 @@ export function positionIsShare(asset, part, axis) {
455
455
  const writer = ref ? m.nodes.find(n => Object.values(n.outputs).includes(ref)) : undefined;
456
456
  return !!writer && writer.op === 'mul@1' && writer.args[0] === `size.${axis}` && m.constants.some(c => c.id === writer.args[1]);
457
457
  }
458
- export function motionOf(asset, part) {
458
+ export function motionOf(asset, part, k = 0) {
459
459
  const m = modelOf(asset);
460
- const node = m.nodes.find(n => n.id === `${part}.motion`);
461
- if (!node)
462
- return undefined;
463
460
  const joint = asset.joints?.find(j => j.body1.part === part);
464
- /* The motion the form shows is the joint's first axis, `<part>.motion` (ADR-0159). */
465
- const record = joint && figureMotionsOf(joint)[0];
461
+ const record = joint && figureMotionsOf(joint)[k];
466
462
  /* A follower has no limits of its own: its range is its leaders', not a turn without end. */
467
463
  const endless = record?.type === 'revolute' && record.lowerLimit === undefined && record.upperLimit === undefined && !record.mimic;
468
464
  const vector = jointAxisVectorOf(record?.axis);
465
+ const one = record?.travel && 'source' in record.travel ? record.travel : undefined;
466
+ const travel = one?.source && 'part' in one.source ? { part: one.source.part, axis: one.source.axis, ...(one.plus ? { plus: one.plus } : {}) } : undefined;
467
+ const said = { ...(vector ? { vector } : {}), ...(record?.mimic ? { follows: figureMimicTermsOf(record.mimic) } : {}), ...(travel ? { travel } : {}) };
468
+ /*
469
+ An axis after the first is said as its record writes it -- the joint frame's axis, the control by its id -- the frame
470
+ the form edits it in (ADR-0159). The first is read from its node, `<part>.motion`, as before.
471
+ */
472
+ if (k > 0) {
473
+ if (!record)
474
+ return undefined;
475
+ const input = m.inputs.find(i => i.id === motionStateIdOf(record) && i.role === 'state');
476
+ return {
477
+ kind: endless ? 'continuous' : record.type === 'revolute' ? 'turn' : 'slide',
478
+ axis: jointAxisToken(record.axis) ?? '?',
479
+ ...(record.mimic || !input ? {} : { control: { id: input.id, unit: input.unit, min: input.min, max: input.max } }),
480
+ ...said
481
+ };
482
+ }
483
+ const node = m.nodes.find(n => n.id === `${part}.motion`);
484
+ if (!node)
485
+ return undefined;
469
486
  const kind = endless ? 'continuous' : node.op === 'axis-turn@1' ? 'turn' : 'slide';
470
487
  const unit = node.args.slice(0, 3).map(ref => m.constants.find(c => c.id === ref)?.value);
471
488
  const axis = AXES[unit.findIndex(v => v !== undefined && v !== 0)] ?? '?';
@@ -487,18 +504,22 @@ export function motionOf(asset, part) {
487
504
  }
488
505
  return undefined;
489
506
  };
490
- const one = record?.travel && 'source' in record.travel ? record.travel : undefined;
491
- const travel = one?.source && 'part' in one.source ? { part: one.source.part, axis: one.source.axis, ...(one.plus ? { plus: one.plus } : {}) } : undefined;
492
- return { kind, axis, ...(vector ? { vector } : {}), control: control(quantity), ...(record?.mimic ? { follows: figureMimicTermsOf(record.mimic) } : {}), ...(travel ? { travel } : {}) };
507
+ return { kind, axis, control: control(quantity), ...said };
493
508
  }
494
509
  /**
495
510
  * The motion form filled from a joint as it is, so a motion can be changed where it is -- its range, the length it
496
511
  * travels by, what it follows -- rather than taken away and made again. Taking it away is refused while another joint
497
512
  * follows it, which left a leader's stroke fixed for good (the telescopic conveyor, 2026-09-25). The form is the
498
- * joint's first axis (ADR-0159); the axes after it are kept as they are when it is written (`motionJoint`).
499
- */
500
- export function motionFormOf(joint) {
501
- const a = figureMotionsOf(joint)[0];
513
+ * joint's axis `k` (ADR-0159), the first unless said; the other axes are kept as they are when it is written
514
+ * (`motionJoint`). An axis past the last is a new one: a turn about the first of X, Y, Z the joint's axes do not use.
515
+ */
516
+ export function motionFormOf(joint, k = 0) {
517
+ const axes = figureMotionsOf(joint);
518
+ const a = axes[k];
519
+ if (!a && k > 0) {
520
+ const used = new Set(axes.map(x => jointAxisToken(x.axis)));
521
+ return { kind: 'turn', axis: AXES.find(x => !used.has(x)) ?? 'x', id: '', min: -90, max: 90, follow: '', multiplier: 1, offset: 0, travel: '', travelPlus: 0 };
522
+ }
502
523
  const kind = a?.type === 'prismatic' ? 'slide' : a?.lowerLimit === undefined && a?.upperLimit === undefined && !a?.mimic ? 'continuous' : 'turn';
503
524
  const one = a?.travel && 'source' in a.travel ? a.travel : undefined;
504
525
  const travel = one?.source && 'part' in one.source ? `${one.source.part}:${one.source.axis}` : '';
@@ -527,9 +548,10 @@ export function motionFormOf(joint) {
527
548
  * The axes a part's joint can move with (URDF's mimic): one of another part's that moves the same way — a slide follows
528
549
  * a slide, a turn a turn — and has a control of its own, since a follower follows the axis that is driven (ADR-0159).
529
550
  */
530
- export function leadersFor(asset, part, kind) {
551
+ export function leadersFor(asset, part, kind, self) {
552
+ /* Another axis of the same joint may lead (a shoulder's second axis following its first); never the axis itself. */
531
553
  return figureAxesOf(asset.joints)
532
- .filter(({ joint, motion }) => joint.body1.part !== part && !motion.mimic && (motion.type === 'prismatic') === (kind === 'slide'))
554
+ .filter(({ joint, motion }) => (self === undefined ? joint.body1.part !== part : motion.id !== self) && !motion.mimic && (motion.type === 'prismatic') === (kind === 'slide'))
533
555
  .map(({ joint, motion }) => ({ ...motion, part: joint.body1.part }));
534
556
  }
535
557
  /**
@@ -537,30 +559,46 @@ export function leadersFor(asset, part, kind) {
537
559
  * a turn without end, as in USD) and a control; a follower gets the leader, the multiplier and the offset, and no
538
560
  * control or limits of its own — its range is the leader's times the multiplier.
539
561
  */
540
- export function motionJoint(joint, m, part) {
562
+ export function motionJoint(joint, m, part, k = 0) {
541
563
  /*
542
564
  Where it is fastened -- its row and its frame's own words too -- is the kernel's to keep (`figureJointWithMotion`,
543
- ADR-0140 D6). The form writes the first axis, under the name it had; the axes after it stay as they are (ADR-0159).
565
+ ADR-0140 D6). The form writes axis `k` under the name it had, or adds it past the last; the others stay as they are
566
+ (ADR-0159).
544
567
  */
545
- const was = joint.motion?.[0];
568
+ const was = joint.motion?.[k];
546
569
  const moving = {
547
570
  ...(was?.id !== undefined ? { id: was.id } : {}),
548
571
  type: m.kind === 'slide' ? 'prismatic' : 'revolute',
549
572
  axis: (m.axisVector ?? m.axis.toUpperCase()),
550
573
  ...(was?.through ? { through: was.through } : {})
551
574
  };
552
- const given = (motion) => figureJointWithMotion(joint, [{ ...moving, ...motion }, ...(joint.motion?.slice(1) ?? [])]);
575
+ const axes = joint.motion ?? [];
576
+ const given = (motion) => figureJointWithMotion(joint, [...axes.slice(0, k), { ...moving, ...motion }, ...axes.slice(k + 1)]);
577
+ const control = m.id || controlIdFor(part, m.kind, k);
553
578
  if (m.follow)
554
579
  return given({ mimic: { motion: m.follow, multiplier: m.multiplier, ...(m.offset ? { offset: m.offset } : {}) } });
555
580
  if (m.followSum)
556
581
  return given({ mimic: { sum: m.followSum.map(t => ({ motion: t.joint, multiplier: t.multiplier })), ...(m.offset ? { offset: m.offset } : {}) } });
557
582
  if (m.kind === 'slide' && m.travelSum && !m.travel)
558
- return given({ travel: m.travelSum, state: { id: m.id || `${part}-${m.kind}` } });
583
+ return given({ travel: m.travelSum, state: { id: control } });
559
584
  if (m.kind === 'slide' && m.travel) {
560
585
  const [of, axis] = m.travel.split(':');
561
- return given({ travel: { source: { of: 'part', part: of, axis: axis }, ...(m.travelPlus ? { plus: m.travelPlus } : {}) }, state: { id: m.id || `${part}-${m.kind}` } });
586
+ return given({ travel: { source: { of: 'part', part: of, axis: axis }, ...(m.travelPlus ? { plus: m.travelPlus } : {}) }, state: { id: control } });
562
587
  }
563
- return given({ ...(m.kind === 'continuous' ? {} : { lowerLimit: m.min, upperLimit: m.max }), state: { id: m.id || `${part}-${m.kind}` } });
588
+ return given({ ...(m.kind === 'continuous' ? {} : { lowerLimit: m.min, upperLimit: m.max }), state: { id: control } });
589
+ }
590
+ /** The control an axis gets when the author names none: `<part>-<kind>`, and `<part>-<kind>-<n>` for the n-th axis. */
591
+ export function controlIdFor(part, kind, k) {
592
+ return k === 0 ? `${part}-${kind}` : `${part}-${kind}-${k + 1}`;
593
+ }
594
+ /**
595
+ * The joint without its axis `k`. The axes after it keep the names they went by: one named by its place
596
+ * (`<joint>.<k>`, figure-model `motionIdOf`) is given that name before it moves up, so its control is not renamed under
597
+ * a board that writes to it.
598
+ */
599
+ export function withoutAxis(joint, k) {
600
+ const axes = joint.motion ?? [];
601
+ return figureJointWithMotion(joint, [...axes.slice(0, k), ...axes.slice(k + 1).map((a, i) => (a.id !== undefined ? a : { ...a, id: motionIdOf(joint, k + 1 + i) }))]);
564
602
  }
565
603
  /**
566
604
  * Why the motion form cannot be saved as it stands, as the kernel would refuse it: a follower's multiplier that is not