@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
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import { compileV3Asset, compileV3Document, LIMITS, SEGMENT_PRESETS, V3_BUDGET_KEYS, V3_GRADE_SCALE, v3CostOf, v3GradeOf, v3MaterialKeyOf, v3ScoreOf, v3WorldBoxesOf, v3WritersOf } from '@hatiolab/figure-model';
|
|
2
|
+
import { capacityFrom } from './scene-budget.js';
|
|
3
|
+
/*
|
|
4
|
+
* The V2 modeller's maturity report, read off a V3 asset (user, 2026-09-24: the V3 tab showed a count and nothing
|
|
5
|
+
* else — 「왜 이렇게 너프되었는지」).
|
|
6
|
+
*
|
|
7
|
+
* V2's report was `figure-report.ts` over `costOf` · `scoreOf` · `costCurveSvg` (figure-model) and `maturity.ts`
|
|
8
|
+
* (figure-ui). Those read a V2 blueprint. This module asks the same questions of the evaluated V3 asset and keeps
|
|
9
|
+
* V2's answers where the question still has one:
|
|
10
|
+
*
|
|
11
|
+
* grade A–D and its scale V2's bounds, over figure-model's `v3ScoreOf`: one screen's target shared per instance
|
|
12
|
+
* (groups, triangles) and V2's declared rule limits (independent, transparent)
|
|
13
|
+
* radar · axes V2's six axes; the two V2 could not measure stay unmeasured, detail has no V3 field
|
|
14
|
+
* drawing cost triangles, distinct shapes, distinct materials
|
|
15
|
+
* trend what grows with the instance count and what the instances share
|
|
16
|
+
*
|
|
17
|
+
* Kept apart from the element so it can be tested without a browser.
|
|
18
|
+
*
|
|
19
|
+
* ## What the V3 renderer shares, which is what the numbers count
|
|
20
|
+
*
|
|
21
|
+
* things-scene draws one mesh per drawn body (`figure-v3-rig.ts`) — no merging by material as V2 did — so a body is
|
|
22
|
+
* a draw call. It shares geometry by provider, dimensions, segments and profile (`figure-v3-geometry.ts`, `keyOf`)
|
|
23
|
+
* and materials by token, preset, transparency and light (`materialFor`). The keys below are those keys; a
|
|
24
|
+
* distinct-shape count that used another key would describe a renderer that does not exist.
|
|
25
|
+
*
|
|
26
|
+
* ## Budgets V3 does not have
|
|
27
|
+
*
|
|
28
|
+
* V2 had five. Two are not measured here, and are not shown as zero:
|
|
29
|
+
* - off-palette surfaces: the V3 material has no surface field, so there is nothing to count;
|
|
30
|
+
* - part count by detail level: V3 does not carry the detail level (chief architect, 2026-09-24, ADR-0087).
|
|
31
|
+
*/
|
|
32
|
+
/*
|
|
33
|
+
The grade, its budgets and its bounds are figure-model's (`v3ScoreOf`), so the catalogue grades a figure as this tab
|
|
34
|
+
does. The grade is the headroom against one screen's target (`LIMITS.screenInstances`), not a draw-call limit.
|
|
35
|
+
*/
|
|
36
|
+
export { V3_BUDGET_KEYS, V3_GRADE_SCALE as GRADE_SCALE, v3GradeOf as gradeOf };
|
|
37
|
+
/** V2's board target for the scale axis: 500 assets on one drawing (design document §4.15). */
|
|
38
|
+
export const BOARD_ASSETS = 500;
|
|
39
|
+
/**
|
|
40
|
+
* The report for one asset, or nothing when no body is drawn — then there is nothing to measure, and the screen
|
|
41
|
+
* says so rather than drawing zeros.
|
|
42
|
+
*
|
|
43
|
+
* Evaluated at the asset's own design and state defaults, as `v3CostOf` is.
|
|
44
|
+
*/
|
|
45
|
+
export function v3ReportOf(asset) {
|
|
46
|
+
const cost = v3CostOf(asset);
|
|
47
|
+
const capacity = capacityFrom(cost.groups, cost.triangles);
|
|
48
|
+
if (!capacity)
|
|
49
|
+
return undefined;
|
|
50
|
+
const drawn = compileV3Asset(asset)
|
|
51
|
+
.evaluate()
|
|
52
|
+
.geometry.filter(body => body.visible !== false);
|
|
53
|
+
const plan = compileV3Document(asset.document);
|
|
54
|
+
const writers = v3WritersOf(asset);
|
|
55
|
+
const shapes = new Set(drawn.map(shapeKeyOf));
|
|
56
|
+
const materials = new Set(drawn.map(materialKeyOf));
|
|
57
|
+
const score = v3ScoreOf(asset);
|
|
58
|
+
const reuse = { distinct: shapes.size, parts: drawn.length, saved: drawn.length - shapes.size };
|
|
59
|
+
const axes = [
|
|
60
|
+
{
|
|
61
|
+
key: 'scale',
|
|
62
|
+
// The board target filled is 1; room beyond it is better spent on the other axes, so it does not count more.
|
|
63
|
+
ratio: Math.min(1, capacity.instances / BOARD_ASSETS),
|
|
64
|
+
reading: String(capacity.instances),
|
|
65
|
+
because: 'figure.text.maturity-scale-because'
|
|
66
|
+
},
|
|
67
|
+
consistency(asset, materials.size),
|
|
68
|
+
{
|
|
69
|
+
key: 'reuse',
|
|
70
|
+
// A low distinct-to-parts ratio is much reuse; turned over so that more is higher.
|
|
71
|
+
ratio: reuse.parts === 0 ? 0 : 1 - reuse.distinct / reuse.parts,
|
|
72
|
+
reading: String(reuse.saved),
|
|
73
|
+
because: 'figure.text.maturity-reuse-because'
|
|
74
|
+
},
|
|
75
|
+
{ key: 'detail', blocked: 'figure.text.maturity-blocked-no-detail-level' },
|
|
76
|
+
{ key: 'reactive', blocked: 'figure.text.maturity-blocked-no-binding' },
|
|
77
|
+
readable(asset, plan, drawn, writers)
|
|
78
|
+
];
|
|
79
|
+
return {
|
|
80
|
+
cost,
|
|
81
|
+
capacity,
|
|
82
|
+
score,
|
|
83
|
+
distinctShapes: shapes.size,
|
|
84
|
+
distinctMaterials: materials.size,
|
|
85
|
+
reuse,
|
|
86
|
+
axes,
|
|
87
|
+
curve: decadeSteps(1, 10000).map(instances => ({
|
|
88
|
+
instances,
|
|
89
|
+
triangles: cost.triangles * instances,
|
|
90
|
+
drawCalls: cost.groups * instances,
|
|
91
|
+
geometries: shapes.size,
|
|
92
|
+
materials: materials.size
|
|
93
|
+
}))
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* The key things-scene shares a body's geometry by (`figure-v3-geometry.ts` `keyOf`): provider, dimensions to a
|
|
98
|
+
* micrometre, segments (12 when unset), and a polygon's corners.
|
|
99
|
+
*/
|
|
100
|
+
export function shapeKeyOf(body) {
|
|
101
|
+
if (!('shape' in body)) {
|
|
102
|
+
// A scalar body has no mesh of its own; it is still one drawn body, told apart by what it declares.
|
|
103
|
+
const dims = Object.keys(body.dimensions).sort().map(k => `${k}=${body.dimensions[k]}`).join(',');
|
|
104
|
+
return `${body.provider}|${dims}`;
|
|
105
|
+
}
|
|
106
|
+
const shape = body.shape;
|
|
107
|
+
const round = (v) => Math.round(v * 1e6) / 1e6;
|
|
108
|
+
const dims = Object.keys(shape.dimensions)
|
|
109
|
+
.sort()
|
|
110
|
+
.map(k => `${k}=${round(shape.dimensions[k])}`)
|
|
111
|
+
.join(',');
|
|
112
|
+
const profile = shape.profile ? '|' + shape.profile.map(p => `${round(p.x)}:${round(p.y)}`).join(';') : '';
|
|
113
|
+
const segments = body.segments ?? 12;
|
|
114
|
+
return `${shape.provider}|${dims}|s${segments}${profile}`;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* The key things-scene shares a material by (`figure-v3-rig.ts` `materialFor`), within one asset: token, preset,
|
|
118
|
+
* transparency, and the light when it is on. The colour follows from the token, so it is not a second key.
|
|
119
|
+
*/
|
|
120
|
+
export function materialKeyOf(body) {
|
|
121
|
+
return v3MaterialKeyOf(body);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Does it look like one set — V2's baselines 3 and 4. V2 checks three things; V3 keeps the same three.
|
|
125
|
+
*
|
|
126
|
+
* Two of them hold by construction in V3: the material has no surface field, so there is no texture, and the
|
|
127
|
+
* format refuses a segment count outside the presets (`compileV3Asset`, `QUALITY`). They are still counted, as
|
|
128
|
+
* V2 counts them, so the ratio means what it meant: one fault in three.
|
|
129
|
+
*/
|
|
130
|
+
function consistency(asset, distinctMaterials) {
|
|
131
|
+
const textures = 0;
|
|
132
|
+
const offPreset = asset.appearance.filter(look => look.segments !== undefined && !SEGMENT_PRESETS.includes(look.segments)).length;
|
|
133
|
+
const faults = (textures > 0 ? 1 : 0) + (offPreset > 0 ? 1 : 0) + (distinctMaterials > LIMITS.distinctMaterials ? 1 : 0);
|
|
134
|
+
return {
|
|
135
|
+
key: 'consistency',
|
|
136
|
+
ratio: (3 - faults) / 3,
|
|
137
|
+
readingKey: faults === 0 ? 'figure.text.maturity-consistency-clean' : 'figure.text.maturity-consistency-faults',
|
|
138
|
+
because: 'figure.text.maturity-consistency-because'
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
/** Seen from above, these do not fill their footprint box. V2's list, in V3's provider names. */
|
|
142
|
+
const ROUND_FOOTPRINT = ['cylinder', 'ellipticCylinder', 'frustum', 'sphere'];
|
|
143
|
+
/**
|
|
144
|
+
* Can it be told apart from above — V2's baseline 1, the same measure.
|
|
145
|
+
*
|
|
146
|
+
* Viewpoint entropy (Vázquez et al.) over the top view, counted by colour region rather than by face so that a
|
|
147
|
+
* finer segment count does not raise it: H = −Σ p·log p over each material's share of the footprint plus the
|
|
148
|
+
* background, divided by log(`LIMITS.distinctMaterials` + 1), the most this measure can reach.
|
|
149
|
+
*
|
|
150
|
+
* The footprint is V2's base box: the space the asset declares when it declares one (ADR-0044's box is V3's
|
|
151
|
+
* occupancy), otherwise the drawn bodies' own extent. Bodies are their axis-aligned world boxes
|
|
152
|
+
* (`v3WorldBoxesOf`, the gate's reading); the higher one covers the lower.
|
|
153
|
+
*/
|
|
154
|
+
function readable(asset, plan, drawn, writers) {
|
|
155
|
+
const placed = drawn
|
|
156
|
+
.filter(body => 'pose' in body)
|
|
157
|
+
// One placed, drawn body gives exactly one box; boxes are taken one by one to keep each with its material.
|
|
158
|
+
.map(body => ({ body, box: v3WorldBoxesOf([body], writers)[0] }));
|
|
159
|
+
if (placed.length === 0)
|
|
160
|
+
return { key: 'readable', blocked: 'figure.text.maturity-blocked-no-parts' };
|
|
161
|
+
let frame;
|
|
162
|
+
if (asset.occupancy) {
|
|
163
|
+
const values = plan.evaluate({ ...asset.designInputs, ...asset.stateDefaults }).values;
|
|
164
|
+
const at = (ref) => values[ref];
|
|
165
|
+
const b = asset.occupancy.bounds;
|
|
166
|
+
frame = { x0: at(b.x.min), x1: at(b.x.max), z0: at(b.z.min), z1: at(b.z.max) };
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
frame = { x0: Infinity, x1: -Infinity, z0: Infinity, z1: -Infinity };
|
|
170
|
+
for (const { box } of placed) {
|
|
171
|
+
frame.x0 = Math.min(frame.x0, box.centre.x - box.extent.x / 2);
|
|
172
|
+
frame.x1 = Math.max(frame.x1, box.centre.x + box.extent.x / 2);
|
|
173
|
+
frame.z0 = Math.min(frame.z0, box.centre.z - box.extent.z / 2);
|
|
174
|
+
frame.z1 = Math.max(frame.z1, box.centre.z + box.extent.z / 2);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
const regions = topViewRegions(placed, frame);
|
|
178
|
+
if (regions.total === 0)
|
|
179
|
+
return { key: 'readable', blocked: 'figure.text.maturity-blocked-no-parts' };
|
|
180
|
+
const shares = [...regions.byMaterial.values(), regions.background].map(area => area / regions.footprint).filter(share => share > 0);
|
|
181
|
+
const entropy = -shares.reduce((sum, p) => sum + p * Math.log(p), 0);
|
|
182
|
+
const ceiling = Math.log(LIMITS.distinctMaterials + 1);
|
|
183
|
+
return {
|
|
184
|
+
key: 'readable',
|
|
185
|
+
// Clamped both ways: one region alone gives −0, which is not a reading anyone should see.
|
|
186
|
+
ratio: Math.max(0, Math.min(1, entropy / ceiling)),
|
|
187
|
+
reading: String(regions.byMaterial.size),
|
|
188
|
+
because: 'figure.text.maturity-readable-because'
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
/** Which material shows from above, on a 64 × 64 grid over the footprint. V2's `topViewRegions`, over world boxes. */
|
|
192
|
+
function topViewRegions(placed, frame) {
|
|
193
|
+
const GRID = 64;
|
|
194
|
+
const cells = new Array(GRID * GRID).fill(null);
|
|
195
|
+
const height = new Float64Array(GRID * GRID);
|
|
196
|
+
const sx = GRID / Math.max(frame.x1 - frame.x0, 1e-6);
|
|
197
|
+
const sz = GRID / Math.max(frame.z1 - frame.z0, 1e-6);
|
|
198
|
+
for (const { body, box } of placed) {
|
|
199
|
+
const top = box.centre.y + box.extent.y / 2;
|
|
200
|
+
const key = materialKeyOf(body);
|
|
201
|
+
const x0 = Math.max(0, Math.floor((box.centre.x - box.extent.x / 2 - frame.x0) * sx));
|
|
202
|
+
const x1 = Math.min(GRID, Math.ceil((box.centre.x + box.extent.x / 2 - frame.x0) * sx));
|
|
203
|
+
const y0 = Math.max(0, Math.floor((box.centre.z - box.extent.z / 2 - frame.z0) * sz));
|
|
204
|
+
const y1 = Math.min(GRID, Math.ceil((box.centre.z + box.extent.z / 2 - frame.z0) * sz));
|
|
205
|
+
const round = 'shape' in body && ROUND_FOOTPRINT.includes(body.shape.provider);
|
|
206
|
+
for (let y = y0; y < y1; y++) {
|
|
207
|
+
for (let x = x0; x < x1; x++) {
|
|
208
|
+
// A round body does not fill its box; the corners are cut, as V2 cuts them.
|
|
209
|
+
if (round) {
|
|
210
|
+
const cx = (x + 0.5 - (x0 + x1) / 2) / ((x1 - x0) / 2 || 1);
|
|
211
|
+
const cy = (y + 0.5 - (y0 + y1) / 2) / ((y1 - y0) / 2 || 1);
|
|
212
|
+
if (cx * cx + cy * cy > 1)
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
const i = y * GRID + x;
|
|
216
|
+
if (cells[i] === null || top > height[i]) {
|
|
217
|
+
cells[i] = key;
|
|
218
|
+
height[i] = top;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
const byMaterial = new Map();
|
|
224
|
+
let covered = 0;
|
|
225
|
+
for (const key of cells) {
|
|
226
|
+
if (key === null)
|
|
227
|
+
continue;
|
|
228
|
+
covered++;
|
|
229
|
+
byMaterial.set(key, (byMaterial.get(key) ?? 0) + 1);
|
|
230
|
+
}
|
|
231
|
+
return { byMaterial, total: covered, background: GRID * GRID - covered, footprint: GRID * GRID };
|
|
232
|
+
}
|
|
233
|
+
/** 1 · 2 · 5 per decade, as V2's trend (`cost-chart.ts`): even steps on a log axis. */
|
|
234
|
+
export function decadeSteps(from, to) {
|
|
235
|
+
const out = [];
|
|
236
|
+
for (let decade = 1; decade <= to; decade *= 10) {
|
|
237
|
+
for (const m of [1, 2, 5]) {
|
|
238
|
+
const v = decade * m;
|
|
239
|
+
if (v >= from && v <= to)
|
|
240
|
+
out.push(v);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if (out.length === 0 || out[out.length - 1] !== to)
|
|
244
|
+
out.push(to);
|
|
245
|
+
return [...new Set(out)].sort((a, b) => a - b);
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* The four lines V2 drew: two that grow with the instance count and two that stay flat — the flat ones are what the
|
|
249
|
+
* instances share. Materials are dashed and drawn last, so a line under them still shows where the two meet.
|
|
250
|
+
*/
|
|
251
|
+
export const CURVE_SERIES = [
|
|
252
|
+
{ key: 'triangles', flat: false },
|
|
253
|
+
{ key: 'drawCalls', flat: false },
|
|
254
|
+
{ key: 'geometries', flat: true },
|
|
255
|
+
{ key: 'materials', flat: true, dashed: true }
|
|
256
|
+
];
|
|
257
|
+
/** V2's chart size. The chart is shown at this size in a sheet; shrunk into the side, its words are unreadable. */
|
|
258
|
+
export const CURVE_SIZE = { width: 680, height: 380, pad: { top: 52, right: 208, bottom: 44, left: 62 } };
|
|
259
|
+
const log10 = (v) => Math.log(Math.max(v, 1)) / Math.LN10;
|
|
260
|
+
/** 1, 10, 100 … written short. */
|
|
261
|
+
export function shortNumber(v) {
|
|
262
|
+
if (v >= 1_000_000)
|
|
263
|
+
return `${v / 1_000_000}M`;
|
|
264
|
+
if (v >= 1_000)
|
|
265
|
+
return `${v / 1_000}k`;
|
|
266
|
+
return String(v);
|
|
267
|
+
}
|
|
268
|
+
/** Where V2's chart puts each line and tick. Both axes are logarithmic, as V2's are. */
|
|
269
|
+
export function curveLayout(points) {
|
|
270
|
+
if (points.length === 0)
|
|
271
|
+
return undefined;
|
|
272
|
+
const { width, height, pad } = CURVE_SIZE;
|
|
273
|
+
const minX = points[0].instances;
|
|
274
|
+
const maxX = points[points.length - 1].instances;
|
|
275
|
+
let maxY = 1;
|
|
276
|
+
for (const p of points)
|
|
277
|
+
for (const s of CURVE_SERIES)
|
|
278
|
+
maxY = Math.max(maxY, p[s.key]);
|
|
279
|
+
const plotW = width - pad.left - pad.right;
|
|
280
|
+
const plotH = height - pad.top - pad.bottom;
|
|
281
|
+
const spanX = Math.max(log10(maxX) - log10(minX), 1e-9);
|
|
282
|
+
const spanY = Math.max(log10(maxY), 1e-9);
|
|
283
|
+
const px = (v) => pad.left + ((log10(v) - log10(minX)) / spanX) * plotW;
|
|
284
|
+
const py = (v) => pad.top + plotH - (log10(v) / spanY) * plotH;
|
|
285
|
+
const yTicks = [];
|
|
286
|
+
for (let e = 0; Math.pow(10, e) <= maxY * 1.001; e++)
|
|
287
|
+
yTicks.push({ y: py(Math.pow(10, e)), label: shortNumber(Math.pow(10, e)) });
|
|
288
|
+
const xTicks = [];
|
|
289
|
+
for (let e = 0; Math.pow(10, e) <= maxX * 1.001; e++) {
|
|
290
|
+
const v = Math.pow(10, e);
|
|
291
|
+
if (v >= minX)
|
|
292
|
+
xTicks.push({ x: px(v), label: shortNumber(v) });
|
|
293
|
+
}
|
|
294
|
+
const last = points[points.length - 1];
|
|
295
|
+
const lines = CURVE_SERIES.map(s => ({
|
|
296
|
+
key: s.key,
|
|
297
|
+
d: points.map((p, i) => `${i === 0 ? 'M' : 'L'}${px(p.instances).toFixed(1)},${py(p[s.key]).toFixed(1)}`).join(' '),
|
|
298
|
+
flat: s.flat,
|
|
299
|
+
dashed: !!s.dashed,
|
|
300
|
+
last: last[s.key]
|
|
301
|
+
}));
|
|
302
|
+
const flat = new Set(CURVE_SERIES.filter(s => s.flat).map(s => last[s.key]));
|
|
303
|
+
return { xTicks, yTicks, lines, overlap: flat.size === 1 ? [...flat][0] : undefined };
|
|
304
|
+
}
|
|
305
|
+
export function budgetOf(report) {
|
|
306
|
+
if (!report)
|
|
307
|
+
return undefined;
|
|
308
|
+
const { tightest } = report.score;
|
|
309
|
+
return {
|
|
310
|
+
triangles: report.cost.triangles,
|
|
311
|
+
groups: report.cost.groups,
|
|
312
|
+
instances: report.capacity.instances,
|
|
313
|
+
grade: report.score.grade,
|
|
314
|
+
tightest: { key: tightest.key, used: tightest.used, limit: Math.floor(tightest.limit) }
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
//# sourceMappingURL=v3-maturity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"v3-maturity.js","sourceRoot":"","sources":["../../client/modeller/v3-maturity.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n compileV3Asset,\n compileV3Document,\n LIMITS,\n SEGMENT_PRESETS,\n V3_BUDGET_KEYS,\n V3_GRADE_SCALE,\n v3CostOf,\n v3GradeOf,\n v3MaterialKeyOf,\n v3ScoreOf,\n v3WorldBoxesOf,\n v3WritersOf\n} from '@hatiolab/figure-model'\nimport type { V3Asset, V3AssetGeometry, V3BudgetKey, V3BudgetUse, V3Cost, V3Geometry, V3Score } from '@hatiolab/figure-model'\n\nimport { capacityFrom } from './scene-budget.js'\nimport type { FigureCapacity } from './scene-budget.js'\n\n/*\n * The V2 modeller's maturity report, read off a V3 asset (user, 2026-09-24: the V3 tab showed a count and nothing\n * else — 「왜 이렇게 너프되었는지」).\n *\n * V2's report was `figure-report.ts` over `costOf` · `scoreOf` · `costCurveSvg` (figure-model) and `maturity.ts`\n * (figure-ui). Those read a V2 blueprint. This module asks the same questions of the evaluated V3 asset and keeps\n * V2's answers where the question still has one:\n *\n * grade A–D and its scale V2's bounds, over figure-model's `v3ScoreOf`: one screen's target shared per instance\n * (groups, triangles) and V2's declared rule limits (independent, transparent)\n * radar · axes V2's six axes; the two V2 could not measure stay unmeasured, detail has no V3 field\n * drawing cost triangles, distinct shapes, distinct materials\n * trend what grows with the instance count and what the instances share\n *\n * Kept apart from the element so it can be tested without a browser.\n *\n * ## What the V3 renderer shares, which is what the numbers count\n *\n * things-scene draws one mesh per drawn body (`figure-v3-rig.ts`) — no merging by material as V2 did — so a body is\n * a draw call. It shares geometry by provider, dimensions, segments and profile (`figure-v3-geometry.ts`, `keyOf`)\n * and materials by token, preset, transparency and light (`materialFor`). The keys below are those keys; a\n * distinct-shape count that used another key would describe a renderer that does not exist.\n *\n * ## Budgets V3 does not have\n *\n * V2 had five. Two are not measured here, and are not shown as zero:\n * - off-palette surfaces: the V3 material has no surface field, so there is nothing to count;\n * - part count by detail level: V3 does not carry the detail level (chief architect, 2026-09-24, ADR-0087).\n */\n\n/*\n The grade, its budgets and its bounds are figure-model's (`v3ScoreOf`), so the catalogue grades a figure as this tab\n does. The grade is the headroom against one screen's target (`LIMITS.screenInstances`), not a draw-call limit.\n*/\nexport { V3_BUDGET_KEYS, V3_GRADE_SCALE as GRADE_SCALE, v3GradeOf as gradeOf }\nexport type { V3BudgetKey, V3BudgetUse, V3Score }\n\nexport type V3AxisKey = 'scale' | 'consistency' | 'reuse' | 'detail' | 'reactive' | 'readable'\n\n/** One maturity axis. The same shape V2's `maturity.ts` gave the report. */\nexport interface V3MaturityAxis {\n key: V3AxisKey\n /** 0–1. Absent when the axis cannot be measured. */\n ratio?: number\n reading?: string\n /** A reading that is words, not a number. */\n readingKey?: string\n because?: string\n /** Why the axis is not measured. When present there is no ratio — an unmeasured axis is not a zero. */\n blocked?: string\n}\n\n/** Instances on one screen, and what each series costs at that count. */\nexport interface V3CurvePoint {\n instances: number\n triangles: number\n drawCalls: number\n /** Vertex buffers: one per distinct shape, shared by every instance of the same size. */\n geometries: number\n materials: number\n}\n\nexport interface V3Report {\n cost: V3Cost\n capacity: FigureCapacity\n score: V3Score\n /** Distinct shapes and materials among the drawn bodies — V2's 「그리는 비용」 rows. */\n distinctShapes: number\n distinctMaterials: number\n reuse: { distinct: number; parts: number; saved: number }\n axes: V3MaturityAxis[]\n curve: V3CurvePoint[]\n}\n\n/** V2's board target for the scale axis: 500 assets on one drawing (design document §4.15). */\nexport const BOARD_ASSETS = 500\n\n/**\n * The report for one asset, or nothing when no body is drawn — then there is nothing to measure, and the screen\n * says so rather than drawing zeros.\n *\n * Evaluated at the asset's own design and state defaults, as `v3CostOf` is.\n */\nexport function v3ReportOf(asset: V3Asset): V3Report | undefined {\n const cost = v3CostOf(asset)\n const capacity = capacityFrom(cost.groups, cost.triangles)\n if (!capacity) return undefined\n\n const drawn = compileV3Asset(asset)\n .evaluate()\n .geometry.filter(body => body.visible !== false)\n const plan = compileV3Document(asset.document)\n const writers = v3WritersOf(asset)\n\n const shapes = new Set(drawn.map(shapeKeyOf))\n const materials = new Set(drawn.map(materialKeyOf))\n\n const score = v3ScoreOf(asset)!\n\n const reuse = { distinct: shapes.size, parts: drawn.length, saved: drawn.length - shapes.size }\n\n const axes: V3MaturityAxis[] = [\n {\n key: 'scale',\n // The board target filled is 1; room beyond it is better spent on the other axes, so it does not count more.\n ratio: Math.min(1, capacity.instances / BOARD_ASSETS),\n reading: String(capacity.instances),\n because: 'figure.text.maturity-scale-because'\n },\n consistency(asset, materials.size),\n {\n key: 'reuse',\n // A low distinct-to-parts ratio is much reuse; turned over so that more is higher.\n ratio: reuse.parts === 0 ? 0 : 1 - reuse.distinct / reuse.parts,\n reading: String(reuse.saved),\n because: 'figure.text.maturity-reuse-because'\n },\n { key: 'detail', blocked: 'figure.text.maturity-blocked-no-detail-level' },\n { key: 'reactive', blocked: 'figure.text.maturity-blocked-no-binding' },\n readable(asset, plan, drawn, writers)\n ]\n\n return {\n cost,\n capacity,\n score,\n distinctShapes: shapes.size,\n distinctMaterials: materials.size,\n reuse,\n axes,\n curve: decadeSteps(1, 10000).map(instances => ({\n instances,\n triangles: cost.triangles * instances,\n drawCalls: cost.groups * instances,\n geometries: shapes.size,\n materials: materials.size\n }))\n }\n}\n\n/**\n * The key things-scene shares a body's geometry by (`figure-v3-geometry.ts` `keyOf`): provider, dimensions to a\n * micrometre, segments (12 when unset), and a polygon's corners.\n */\nexport function shapeKeyOf(body: V3AssetGeometry | V3Geometry): string {\n if (!('shape' in body)) {\n // A scalar body has no mesh of its own; it is still one drawn body, told apart by what it declares.\n const dims = Object.keys(body.dimensions).sort().map(k => `${k}=${body.dimensions[k]}`).join(',')\n return `${body.provider}|${dims}`\n }\n const shape = body.shape\n const round = (v: number) => Math.round(v * 1e6) / 1e6\n const dims = Object.keys(shape.dimensions)\n .sort()\n .map(k => `${k}=${round(shape.dimensions[k]!)}`)\n .join(',')\n const profile = shape.profile ? '|' + shape.profile.map(p => `${round(p.x)}:${round(p.y)}`).join(';') : ''\n const segments = (body as { segments?: number }).segments ?? 12\n return `${shape.provider}|${dims}|s${segments}${profile}`\n}\n\n/**\n * The key things-scene shares a material by (`figure-v3-rig.ts` `materialFor`), within one asset: token, preset,\n * transparency, and the light when it is on. The colour follows from the token, so it is not a second key.\n */\nexport function materialKeyOf(body: V3AssetGeometry): string {\n return v3MaterialKeyOf(body)\n}\n\n/**\n * Does it look like one set — V2's baselines 3 and 4. V2 checks three things; V3 keeps the same three.\n *\n * Two of them hold by construction in V3: the material has no surface field, so there is no texture, and the\n * format refuses a segment count outside the presets (`compileV3Asset`, `QUALITY`). They are still counted, as\n * V2 counts them, so the ratio means what it meant: one fault in three.\n */\nfunction consistency(asset: V3Asset, distinctMaterials: number): V3MaturityAxis {\n const textures = 0\n const offPreset = asset.appearance.filter(look => look.segments !== undefined && !SEGMENT_PRESETS.includes(look.segments)).length\n const faults = (textures > 0 ? 1 : 0) + (offPreset > 0 ? 1 : 0) + (distinctMaterials > LIMITS.distinctMaterials ? 1 : 0)\n return {\n key: 'consistency',\n ratio: (3 - faults) / 3,\n readingKey: faults === 0 ? 'figure.text.maturity-consistency-clean' : 'figure.text.maturity-consistency-faults',\n because: 'figure.text.maturity-consistency-because'\n }\n}\n\n/** Seen from above, these do not fill their footprint box. V2's list, in V3's provider names. */\nconst ROUND_FOOTPRINT: readonly string[] = ['cylinder', 'ellipticCylinder', 'frustum', 'sphere']\n\n/**\n * Can it be told apart from above — V2's baseline 1, the same measure.\n *\n * Viewpoint entropy (Vázquez et al.) over the top view, counted by colour region rather than by face so that a\n * finer segment count does not raise it: H = −Σ p·log p over each material's share of the footprint plus the\n * background, divided by log(`LIMITS.distinctMaterials` + 1), the most this measure can reach.\n *\n * The footprint is V2's base box: the space the asset declares when it declares one (ADR-0044's box is V3's\n * occupancy), otherwise the drawn bodies' own extent. Bodies are their axis-aligned world boxes\n * (`v3WorldBoxesOf`, the gate's reading); the higher one covers the lower.\n */\nfunction readable(\n asset: V3Asset,\n plan: ReturnType<typeof compileV3Document>,\n drawn: V3AssetGeometry[],\n writers: Map<string, string>\n): V3MaturityAxis {\n const placed = drawn\n .filter(body => 'pose' in body)\n // One placed, drawn body gives exactly one box; boxes are taken one by one to keep each with its material.\n .map(body => ({ body, box: v3WorldBoxesOf([body], writers)[0]! }))\n if (placed.length === 0) return { key: 'readable', blocked: 'figure.text.maturity-blocked-no-parts' }\n\n let frame: { x0: number; x1: number; z0: number; z1: number }\n if (asset.occupancy) {\n const values = plan.evaluate({ ...asset.designInputs, ...asset.stateDefaults }).values\n const at = (ref: string) => values[ref] as number\n const b = asset.occupancy.bounds\n frame = { x0: at(b.x.min), x1: at(b.x.max), z0: at(b.z.min), z1: at(b.z.max) }\n } else {\n frame = { x0: Infinity, x1: -Infinity, z0: Infinity, z1: -Infinity }\n for (const { box } of placed) {\n frame.x0 = Math.min(frame.x0, box.centre.x - box.extent.x / 2)\n frame.x1 = Math.max(frame.x1, box.centre.x + box.extent.x / 2)\n frame.z0 = Math.min(frame.z0, box.centre.z - box.extent.z / 2)\n frame.z1 = Math.max(frame.z1, box.centre.z + box.extent.z / 2)\n }\n }\n\n const regions = topViewRegions(placed, frame)\n if (regions.total === 0) return { key: 'readable', blocked: 'figure.text.maturity-blocked-no-parts' }\n\n const shares = [...regions.byMaterial.values(), regions.background].map(area => area / regions.footprint).filter(share => share > 0)\n const entropy = -shares.reduce((sum, p) => sum + p * Math.log(p), 0)\n const ceiling = Math.log(LIMITS.distinctMaterials + 1)\n return {\n key: 'readable',\n // Clamped both ways: one region alone gives −0, which is not a reading anyone should see.\n ratio: Math.max(0, Math.min(1, entropy / ceiling)),\n reading: String(regions.byMaterial.size),\n because: 'figure.text.maturity-readable-because'\n }\n}\n\n/** Which material shows from above, on a 64 × 64 grid over the footprint. V2's `topViewRegions`, over world boxes. */\nfunction topViewRegions(\n placed: { body: V3AssetGeometry; box: ReturnType<typeof v3WorldBoxesOf>[number] }[],\n frame: { x0: number; x1: number; z0: number; z1: number }\n) {\n const GRID = 64\n const cells = new Array<string | null>(GRID * GRID).fill(null)\n const height = new Float64Array(GRID * GRID)\n const sx = GRID / Math.max(frame.x1 - frame.x0, 1e-6)\n const sz = GRID / Math.max(frame.z1 - frame.z0, 1e-6)\n\n for (const { body, box } of placed) {\n const top = box.centre.y + box.extent.y / 2\n const key = materialKeyOf(body)\n const x0 = Math.max(0, Math.floor((box.centre.x - box.extent.x / 2 - frame.x0) * sx))\n const x1 = Math.min(GRID, Math.ceil((box.centre.x + box.extent.x / 2 - frame.x0) * sx))\n const y0 = Math.max(0, Math.floor((box.centre.z - box.extent.z / 2 - frame.z0) * sz))\n const y1 = Math.min(GRID, Math.ceil((box.centre.z + box.extent.z / 2 - frame.z0) * sz))\n const round = 'shape' in body && ROUND_FOOTPRINT.includes(body.shape.provider)\n\n for (let y = y0; y < y1; y++) {\n for (let x = x0; x < x1; x++) {\n // A round body does not fill its box; the corners are cut, as V2 cuts them.\n if (round) {\n const cx = (x + 0.5 - (x0 + x1) / 2) / ((x1 - x0) / 2 || 1)\n const cy = (y + 0.5 - (y0 + y1) / 2) / ((y1 - y0) / 2 || 1)\n if (cx * cx + cy * cy > 1) continue\n }\n const i = y * GRID + x\n if (cells[i] === null || top > height[i]!) {\n cells[i] = key\n height[i] = top\n }\n }\n }\n }\n\n const byMaterial = new Map<string, number>()\n let covered = 0\n for (const key of cells) {\n if (key === null) continue\n covered++\n byMaterial.set(key, (byMaterial.get(key) ?? 0) + 1)\n }\n return { byMaterial, total: covered, background: GRID * GRID - covered, footprint: GRID * GRID }\n}\n\n/** 1 · 2 · 5 per decade, as V2's trend (`cost-chart.ts`): even steps on a log axis. */\nexport function decadeSteps(from: number, to: number): number[] {\n const out: number[] = []\n for (let decade = 1; decade <= to; decade *= 10) {\n for (const m of [1, 2, 5]) {\n const v = decade * m\n if (v >= from && v <= to) out.push(v)\n }\n }\n if (out.length === 0 || out[out.length - 1] !== to) out.push(to)\n return [...new Set(out)].sort((a, b) => a - b)\n}\n\n// ─── The trend chart ─────────────────────────────────────────────────────────\n\nexport type V3CurveSeries = 'triangles' | 'drawCalls' | 'geometries' | 'materials'\n\n/**\n * The four lines V2 drew: two that grow with the instance count and two that stay flat — the flat ones are what the\n * instances share. Materials are dashed and drawn last, so a line under them still shows where the two meet.\n */\nexport const CURVE_SERIES: readonly { key: V3CurveSeries; flat: boolean; dashed?: boolean }[] = [\n { key: 'triangles', flat: false },\n { key: 'drawCalls', flat: false },\n { key: 'geometries', flat: true },\n { key: 'materials', flat: true, dashed: true }\n]\n\n/** V2's chart size. The chart is shown at this size in a sheet; shrunk into the side, its words are unreadable. */\nexport const CURVE_SIZE = { width: 680, height: 380, pad: { top: 52, right: 208, bottom: 44, left: 62 } } as const\n\nexport interface V3CurveLayout {\n xTicks: { x: number; label: string }[]\n yTicks: { y: number; label: string }[]\n lines: { key: V3CurveSeries; d: string; flat: boolean; dashed: boolean; last: number }[]\n /** The value the flat lines share when they lie on one another, so the screen can say why one line is missing. */\n overlap?: number\n}\n\nconst log10 = (v: number) => Math.log(Math.max(v, 1)) / Math.LN10\n\n/** 1, 10, 100 … written short. */\nexport function shortNumber(v: number): string {\n if (v >= 1_000_000) return `${v / 1_000_000}M`\n if (v >= 1_000) return `${v / 1_000}k`\n return String(v)\n}\n\n/** Where V2's chart puts each line and tick. Both axes are logarithmic, as V2's are. */\nexport function curveLayout(points: V3CurvePoint[]): V3CurveLayout | undefined {\n if (points.length === 0) return undefined\n const { width, height, pad } = CURVE_SIZE\n const minX = points[0]!.instances\n const maxX = points[points.length - 1]!.instances\n let maxY = 1\n for (const p of points) for (const s of CURVE_SERIES) maxY = Math.max(maxY, p[s.key])\n\n const plotW = width - pad.left - pad.right\n const plotH = height - pad.top - pad.bottom\n const spanX = Math.max(log10(maxX) - log10(minX), 1e-9)\n const spanY = Math.max(log10(maxY), 1e-9)\n const px = (v: number) => pad.left + ((log10(v) - log10(minX)) / spanX) * plotW\n const py = (v: number) => pad.top + plotH - (log10(v) / spanY) * plotH\n\n const yTicks: V3CurveLayout['yTicks'] = []\n for (let e = 0; Math.pow(10, e) <= maxY * 1.001; e++) yTicks.push({ y: py(Math.pow(10, e)), label: shortNumber(Math.pow(10, e)) })\n const xTicks: V3CurveLayout['xTicks'] = []\n for (let e = 0; Math.pow(10, e) <= maxX * 1.001; e++) {\n const v = Math.pow(10, e)\n if (v >= minX) xTicks.push({ x: px(v), label: shortNumber(v) })\n }\n\n const last = points[points.length - 1]!\n const lines = CURVE_SERIES.map(s => ({\n key: s.key,\n d: points.map((p, i) => `${i === 0 ? 'M' : 'L'}${px(p.instances).toFixed(1)},${py(p[s.key]).toFixed(1)}`).join(' '),\n flat: s.flat,\n dashed: !!s.dashed,\n last: last[s.key]\n }))\n const flat = new Set(CURVE_SERIES.filter(s => s.flat).map(s => last[s.key]))\n return { xTicks, yTicks, lines, overlap: flat.size === 1 ? [...flat][0] : undefined }\n}\n\n/** What the parts list shows under its title: the cost, how many fit, the grade and what decides it. */\nexport interface V3BudgetLine {\n triangles: number\n groups: number\n instances: number\n grade: string\n tightest: { key: string; used: number; limit: number }\n}\n\nexport function budgetOf(report: V3Report | undefined): V3BudgetLine | undefined {\n if (!report) return undefined\n const { tightest } = report.score\n return {\n triangles: report.cost.triangles,\n groups: report.cost.groups,\n instances: report.capacity.instances,\n grade: report.score.grade,\n tightest: { key: tightest.key, used: tightest.used, limit: Math.floor(tightest.limit) }\n }\n}\n"]}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { V3PrimitiveCreation } from '@hatiolab/figure-model';
|
|
2
|
+
import type { V3PaletteKind } from './figure-v3-parts.js';
|
|
3
|
+
/**
|
|
4
|
+
* What a palette button makes: the part at a starting size a person then changes. Standing on the floor (its lowest
|
|
5
|
+
* point at y 0) where the provider's frame is centred; a portal's frame already rises from its bottom.
|
|
6
|
+
*/
|
|
7
|
+
export declare function paletteRecipe(kind: V3PaletteKind, id: string): V3PrimitiveCreation;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a palette button makes: the part at a starting size a person then changes. Standing on the floor (its lowest
|
|
3
|
+
* point at y 0) where the provider's frame is centred; a portal's frame already rises from its bottom.
|
|
4
|
+
*/
|
|
5
|
+
export function paletteRecipe(kind, id) {
|
|
6
|
+
const standing = (height) => ({ id, position: { x: 0, y: height / 2, z: 0 } });
|
|
7
|
+
switch (kind) {
|
|
8
|
+
case 'box':
|
|
9
|
+
return { ...standing(100), kind, dimensions: { width: 100, height: 100, depth: 100 } };
|
|
10
|
+
case 'cylinder':
|
|
11
|
+
return { ...standing(100), kind, dimensions: { radius: 50, length: 100 } };
|
|
12
|
+
case 'rounded-box':
|
|
13
|
+
return { ...standing(100), kind, dimensions: { width: 100, height: 100, depth: 100, round: 10 } };
|
|
14
|
+
case 'sphere':
|
|
15
|
+
return { ...standing(100), kind, dimensions: { radiusX: 50, radiusY: 50, radiusZ: 50 } };
|
|
16
|
+
case 'frustum':
|
|
17
|
+
return { ...standing(100), kind, dimensions: { radiusTop: 0, radiusBottom: 50, height: 100 } };
|
|
18
|
+
case 'polygon':
|
|
19
|
+
return {
|
|
20
|
+
...standing(100),
|
|
21
|
+
kind,
|
|
22
|
+
dimensions: { height: 100 },
|
|
23
|
+
points: [
|
|
24
|
+
{ x: -50, z: -43 },
|
|
25
|
+
{ x: 50, z: -43 },
|
|
26
|
+
{ x: 0, z: 43 }
|
|
27
|
+
]
|
|
28
|
+
};
|
|
29
|
+
case 'plate':
|
|
30
|
+
return { ...standing(10), kind: 'box', dimensions: { width: 600, height: 10, depth: 400 } };
|
|
31
|
+
case 'wall':
|
|
32
|
+
return { ...standing(2400), kind: 'box', dimensions: { width: 1000, height: 2400, depth: 100 } };
|
|
33
|
+
case 'portal':
|
|
34
|
+
return {
|
|
35
|
+
id,
|
|
36
|
+
position: { x: 0, y: 0, z: 0 },
|
|
37
|
+
kind: 'portal',
|
|
38
|
+
dimensions: { left: -100, right: 100, bottom: 0, top: 200, openingLeft: -70, openingRight: 70, openingTop: 170, depth: 30 }
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=v3-palette.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"v3-palette.js","sourceRoot":"","sources":["../../client/modeller/v3-palette.ts"],"names":[],"mappings":"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","sourcesContent":["import type { V3PrimitiveCreation } from '@hatiolab/figure-model'\n\nimport type { V3PaletteKind } from './figure-v3-parts.js'\n\n/**\n * What a palette button makes: the part at a starting size a person then changes. Standing on the floor (its lowest\n * point at y 0) where the provider's frame is centred; a portal's frame already rises from its bottom.\n */\nexport function paletteRecipe(kind: V3PaletteKind, id: string): V3PrimitiveCreation {\n const standing = (height: number) => ({ id, position: { x: 0, y: height / 2, z: 0 } })\n switch (kind) {\n case 'box':\n return { ...standing(100), kind, dimensions: { width: 100, height: 100, depth: 100 } }\n case 'cylinder':\n return { ...standing(100), kind, dimensions: { radius: 50, length: 100 } }\n case 'rounded-box':\n return { ...standing(100), kind, dimensions: { width: 100, height: 100, depth: 100, round: 10 } }\n case 'sphere':\n return { ...standing(100), kind, dimensions: { radiusX: 50, radiusY: 50, radiusZ: 50 } }\n case 'frustum':\n return { ...standing(100), kind, dimensions: { radiusTop: 0, radiusBottom: 50, height: 100 } }\n case 'polygon':\n return {\n ...standing(100),\n kind,\n dimensions: { height: 100 },\n points: [\n { x: -50, z: -43 },\n { x: 50, z: -43 },\n { x: 0, z: 43 }\n ]\n }\n case 'plate':\n return { ...standing(10), kind: 'box', dimensions: { width: 600, height: 10, depth: 400 } }\n case 'wall':\n return { ...standing(2400), kind: 'box', dimensions: { width: 1000, height: 2400, depth: 100 } }\n case 'portal':\n return {\n id,\n position: { x: 0, y: 0, z: 0 },\n kind: 'portal',\n dimensions: { left: -100, right: 100, bottom: 0, top: 200, openingLeft: -70, openingRight: 70, openingTop: 170, depth: 30 }\n }\n }\n}\n"]}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { V3Asset } from '@hatiolab/figure-model';
|
|
2
|
+
export declare const PREVIEW_FACTOR = 2;
|
|
3
|
+
export declare const PREVIEW_WAYS: readonly {
|
|
4
|
+
name: string;
|
|
5
|
+
axes: readonly ('x' | 'y' | 'z')[];
|
|
6
|
+
}[];
|
|
7
|
+
/** The size the figure was made at: its instance size when it declares one, else the room its bodies take. */
|
|
8
|
+
export declare function authoredSizeOf(asset: V3Asset): {
|
|
9
|
+
x: number;
|
|
10
|
+
y: number;
|
|
11
|
+
z: number;
|
|
12
|
+
};
|
|
13
|
+
/** The eight instances: where each stands on the board and how large, in the scene's words. */
|
|
14
|
+
export declare function previewInstances(type: string, size: {
|
|
15
|
+
x: number;
|
|
16
|
+
y: number;
|
|
17
|
+
z: number;
|
|
18
|
+
}): {
|
|
19
|
+
components: {
|
|
20
|
+
type: string;
|
|
21
|
+
id: string;
|
|
22
|
+
left: number;
|
|
23
|
+
top: number;
|
|
24
|
+
width: number;
|
|
25
|
+
height: number;
|
|
26
|
+
depth: number;
|
|
27
|
+
}[];
|
|
28
|
+
board: {
|
|
29
|
+
width: number;
|
|
30
|
+
height: number;
|
|
31
|
+
};
|
|
32
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { compileV3Asset, v3OccupiedBoxesOf, v3WritersOf, V3_INSTANCE_SIZE_INPUTS } from '@hatiolab/figure-model';
|
|
2
|
+
/* The preview's arithmetic, apart from the element so it is tested without a browser (see figure-v3-preview.ts). */
|
|
3
|
+
export const PREVIEW_FACTOR = 2;
|
|
4
|
+
export const PREVIEW_WAYS = [
|
|
5
|
+
{ name: 'base', axes: [] },
|
|
6
|
+
{ name: 'x', axes: ['x'] },
|
|
7
|
+
{ name: 'y', axes: ['y'] },
|
|
8
|
+
{ name: 'z', axes: ['z'] },
|
|
9
|
+
{ name: 'x·y', axes: ['x', 'y'] },
|
|
10
|
+
{ name: 'x·z', axes: ['x', 'z'] },
|
|
11
|
+
{ name: 'y·z', axes: ['y', 'z'] },
|
|
12
|
+
{ name: 'x·y·z', axes: ['x', 'y', 'z'] }
|
|
13
|
+
];
|
|
14
|
+
/** The size the figure was made at: its instance size when it declares one, else the room its bodies take. */
|
|
15
|
+
export function authoredSizeOf(asset) {
|
|
16
|
+
const d = asset.designInputs;
|
|
17
|
+
const declared = ['x', 'y', 'z'].map(axis => d[V3_INSTANCE_SIZE_INPUTS[axis]]);
|
|
18
|
+
if (declared.every(v => typeof v === 'number' && v > 0))
|
|
19
|
+
return { x: declared[0], y: declared[1], z: declared[2] };
|
|
20
|
+
/* No declared size: the room its bodies take up, so the instance is placed at the size it draws. */
|
|
21
|
+
const boxes = v3OccupiedBoxesOf(compileV3Asset(asset).evaluate().geometry, v3WritersOf(asset));
|
|
22
|
+
const span = (axis) => boxes.length ? Math.max(...boxes.map(b => b.centre[axis] + b.extent[axis] / 2)) - Math.min(...boxes.map(b => b.centre[axis] - b.extent[axis] / 2)) : 0;
|
|
23
|
+
return { x: Math.max(1, Math.round(span('x'))), y: Math.max(1, Math.round(span('y'))), z: Math.max(1, Math.round(span('z'))) };
|
|
24
|
+
}
|
|
25
|
+
/** The eight instances: where each stands on the board and how large, in the scene's words. */
|
|
26
|
+
export function previewInstances(type, size) {
|
|
27
|
+
const COLUMNS = 4;
|
|
28
|
+
const cell = { x: size.x * PREVIEW_FACTOR * 1.25, z: size.z * PREVIEW_FACTOR * 1.25 };
|
|
29
|
+
const components = PREVIEW_WAYS.map((way, at) => {
|
|
30
|
+
const grow = (axis) => (way.axes.includes(axis) ? PREVIEW_FACTOR : 1);
|
|
31
|
+
const width = Math.round(size.x * grow('x'));
|
|
32
|
+
const height = Math.round(size.z * grow('z'));
|
|
33
|
+
const column = at % COLUMNS;
|
|
34
|
+
const row = Math.floor(at / COLUMNS);
|
|
35
|
+
return {
|
|
36
|
+
type,
|
|
37
|
+
id: `at-${way.name}`,
|
|
38
|
+
left: Math.round(column * cell.x + (cell.x - width) / 2),
|
|
39
|
+
top: Math.round(row * cell.z + (cell.z - height) / 2),
|
|
40
|
+
width,
|
|
41
|
+
height,
|
|
42
|
+
/* Always written: without it the height is taken as the plan's mean and "grow y only" would not grow it. */
|
|
43
|
+
depth: Math.round(size.y * grow('y'))
|
|
44
|
+
};
|
|
45
|
+
});
|
|
46
|
+
return { components, board: { width: Math.round(cell.x * COLUMNS), height: Math.round(cell.z * Math.ceil(PREVIEW_WAYS.length / COLUMNS)) } };
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=v3-preview-model.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"v3-preview-model.js","sourceRoot":"","sources":["../../client/modeller/v3-preview-model.ts"],"names":[],"mappings":"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","sourcesContent":["import { compileV3Asset, v3OccupiedBoxesOf, v3WritersOf, V3_INSTANCE_SIZE_INPUTS } from '@hatiolab/figure-model'\nimport type { V3Asset } from '@hatiolab/figure-model'\n\n/* The preview's arithmetic, apart from the element so it is tested without a browser (see figure-v3-preview.ts). */\n\nexport const PREVIEW_FACTOR = 2\n\nexport const PREVIEW_WAYS: readonly { name: string; axes: readonly ('x' | 'y' | 'z')[] }[] = [\n { name: 'base', axes: [] },\n { name: 'x', axes: ['x'] },\n { name: 'y', axes: ['y'] },\n { name: 'z', axes: ['z'] },\n { name: 'x·y', axes: ['x', 'y'] },\n { name: 'x·z', axes: ['x', 'z'] },\n { name: 'y·z', axes: ['y', 'z'] },\n { name: 'x·y·z', axes: ['x', 'y', 'z'] }\n]\n\n/** The size the figure was made at: its instance size when it declares one, else the room its bodies take. */\nexport function authoredSizeOf(asset: V3Asset): { x: number; y: number; z: number } {\n const d = asset.designInputs\n const declared = (['x', 'y', 'z'] as const).map(axis => d[V3_INSTANCE_SIZE_INPUTS[axis]])\n if (declared.every(v => typeof v === 'number' && v > 0)) return { x: declared[0]!, y: declared[1]!, z: declared[2]! }\n /* No declared size: the room its bodies take up, so the instance is placed at the size it draws. */\n const boxes = v3OccupiedBoxesOf(compileV3Asset(asset).evaluate().geometry, v3WritersOf(asset))\n const span = (axis: 'x' | 'y' | 'z') =>\n boxes.length ? Math.max(...boxes.map(b => b.centre[axis] + b.extent[axis] / 2)) - Math.min(...boxes.map(b => b.centre[axis] - b.extent[axis] / 2)) : 0\n return { x: Math.max(1, Math.round(span('x'))), y: Math.max(1, Math.round(span('y'))), z: Math.max(1, Math.round(span('z'))) }\n}\n\n/** The eight instances: where each stands on the board and how large, in the scene's words. */\nexport function previewInstances(type: string, size: { x: number; y: number; z: number }) {\n const COLUMNS = 4\n const cell = { x: size.x * PREVIEW_FACTOR * 1.25, z: size.z * PREVIEW_FACTOR * 1.25 }\n const components = PREVIEW_WAYS.map((way, at) => {\n const grow = (axis: 'x' | 'y' | 'z') => (way.axes.includes(axis) ? PREVIEW_FACTOR : 1)\n const width = Math.round(size.x * grow('x'))\n const height = Math.round(size.z * grow('z'))\n const column = at % COLUMNS\n const row = Math.floor(at / COLUMNS)\n return {\n type,\n id: `at-${way.name}`,\n left: Math.round(column * cell.x + (cell.x - width) / 2),\n top: Math.round(row * cell.z + (cell.z - height) / 2),\n width,\n height,\n /* Always written: without it the height is taken as the plan's mean and \"grow y only\" would not grow it. */\n depth: Math.round(size.y * grow('y'))\n }\n })\n return { components, board: { width: Math.round(cell.x * COLUMNS), height: Math.round(cell.z * Math.ceil(PREVIEW_WAYS.length / COLUMNS)) } }\n}\n\n"]}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
import type { GizmoResult } from './v3-gizmo.js';
|
|
3
|
+
import type { V3RenderPart } from './v3-renderer.js';
|
|
4
|
+
export interface Board {
|
|
5
|
+
width: number;
|
|
6
|
+
height: number;
|
|
7
|
+
}
|
|
8
|
+
export interface BodyFrame {
|
|
9
|
+
/** The body's box: its centre in the body's own frame, and its extent. */
|
|
10
|
+
centre: THREE.Vector3;
|
|
11
|
+
extent: THREE.Vector3;
|
|
12
|
+
rotation: THREE.Matrix4;
|
|
13
|
+
origin: THREE.Vector3;
|
|
14
|
+
}
|
|
15
|
+
export declare function frameOf(part: V3RenderPart): BodyFrame | null;
|
|
16
|
+
/** The component state that draws a body where figure-model put it. */
|
|
17
|
+
export declare function componentOf(part: V3RenderPart, palette: Record<string, string>, board: Board): Record<string, unknown> | null;
|
|
18
|
+
/**
|
|
19
|
+
* What the gizmo did, in the inputs' terms: where the body's origin is now, how it is turned (degrees, rigid@1's
|
|
20
|
+
* order) and how much each of its own axes grew. Only what changed is given; a component the gizmo did not move gives
|
|
21
|
+
* nothing.
|
|
22
|
+
*/
|
|
23
|
+
export declare function gizmoResultOf(part: V3RenderPart, after: Record<string, unknown>, board: Board): GizmoResult;
|
|
24
|
+
/** The keys of `next` whose values differ from `current` -- only those are set, so the scene raises no echo. */
|
|
25
|
+
export declare function changedKeys(current: Record<string, unknown>, next: Record<string, unknown>): Record<string, unknown> | null;
|
|
26
|
+
/** A board wide and deep enough for the figure, with room around it; its size only grows while a figure is edited. */
|
|
27
|
+
export declare function boardFor(parts: readonly V3RenderPart[], at?: Board & {
|
|
28
|
+
depth?: number;
|
|
29
|
+
}): Board & {
|
|
30
|
+
depth: number;
|
|
31
|
+
};
|