@things-factory/figure-ui 10.1.208 → 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.
- package/client/modeller/data-interface.ts +5 -5
- package/client/modeller/editor.ts +97 -88
- package/client/modeller/fasten-geometry.ts +2 -1
- package/client/modeller/figure-links.ts +87 -157
- package/client/modeller/figure-size-editor.ts +233 -0
- package/client/modeller/joint-drag.ts +25 -21
- package/client/modeller/joint-fan.ts +4 -3
- package/client/modeller/links.ts +87 -36
- package/client/modeller/live.ts +33 -0
- package/client/modeller/motion-ends.ts +10 -6
- package/client/modeller/size-response.ts +95 -0
- package/dist-client/modeller/data-interface.d.ts +1 -1
- package/dist-client/modeller/data-interface.js +5 -5
- package/dist-client/modeller/data-interface.js.map +1 -1
- package/dist-client/modeller/editor.d.ts +14 -1
- package/dist-client/modeller/editor.js +103 -89
- package/dist-client/modeller/editor.js.map +1 -1
- package/dist-client/modeller/fasten-geometry.js +1 -1
- package/dist-client/modeller/fasten-geometry.js.map +1 -1
- package/dist-client/modeller/figure-links.d.ts +3 -4
- package/dist-client/modeller/figure-links.js +91 -145
- package/dist-client/modeller/figure-links.js.map +1 -1
- package/dist-client/modeller/figure-size-editor.d.ts +29 -0
- package/dist-client/modeller/figure-size-editor.js +262 -0
- package/dist-client/modeller/figure-size-editor.js.map +1 -0
- package/dist-client/modeller/joint-drag.d.ts +1 -1
- package/dist-client/modeller/joint-drag.js +27 -22
- package/dist-client/modeller/joint-drag.js.map +1 -1
- package/dist-client/modeller/joint-fan.js +4 -3
- package/dist-client/modeller/joint-fan.js.map +1 -1
- package/dist-client/modeller/links.d.ts +21 -9
- package/dist-client/modeller/links.js +85 -35
- package/dist-client/modeller/links.js.map +1 -1
- package/dist-client/modeller/live.d.ts +14 -0
- package/dist-client/modeller/live.js +31 -0
- package/dist-client/modeller/live.js.map +1 -1
- package/dist-client/modeller/motion-ends.d.ts +4 -1
- package/dist-client/modeller/motion-ends.js +10 -6
- package/dist-client/modeller/motion-ends.js.map +1 -1
- package/dist-client/modeller/size-response.d.ts +29 -0
- package/dist-client/modeller/size-response.js +99 -0
- package/dist-client/modeller/size-response.js.map +1 -0
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/docs/evidence/2026-10-24/current-size-panel.png +0 -0
- package/docs/evidence/2026-10-24/integrated-size.png +0 -0
- package/docs/evidence/2026-10-24/multipart-response.png +0 -0
- package/docs/evidence/2026-10-24/repeat-layout.png +0 -0
- package/docs/evidence/2026-10-24/repeat-shared-style.png +0 -0
- package/docs/evidence/2026-10-24/size-in-layout.png +0 -0
- package/docs/evidence/2026-10-24/size-shared-style.png +0 -0
- package/docs/evidence/2026-10-24/width-preview.png +0 -0
- package/docs/multipart-response-review-2026-10-24.md +77 -0
- package/docs/response-authoring-2026-10-24.md +46 -0
- package/docs/size-and-repeat-ux-2026-10-24.md +67 -0
- package/package.json +7 -7
- package/test/attachment-size-contract.test.ts +47 -0
- package/test/browser/acceptance-page.js +1 -1
- package/test/browser/detail-page.js +1 -1
- package/test/browser/detail-tabs-page.js +1 -1
- package/test/browser/editing-page.js +1 -1
- package/test/browser/editing.check.mjs +66 -14
- package/test/browser/links.check.mjs +41 -22
- package/test/browser/modeller-page.js +1 -1
- package/test/browser/modeller-revision.check.mjs +21 -7
- package/test/browser/modeller.check.mjs +106 -39
- package/test/browser/multipart-response-page.js +8 -0
- package/test/browser/parts.check.mjs +2 -1
- package/test/browser/product-page.js +1 -1
- package/test/browser/props.check.mjs +17 -14
- package/test/browser/response-page.js +12 -0
- package/test/browser/row-preview.check.mjs +3 -3
- package/test/browser/screen.check.mjs +57 -13
- package/test/browser/serve-response.mjs +24 -0
- package/test/clip-ai-proposal.test.ts +1 -1
- package/test/clip-consent.test.ts +2 -2
- package/test/command-preview.test.ts +7 -7
- package/test/joint-drag.test.ts +6 -6
- package/test/joint-frames.test.ts +33 -32
- package/test/joints.ts +1 -6
- package/test/links.test.ts +60 -20
- package/test/multipart-response-fixture.js +17 -0
- package/test/multipart-response.test.ts +37 -0
- package/test/refusals.test.ts +2 -2
- package/test/resize-with-placement.test.ts +23 -1
- package/test/size-response.test.ts +49 -0
- package/test/size-source-pairing.test.ts +66 -0
- package/translations/en.json +47 -10
- package/translations/es.json +47 -10
- package/translations/ja.json +47 -10
- package/translations/ko.json +52 -15
- package/translations/ms.json +47 -10
- package/translations/zh.json +47 -10
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import { __decorate, __metadata } from "tslib";
|
|
2
|
+
import { LitElement, html, css, nothing } from 'lit';
|
|
3
|
+
import { customElement, property, state } from 'lit/decorators.js';
|
|
4
|
+
import { live } from 'lit/directives/live.js';
|
|
5
|
+
import { i18next, localize } from '@operato/i18n';
|
|
6
|
+
import { SidePanelStyles } from '@things-factory/figure-viewer/dist-client/side-panel-styles.js';
|
|
7
|
+
import { AXES, followOf, followableAxes, measureNow } from './links.js';
|
|
8
|
+
import { scrubLabel } from './live.js';
|
|
9
|
+
import { measureFactor, sameSizeSource, sizeInputOf, sizeRelationOf, sizeRelationSteps, sourceMeasureOf, suggestedSizeSource } from './size-response.js';
|
|
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
|
|
12
|
+
* hangs from, along the axis its own axis lies on as it stands -- the author turns it on, nothing more. Another source
|
|
13
|
+
* is there when asked for, or when there is no such axis (the part lies at an angle) or the measure already follows
|
|
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.
|
|
16
|
+
*/
|
|
17
|
+
let FigureSizeEditor = class FigureSizeEditor extends localize(i18next)(LitElement) {
|
|
18
|
+
constructor() {
|
|
19
|
+
super(...arguments);
|
|
20
|
+
this.partId = '';
|
|
21
|
+
this.locked = false;
|
|
22
|
+
this.error = '';
|
|
23
|
+
/* Rows whose source the author asked to choose: `<part>:<axis>`. */
|
|
24
|
+
this.choosing = new Set();
|
|
25
|
+
this.equivalentModes = new Map();
|
|
26
|
+
}
|
|
27
|
+
static { this.styles = [SidePanelStyles, css `
|
|
28
|
+
:host { display: block; min-width: 0; }
|
|
29
|
+
.heading, .fields {
|
|
30
|
+
display: grid;
|
|
31
|
+
grid-template-columns: 24px minmax(55px, .85fr) 32px minmax(65px, 1fr) 42px;
|
|
32
|
+
gap: 6px;
|
|
33
|
+
align-items: center;
|
|
34
|
+
}
|
|
35
|
+
.heading { color: var(--md-sys-color-on-surface-variant); padding-bottom: 6px; }
|
|
36
|
+
.dimension { padding: 3px 0; }
|
|
37
|
+
.variable { display: flex; align-items: center; justify-content: center; min-height: var(--figure-control-height); }
|
|
38
|
+
.source { grid-column: 4 / -1; display: flex; align-items: center; gap: 6px; min-width: 0; }
|
|
39
|
+
.source span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
40
|
+
.modes { margin: 4px 0 2px 30px; }
|
|
41
|
+
.result { margin: 0 0 6px 30px; }
|
|
42
|
+
[role=alert] { color: var(--md-sys-color-error); }
|
|
43
|
+
`]; }
|
|
44
|
+
tell(name, detail) {
|
|
45
|
+
this.dispatchEvent(new CustomEvent(name, { detail, bubbles: true, composed: true }));
|
|
46
|
+
}
|
|
47
|
+
apply(steps) {
|
|
48
|
+
if (this.locked)
|
|
49
|
+
return;
|
|
50
|
+
this.error = '';
|
|
51
|
+
this.dispatchEvent(new CustomEvent('size-recipe', { detail: { steps }, bubbles: true, composed: true }));
|
|
52
|
+
this.requestUpdate();
|
|
53
|
+
}
|
|
54
|
+
configure(axis, source, mode, mm) {
|
|
55
|
+
if (this.locked || this.revision?.asset.sizing === 'uniform')
|
|
56
|
+
return;
|
|
57
|
+
try {
|
|
58
|
+
this.apply(sizeRelationSteps(this.revision, this.partId, axis, source, mode, mm));
|
|
59
|
+
this.equivalentModes.set(`${this.partId}:${axis}`, mode);
|
|
60
|
+
}
|
|
61
|
+
catch (e) {
|
|
62
|
+
this.error = i18next.t(e.message === 'SIZE_VALUE' ? 'figure.text.size-invalid' : 'figure.text.size-unavailable-source');
|
|
63
|
+
this.requestUpdate();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
choose(key, on) {
|
|
67
|
+
const next = new Set(this.choosing);
|
|
68
|
+
if (on)
|
|
69
|
+
next.add(key);
|
|
70
|
+
else
|
|
71
|
+
next.delete(key);
|
|
72
|
+
this.choosing = next;
|
|
73
|
+
}
|
|
74
|
+
/** What a source is called on the panel: the figure, or the part, with the axis it is measured along. */
|
|
75
|
+
sourceWords(source) {
|
|
76
|
+
return `${source.of === 'instance' ? i18next.t('figure.label.size-figure') : source.part} ${source.axis.toUpperCase()}`;
|
|
77
|
+
}
|
|
78
|
+
/** What following this way does, in millimetres: the share it is of its source, or the difference it keeps. */
|
|
79
|
+
resultWords(revision, source, mode, mm) {
|
|
80
|
+
const theirs = sourceMeasureOf(revision, source);
|
|
81
|
+
if (mm === undefined || theirs === undefined || !(theirs > 0))
|
|
82
|
+
return nothing;
|
|
83
|
+
const words = { source: this.sourceWords(source), size: Number(theirs.toFixed(1)) };
|
|
84
|
+
if (mode === 'ratio')
|
|
85
|
+
return i18next.t('figure.text.size-ratio-result', { ...words, pct: Number(((mm / theirs) * 100).toFixed(1)) });
|
|
86
|
+
const gap = Number((theirs - mm).toFixed(1));
|
|
87
|
+
if (Math.abs(gap) < 0.05)
|
|
88
|
+
return i18next.t('figure.text.size-difference-same', words);
|
|
89
|
+
return i18next.t(gap > 0 ? 'figure.text.size-difference-less' : 'figure.text.size-difference-more', { ...words, gap: Math.abs(gap) });
|
|
90
|
+
}
|
|
91
|
+
render() {
|
|
92
|
+
const revision = this.revision;
|
|
93
|
+
if (!revision)
|
|
94
|
+
return nothing;
|
|
95
|
+
const asset = revision.asset;
|
|
96
|
+
const axes = followableAxes(asset, this.partId);
|
|
97
|
+
const targets = asset.appearance.map(a => a.target).filter(p => p !== this.partId && followableAxes(asset, p).length);
|
|
98
|
+
const uniform = asset.sizing === 'uniform';
|
|
99
|
+
return html `
|
|
100
|
+
<div class="heading" aria-hidden="true">
|
|
101
|
+
<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>
|
|
102
|
+
</div>
|
|
103
|
+
${axes.map(axis => {
|
|
104
|
+
const key = `${this.partId}:${axis}`;
|
|
105
|
+
const mm = measureNow(revision, this.partId, axis);
|
|
106
|
+
const relation = sizeRelationOf(revision, this.partId, axis);
|
|
107
|
+
const f = followOf(asset, this.partId, axis);
|
|
108
|
+
const source = relation.source;
|
|
109
|
+
const suggested = suggestedSizeSource(asset, this.partId, axis);
|
|
110
|
+
let mode = relation.mode;
|
|
111
|
+
if (source && (f.of === 'instance' || f.of === 'part') && !f.plus) {
|
|
112
|
+
const same = (source.of === 'part' ? measureFactor(asset, source.part, source.axis) : 1) / measureFactor(asset, this.partId, axis);
|
|
113
|
+
if (Math.abs((f.times ?? 1) - same) < 1e-6)
|
|
114
|
+
mode = this.equivalentModes.get(key) ?? mode;
|
|
115
|
+
}
|
|
116
|
+
const currentMode = mode === 'custom' ? 'ratio' : mode;
|
|
117
|
+
const input = sizeInputOf(asset, this.partId, axis);
|
|
118
|
+
const label = `${axis.toUpperCase()}${measureFactor(asset, this.partId, axis) === 2 ? ' · ' + i18next.t('figure.label.size-diameter') : ''}`;
|
|
119
|
+
/* The selects show when asked for, or when what it follows is not what its fastening gives -- or nothing is. */
|
|
120
|
+
const choosing = relation.variable && (this.choosing.has(key) || !sameSizeSource(source, suggested));
|
|
121
|
+
const selectedSource = source ? (source.of === 'instance' ? '@figure' : source.part) : '@custom';
|
|
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
|
+
};
|
|
159
|
+
const changeSize = (e) => {
|
|
160
|
+
const value = e.target.valueAsNumber;
|
|
161
|
+
if (!(Number.isFinite(value) && value > 0)) {
|
|
162
|
+
this.error = i18next.t('figure.text.size-invalid');
|
|
163
|
+
this.requestUpdate();
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
if (relation.variable && source && mode !== 'custom')
|
|
167
|
+
this.configure(axis, source, mode, value);
|
|
168
|
+
else if (!relation.variable && input) {
|
|
169
|
+
this.error = '';
|
|
170
|
+
this.tell('size-input', { input, value: value / factor, part: this.partId });
|
|
171
|
+
this.requestUpdate();
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
const follows = (e) => {
|
|
175
|
+
if (e.target.checked) {
|
|
176
|
+
if (suggested)
|
|
177
|
+
this.configure(axis, suggested, 'ratio');
|
|
178
|
+
else {
|
|
179
|
+
/* At an angle to what it hangs from: which of its measures it follows is the author's to pick. */
|
|
180
|
+
this.choose(key, true);
|
|
181
|
+
this.error = i18next.t('figure.text.size-unpaired');
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
else if (mm !== undefined) {
|
|
185
|
+
this.choose(key, false);
|
|
186
|
+
this.apply([{ kind: 'unlink-dimension', part: this.partId, axis, mm: mm / measureFactor(asset, this.partId, axis) }]);
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
return html `<div class="dimension" data-axis=${axis}>
|
|
190
|
+
<div class="fields">
|
|
191
|
+
<span ?scrub=${own} @pointerdown=${scrubSize}>${label}</span>
|
|
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))))}
|
|
193
|
+
?disabled=${this.locked} ?readonly=${relation.variable ? mode === 'custom' || uniform : !input} @input=${typingSize} @change=${changeSize}>
|
|
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}
|
|
195
|
+
@change=${follows}></label>
|
|
196
|
+
${choosing || (this.choosing.has(key) && !relation.variable)
|
|
197
|
+
? html `
|
|
198
|
+
<select aria-label=${`${this.partId} ${label} ${i18next.t('figure.label.size-source')}`} .value=${live(selectedSource)} ?disabled=${this.locked || uniform}
|
|
199
|
+
@change=${(e) => {
|
|
200
|
+
const target = e.target.value;
|
|
201
|
+
const possible = target === '@figure' ? AXES : followableAxes(asset, target);
|
|
202
|
+
const targetAxis = possible.includes(source?.axis ?? axis) ? (source?.axis ?? axis) : possible[0];
|
|
203
|
+
this.configure(axis, target === '@figure' ? { of: 'instance', axis: targetAxis } : { of: 'part', part: target, axis: targetAxis }, currentMode);
|
|
204
|
+
}}>
|
|
205
|
+
${!source ? html `<option value="@custom" disabled>${i18next.t(relation.variable ? 'figure.label.size-existing-rule' : 'figure.label.size-pick-source')}</option>` : nothing}
|
|
206
|
+
<option value="@figure" ?selected=${selectedSource === '@figure'}>${i18next.t('figure.label.size-figure')}</option>
|
|
207
|
+
${targets.map(p => html `<option value=${p} ?selected=${selectedSource === p}>${p}</option>`)}
|
|
208
|
+
</select>
|
|
209
|
+
<select aria-label=${`${this.partId} ${label} ${i18next.t('figure.label.size-source-axis')}`} .value=${live(source?.axis ?? axis)} ?disabled=${this.locked || uniform || !source}
|
|
210
|
+
@change=${(e) => source && this.configure(axis, { ...source, axis: e.target.value }, currentMode)}>
|
|
211
|
+
${sourceAxes.map(a => html `<option value=${a} ?selected=${a === (source?.axis ?? axis)}>${a.toUpperCase()}</option>`)}
|
|
212
|
+
</select>`
|
|
213
|
+
: relation.variable && source
|
|
214
|
+
? html `<div class="source">
|
|
215
|
+
<span>${this.sourceWords(source)}</span>
|
|
216
|
+
<button quiet ?disabled=${this.locked || uniform} @click=${() => this.choose(key, true)}>${i18next.t('figure.button.size-other-source')}</button>
|
|
217
|
+
</div>`
|
|
218
|
+
: html `<span></span><span></span>`}
|
|
219
|
+
</div>
|
|
220
|
+
${relation.variable
|
|
221
|
+
? html `<div class="modes" choice-row>
|
|
222
|
+
${['ratio', 'difference'].map(m => html `<label toggle><input type="radio" name=${`mode-${this.partId}-${axis}`} .checked=${live(mode === m)} ?disabled=${this.locked || uniform || !source}
|
|
223
|
+
@change=${() => source && this.configure(axis, source, m)}>${i18next.t(`figure.label.size-${m}`)}</label>`)}
|
|
224
|
+
</div>
|
|
225
|
+
${mode === 'custom'
|
|
226
|
+
? html `<p hint>${i18next.t('figure.text.size-custom-rule')}</p>`
|
|
227
|
+
: source
|
|
228
|
+
? html `<p hint class="result">${this.resultWords(revision, source, currentMode, mm)}</p>`
|
|
229
|
+
: nothing}`
|
|
230
|
+
: nothing}
|
|
231
|
+
</div>`;
|
|
232
|
+
})}
|
|
233
|
+
${uniform ? html `<p hint>${i18next.t('figure.text.size-uniform')}</p>` : nothing}
|
|
234
|
+
${this.error ? html `<p hint role="alert">${this.error}</p>` : nothing}
|
|
235
|
+
`;
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
__decorate([
|
|
239
|
+
property({ attribute: false }),
|
|
240
|
+
__metadata("design:type", Object)
|
|
241
|
+
], FigureSizeEditor.prototype, "revision", void 0);
|
|
242
|
+
__decorate([
|
|
243
|
+
property(),
|
|
244
|
+
__metadata("design:type", Object)
|
|
245
|
+
], FigureSizeEditor.prototype, "partId", void 0);
|
|
246
|
+
__decorate([
|
|
247
|
+
property({ type: Boolean }),
|
|
248
|
+
__metadata("design:type", Object)
|
|
249
|
+
], FigureSizeEditor.prototype, "locked", void 0);
|
|
250
|
+
__decorate([
|
|
251
|
+
state(),
|
|
252
|
+
__metadata("design:type", Object)
|
|
253
|
+
], FigureSizeEditor.prototype, "error", void 0);
|
|
254
|
+
__decorate([
|
|
255
|
+
state(),
|
|
256
|
+
__metadata("design:type", Object)
|
|
257
|
+
], FigureSizeEditor.prototype, "choosing", void 0);
|
|
258
|
+
FigureSizeEditor = __decorate([
|
|
259
|
+
customElement('figure-size-editor')
|
|
260
|
+
], FigureSizeEditor);
|
|
261
|
+
export { FigureSizeEditor };
|
|
262
|
+
//# sourceMappingURL=figure-size-editor.js.map
|
|
@@ -0,0 +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 { 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"]}
|
|
@@ -11,7 +11,7 @@ export type JointDrag = {
|
|
|
11
11
|
joint: FigureJoint;
|
|
12
12
|
} | {
|
|
13
13
|
kind: 'refused';
|
|
14
|
-
reason: 'mimic' | 'fixed-turn' | 'origin-follows' | 'turned-slide' | 'off-axis';
|
|
14
|
+
reason: 'mimic' | 'fixed-turn' | 'origin-follows' | 'turned-slide' | 'off-axis' | 'several-axes';
|
|
15
15
|
joint: string;
|
|
16
16
|
leader?: string;
|
|
17
17
|
axis?: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as THREE from 'three';
|
|
2
|
-
import { compileFigureAsset, figureMimicTermsOf } from '@hatiolab/figure-model';
|
|
2
|
+
import { compileFigureAsset, figureMimicTermsOf, figureMotionsOf, motionStateIdOf } from '@hatiolab/figure-model';
|
|
3
3
|
import { jointAxisUnit, jointAxisWord } from './joint-axis.js';
|
|
4
4
|
const AXES = ['x', 'y', 'z'];
|
|
5
5
|
/** The axis each face faces along, and which way (as figure-model's `FIGURE_FACES`). */
|
|
@@ -14,11 +14,11 @@ const rotateMatrix = (j) => eulerMatrixOrder(AXES.map(a => j.origin?.parentRotat
|
|
|
14
14
|
const eulerMatrixOrder = (deg, order) => new THREE.Matrix4().makeRotationFromEuler(new THREE.Euler(...deg.map(THREE.MathUtils.degToRad), order));
|
|
15
15
|
/** The part's own pose on its end, any angle, as the record says it (`origin.childRotate`, Rz·Ry·Rx, ADR-0140 3.11). */
|
|
16
16
|
const turnMatrix = (j) => eulerMatrixOrder(AXES.map(a => j.origin?.childRotate?.[a] ?? 0), 'ZYX');
|
|
17
|
-
/** The joint frame's rotation in the figure: the part's rotation now, less its own turn and less
|
|
18
|
-
function jointFrameRotation(j, part, angle) {
|
|
17
|
+
/** The joint frame's rotation in the figure: the part's rotation now, less its own turn and less its one axis's. */
|
|
18
|
+
function jointFrameRotation(j, a, part, angle) {
|
|
19
19
|
const now = matrixOf(part.pose.r);
|
|
20
20
|
const own = turnMatrix(j);
|
|
21
|
-
const turn =
|
|
21
|
+
const turn = a?.type === 'revolute' ? new THREE.Matrix4().makeRotationAxis(unit(a.axis), THREE.MathUtils.degToRad(angle)) : new THREE.Matrix4();
|
|
22
22
|
return now.multiply(own.invert()).multiply(turn.invert());
|
|
23
23
|
}
|
|
24
24
|
export function jointDragOf(asset, id, part, result, values) {
|
|
@@ -26,51 +26,56 @@ export function jointDragOf(asset, id, part, result, values) {
|
|
|
26
26
|
/* A size is the part's own, not the joint's: a scale drag goes to the part's measures (the editor drops the shift). */
|
|
27
27
|
if (!j || result.scale || (!result.rotation && !result.position))
|
|
28
28
|
return null;
|
|
29
|
-
|
|
29
|
+
/* A drag moves one axis, or the joint's place: a joint of several axes (ADR-0159) is moved by its controls. */
|
|
30
|
+
const axes = figureMotionsOf(j);
|
|
31
|
+
if (axes.length > 1)
|
|
32
|
+
return { kind: 'refused', reason: 'several-axes', joint: j.id };
|
|
33
|
+
const a = axes[0];
|
|
34
|
+
const stateId = a ? motionStateIdOf(a) : j.id;
|
|
30
35
|
const now = values[stateId] ?? asset.stateDefaults[stateId] ?? 0;
|
|
31
|
-
const turns =
|
|
32
|
-
/* A revolute
|
|
33
|
-
const endless = turns &&
|
|
36
|
+
const turns = a?.type === 'revolute';
|
|
37
|
+
/* A revolute axis without limits turns without end (USD). */
|
|
38
|
+
const endless = turns && a.lowerLimit === undefined && a.upperLimit === undefined;
|
|
34
39
|
if (result.rotation) {
|
|
35
40
|
if (!turns)
|
|
36
|
-
return { kind: 'refused', reason:
|
|
37
|
-
if (
|
|
38
|
-
return { kind: 'refused', reason: 'mimic', joint: j.id, leader: figureMimicTermsOf(
|
|
41
|
+
return { kind: 'refused', reason: a?.type === 'prismatic' ? 'turned-slide' : 'fixed-turn', joint: j.id };
|
|
42
|
+
if (a.mimic)
|
|
43
|
+
return { kind: 'refused', reason: 'mimic', joint: j.id, leader: figureMimicTermsOf(a.mimic).sum.map(t => t.joint).join(', ') };
|
|
39
44
|
/* The turn about the joint's axis (the twist of the change): the only turn the joint can make. */
|
|
40
45
|
const before = new THREE.Quaternion().setFromRotationMatrix(matrixOf(part.pose.r));
|
|
41
46
|
const after = new THREE.Quaternion().setFromRotationMatrix(eulerMatrix(result.rotation));
|
|
42
47
|
const change = after.multiply(before.invert());
|
|
43
|
-
const axis = unit(
|
|
48
|
+
const axis = unit(a.axis).applyMatrix4(jointFrameRotation(j, a, part, now)).normalize();
|
|
44
49
|
const twist = 2 * Math.atan2(change.x * axis.x + change.y * axis.y + change.z * axis.z, change.w);
|
|
45
50
|
/* Turned about another axis: the joint cannot make that turn, and nothing is written for it. */
|
|
46
51
|
const whole = 2 * Math.acos(Math.min(1, Math.abs(change.w)));
|
|
47
52
|
if (Math.abs(twist) < whole * 0.5)
|
|
48
|
-
return { kind: 'refused', reason: 'off-axis', joint: j.id, axis: jointAxisWord(
|
|
53
|
+
return { kind: 'refused', reason: 'off-axis', joint: j.id, axis: jointAxisWord(a.axis) };
|
|
49
54
|
let value = now + THREE.MathUtils.radToDeg(twist);
|
|
50
55
|
if (endless)
|
|
51
56
|
value = ((((value + 180) % 360) + 360) % 360) - 180;
|
|
52
57
|
else
|
|
53
|
-
value = Math.min(Math.max(value,
|
|
58
|
+
value = Math.min(Math.max(value, a.lowerLimit), a.upperLimit);
|
|
54
59
|
return { kind: 'value', input: stateId, unit: 'deg', value: round(value, 0.1) };
|
|
55
60
|
}
|
|
56
61
|
const moved = new THREE.Vector3(...result.position).sub(new THREE.Vector3(...part.pose.t));
|
|
57
62
|
if (moved.length() < 0.5)
|
|
58
63
|
return null;
|
|
59
|
-
const frame = jointFrameRotation(j, part, turns ? now : 0);
|
|
60
|
-
if (
|
|
61
|
-
if (
|
|
62
|
-
return { kind: 'refused', reason: 'mimic', joint: j.id, leader: figureMimicTermsOf(
|
|
64
|
+
const frame = jointFrameRotation(j, a, part, turns ? now : 0);
|
|
65
|
+
if (a?.type === 'prismatic') {
|
|
66
|
+
if (a.mimic)
|
|
67
|
+
return { kind: 'refused', reason: 'mimic', joint: j.id, leader: figureMimicTermsOf(a.mimic).sum.map(t => t.joint).join(', ') };
|
|
63
68
|
/* How far one unit of the value moves the part along its axis, measured on the figure itself (a ratio travels a length). */
|
|
64
|
-
const along = unit(
|
|
69
|
+
const along = unit(a.axis).applyMatrix4(frame).normalize();
|
|
65
70
|
const at = (v) => {
|
|
66
71
|
const g = compileFigureAsset(asset).evaluate({ ...values, [stateId]: v }).geometry.find(g => g.id === id && 'pose' in g);
|
|
67
72
|
return g ? new THREE.Vector3(...g.pose.t).dot(along) : 0;
|
|
68
73
|
};
|
|
69
|
-
const step =
|
|
74
|
+
const step = a.travel ? 0.01 : 1;
|
|
70
75
|
const rate = (at(now + step) - at(now)) / step;
|
|
71
76
|
if (Math.abs(rate) > 1e-9 && Math.abs(moved.dot(along)) >= moved.length() * 0.7) {
|
|
72
|
-
const value = Math.min(Math.max(now + moved.dot(along) / rate,
|
|
73
|
-
return { kind: 'value', input: stateId, unit:
|
|
77
|
+
const value = Math.min(Math.max(now + moved.dot(along) / rate, a.lowerLimit), a.upperLimit);
|
|
78
|
+
return { kind: 'value', input: stateId, unit: a.travel ? 'ratio' : 'mm', value: round(value, a.travel ? 0.001 : 1) };
|
|
74
79
|
}
|
|
75
80
|
}
|
|
76
81
|
/* Moving the joint's origin: the change in the frame it is placed in, before `origin.parentRotate` turns the joint frame. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"joint-drag.js","sourceRoot":"","sources":["../../client/modeller/joint-drag.ts"],"names":[],"mappings":"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","sourcesContent":["import * as THREE from 'three'\nimport { compileFigureAsset, figureMimicTermsOf } from '@hatiolab/figure-model'\nimport type { FigureAsset, FigureJoint } from '@hatiolab/figure-model'\n\nimport type { GizmoResult } from './gizmo.js'\nimport { jointAxisUnit, jointAxisWord } from './joint-axis.js'\nimport type { FigureRenderPart } from './renderer.js'\n\n/*\n * A gizmo drag on a part that hangs from a joint edits the joint, not the part (ADR-0093; r3 §3: the modeller changes\n * the inputs of the relation, and never breaks it because a computed place was moved):\n *\n * - turning a part on a revolute or continuous joint sets the joint's value -- the turn about the joint's axis;\n * - sliding a part on a prismatic joint along its axis sets its value;\n * - moving a part any other way moves the joint's origin: the gap along the face it sits on, the offset across it;\n * at a parent's origin (ADR-0107 decision 4), its `origin.offset` in the parent's frame.\n *\n * What would break a relation is refused and said: an origin that follows another part's size, a joint that mimics\n * another, a turn on a joint that does not turn.\n */\n\nexport type JointDrag =\n | { kind: 'value'; input: string; unit: 'deg' | 'mm' | 'ratio'; value: number }\n | { kind: 'joint'; joint: FigureJoint }\n | { kind: 'refused'; reason: 'mimic' | 'fixed-turn' | 'origin-follows' | 'turned-slide' | 'off-axis'; joint: string; leader?: string; axis?: string }\n\ntype Axis = 'x' | 'y' | 'z'\nconst AXES: Axis[] = ['x', 'y', 'z']\n/** The axis each face faces along, and which way (as figure-model's `FIGURE_FACES`). */\nconst FACE_AXIS: Record<string, Axis> = { left: 'x', right: 'x', bottom: 'y', top: 'y', back: 'z', front: 'z' }\nconst FACE_SIGN: Record<string, number> = { left: -1, right: 1, bottom: -1, top: 1, back: -1, front: 1 }\n\nconst matrixOf = (r: number[][]) => new THREE.Matrix4().set(r[0]![0]!, r[0]![1]!, r[0]![2]!, 0, r[1]![0]!, r[1]![1]!, r[1]![2]!, 0, r[2]![0]!, r[2]![1]!, r[2]![2]!, 0, 0, 0, 0, 1)\nconst eulerMatrix = (deg: number[]) => new THREE.Matrix4().makeRotationFromEuler(new THREE.Euler(...(deg.map(THREE.MathUtils.degToRad) as [number, number, number]), 'XYZ'))\nconst unit = (axis: unknown) => new THREE.Vector3(...(jointAxisUnit(axis) ?? [0, 0, 0]))\nconst round = (v: number, step: number) => Number((Math.round(v / step) * step).toFixed(Math.max(0, -Math.floor(Math.log10(step)))))\n\n/** `origin.parentRotate` (ADR-0107, named for its end by ADR-0140 3.6), URDF's rpy: Rz·Ry·Rx about the fixed axes. */\nconst rotateMatrix = (j: FigureJoint) => eulerMatrixOrder(AXES.map(a => j.origin?.parentRotate?.[a] ?? 0), 'ZYX')\nconst eulerMatrixOrder = (deg: number[], order: THREE.EulerOrder) => new THREE.Matrix4().makeRotationFromEuler(new THREE.Euler(...(deg.map(THREE.MathUtils.degToRad) as [number, number, number]), order))\n\n/** The part's own pose on its end, any angle, as the record says it (`origin.childRotate`, Rz·Ry·Rx, ADR-0140 3.11). */\nconst turnMatrix = (j: FigureJoint) => eulerMatrixOrder(AXES.map(a => j.origin?.childRotate?.[a] ?? 0), 'ZYX')\n\n/** The joint frame's rotation in the figure: the part's rotation now, less its own turn and less the joint's. */\nfunction jointFrameRotation(j: FigureJoint, part: FigureRenderPart, angle: number): THREE.Matrix4 {\n const now = matrixOf(part.pose.r as number[][])\n const own = turnMatrix(j)\n const turn = j.type === 'revolute' ? new THREE.Matrix4().makeRotationAxis(unit(j.axis!), THREE.MathUtils.degToRad(angle)) : new THREE.Matrix4()\n return now.multiply(own.invert()).multiply(turn.invert())\n}\n\nexport function jointDragOf(asset: FigureAsset, id: string, part: FigureRenderPart, result: GizmoResult, values: Record<string, number>): JointDrag | null {\n const j = asset.joints?.find(joint => joint.body1.part === id)\n /* A size is the part's own, not the joint's: a scale drag goes to the part's measures (the editor drops the shift). */\n if (!j || result.scale || (!result.rotation && !result.position)) return null\n const stateId = j.state?.id ?? j.id\n const now = values[stateId] ?? asset.stateDefaults[stateId] ?? 0\n const turns = j.type === 'revolute'\n /* A revolute joint without limits turns without end (USD). */\n const endless = turns && j.lowerLimit === undefined && j.upperLimit === undefined\n\n if (result.rotation) {\n if (!turns) return { kind: 'refused', reason: j.type === 'prismatic' ? 'turned-slide' : 'fixed-turn', joint: j.id }\n if (j.mimic) return { kind: 'refused', reason: 'mimic', joint: j.id, leader: figureMimicTermsOf(j.mimic)!.sum.map(t => t.joint).join(', ') }\n /* The turn about the joint's axis (the twist of the change): the only turn the joint can make. */\n const before = new THREE.Quaternion().setFromRotationMatrix(matrixOf(part.pose.r as number[][]))\n const after = new THREE.Quaternion().setFromRotationMatrix(eulerMatrix(result.rotation))\n const change = after.multiply(before.invert())\n const axis = unit(j.axis!).applyMatrix4(jointFrameRotation(j, part, now)).normalize()\n const twist = 2 * Math.atan2(change.x * axis.x + change.y * axis.y + change.z * axis.z, change.w)\n /* Turned about another axis: the joint cannot make that turn, and nothing is written for it. */\n const whole = 2 * Math.acos(Math.min(1, Math.abs(change.w)))\n if (Math.abs(twist) < whole * 0.5) return { kind: 'refused', reason: 'off-axis', joint: j.id, axis: jointAxisWord(j.axis) }\n let value = now + THREE.MathUtils.radToDeg(twist)\n if (endless) value = ((((value + 180) % 360) + 360) % 360) - 180\n else value = Math.min(Math.max(value, j.lowerLimit!), j.upperLimit!)\n return { kind: 'value', input: stateId, unit: 'deg', value: round(value, 0.1) }\n }\n\n const moved = new THREE.Vector3(...result.position!).sub(new THREE.Vector3(...(part.pose.t as [number, number, number])))\n if (moved.length() < 0.5) return null\n const frame = jointFrameRotation(j, part, turns ? now : 0)\n\n if (j.type === 'prismatic') {\n if (j.mimic) return { kind: 'refused', reason: 'mimic', joint: j.id, leader: figureMimicTermsOf(j.mimic)!.sum.map(t => t.joint).join(', ') }\n /* How far one unit of the value moves the part along its axis, measured on the figure itself (a ratio travels a length). */\n const along = unit(j.axis!).applyMatrix4(frame).normalize()\n const at = (v: number) => {\n const g = compileFigureAsset(asset).evaluate({ ...values, [stateId]: v }).geometry.find(g => g.id === id && 'pose' in g) as { pose: { t: number[] } } | undefined\n return g ? new THREE.Vector3(...(g.pose.t as [number, number, number])).dot(along) : 0\n }\n const step = j.travel ? 0.01 : 1\n const rate = (at(now + step) - at(now)) / step\n if (Math.abs(rate) > 1e-9 && Math.abs(moved.dot(along)) >= moved.length() * 0.7) {\n const value = Math.min(Math.max(now + moved.dot(along) / rate, j.lowerLimit!), j.upperLimit!)\n return { kind: 'value', input: stateId, unit: j.travel ? 'ratio' : 'mm', value: round(value, j.travel ? 0.001 : 1) }\n }\n }\n\n /* Moving the joint's origin: the change in the frame it is placed in, before `origin.parentRotate` turns the joint frame. */\n const local = moved.clone().applyMatrix4(frame.clone().multiply(rotateMatrix(j).invert()).invert())\n const origin: NonNullable<FigureJoint['origin']> = { ...(j.origin ?? {}) }\n if (!('plane' in j.body0) && !('face' in j.body0)) {\n /* At the parent's origin there is no face: the frame moves by its offset, along the parent's axes. */\n const offset = { ...(j.origin?.offset ?? {}) }\n for (const axis of AXES) {\n const d = local[axis]\n if (Math.abs(d) < 0.5) continue\n const was = offset[axis] ?? 0\n if (typeof was !== 'number') return { kind: 'refused', reason: 'origin-follows', joint: j.id }\n offset[axis] = round(was + d, 1)\n }\n return { kind: 'joint', joint: { ...j, origin: { ...origin, offset } } as FigureJoint }\n }\n const normal: Axis = 'plane' in j.body0 ? (asset.placement === 'wall' ? 'z' : 'y') : FACE_AXIS[j.body0.face]!\n /* Which way a larger gap moves the part: out of the face it meets or sits by its middle on, into it when flush with it (ADR-0140 3.11). */\n const outward = ((j.origin?.facing ?? 'meet') === 'flush' ? -1 : 1) * ('plane' in j.body0 ? 1 : FACE_SIGN[j.body0.face]!)\n const along = local[normal]\n if (Math.abs(along) >= 0.5) {\n const gap = origin.gap ?? 0\n if (typeof gap !== 'number') return { kind: 'refused', reason: 'origin-follows', joint: j.id }\n origin.gap = round(gap + outward * along, 1)\n }\n for (const axis of AXES.filter(a => a !== normal)) {\n const d = local[axis]\n if (Math.abs(d) < 0.5) continue\n const how = origin.align?.[axis] ?? 'centre'\n const was = how === 'centre' ? 0 : typeof how === 'object' && typeof how.mm === 'number' ? how.mm : null\n if (was === null) return { kind: 'refused', reason: 'origin-follows', joint: j.id }\n origin.align = { ...(origin.align ?? {}), [axis]: { mm: round(was + d, 1) } }\n }\n return { kind: 'joint', joint: { ...j, origin } }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"joint-drag.js","sourceRoot":"","sources":["../../client/modeller/joint-drag.ts"],"names":[],"mappings":"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","sourcesContent":["import * as THREE from 'three'\nimport { compileFigureAsset, figureMimicTermsOf, figureMotionsOf, motionStateIdOf, type FigureNamedMotion } from '@hatiolab/figure-model'\nimport type { FigureAsset, FigureJoint } from '@hatiolab/figure-model'\n\nimport type { GizmoResult } from './gizmo.js'\nimport { jointAxisUnit, jointAxisWord } from './joint-axis.js'\nimport type { FigureRenderPart } from './renderer.js'\n\n/*\n * A gizmo drag on a part that hangs from a joint edits the joint, not the part (ADR-0093; r3 §3: the modeller changes\n * the inputs of the relation, and never breaks it because a computed place was moved):\n *\n * - turning a part on a revolute or continuous joint sets the joint's value -- the turn about the joint's axis;\n * - sliding a part on a prismatic joint along its axis sets its value;\n * - moving a part any other way moves the joint's origin: the gap along the face it sits on, the offset across it;\n * at a parent's origin (ADR-0107 decision 4), its `origin.offset` in the parent's frame.\n *\n * What would break a relation is refused and said: an origin that follows another part's size, a joint that mimics\n * another, a turn on a joint that does not turn.\n */\n\nexport type JointDrag =\n | { kind: 'value'; input: string; unit: 'deg' | 'mm' | 'ratio'; value: number }\n | { kind: 'joint'; joint: FigureJoint }\n | { kind: 'refused'; reason: 'mimic' | 'fixed-turn' | 'origin-follows' | 'turned-slide' | 'off-axis' | 'several-axes'; joint: string; leader?: string; axis?: string }\n\ntype Axis = 'x' | 'y' | 'z'\nconst AXES: Axis[] = ['x', 'y', 'z']\n/** The axis each face faces along, and which way (as figure-model's `FIGURE_FACES`). */\nconst FACE_AXIS: Record<string, Axis> = { left: 'x', right: 'x', bottom: 'y', top: 'y', back: 'z', front: 'z' }\nconst FACE_SIGN: Record<string, number> = { left: -1, right: 1, bottom: -1, top: 1, back: -1, front: 1 }\n\nconst matrixOf = (r: number[][]) => new THREE.Matrix4().set(r[0]![0]!, r[0]![1]!, r[0]![2]!, 0, r[1]![0]!, r[1]![1]!, r[1]![2]!, 0, r[2]![0]!, r[2]![1]!, r[2]![2]!, 0, 0, 0, 0, 1)\nconst eulerMatrix = (deg: number[]) => new THREE.Matrix4().makeRotationFromEuler(new THREE.Euler(...(deg.map(THREE.MathUtils.degToRad) as [number, number, number]), 'XYZ'))\nconst unit = (axis: unknown) => new THREE.Vector3(...(jointAxisUnit(axis) ?? [0, 0, 0]))\nconst round = (v: number, step: number) => Number((Math.round(v / step) * step).toFixed(Math.max(0, -Math.floor(Math.log10(step)))))\n\n/** `origin.parentRotate` (ADR-0107, named for its end by ADR-0140 3.6), URDF's rpy: Rz·Ry·Rx about the fixed axes. */\nconst rotateMatrix = (j: FigureJoint) => eulerMatrixOrder(AXES.map(a => j.origin?.parentRotate?.[a] ?? 0), 'ZYX')\nconst eulerMatrixOrder = (deg: number[], order: THREE.EulerOrder) => new THREE.Matrix4().makeRotationFromEuler(new THREE.Euler(...(deg.map(THREE.MathUtils.degToRad) as [number, number, number]), order))\n\n/** The part's own pose on its end, any angle, as the record says it (`origin.childRotate`, Rz·Ry·Rx, ADR-0140 3.11). */\nconst turnMatrix = (j: FigureJoint) => eulerMatrixOrder(AXES.map(a => j.origin?.childRotate?.[a] ?? 0), 'ZYX')\n\n/** The joint frame's rotation in the figure: the part's rotation now, less its own turn and less its one axis's. */\nfunction jointFrameRotation(j: FigureJoint, a: FigureNamedMotion | undefined, part: FigureRenderPart, angle: number): THREE.Matrix4 {\n const now = matrixOf(part.pose.r as number[][])\n const own = turnMatrix(j)\n const turn = a?.type === 'revolute' ? new THREE.Matrix4().makeRotationAxis(unit(a.axis), THREE.MathUtils.degToRad(angle)) : new THREE.Matrix4()\n return now.multiply(own.invert()).multiply(turn.invert())\n}\n\nexport function jointDragOf(asset: FigureAsset, id: string, part: FigureRenderPart, result: GizmoResult, values: Record<string, number>): JointDrag | null {\n const j = asset.joints?.find(joint => joint.body1.part === id)\n /* A size is the part's own, not the joint's: a scale drag goes to the part's measures (the editor drops the shift). */\n if (!j || result.scale || (!result.rotation && !result.position)) return null\n /* A drag moves one axis, or the joint's place: a joint of several axes (ADR-0159) is moved by its controls. */\n const axes = figureMotionsOf(j)\n if (axes.length > 1) return { kind: 'refused', reason: 'several-axes', joint: j.id }\n const a = axes[0]\n const stateId = a ? motionStateIdOf(a) : j.id\n const now = values[stateId] ?? asset.stateDefaults[stateId] ?? 0\n const turns = a?.type === 'revolute'\n /* A revolute axis without limits turns without end (USD). */\n const endless = turns && a.lowerLimit === undefined && a.upperLimit === undefined\n\n if (result.rotation) {\n if (!turns) return { kind: 'refused', reason: a?.type === 'prismatic' ? 'turned-slide' : 'fixed-turn', joint: j.id }\n if (a.mimic) return { kind: 'refused', reason: 'mimic', joint: j.id, leader: figureMimicTermsOf(a.mimic)!.sum.map(t => t.joint).join(', ') }\n /* The turn about the joint's axis (the twist of the change): the only turn the joint can make. */\n const before = new THREE.Quaternion().setFromRotationMatrix(matrixOf(part.pose.r as number[][]))\n const after = new THREE.Quaternion().setFromRotationMatrix(eulerMatrix(result.rotation))\n const change = after.multiply(before.invert())\n const axis = unit(a.axis).applyMatrix4(jointFrameRotation(j, a, part, now)).normalize()\n const twist = 2 * Math.atan2(change.x * axis.x + change.y * axis.y + change.z * axis.z, change.w)\n /* Turned about another axis: the joint cannot make that turn, and nothing is written for it. */\n const whole = 2 * Math.acos(Math.min(1, Math.abs(change.w)))\n if (Math.abs(twist) < whole * 0.5) return { kind: 'refused', reason: 'off-axis', joint: j.id, axis: jointAxisWord(a.axis) }\n let value = now + THREE.MathUtils.radToDeg(twist)\n if (endless) value = ((((value + 180) % 360) + 360) % 360) - 180\n else value = Math.min(Math.max(value, a.lowerLimit!), a.upperLimit!)\n return { kind: 'value', input: stateId, unit: 'deg', value: round(value, 0.1) }\n }\n\n const moved = new THREE.Vector3(...result.position!).sub(new THREE.Vector3(...(part.pose.t as [number, number, number])))\n if (moved.length() < 0.5) return null\n const frame = jointFrameRotation(j, a, part, turns ? now : 0)\n\n if (a?.type === 'prismatic') {\n if (a.mimic) return { kind: 'refused', reason: 'mimic', joint: j.id, leader: figureMimicTermsOf(a.mimic)!.sum.map(t => t.joint).join(', ') }\n /* How far one unit of the value moves the part along its axis, measured on the figure itself (a ratio travels a length). */\n const along = unit(a.axis).applyMatrix4(frame).normalize()\n const at = (v: number) => {\n const g = compileFigureAsset(asset).evaluate({ ...values, [stateId]: v }).geometry.find(g => g.id === id && 'pose' in g) as { pose: { t: number[] } } | undefined\n return g ? new THREE.Vector3(...(g.pose.t as [number, number, number])).dot(along) : 0\n }\n const step = a.travel ? 0.01 : 1\n const rate = (at(now + step) - at(now)) / step\n if (Math.abs(rate) > 1e-9 && Math.abs(moved.dot(along)) >= moved.length() * 0.7) {\n const value = Math.min(Math.max(now + moved.dot(along) / rate, a.lowerLimit!), a.upperLimit!)\n return { kind: 'value', input: stateId, unit: a.travel ? 'ratio' : 'mm', value: round(value, a.travel ? 0.001 : 1) }\n }\n }\n\n /* Moving the joint's origin: the change in the frame it is placed in, before `origin.parentRotate` turns the joint frame. */\n const local = moved.clone().applyMatrix4(frame.clone().multiply(rotateMatrix(j).invert()).invert())\n const origin: NonNullable<FigureJoint['origin']> = { ...(j.origin ?? {}) }\n if (!('plane' in j.body0) && !('face' in j.body0)) {\n /* At the parent's origin there is no face: the frame moves by its offset, along the parent's axes. */\n const offset = { ...(j.origin?.offset ?? {}) }\n for (const axis of AXES) {\n const d = local[axis]\n if (Math.abs(d) < 0.5) continue\n const was = offset[axis] ?? 0\n if (typeof was !== 'number') return { kind: 'refused', reason: 'origin-follows', joint: j.id }\n offset[axis] = round(was + d, 1)\n }\n return { kind: 'joint', joint: { ...j, origin: { ...origin, offset } } as FigureJoint }\n }\n const normal: Axis = 'plane' in j.body0 ? (asset.placement === 'wall' ? 'z' : 'y') : FACE_AXIS[j.body0.face]!\n /* Which way a larger gap moves the part: out of the face it meets or sits by its middle on, into it when flush with it (ADR-0140 3.11). */\n const outward = ((j.origin?.facing ?? 'meet') === 'flush' ? -1 : 1) * ('plane' in j.body0 ? 1 : FACE_SIGN[j.body0.face]!)\n const along = local[normal]\n if (Math.abs(along) >= 0.5) {\n const gap = origin.gap ?? 0\n if (typeof gap !== 'number') return { kind: 'refused', reason: 'origin-follows', joint: j.id }\n origin.gap = round(gap + outward * along, 1)\n }\n for (const axis of AXES.filter(a => a !== normal)) {\n const d = local[axis]\n if (Math.abs(d) < 0.5) continue\n const how = origin.align?.[axis] ?? 'centre'\n const was = how === 'centre' ? 0 : typeof how === 'object' && typeof how.mm === 'number' ? how.mm : null\n if (was === null) return { kind: 'refused', reason: 'origin-follows', joint: j.id }\n origin.align = { ...(origin.align ?? {}), [axis]: { mm: round(was + d, 1) } }\n }\n return { kind: 'joint', joint: { ...j, origin } }\n}\n"]}
|
|
@@ -66,10 +66,11 @@ function turnOf(asset, part, values, box) {
|
|
|
66
66
|
about the joint's axis. A part that does not turn itself shows the joint that sits on it, when there is one: the
|
|
67
67
|
elbow block shows the elbow (user, 2026-09-24: 「elbow-housing itself is a axis」).
|
|
68
68
|
*/
|
|
69
|
-
|
|
69
|
+
/* A joint's fan is its first axis's (ADR-0159): `<child>.motion`. */
|
|
70
|
+
const turning = (asset.joints ?? []).filter(j => j.motion?.[0]?.type === 'revolute');
|
|
70
71
|
const record = turning.find(j => j.body1.part === part) ?? (() => {
|
|
71
72
|
const on = turning.filter(j => 'part' in j.body0 && j.body0.part === part);
|
|
72
|
-
return on.length === 1 && !(asset.joints ?? []).some(j => j.body1.part === part && j.
|
|
73
|
+
return on.length === 1 && !(asset.joints ?? []).some(j => j.body1.part === part && j.motion?.length) ? on[0] : undefined;
|
|
73
74
|
})();
|
|
74
75
|
if (record) {
|
|
75
76
|
const child = record.body1.part;
|
|
@@ -82,7 +83,7 @@ function turnOf(asset, part, values, box) {
|
|
|
82
83
|
/* In the child's own frame: the joint is where its pose puts the seat's origin, the axis turned back by its pose. */
|
|
83
84
|
const back = (v) => [0, 1, 2].map(i => pose.r[0][i] * v[0] + pose.r[1][i] * v[1] + pose.r[2][i] * v[2]);
|
|
84
85
|
const pivot = back(pose.t.map(v => -v));
|
|
85
|
-
const axis = norm(back(jointAxisUnit(record.axis) ?? [0, 0, 0]));
|
|
86
|
+
const axis = norm(back(jointAxisUnit(record.motion[0].axis) ?? [0, 0, 0]));
|
|
86
87
|
if (!axis)
|
|
87
88
|
return null;
|
|
88
89
|
return { carrier, pivot, axis, toward: [0, 0, 0], control, turnedPart: child };
|