@things-factory/figure-ui 10.1.45 → 10.1.47
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/angle-dial.ts +70 -0
- package/client/modeller/figure-settings.ts +328 -0
- package/client/modeller/figure-v3-capability.ts +137 -0
- package/client/modeller/figure-v3-clips.ts +253 -0
- package/client/modeller/figure-v3-controls.ts +212 -0
- package/client/modeller/figure-v3-links.ts +150 -79
- package/client/modeller/figure-v3-material.ts +108 -35
- package/client/modeller/figure-v3-parts.ts +96 -3
- package/client/modeller/figure-v3-preview.ts +326 -0
- package/client/modeller/figure-v3-properties.ts +29 -57
- package/client/modeller/figure-v3-verdict.ts +598 -60
- package/client/modeller/figure-view.ts +152 -0
- package/client/modeller/side-panel-styles.ts +326 -0
- package/client/modeller/v3-canvas.ts +221 -263
- package/client/modeller/v3-clips.ts +89 -0
- package/client/modeller/v3-controls.ts +54 -0
- package/client/modeller/v3-editor.ts +532 -223
- package/client/modeller/v3-fields.ts +62 -0
- package/client/modeller/v3-help.ts +41 -0
- package/client/modeller/v3-keys.ts +34 -0
- package/client/modeller/v3-links.ts +126 -10
- package/client/modeller/v3-live.ts +79 -0
- package/client/modeller/v3-maturity.ts +415 -0
- package/client/modeller/v3-palette.ts +45 -0
- package/client/modeller/v3-preview-model.ts +54 -0
- package/client/modeller/v3-stage-model.ts +159 -0
- package/client/pages/figure-list-page.ts +4 -4
- package/client/pages/figure-v3-modeller-page.ts +66 -134
- package/dist-client/modeller/angle-dial.d.ts +32 -0
- package/dist-client/modeller/angle-dial.js +41 -0
- package/dist-client/modeller/angle-dial.js.map +1 -0
- package/dist-client/modeller/figure-settings.d.ts +39 -0
- package/dist-client/modeller/figure-settings.js +317 -0
- package/dist-client/modeller/figure-settings.js.map +1 -0
- package/dist-client/modeller/figure-v3-capability.d.ts +22 -0
- package/dist-client/modeller/figure-v3-capability.js +141 -0
- package/dist-client/modeller/figure-v3-capability.js.map +1 -0
- package/dist-client/modeller/figure-v3-clips.d.ts +31 -0
- package/dist-client/modeller/figure-v3-clips.js +268 -0
- package/dist-client/modeller/figure-v3-clips.js.map +1 -0
- package/dist-client/modeller/figure-v3-controls.d.ts +36 -0
- package/dist-client/modeller/figure-v3-controls.js +226 -0
- package/dist-client/modeller/figure-v3-controls.js.map +1 -0
- package/dist-client/modeller/figure-v3-links.d.ts +10 -2
- package/dist-client/modeller/figure-v3-links.js +134 -79
- package/dist-client/modeller/figure-v3-links.js.map +1 -1
- package/dist-client/modeller/figure-v3-material.d.ts +5 -2
- package/dist-client/modeller/figure-v3-material.js +109 -35
- package/dist-client/modeller/figure-v3-material.js.map +1 -1
- package/dist-client/modeller/figure-v3-parts.d.ts +10 -0
- package/dist-client/modeller/figure-v3-parts.js +88 -3
- package/dist-client/modeller/figure-v3-parts.js.map +1 -1
- package/dist-client/modeller/figure-v3-preview.d.ts +34 -0
- package/dist-client/modeller/figure-v3-preview.js +322 -0
- package/dist-client/modeller/figure-v3-preview.js.map +1 -0
- package/dist-client/modeller/figure-v3-properties.d.ts +2 -2
- package/dist-client/modeller/figure-v3-properties.js +29 -57
- package/dist-client/modeller/figure-v3-properties.js.map +1 -1
- package/dist-client/modeller/figure-v3-verdict.d.ts +31 -17
- package/dist-client/modeller/figure-v3-verdict.js +579 -59
- package/dist-client/modeller/figure-v3-verdict.js.map +1 -1
- package/dist-client/modeller/figure-view.d.ts +118 -0
- package/dist-client/modeller/figure-view.js +78 -0
- package/dist-client/modeller/figure-view.js.map +1 -0
- package/dist-client/modeller/side-panel-styles.d.ts +23 -0
- package/dist-client/modeller/side-panel-styles.js +326 -0
- package/dist-client/modeller/side-panel-styles.js.map +1 -0
- package/dist-client/modeller/v3-canvas.d.ts +29 -37
- package/dist-client/modeller/v3-canvas.js +188 -260
- package/dist-client/modeller/v3-canvas.js.map +1 -1
- package/dist-client/modeller/v3-clips.d.ts +39 -0
- package/dist-client/modeller/v3-clips.js +58 -0
- package/dist-client/modeller/v3-clips.js.map +1 -0
- package/dist-client/modeller/v3-controls.d.ts +28 -0
- package/dist-client/modeller/v3-controls.js +28 -0
- package/dist-client/modeller/v3-controls.js.map +1 -0
- package/dist-client/modeller/v3-editor.d.ts +58 -1
- package/dist-client/modeller/v3-editor.js +556 -218
- package/dist-client/modeller/v3-editor.js.map +1 -1
- package/dist-client/modeller/v3-fields.d.ts +30 -0
- package/dist-client/modeller/v3-fields.js +46 -0
- package/dist-client/modeller/v3-fields.js.map +1 -0
- package/dist-client/modeller/v3-help.d.ts +18 -0
- package/dist-client/modeller/v3-help.js +37 -0
- package/dist-client/modeller/v3-help.js.map +1 -0
- package/dist-client/modeller/v3-keys.d.ts +14 -0
- package/dist-client/modeller/v3-keys.js +23 -0
- package/dist-client/modeller/v3-keys.js.map +1 -0
- package/dist-client/modeller/v3-links.d.ts +44 -2
- package/dist-client/modeller/v3-links.js +125 -8
- package/dist-client/modeller/v3-links.js.map +1 -1
- package/dist-client/modeller/v3-live.d.ts +31 -0
- package/dist-client/modeller/v3-live.js +58 -0
- package/dist-client/modeller/v3-live.js.map +1 -0
- package/dist-client/modeller/v3-maturity.d.ts +120 -0
- package/dist-client/modeller/v3-maturity.js +317 -0
- package/dist-client/modeller/v3-maturity.js.map +1 -0
- package/dist-client/modeller/v3-palette.d.ts +7 -0
- package/dist-client/modeller/v3-palette.js +42 -0
- package/dist-client/modeller/v3-palette.js.map +1 -0
- package/dist-client/modeller/v3-preview-model.d.ts +32 -0
- package/dist-client/modeller/v3-preview-model.js +48 -0
- package/dist-client/modeller/v3-preview-model.js.map +1 -0
- package/dist-client/modeller/v3-stage-model.d.ts +31 -0
- package/dist-client/modeller/v3-stage-model.js +121 -0
- package/dist-client/modeller/v3-stage-model.js.map +1 -0
- package/dist-client/pages/figure-list-page.js +4 -4
- package/dist-client/pages/figure-list-page.js.map +1 -1
- package/dist-client/pages/figure-v3-modeller-page.d.ts +2 -6
- package/dist-client/pages/figure-v3-modeller-page.js +64 -136
- package/dist-client/pages/figure-v3-modeller-page.js.map +1 -1
- package/dist-client/tsconfig.chk.tsbuildinfo +1 -0
- package/dist-client/tsconfig.scratch-fm.tsbuildinfo +1 -0
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -6
- package/test/v3-clips.test.ts +70 -0
- package/test/v3-controls.test.ts +68 -0
- package/test/v3-fields.test.ts +62 -0
- package/test/v3-gizmo.test.ts +3 -5
- package/test/v3-keys.test.ts +37 -0
- package/test/v3-layout.test.ts +78 -0
- package/test/v3-links.test.ts +118 -7
- package/test/v3-live.test.ts +99 -0
- package/test/v3-material.test.ts +6 -0
- package/test/v3-maturity.test.ts +268 -0
- package/test/v3-parts-wiring.test.ts +3 -1
- package/test/v3-preview.test.ts +42 -0
- package/test/v3-properties.test.ts +4 -2
- package/test/v3-scene-canvas.test.ts +85 -0
- package/test/v3-stage-model.test.ts +25 -0
- package/test/v3-theme.test.ts +16 -25
- package/test/v3-verdict.test.ts +12 -6
- package/test/v3-view.test.ts +20 -0
- package/translations/en.json +98 -5
- package/translations/ja.json +100 -7
- package/translations/ko.json +98 -5
- package/translations/ms.json +100 -7
- package/translations/zh.json +100 -7
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { v3AttachmentsOf } from '@hatiolab/figure-model';
|
|
1
|
+
import { compileV3Graph, defaultInstanceSizeRange, foldInstanceSizeRange, v3AttachmentsOf, v3BindingsOf, v3InstanceSizeLimitsOf } from '@hatiolab/figure-model';
|
|
2
2
|
export const AXES = ['x', 'y', 'z'];
|
|
3
3
|
const modelOf = (asset) => asset.document.model;
|
|
4
4
|
/**
|
|
@@ -8,18 +8,26 @@ const modelOf = (asset) => asset.document.model;
|
|
|
8
8
|
*/
|
|
9
9
|
export function followOf(asset, part, axis) {
|
|
10
10
|
const m = modelOf(asset);
|
|
11
|
+
/*
|
|
12
|
+
A link routes the dimension through `<part>.dimension.<axis>`; before any link the shape reads its measure directly,
|
|
13
|
+
and that may already be worked out -- the converter writes V2's rules there (ROBOT_ARM's flange filled what was left).
|
|
14
|
+
*/
|
|
11
15
|
const node = m.nodes.find(n => n.id === `${part}.dimension.${axis}`);
|
|
12
|
-
|
|
16
|
+
const start = node ? node.args[0] : m.nodes.find(n => n.id === `${part}.shape`)?.args[AXES.indexOf(axis)];
|
|
17
|
+
if (!start || m.constants.some(c => c.id === start))
|
|
13
18
|
return { of: 'own' };
|
|
14
19
|
const writerOf = (ref) => m.nodes.find(n => Object.values(n.outputs).includes(ref));
|
|
15
20
|
const seen = new Set();
|
|
21
|
+
const constant = (ref) => m.constants.find(c => c.id === ref);
|
|
22
|
+
/* The constant factors met on the way back: a share of the instance size is `size × share`. */
|
|
23
|
+
let times = 1;
|
|
16
24
|
const walk = (ref) => {
|
|
17
25
|
if (seen.has(ref))
|
|
18
26
|
return { of: 'expression' };
|
|
19
27
|
seen.add(ref);
|
|
20
28
|
const instance = /^size\.([xyz])$/.exec(ref);
|
|
21
29
|
if (instance)
|
|
22
|
-
return { of: 'instance', axis: instance[1] };
|
|
30
|
+
return { of: 'instance', axis: instance[1], times: Math.round(times * 1e6) / 1e6 };
|
|
23
31
|
const other = /^(.+)\.dimension\.([xyz])\.value$/.exec(ref);
|
|
24
32
|
if (other)
|
|
25
33
|
return { of: 'part', part: other[1], axis: other[2] };
|
|
@@ -30,10 +38,23 @@ export function followOf(asset, part, axis) {
|
|
|
30
38
|
if (!writer)
|
|
31
39
|
return { of: 'expression' };
|
|
32
40
|
/* Follow the one argument that is not a constant factor. */
|
|
33
|
-
const onward = writer.args.filter(arg => !
|
|
34
|
-
|
|
41
|
+
const onward = writer.args.filter(arg => !constant(arg));
|
|
42
|
+
if (onward.length !== 1)
|
|
43
|
+
return { of: 'expression' };
|
|
44
|
+
/*
|
|
45
|
+
Only a product is a share of its source. A sum with a number is not (V2's 「fill what is left」 is the size plus a
|
|
46
|
+
negative gap -- ROBOT_ARM's tool flange), nor anything else; those read as worked out some other way.
|
|
47
|
+
*/
|
|
48
|
+
const factors = writer.args.filter(arg => constant(arg)).map(arg => constant(arg).value);
|
|
49
|
+
if (writer.op === 'mul@1')
|
|
50
|
+
times *= factors.reduce((a, b) => a * b, 1);
|
|
51
|
+
else if (writer.op === 'div@1' && writer.args[0] === onward[0] && factors.length === 1 && factors[0] !== 0)
|
|
52
|
+
times /= factors[0];
|
|
53
|
+
else if (factors.some(v => v !== 0) || !['mul@1', 'div@1', 'add@1', 'sub@1'].includes(writer.op))
|
|
54
|
+
return { of: 'expression' };
|
|
55
|
+
return walk(onward[0]);
|
|
35
56
|
};
|
|
36
|
-
return walk(
|
|
57
|
+
return walk(start);
|
|
37
58
|
}
|
|
38
59
|
/** Whether the asset declares an instance size on an axis — only then can a dimension follow it. */
|
|
39
60
|
export const hasInstanceSize = (asset, axis) => modelOf(asset).inputs.some(i => i.id === `size.${axis}` && i.role === 'design');
|
|
@@ -43,8 +64,37 @@ export function fastenedTo(asset, part) {
|
|
|
43
64
|
}
|
|
44
65
|
/** Whether a dimension of this part can follow something — box-shaped, whose three sizes are X, Y and Z. */
|
|
45
66
|
export function canFollow(asset, part) {
|
|
67
|
+
return followableAxes(asset, part).length > 0;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The axes whose size can follow something, by the shape's own measures: a box's X · Y · Z; a cylinder's radius (read
|
|
71
|
+
* as X) and length (Y) -- its shape takes those two, so it has no Z of its own (V2's sizing cards had the cylinder's
|
|
72
|
+
* two as well).
|
|
73
|
+
*/
|
|
74
|
+
export function followableAxes(asset, part) {
|
|
46
75
|
const shape = modelOf(asset).nodes.find(n => n.id === `${part}.shape`);
|
|
47
|
-
|
|
76
|
+
if (!shape)
|
|
77
|
+
return [];
|
|
78
|
+
if (shape.op === 'box-shape@1' || shape.op === 'rounded-box@1')
|
|
79
|
+
return [...AXES];
|
|
80
|
+
if (shape.op === 'cylinder-shape@1')
|
|
81
|
+
return ['x', 'y'];
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
/** What a part's own measure on an axis is now (a box's side, a cylinder's radius or length), at the design as it stands. */
|
|
85
|
+
export function dimensionNow(asset, part, axis) {
|
|
86
|
+
const m = modelOf(asset);
|
|
87
|
+
const ref = m.nodes.find(n => n.id === `${part}.shape`)?.args[AXES.indexOf(axis)];
|
|
88
|
+
if (!ref)
|
|
89
|
+
return undefined;
|
|
90
|
+
try {
|
|
91
|
+
const values = compileV3Graph(asset.document.model).evaluate({ ...asset.designInputs, ...asset.stateDefaults }).values;
|
|
92
|
+
const value = values[ref];
|
|
93
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
48
98
|
}
|
|
49
99
|
/** Whether `attach` can take this part: a box, or a cylinder (figure-model measures its faces since 0.1.47). */
|
|
50
100
|
export function canFasten(asset, part) {
|
|
@@ -64,6 +114,16 @@ export function positionsWorkedOut(asset, part) {
|
|
|
64
114
|
const constant = (ref) => m.constants.some(c => c.id === ref);
|
|
65
115
|
return AXES.filter((_axis, i) => !own(pose.args[i]) && !constant(pose.args[i]));
|
|
66
116
|
}
|
|
117
|
+
/**
|
|
118
|
+
* Whether the part's position on an axis is a share of the instance size (`scale-position`): `size.<axis> × share`.
|
|
119
|
+
* Such a position is worked out too, but it already grows with the figure, so it is not offered again.
|
|
120
|
+
*/
|
|
121
|
+
export function positionIsShare(asset, part, axis) {
|
|
122
|
+
const m = modelOf(asset);
|
|
123
|
+
const ref = m.nodes.find(n => n.id === `${part}.pose`)?.args[AXES.indexOf(axis)];
|
|
124
|
+
const writer = ref ? m.nodes.find(n => Object.values(n.outputs).includes(ref)) : undefined;
|
|
125
|
+
return !!writer && writer.op === 'mul@1' && writer.args[0] === `size.${axis}` && m.constants.some(c => c.id === writer.args[1]);
|
|
126
|
+
}
|
|
67
127
|
export function motionOf(asset, part) {
|
|
68
128
|
const m = modelOf(asset);
|
|
69
129
|
const node = m.nodes.find(n => n.id === `${part}.motion`);
|
|
@@ -98,7 +158,7 @@ export function choiceOf(follow) {
|
|
|
98
158
|
case 'own':
|
|
99
159
|
return 'own';
|
|
100
160
|
case 'instance':
|
|
101
|
-
return `instance:${follow.axis}`;
|
|
161
|
+
return follow.times === 1 ? `instance:${follow.axis}` : `share:${follow.axis}`;
|
|
102
162
|
case 'part':
|
|
103
163
|
return `part:${follow.part}:${follow.axis}`;
|
|
104
164
|
case 'input':
|
|
@@ -107,4 +167,61 @@ export function choiceOf(follow) {
|
|
|
107
167
|
return 'expression';
|
|
108
168
|
}
|
|
109
169
|
}
|
|
170
|
+
/** The faces a place can sit on for this part: a box's six, a rounded box's top and bottom, none for a round body. */
|
|
171
|
+
export function placeFacesOf(asset, part) {
|
|
172
|
+
const shape = modelOf(asset).nodes.find(n => n.id === `${part}.shape`);
|
|
173
|
+
if (shape?.op === 'box-shape@1')
|
|
174
|
+
return ['left', 'right', 'bottom', 'top', 'back', 'front'];
|
|
175
|
+
if (shape?.op === 'rounded-box@1')
|
|
176
|
+
return ['bottom', 'top'];
|
|
177
|
+
return [];
|
|
178
|
+
}
|
|
179
|
+
/** The places things are held that sit on this part. */
|
|
180
|
+
export function placesOn(asset, part) {
|
|
181
|
+
return v3BindingsOf(asset).filter(one => one.part === part);
|
|
182
|
+
}
|
|
183
|
+
/** An id for a new place on this part, not yet taken: `<part>-<role>`, then `-2`, `-3`. */
|
|
184
|
+
export function placeIdFor(asset, part, role) {
|
|
185
|
+
const taken = new Set(asset.document.capabilities.bindings.map(b => b.id));
|
|
186
|
+
const base = `${part}-${role}`;
|
|
187
|
+
if (!taken.has(base))
|
|
188
|
+
return base;
|
|
189
|
+
let n = 2;
|
|
190
|
+
while (taken.has(`${base}-${n}`))
|
|
191
|
+
n++;
|
|
192
|
+
return `${base}-${n}`;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Whether a board will hand this figure anything: a place is a slot only on a figure that declares it holds things
|
|
196
|
+
* (`Capacity`, or `FlowNode`, which includes it). Without it the places are declared and nothing arrives.
|
|
197
|
+
*/
|
|
198
|
+
export const holdsThings = (asset) => (asset.document.capabilities.names ?? []).some(n => n === 'Capacity' || n === 'FlowNode');
|
|
199
|
+
/** What `accepts` a person typed: names split on commas, trimmed, blanks and repeats dropped. */
|
|
200
|
+
export const acceptsOf = (typed) => [...new Set(typed.split(',').map(one => one.trim()).filter(Boolean))];
|
|
201
|
+
/**
|
|
202
|
+
* The sizes a board may give this figure: the range it declares now, and the range measured afresh -- the default
|
|
203
|
+
* (1 mm to ten times the authored size) narrowed only where the figure cannot follow, by the converter's own functions.
|
|
204
|
+
* A range measured when a part filled what the others left (ROBOT_ARM: Y from 1,854 mm) stays narrow after the part is
|
|
205
|
+
* made to grow with the figure; measuring again gives the figure its whole range back. Nothing when no size is declared.
|
|
206
|
+
*/
|
|
207
|
+
export function instanceRangeOf(asset) {
|
|
208
|
+
const inputs = modelOf(asset).inputs;
|
|
209
|
+
const declared = {};
|
|
210
|
+
const authored = {};
|
|
211
|
+
for (const axis of AXES) {
|
|
212
|
+
const input = inputs.find(i => i.id === `size.${axis}` && i.role === 'design');
|
|
213
|
+
const at = asset.designInputs[`size.${axis}`];
|
|
214
|
+
if (!input || typeof at !== 'number')
|
|
215
|
+
return undefined;
|
|
216
|
+
declared[axis] = { min: input.min ?? 0, max: input.max ?? Infinity };
|
|
217
|
+
authored[axis] = at;
|
|
218
|
+
}
|
|
219
|
+
const start = defaultInstanceSizeRange(authored);
|
|
220
|
+
const measured = foldInstanceSizeRange(start, v3InstanceSizeLimitsOf(asset, start)).range;
|
|
221
|
+
return { declared, measured };
|
|
222
|
+
}
|
|
223
|
+
/** Whether measuring again would change the declared range (to a micrometre). */
|
|
224
|
+
export function rangeDiffers(a, b) {
|
|
225
|
+
return AXES.some(axis => Math.abs(a[axis].min - b[axis].min) > 1e-3 || Math.abs(a[axis].max - b[axis].max) > 1e-3);
|
|
226
|
+
}
|
|
110
227
|
//# sourceMappingURL=v3-links.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"v3-links.js","sourceRoot":"","sources":["../../client/modeller/v3-links.ts"],"names":[],"mappings":"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","sourcesContent":["import { v3AttachmentsOf } from '@hatiolab/figure-model'\nimport type { V3Asset } from '@hatiolab/figure-model'\n\n/*\n * What a V3 part is tied to — the readers behind the parts' size response, fastening and motion sections (parity rows\n * ④ ⑤ ⑥). Kept apart from the element so they can be tested without a browser.\n */\n\nexport type Axis = 'x' | 'y' | 'z'\nexport const AXES: readonly Axis[] = ['x', 'y', 'z']\n\ninterface Node {\n id: string\n op: string\n args: string[]\n outputs: Record<string, string>\n}\nconst modelOf = (asset: V3Asset) =>\n asset.document.model as unknown as { nodes: Node[]; inputs: Array<{ id: string; role?: string; unit?: string; min?: number; max?: number }>; constants: Array<{ id: string; value: number }> }\n\n/** What one dimension of a part is worked out from. */\nexport type Follow =\n | { of: 'own' }\n | { of: 'instance'; axis: Axis }\n | { of: 'part'; part: string; axis: Axis }\n | { of: 'input'; input: string }\n /** Worked out some other way (the converter writes expressions) — shown, not offered as a choice. */\n | { of: 'expression' }\n\n/**\n * `link-dimension` routes a part's dimension through `<part>.dimension.<axis>` and points its first argument at the\n * source, directly or through the nodes that scale it. Walking back through those nodes finds the source: the\n * instance size (`size.<axis>`), another part's dimension (`<part>.dimension.<axis>.value`) or a declared input.\n */\nexport function followOf(asset: V3Asset, part: string, axis: Axis): Follow {\n const m = modelOf(asset)\n const node = m.nodes.find(n => n.id === `${part}.dimension.${axis}`)\n if (!node) return { of: 'own' }\n const writerOf = (ref: string) => m.nodes.find(n => Object.values(n.outputs).includes(ref))\n const seen = new Set<string>()\n const walk = (ref: string): Follow => {\n if (seen.has(ref)) return { of: 'expression' }\n seen.add(ref)\n const instance = /^size\\.([xyz])$/.exec(ref)\n if (instance) return { of: 'instance', axis: instance[1] as Axis }\n const other = /^(.+)\\.dimension\\.([xyz])\\.value$/.exec(ref)\n if (other) return { of: 'part', part: other[1]!, axis: other[2] as Axis }\n const input = m.inputs.find(i => i.id === ref)\n if (input) return ref.startsWith(`${part}.`) ? { of: 'own' } : { of: 'input', input: ref }\n const writer = writerOf(ref)\n if (!writer) return { of: 'expression' }\n /* Follow the one argument that is not a constant factor. */\n const onward = writer.args.filter(arg => !m.constants.some(c => c.id === arg))\n return onward.length === 1 ? walk(onward[0]!) : { of: 'expression' }\n }\n return walk(node.args[0]!)\n}\n\n/** Whether the asset declares an instance size on an axis — only then can a dimension follow it. */\nexport const hasInstanceSize = (asset: V3Asset, axis: Axis) => modelOf(asset).inputs.some(i => i.id === `size.${axis}` && i.role === 'design')\n\n/** The part this one is fastened to, or `null`. The mounting plane counts as fastened (the model names no part). */\nexport function fastenedTo(asset: V3Asset, part: string): string | null {\n return v3AttachmentsOf(asset)[part] ?? null\n}\n\n/** Whether a dimension of this part can follow something — box-shaped, whose three sizes are X, Y and Z. */\nexport function canFollow(asset: V3Asset, part: string): boolean {\n const shape = modelOf(asset).nodes.find(n => n.id === `${part}.shape`)\n return !!shape && (shape.op === 'box-shape@1' || shape.op === 'rounded-box@1')\n}\n\n/** Whether `attach` can take this part: a box, or a cylinder (figure-model measures its faces since 0.1.47). */\nexport function canFasten(asset: V3Asset, part: string): boolean {\n const shape = modelOf(asset).nodes.find(n => n.id === `${part}.shape`)\n return canFollow(asset, part) || shape?.op === 'cylinder-shape@1'\n}\n\n/**\n * The axes on which the part's position is worked out from other values rather than being its own number — the\n * converter writes V2's anchor rules that way. `fix-position` makes one its own number where it is now.\n */\nexport function positionsWorkedOut(asset: V3Asset, part: string): Axis[] {\n const m = modelOf(asset)\n const pose = m.nodes.find(n => n.id === `${part}.pose`)\n if (!pose || pose.op !== 'rigid@1' || fastenedTo(asset, part)) return []\n const own = (ref: string) => ref.startsWith(`${part}.`) && m.inputs.some(i => i.id === ref && i.role === 'design')\n const constant = (ref: string) => m.constants.some(c => c.id === ref)\n return AXES.filter((_axis, i) => !own(pose.args[i]!) && !constant(pose.args[i]!))\n}\n\n/** How a part moves, if it does: slide or turn, along which axis, driven by which control. */\nexport interface Motion {\n kind: 'slide' | 'turn'\n axis: Axis | '?'\n control?: { id: string; unit?: string; min?: number; max?: number }\n}\n\nexport function motionOf(asset: V3Asset, part: string): Motion | undefined {\n const m = modelOf(asset)\n const node = m.nodes.find(n => n.id === `${part}.motion`)\n if (!node) return undefined\n const kind = node.op === 'axis-turn@1' ? 'turn' : 'slide'\n const unit = node.args.slice(0, 3).map(ref => m.constants.find(c => c.id === ref)?.value)\n const axis = AXES[unit.findIndex(v => v !== undefined && v !== 0)] ?? '?'\n /* The quantity is the fourth argument, a state input directly or through the nodes that scale it. */\n const quantity = node.args[3]\n const seen = new Set<string>()\n const control = (ref?: string): Motion['control'] => {\n if (!ref || seen.has(ref)) return undefined\n seen.add(ref)\n const input = m.inputs.find(i => i.id === ref && i.role === 'state')\n if (input) return { id: input.id, unit: input.unit, min: input.min, max: input.max }\n const writer = m.nodes.find(n => Object.values(n.outputs).includes(ref))\n for (const arg of writer?.args ?? []) {\n const found = control(arg)\n if (found) return found\n }\n return undefined\n }\n return { kind, axis, control: control(quantity) }\n}\n\n/** The select's value for what a dimension follows now. */\nexport function choiceOf(follow: Follow): string {\n switch (follow.of) {\n case 'own':\n return 'own'\n case 'instance':\n return `instance:${follow.axis}`\n case 'part':\n return `part:${follow.part}:${follow.axis}`\n case 'input':\n return `input:${follow.input}`\n case 'expression':\n return 'expression'\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"v3-links.js","sourceRoot":"","sources":["../../client/modeller/v3-links.ts"],"names":[],"mappings":"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","sourcesContent":["import { compileV3Graph, defaultInstanceSizeRange, foldInstanceSizeRange, v3AttachmentsOf, v3BindingsOf, v3InstanceSizeLimitsOf } from '@hatiolab/figure-model'\nimport type { V3SizeRange } from '@hatiolab/figure-model'\nimport type { V3Asset, V3BindingPlace, V3Face } from '@hatiolab/figure-model'\n\n/*\n * What a V3 part is tied to — the readers behind the parts' size response, fastening and motion sections (parity rows\n * ④ ⑤ ⑥). Kept apart from the element so they can be tested without a browser.\n */\n\nexport type Axis = 'x' | 'y' | 'z'\nexport const AXES: readonly Axis[] = ['x', 'y', 'z']\n\ninterface Node {\n id: string\n op: string\n args: string[]\n outputs: Record<string, string>\n}\nconst modelOf = (asset: V3Asset) =>\n asset.document.model as unknown as { nodes: Node[]; inputs: Array<{ id: string; role?: string; unit?: string; min?: number; max?: number }>; constants: Array<{ id: string; value: number }> }\n\n/** What one dimension of a part is worked out from. */\nexport type Follow =\n | { of: 'own' }\n /** `times` 1 is the instance size itself; any other is a share of it -- V2's proportional rule. */\n | { of: 'instance'; axis: Axis; times: number }\n | { of: 'part'; part: string; axis: Axis }\n | { of: 'input'; input: string }\n /** Worked out some other way (the converter writes expressions) — shown, not offered as a choice. */\n | { of: 'expression' }\n\n/**\n * `link-dimension` routes a part's dimension through `<part>.dimension.<axis>` and points its first argument at the\n * source, directly or through the nodes that scale it. Walking back through those nodes finds the source: the\n * instance size (`size.<axis>`), another part's dimension (`<part>.dimension.<axis>.value`) or a declared input.\n */\nexport function followOf(asset: V3Asset, part: string, axis: Axis): Follow {\n const m = modelOf(asset)\n /*\n A link routes the dimension through `<part>.dimension.<axis>`; before any link the shape reads its measure directly,\n and that may already be worked out -- the converter writes V2's rules there (ROBOT_ARM's flange filled what was left).\n */\n const node = m.nodes.find(n => n.id === `${part}.dimension.${axis}`)\n const start = node ? node.args[0] : m.nodes.find(n => n.id === `${part}.shape`)?.args[AXES.indexOf(axis)]\n if (!start || m.constants.some(c => c.id === start)) return { of: 'own' }\n const writerOf = (ref: string) => m.nodes.find(n => Object.values(n.outputs).includes(ref))\n const seen = new Set<string>()\n const constant = (ref: string) => m.constants.find(c => c.id === ref)\n /* The constant factors met on the way back: a share of the instance size is `size × share`. */\n let times = 1\n const walk = (ref: string): Follow => {\n if (seen.has(ref)) return { of: 'expression' }\n seen.add(ref)\n const instance = /^size\\.([xyz])$/.exec(ref)\n if (instance) return { of: 'instance', axis: instance[1] as Axis, times: Math.round(times * 1e6) / 1e6 }\n const other = /^(.+)\\.dimension\\.([xyz])\\.value$/.exec(ref)\n if (other) return { of: 'part', part: other[1]!, axis: other[2] as Axis }\n const input = m.inputs.find(i => i.id === ref)\n if (input) return ref.startsWith(`${part}.`) ? { of: 'own' } : { of: 'input', input: ref }\n const writer = writerOf(ref)\n if (!writer) return { of: 'expression' }\n /* Follow the one argument that is not a constant factor. */\n const onward = writer.args.filter(arg => !constant(arg))\n if (onward.length !== 1) return { of: 'expression' }\n /*\n Only a product is a share of its source. A sum with a number is not (V2's 「fill what is left」 is the size plus a\n negative gap -- ROBOT_ARM's tool flange), nor anything else; those read as worked out some other way.\n */\n const factors = writer.args.filter(arg => constant(arg)).map(arg => constant(arg)!.value)\n if (writer.op === 'mul@1') times *= factors.reduce((a, b) => a * b, 1)\n else if (writer.op === 'div@1' && writer.args[0] === onward[0] && factors.length === 1 && factors[0] !== 0) times /= factors[0]!\n else if (factors.some(v => v !== 0) || !['mul@1', 'div@1', 'add@1', 'sub@1'].includes(writer.op)) return { of: 'expression' }\n return walk(onward[0]!)\n }\n return walk(start)\n}\n\n/** Whether the asset declares an instance size on an axis — only then can a dimension follow it. */\nexport const hasInstanceSize = (asset: V3Asset, axis: Axis) => modelOf(asset).inputs.some(i => i.id === `size.${axis}` && i.role === 'design')\n\n/** The part this one is fastened to, or `null`. The mounting plane counts as fastened (the model names no part). */\nexport function fastenedTo(asset: V3Asset, part: string): string | null {\n return v3AttachmentsOf(asset)[part] ?? null\n}\n\n/** Whether a dimension of this part can follow something — box-shaped, whose three sizes are X, Y and Z. */\nexport function canFollow(asset: V3Asset, part: string): boolean {\n return followableAxes(asset, part).length > 0\n}\n\n/**\n * The axes whose size can follow something, by the shape's own measures: a box's X · Y · Z; a cylinder's radius (read\n * as X) and length (Y) -- its shape takes those two, so it has no Z of its own (V2's sizing cards had the cylinder's\n * two as well).\n */\nexport function followableAxes(asset: V3Asset, part: string): Axis[] {\n const shape = modelOf(asset).nodes.find(n => n.id === `${part}.shape`)\n if (!shape) return []\n if (shape.op === 'box-shape@1' || shape.op === 'rounded-box@1') return [...AXES]\n if (shape.op === 'cylinder-shape@1') return ['x', 'y']\n return []\n}\n\n/** What a part's own measure on an axis is now (a box's side, a cylinder's radius or length), at the design as it stands. */\nexport function dimensionNow(asset: V3Asset, part: string, axis: Axis): number | undefined {\n const m = modelOf(asset)\n const ref = m.nodes.find(n => n.id === `${part}.shape`)?.args[AXES.indexOf(axis)]\n if (!ref) return undefined\n try {\n const values = compileV3Graph(asset.document.model as never).evaluate({ ...asset.designInputs, ...asset.stateDefaults }).values as Record<string, number>\n const value = values[ref]\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined\n } catch {\n return undefined\n }\n}\n\n/** Whether `attach` can take this part: a box, or a cylinder (figure-model measures its faces since 0.1.47). */\nexport function canFasten(asset: V3Asset, part: string): boolean {\n const shape = modelOf(asset).nodes.find(n => n.id === `${part}.shape`)\n return canFollow(asset, part) || shape?.op === 'cylinder-shape@1'\n}\n\n/**\n * The axes on which the part's position is worked out from other values rather than being its own number — the\n * converter writes V2's anchor rules that way. `fix-position` makes one its own number where it is now.\n */\nexport function positionsWorkedOut(asset: V3Asset, part: string): Axis[] {\n const m = modelOf(asset)\n const pose = m.nodes.find(n => n.id === `${part}.pose`)\n if (!pose || pose.op !== 'rigid@1' || fastenedTo(asset, part)) return []\n const own = (ref: string) => ref.startsWith(`${part}.`) && m.inputs.some(i => i.id === ref && i.role === 'design')\n const constant = (ref: string) => m.constants.some(c => c.id === ref)\n return AXES.filter((_axis, i) => !own(pose.args[i]!) && !constant(pose.args[i]!))\n}\n\n/**\n * Whether the part's position on an axis is a share of the instance size (`scale-position`): `size.<axis> × share`.\n * Such a position is worked out too, but it already grows with the figure, so it is not offered again.\n */\nexport function positionIsShare(asset: V3Asset, part: string, axis: Axis): boolean {\n const m = modelOf(asset)\n const ref = m.nodes.find(n => n.id === `${part}.pose`)?.args[AXES.indexOf(axis)]\n const writer = ref ? m.nodes.find(n => Object.values(n.outputs).includes(ref)) : undefined\n return !!writer && writer.op === 'mul@1' && writer.args[0] === `size.${axis}` && m.constants.some(c => c.id === writer.args[1])\n}\n\n/** How a part moves, if it does: slide or turn, along which axis, driven by which control. */\nexport interface Motion {\n kind: 'slide' | 'turn'\n axis: Axis | '?'\n control?: { id: string; unit?: string; min?: number; max?: number }\n}\n\nexport function motionOf(asset: V3Asset, part: string): Motion | undefined {\n const m = modelOf(asset)\n const node = m.nodes.find(n => n.id === `${part}.motion`)\n if (!node) return undefined\n const kind = node.op === 'axis-turn@1' ? 'turn' : 'slide'\n const unit = node.args.slice(0, 3).map(ref => m.constants.find(c => c.id === ref)?.value)\n const axis = AXES[unit.findIndex(v => v !== undefined && v !== 0)] ?? '?'\n /* The quantity is the fourth argument, a state input directly or through the nodes that scale it. */\n const quantity = node.args[3]\n const seen = new Set<string>()\n const control = (ref?: string): Motion['control'] => {\n if (!ref || seen.has(ref)) return undefined\n seen.add(ref)\n const input = m.inputs.find(i => i.id === ref && i.role === 'state')\n if (input) return { id: input.id, unit: input.unit, min: input.min, max: input.max }\n const writer = m.nodes.find(n => Object.values(n.outputs).includes(ref))\n for (const arg of writer?.args ?? []) {\n const found = control(arg)\n if (found) return found\n }\n return undefined\n }\n return { kind, axis, control: control(quantity) }\n}\n\n/** The select's value for what a dimension follows now. */\nexport function choiceOf(follow: Follow): string {\n switch (follow.of) {\n case 'own':\n return 'own'\n case 'instance':\n return follow.times === 1 ? `instance:${follow.axis}` : `share:${follow.axis}`\n case 'part':\n return `part:${follow.part}:${follow.axis}`\n case 'input':\n return `input:${follow.input}`\n case 'expression':\n return 'expression'\n }\n}\n\n/** The faces a place can sit on for this part: a box's six, a rounded box's top and bottom, none for a round body. */\nexport function placeFacesOf(asset: V3Asset, part: string): V3Face[] {\n const shape = modelOf(asset).nodes.find(n => n.id === `${part}.shape`)\n if (shape?.op === 'box-shape@1') return ['left', 'right', 'bottom', 'top', 'back', 'front']\n if (shape?.op === 'rounded-box@1') return ['bottom', 'top']\n return []\n}\n\n/** The places things are held that sit on this part. */\nexport function placesOn(asset: V3Asset, part: string): V3BindingPlace[] {\n return v3BindingsOf(asset).filter(one => one.part === part)\n}\n\n/** An id for a new place on this part, not yet taken: `<part>-<role>`, then `-2`, `-3`. */\nexport function placeIdFor(asset: V3Asset, part: string, role: 'seat' | 'grip'): string {\n const taken = new Set(asset.document.capabilities.bindings.map(b => b.id))\n const base = `${part}-${role}`\n if (!taken.has(base)) return base\n let n = 2\n while (taken.has(`${base}-${n}`)) n++\n return `${base}-${n}`\n}\n\n/**\n * Whether a board will hand this figure anything: a place is a slot only on a figure that declares it holds things\n * (`Capacity`, or `FlowNode`, which includes it). Without it the places are declared and nothing arrives.\n */\nexport const holdsThings = (asset: V3Asset) => (asset.document.capabilities.names ?? []).some(n => n === 'Capacity' || n === 'FlowNode')\n\n/** What `accepts` a person typed: names split on commas, trimmed, blanks and repeats dropped. */\nexport const acceptsOf = (typed: string) => [...new Set(typed.split(',').map(one => one.trim()).filter(Boolean))]\n\n/**\n * The sizes a board may give this figure: the range it declares now, and the range measured afresh -- the default\n * (1 mm to ten times the authored size) narrowed only where the figure cannot follow, by the converter's own functions.\n * A range measured when a part filled what the others left (ROBOT_ARM: Y from 1,854 mm) stays narrow after the part is\n * made to grow with the figure; measuring again gives the figure its whole range back. Nothing when no size is declared.\n */\nexport function instanceRangeOf(asset: V3Asset): { declared: V3SizeRange; measured: V3SizeRange } | undefined {\n const inputs = modelOf(asset).inputs\n const declared = {} as V3SizeRange\n const authored = {} as Record<Axis, number>\n for (const axis of AXES) {\n const input = inputs.find(i => i.id === `size.${axis}` && i.role === 'design')\n const at = asset.designInputs[`size.${axis}`]\n if (!input || typeof at !== 'number') return undefined\n declared[axis] = { min: input.min ?? 0, max: input.max ?? Infinity }\n authored[axis] = at\n }\n const start = defaultInstanceSizeRange(authored)\n const measured = foldInstanceSizeRange(start, v3InstanceSizeLimitsOf(asset, start)).range\n return { declared, measured }\n}\n\n/** Whether measuring again would change the declared range (to a micrometre). */\nexport function rangeDiffers(a: V3SizeRange, b: V3SizeRange): boolean {\n return AXES.some(axis => Math.abs(a[axis].min - b[axis].min) > 1e-3 || Math.abs(a[axis].max - b[axis].max) > 1e-3)\n}\n\n"]}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { V3Asset, V3PartEdit } from '@hatiolab/figure-model';
|
|
2
|
+
import { type V3RenderPart } from './v3-renderer.js';
|
|
3
|
+
export interface LiveValues {
|
|
4
|
+
/** Input values in hand, design or state, by input id. */
|
|
5
|
+
values: Record<string, number>;
|
|
6
|
+
/** A part edit in hand, not yet committed. */
|
|
7
|
+
edit?: V3PartEdit;
|
|
8
|
+
}
|
|
9
|
+
export declare const NOTHING_LIVE: LiveValues;
|
|
10
|
+
/** Known inputs with a number, split by what they are. An id the figure does not declare is dropped. */
|
|
11
|
+
export declare function liveOverridesOf(asset: V3Asset, values: Record<string, number>): {
|
|
12
|
+
design: Record<string, number>;
|
|
13
|
+
state: Record<string, number>;
|
|
14
|
+
};
|
|
15
|
+
export declare function isLive(live: LiveValues): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* The bodies to draw with what is in hand, or null when nothing is -- then the evaluation as it stands is drawn.
|
|
18
|
+
*
|
|
19
|
+
* Also null when the figure cannot take the value in hand (a size of 0 on the way to typing 0.5, a glow the material
|
|
20
|
+
* refuses): the canvas keeps the figure as it stands until the release, and the release reports why through the same
|
|
21
|
+
* refusal every committed edit gets. A preview never invents a figure the commit would not make.
|
|
22
|
+
*/
|
|
23
|
+
export declare function livePartsOf(asset: V3Asset, live: LiveValues): V3RenderPart[] | null;
|
|
24
|
+
/**
|
|
25
|
+
* How much one pixel of a label drag moves a value: a degree for an angle, a hundredth for a ratio, and for a length a
|
|
26
|
+
* step that suits its size -- a millimetre under 100, ten over 1,000 -- so a 12 m wall and a 5 mm pin both scrub at a
|
|
27
|
+
* usable pace. Shift makes it ten times finer.
|
|
28
|
+
*/
|
|
29
|
+
export declare function scrubStep(unit: string, value: number, fine?: boolean): number;
|
|
30
|
+
/** A scrubbed value on the step's grid, so a drag does not leave 1203.0000000002 behind. */
|
|
31
|
+
export declare function scrubbed(start: number, pixels: number, step: number): number;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { compileV3Asset, editV3Part } from '@hatiolab/figure-model';
|
|
2
|
+
import { drawableV3Parts } from './v3-renderer.js';
|
|
3
|
+
export const NOTHING_LIVE = { values: {} };
|
|
4
|
+
/** Known inputs with a number, split by what they are. An id the figure does not declare is dropped. */
|
|
5
|
+
export function liveOverridesOf(asset, values) {
|
|
6
|
+
const roles = new Map(asset.document.model.inputs.map(i => [i.id, i.role]));
|
|
7
|
+
const design = {}, state = {};
|
|
8
|
+
for (const [id, value] of Object.entries(values)) {
|
|
9
|
+
if (!Number.isFinite(value))
|
|
10
|
+
continue;
|
|
11
|
+
const role = roles.get(id);
|
|
12
|
+
if (role === 'state')
|
|
13
|
+
state[id] = value;
|
|
14
|
+
else if (role !== undefined)
|
|
15
|
+
design[id] = value;
|
|
16
|
+
}
|
|
17
|
+
return { design, state };
|
|
18
|
+
}
|
|
19
|
+
export function isLive(live) {
|
|
20
|
+
return !!live.edit || Object.keys(live.values).length > 0;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The bodies to draw with what is in hand, or null when nothing is -- then the evaluation as it stands is drawn.
|
|
24
|
+
*
|
|
25
|
+
* Also null when the figure cannot take the value in hand (a size of 0 on the way to typing 0.5, a glow the material
|
|
26
|
+
* refuses): the canvas keeps the figure as it stands until the release, and the release reports why through the same
|
|
27
|
+
* refusal every committed edit gets. A preview never invents a figure the commit would not make.
|
|
28
|
+
*/
|
|
29
|
+
export function livePartsOf(asset, live) {
|
|
30
|
+
if (!isLive(live))
|
|
31
|
+
return null;
|
|
32
|
+
const { design, state } = liveOverridesOf(asset, live.values);
|
|
33
|
+
try {
|
|
34
|
+
let next = live.edit ? editV3Part(asset, live.edit) : asset;
|
|
35
|
+
if (Object.keys(design).length)
|
|
36
|
+
next = { ...next, designInputs: { ...next.designInputs, ...design } };
|
|
37
|
+
return drawableV3Parts(compileV3Asset(next).evaluate(state).geometry);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* How much one pixel of a label drag moves a value: a degree for an angle, a hundredth for a ratio, and for a length a
|
|
45
|
+
* step that suits its size -- a millimetre under 100, ten over 1,000 -- so a 12 m wall and a 5 mm pin both scrub at a
|
|
46
|
+
* usable pace. Shift makes it ten times finer.
|
|
47
|
+
*/
|
|
48
|
+
export function scrubStep(unit, value, fine = false) {
|
|
49
|
+
const base = unit === 'deg' ? 1 : unit === 'ratio' ? 0.01 : Math.max(1, Math.pow(10, Math.floor(Math.log10(Math.max(1, Math.abs(value)))) - 2));
|
|
50
|
+
return fine ? base / 10 : base;
|
|
51
|
+
}
|
|
52
|
+
/** A scrubbed value on the step's grid, so a drag does not leave 1203.0000000002 behind. */
|
|
53
|
+
export function scrubbed(start, pixels, step) {
|
|
54
|
+
const value = start + pixels * step;
|
|
55
|
+
const digits = Math.max(0, -Math.floor(Math.log10(step)));
|
|
56
|
+
return Number(value.toFixed(digits));
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=v3-live.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"v3-live.js","sourceRoot":"","sources":["../../client/modeller/v3-live.ts"],"names":[],"mappings":"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","sourcesContent":["import { compileV3Asset, editV3Part } from '@hatiolab/figure-model'\nimport type { V3Asset, V3PartEdit } from '@hatiolab/figure-model'\n\nimport { drawableV3Parts, type V3RenderPart } from './v3-renderer.js'\n\n/*\n * The figure while a value is in hand: evaluated with the values being typed or dragged, drawn at once and written\n * nowhere. The panels committed on release only, so the canvas stood still while a number was typed, a spinner held,\n * a label scrubbed, a slider or the glow dragged (user, 2026-09-24: \"make the editor move naturally while the value\n * changes\"). The value in hand shows here; the release is the one step of the history.\n *\n * What is in hand is either input values -- a design value (a size, a position) or a pose -- or one part edit (the\n * glow of a material). A design value is a change of the figure's design, so it is shown by evaluating a copy with\n * that value; a pose is a state override, as the preview gives it.\n */\n\nexport interface LiveValues {\n /** Input values in hand, design or state, by input id. */\n values: Record<string, number>\n /** A part edit in hand, not yet committed. */\n edit?: V3PartEdit\n}\n\nexport const NOTHING_LIVE: LiveValues = { values: {} }\n\n/** Known inputs with a number, split by what they are. An id the figure does not declare is dropped. */\nexport function liveOverridesOf(asset: V3Asset, values: Record<string, number>): { design: Record<string, number>; state: Record<string, number> } {\n const roles = new Map(asset.document.model.inputs.map(i => [i.id, i.role]))\n const design: Record<string, number> = {},\n state: Record<string, number> = {}\n for (const [id, value] of Object.entries(values)) {\n if (!Number.isFinite(value)) continue\n const role = roles.get(id)\n if (role === 'state') state[id] = value\n else if (role !== undefined) design[id] = value\n }\n return { design, state }\n}\n\nexport function isLive(live: LiveValues): boolean {\n return !!live.edit || Object.keys(live.values).length > 0\n}\n\n/**\n * The bodies to draw with what is in hand, or null when nothing is -- then the evaluation as it stands is drawn.\n *\n * Also null when the figure cannot take the value in hand (a size of 0 on the way to typing 0.5, a glow the material\n * refuses): the canvas keeps the figure as it stands until the release, and the release reports why through the same\n * refusal every committed edit gets. A preview never invents a figure the commit would not make.\n */\nexport function livePartsOf(asset: V3Asset, live: LiveValues): V3RenderPart[] | null {\n if (!isLive(live)) return null\n const { design, state } = liveOverridesOf(asset, live.values)\n try {\n let next = live.edit ? editV3Part(asset, live.edit) : asset\n if (Object.keys(design).length) next = { ...next, designInputs: { ...next.designInputs, ...design } }\n return drawableV3Parts(compileV3Asset(next).evaluate(state).geometry)\n } catch {\n return null\n }\n}\n\n/**\n * How much one pixel of a label drag moves a value: a degree for an angle, a hundredth for a ratio, and for a length a\n * step that suits its size -- a millimetre under 100, ten over 1,000 -- so a 12 m wall and a 5 mm pin both scrub at a\n * usable pace. Shift makes it ten times finer.\n */\nexport function scrubStep(unit: string, value: number, fine = false): number {\n const base =\n unit === 'deg' ? 1 : unit === 'ratio' ? 0.01 : Math.max(1, Math.pow(10, Math.floor(Math.log10(Math.max(1, Math.abs(value)))) - 2))\n return fine ? base / 10 : base\n}\n\n/** A scrubbed value on the step's grid, so a drag does not leave 1203.0000000002 behind. */\nexport function scrubbed(start: number, pixels: number, step: number): number {\n const value = start + pixels * step\n const digits = Math.max(0, -Math.floor(Math.log10(step)))\n return Number(value.toFixed(digits))\n}\n"]}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { V3_BUDGET_KEYS, V3_GRADE_SCALE, v3GradeOf } from '@hatiolab/figure-model';
|
|
2
|
+
import type { V3Asset, V3AssetGeometry, V3BudgetKey, V3BudgetUse, V3Cost, V3Geometry, V3Score } from '@hatiolab/figure-model';
|
|
3
|
+
import type { FigureCapacity } from './scene-budget.js';
|
|
4
|
+
export { V3_BUDGET_KEYS, V3_GRADE_SCALE as GRADE_SCALE, v3GradeOf as gradeOf };
|
|
5
|
+
export type { V3BudgetKey, V3BudgetUse, V3Score };
|
|
6
|
+
export type V3AxisKey = 'scale' | 'consistency' | 'reuse' | 'detail' | 'reactive' | 'readable';
|
|
7
|
+
/** One maturity axis. The same shape V2's `maturity.ts` gave the report. */
|
|
8
|
+
export interface V3MaturityAxis {
|
|
9
|
+
key: V3AxisKey;
|
|
10
|
+
/** 0–1. Absent when the axis cannot be measured. */
|
|
11
|
+
ratio?: number;
|
|
12
|
+
reading?: string;
|
|
13
|
+
/** A reading that is words, not a number. */
|
|
14
|
+
readingKey?: string;
|
|
15
|
+
because?: string;
|
|
16
|
+
/** Why the axis is not measured. When present there is no ratio — an unmeasured axis is not a zero. */
|
|
17
|
+
blocked?: string;
|
|
18
|
+
}
|
|
19
|
+
/** Instances on one screen, and what each series costs at that count. */
|
|
20
|
+
export interface V3CurvePoint {
|
|
21
|
+
instances: number;
|
|
22
|
+
triangles: number;
|
|
23
|
+
drawCalls: number;
|
|
24
|
+
/** Vertex buffers: one per distinct shape, shared by every instance of the same size. */
|
|
25
|
+
geometries: number;
|
|
26
|
+
materials: number;
|
|
27
|
+
}
|
|
28
|
+
export interface V3Report {
|
|
29
|
+
cost: V3Cost;
|
|
30
|
+
capacity: FigureCapacity;
|
|
31
|
+
score: V3Score;
|
|
32
|
+
/** Distinct shapes and materials among the drawn bodies — V2's 「그리는 비용」 rows. */
|
|
33
|
+
distinctShapes: number;
|
|
34
|
+
distinctMaterials: number;
|
|
35
|
+
reuse: {
|
|
36
|
+
distinct: number;
|
|
37
|
+
parts: number;
|
|
38
|
+
saved: number;
|
|
39
|
+
};
|
|
40
|
+
axes: V3MaturityAxis[];
|
|
41
|
+
curve: V3CurvePoint[];
|
|
42
|
+
}
|
|
43
|
+
/** V2's board target for the scale axis: 500 assets on one drawing (design document §4.15). */
|
|
44
|
+
export declare const BOARD_ASSETS = 500;
|
|
45
|
+
/**
|
|
46
|
+
* The report for one asset, or nothing when no body is drawn — then there is nothing to measure, and the screen
|
|
47
|
+
* says so rather than drawing zeros.
|
|
48
|
+
*
|
|
49
|
+
* Evaluated at the asset's own design and state defaults, as `v3CostOf` is.
|
|
50
|
+
*/
|
|
51
|
+
export declare function v3ReportOf(asset: V3Asset): V3Report | undefined;
|
|
52
|
+
/**
|
|
53
|
+
* The key things-scene shares a body's geometry by (`figure-v3-geometry.ts` `keyOf`): provider, dimensions to a
|
|
54
|
+
* micrometre, segments (12 when unset), and a polygon's corners.
|
|
55
|
+
*/
|
|
56
|
+
export declare function shapeKeyOf(body: V3AssetGeometry | V3Geometry): string;
|
|
57
|
+
/**
|
|
58
|
+
* The key things-scene shares a material by (`figure-v3-rig.ts` `materialFor`), within one asset: token, preset,
|
|
59
|
+
* transparency, and the light when it is on. The colour follows from the token, so it is not a second key.
|
|
60
|
+
*/
|
|
61
|
+
export declare function materialKeyOf(body: V3AssetGeometry): string;
|
|
62
|
+
/** 1 · 2 · 5 per decade, as V2's trend (`cost-chart.ts`): even steps on a log axis. */
|
|
63
|
+
export declare function decadeSteps(from: number, to: number): number[];
|
|
64
|
+
export type V3CurveSeries = 'triangles' | 'drawCalls' | 'geometries' | 'materials';
|
|
65
|
+
/**
|
|
66
|
+
* The four lines V2 drew: two that grow with the instance count and two that stay flat — the flat ones are what the
|
|
67
|
+
* instances share. Materials are dashed and drawn last, so a line under them still shows where the two meet.
|
|
68
|
+
*/
|
|
69
|
+
export declare const CURVE_SERIES: readonly {
|
|
70
|
+
key: V3CurveSeries;
|
|
71
|
+
flat: boolean;
|
|
72
|
+
dashed?: boolean;
|
|
73
|
+
}[];
|
|
74
|
+
/** V2's chart size. The chart is shown at this size in a sheet; shrunk into the side, its words are unreadable. */
|
|
75
|
+
export declare const CURVE_SIZE: {
|
|
76
|
+
readonly width: 680;
|
|
77
|
+
readonly height: 380;
|
|
78
|
+
readonly pad: {
|
|
79
|
+
readonly top: 52;
|
|
80
|
+
readonly right: 208;
|
|
81
|
+
readonly bottom: 44;
|
|
82
|
+
readonly left: 62;
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
export interface V3CurveLayout {
|
|
86
|
+
xTicks: {
|
|
87
|
+
x: number;
|
|
88
|
+
label: string;
|
|
89
|
+
}[];
|
|
90
|
+
yTicks: {
|
|
91
|
+
y: number;
|
|
92
|
+
label: string;
|
|
93
|
+
}[];
|
|
94
|
+
lines: {
|
|
95
|
+
key: V3CurveSeries;
|
|
96
|
+
d: string;
|
|
97
|
+
flat: boolean;
|
|
98
|
+
dashed: boolean;
|
|
99
|
+
last: number;
|
|
100
|
+
}[];
|
|
101
|
+
/** The value the flat lines share when they lie on one another, so the screen can say why one line is missing. */
|
|
102
|
+
overlap?: number;
|
|
103
|
+
}
|
|
104
|
+
/** 1, 10, 100 … written short. */
|
|
105
|
+
export declare function shortNumber(v: number): string;
|
|
106
|
+
/** Where V2's chart puts each line and tick. Both axes are logarithmic, as V2's are. */
|
|
107
|
+
export declare function curveLayout(points: V3CurvePoint[]): V3CurveLayout | undefined;
|
|
108
|
+
/** What the parts list shows under its title: the cost, how many fit, the grade and what decides it. */
|
|
109
|
+
export interface V3BudgetLine {
|
|
110
|
+
triangles: number;
|
|
111
|
+
groups: number;
|
|
112
|
+
instances: number;
|
|
113
|
+
grade: string;
|
|
114
|
+
tightest: {
|
|
115
|
+
key: string;
|
|
116
|
+
used: number;
|
|
117
|
+
limit: number;
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
export declare function budgetOf(report: V3Report | undefined): V3BudgetLine | undefined;
|