@things-factory/figure-ui 10.1.75 → 10.1.76
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/figure-assistant.ts +3 -3
- package/client/graphql/{v3-store.ts → store.ts} +6 -6
- package/client/modeller/{v3-ai-proposal.ts → ai-proposal.ts} +8 -8
- package/client/modeller/{v3-align.ts → align.ts} +14 -14
- package/client/modeller/{v3-canvas.ts → canvas.ts} +13 -13
- package/client/modeller/{v3-clips.ts → clips.ts} +5 -5
- package/client/modeller/{v3-controls.ts → controls.ts} +2 -2
- package/client/modeller/{v3-editor.ts → editor.ts} +117 -117
- package/client/modeller/{figure-v3-capability.ts → figure-capability-panel.ts} +7 -7
- package/client/modeller/{figure-v3-clips.ts → figure-clips.ts} +10 -10
- package/client/modeller/{figure-v3-controls.ts → figure-controls.ts} +9 -9
- package/client/modeller/{v3-figure-facts.ts → figure-facts.ts} +11 -11
- package/client/modeller/{figure-v3-first-save.ts → figure-first-save.ts} +3 -3
- package/client/modeller/figure-history-panel.ts +1 -1
- package/client/modeller/{figure-v3-links.ts → figure-links.ts} +35 -35
- package/client/modeller/{figure-v3-material.ts → figure-material-panel.ts} +20 -20
- package/client/modeller/{figure-v3-parts.ts → figure-parts.ts} +15 -15
- package/client/modeller/{figure-v3-preview.ts → figure-preview.ts} +14 -14
- package/client/modeller/{figure-v3-properties.ts → figure-properties.ts} +25 -25
- package/client/modeller/{figure-v3-recipe-catalog.ts → figure-recipe-catalog.ts} +10 -10
- package/client/modeller/{figure-v3-verdict.ts → figure-verdict.ts} +24 -24
- package/client/modeller/gate-findings.ts +2 -2
- package/client/modeller/{v3-gizmo.ts → gizmo.ts} +11 -11
- package/client/modeller/{v3-joint-drag.ts → joint-drag.ts} +11 -11
- package/client/modeller/{v3-joint-fan.ts → joint-fan.ts} +13 -13
- package/client/modeller/{v3-links.ts → links.ts} +59 -59
- package/client/modeller/{v3-live.ts → live.ts} +9 -9
- package/client/modeller/{v3-maturity.ts → maturity.ts} +65 -65
- package/client/modeller/modeller-route.ts +1 -1
- package/client/modeller/{v3-occupancy.ts → occupancy.ts} +6 -6
- package/client/modeller/{v3-palette.ts → palette.ts} +3 -3
- package/client/modeller/{v3-preview-model.ts → preview-model.ts} +8 -8
- package/client/modeller/{v3-product-session.ts → product-session.ts} +25 -25
- package/client/modeller/{v3-recipe-source.ts → recipe-source.ts} +2 -2
- package/client/modeller/{v3-recipes.ts → recipes.ts} +9 -9
- package/client/modeller/{v3-renderer.ts → renderer.ts} +16 -16
- package/client/modeller/{v3-stage-model.ts → stage-model.ts} +15 -15
- package/client/modeller/{v3-thumbnail.ts → thumbnail.ts} +8 -8
- package/client/pages/figure-list-page.ts +4 -4
- package/client/pages/{figure-v3-modeller-page.ts → figure-modeller-page.ts} +37 -37
- package/client/route.ts +1 -5
- package/dist-client/figure-assistant.js +3 -3
- package/dist-client/figure-assistant.js.map +1 -1
- package/dist-client/graphql/store.d.ts +3 -0
- package/dist-client/graphql/store.js +39 -0
- package/dist-client/graphql/store.js.map +1 -0
- package/dist-client/modeller/ai-proposal.d.ts +51 -0
- package/dist-client/modeller/ai-proposal.js +64 -0
- package/dist-client/modeller/ai-proposal.js.map +1 -0
- package/dist-client/modeller/align.d.ts +30 -0
- package/dist-client/modeller/align.js +75 -0
- package/dist-client/modeller/align.js.map +1 -0
- package/dist-client/modeller/canvas.d.ts +72 -0
- package/dist-client/modeller/canvas.js +379 -0
- package/dist-client/modeller/canvas.js.map +1 -0
- package/dist-client/modeller/clips.d.ts +39 -0
- package/dist-client/modeller/clips.js +58 -0
- package/dist-client/modeller/clips.js.map +1 -0
- package/dist-client/modeller/controls.d.ts +28 -0
- package/dist-client/modeller/controls.js +28 -0
- package/dist-client/modeller/controls.js.map +1 -0
- package/dist-client/modeller/editor.d.ts +203 -0
- package/dist-client/modeller/editor.js +1526 -0
- package/dist-client/modeller/editor.js.map +1 -0
- package/dist-client/modeller/fields.d.ts +45 -0
- package/dist-client/modeller/fields.js +83 -0
- package/dist-client/modeller/fields.js.map +1 -0
- package/dist-client/modeller/figure-capability-panel.d.ts +22 -0
- package/dist-client/modeller/figure-capability-panel.js +141 -0
- package/dist-client/modeller/figure-capability-panel.js.map +1 -0
- package/dist-client/modeller/figure-clips.d.ts +31 -0
- package/dist-client/modeller/figure-clips.js +268 -0
- package/dist-client/modeller/figure-clips.js.map +1 -0
- package/dist-client/modeller/figure-controls.d.ts +36 -0
- package/dist-client/modeller/figure-controls.js +226 -0
- package/dist-client/modeller/figure-controls.js.map +1 -0
- package/dist-client/modeller/figure-facts.d.ts +35 -0
- package/dist-client/modeller/figure-facts.js +55 -0
- package/dist-client/modeller/figure-facts.js.map +1 -0
- package/dist-client/modeller/figure-first-save.d.ts +20 -0
- package/dist-client/modeller/figure-first-save.js +118 -0
- package/dist-client/modeller/figure-first-save.js.map +1 -0
- package/dist-client/modeller/figure-history-panel.d.ts +1 -1
- package/dist-client/modeller/figure-history-panel.js.map +1 -1
- package/dist-client/modeller/figure-links.d.ts +77 -0
- package/dist-client/modeller/figure-links.js +649 -0
- package/dist-client/modeller/figure-links.js.map +1 -0
- package/dist-client/modeller/figure-material-panel.d.ts +39 -0
- package/dist-client/modeller/figure-material-panel.js +323 -0
- package/dist-client/modeller/figure-material-panel.js.map +1 -0
- package/dist-client/modeller/figure-parts.d.ts +69 -0
- package/dist-client/modeller/figure-parts.js +559 -0
- package/dist-client/modeller/figure-parts.js.map +1 -0
- package/dist-client/modeller/figure-preview.d.ts +34 -0
- package/dist-client/modeller/figure-preview.js +346 -0
- package/dist-client/modeller/figure-preview.js.map +1 -0
- package/dist-client/modeller/figure-properties.d.ts +45 -0
- package/dist-client/modeller/figure-properties.js +239 -0
- package/dist-client/modeller/figure-properties.js.map +1 -0
- package/dist-client/modeller/figure-recipe-catalog.d.ts +38 -0
- package/dist-client/modeller/figure-recipe-catalog.js +399 -0
- package/dist-client/modeller/figure-recipe-catalog.js.map +1 -0
- package/dist-client/modeller/figure-verdict.d.ts +64 -0
- package/dist-client/modeller/figure-verdict.js +787 -0
- package/dist-client/modeller/figure-verdict.js.map +1 -0
- package/dist-client/modeller/first-save.d.ts +17 -0
- package/dist-client/modeller/first-save.js +26 -0
- package/dist-client/modeller/first-save.js.map +1 -0
- package/dist-client/modeller/gate-findings.d.ts +2 -2
- package/dist-client/modeller/gate-findings.js +2 -2
- package/dist-client/modeller/gate-findings.js.map +1 -1
- package/dist-client/modeller/gizmo.d.ts +72 -0
- package/dist-client/modeller/gizmo.js +111 -0
- package/dist-client/modeller/gizmo.js.map +1 -0
- package/dist-client/modeller/help.d.ts +18 -0
- package/dist-client/modeller/help.js +37 -0
- package/dist-client/modeller/help.js.map +1 -0
- package/dist-client/modeller/joint-drag.d.ts +19 -0
- package/dist-client/modeller/joint-drag.js +97 -0
- package/dist-client/modeller/joint-drag.js.map +1 -0
- package/dist-client/modeller/joint-fan.d.ts +9 -0
- package/dist-client/modeller/joint-fan.js +276 -0
- package/dist-client/modeller/joint-fan.js.map +1 -0
- package/dist-client/modeller/keys.d.ts +22 -0
- package/dist-client/modeller/keys.js +26 -0
- package/dist-client/modeller/keys.js.map +1 -0
- package/dist-client/modeller/links.d.ts +267 -0
- package/dist-client/modeller/links.js +480 -0
- package/dist-client/modeller/links.js.map +1 -0
- package/dist-client/modeller/live.d.ts +37 -0
- package/dist-client/modeller/live.js +68 -0
- package/dist-client/modeller/live.js.map +1 -0
- package/dist-client/modeller/maturity.d.ts +120 -0
- package/dist-client/modeller/maturity.js +313 -0
- package/dist-client/modeller/maturity.js.map +1 -0
- package/dist-client/modeller/modeller-route.d.ts +1 -1
- package/dist-client/modeller/modeller-route.js +1 -1
- package/dist-client/modeller/modeller-route.js.map +1 -1
- package/dist-client/modeller/occupancy.d.ts +18 -0
- package/dist-client/modeller/occupancy.js +24 -0
- package/dist-client/modeller/occupancy.js.map +1 -0
- package/dist-client/modeller/palette.d.ts +7 -0
- package/dist-client/modeller/palette.js +46 -0
- package/dist-client/modeller/palette.js.map +1 -0
- package/dist-client/modeller/preview-model.d.ts +47 -0
- package/dist-client/modeller/preview-model.js +68 -0
- package/dist-client/modeller/preview-model.js.map +1 -0
- package/dist-client/modeller/product-session.d.ts +510 -0
- package/dist-client/modeller/product-session.js +157 -0
- package/dist-client/modeller/product-session.js.map +1 -0
- package/dist-client/modeller/recipe-source.d.ts +8 -0
- package/dist-client/modeller/recipe-source.js +14 -0
- package/dist-client/modeller/recipe-source.js.map +1 -0
- package/dist-client/modeller/recipes.d.ts +37 -0
- package/dist-client/modeller/recipes.js +103 -0
- package/dist-client/modeller/recipes.js.map +1 -0
- package/dist-client/modeller/refusals.d.ts +68 -0
- package/dist-client/modeller/refusals.js +142 -0
- package/dist-client/modeller/refusals.js.map +1 -0
- package/dist-client/modeller/renderer.d.ts +40 -0
- package/dist-client/modeller/renderer.js +73 -0
- package/dist-client/modeller/renderer.js.map +1 -0
- package/dist-client/modeller/stage-model.d.ts +39 -0
- package/dist-client/modeller/stage-model.js +147 -0
- package/dist-client/modeller/stage-model.js.map +1 -0
- package/dist-client/modeller/thumbnail.d.ts +3 -0
- package/dist-client/modeller/thumbnail.js +75 -0
- package/dist-client/modeller/thumbnail.js.map +1 -0
- package/dist-client/pages/figure-list-page.d.ts +1 -1
- 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-modeller-page.d.ts +102 -0
- package/dist-client/pages/figure-modeller-page.js +548 -0
- package/dist-client/pages/figure-modeller-page.js.map +1 -0
- package/dist-client/route.d.ts +1 -1
- package/dist-client/route.js +1 -5
- package/dist-client/route.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/docs/product-cutover.md +25 -0
- package/package.json +5 -5
- package/test/{v3-ai-proposal.test.ts → ai-proposal.test.ts} +6 -6
- package/test/{v3-align.test.ts → align.test.ts} +8 -8
- package/test/browser/modeller-page.js +30 -30
- package/test/browser/modeller.check.mjs +14 -14
- package/test/browser/serve.mjs +1 -1
- package/test/browser/thumbnail-page.js +6 -6
- package/test/{v3-clips.test.ts → clips.test.ts} +18 -18
- package/test/{v3-controls.test.ts → controls.test.ts} +15 -15
- package/test/{v3-fields.test.ts → fields.test.ts} +13 -13
- package/test/figure-assistant-surface.test.ts +3 -3
- package/test/{v3-first-save.test.ts → first-save.test.ts} +10 -10
- package/test/fixtures/joint-column.json +2 -2
- package/test/fixtures/joint-roller.json +2 -2
- package/test/fixtures/robot-arm-6dof.json +2 -2
- package/test/{v3-gizmo.test.ts → gizmo.test.ts} +33 -33
- package/test/{v3-joint-drag.test.ts → joint-drag.test.ts} +17 -17
- package/test/{v3-joint-fan.test.ts → joint-fan.test.ts} +18 -18
- package/test/joints.ts +6 -6
- package/test/{v3-keys.test.ts → keys.test.ts} +2 -2
- package/test/{v3-layout.test.ts → layout.test.ts} +8 -8
- package/test/{v3-links.test.ts → links.test.ts} +201 -201
- package/test/{v3-live.test.ts → live.test.ts} +18 -18
- package/test/{v3-material.test.ts → material.test.ts} +12 -12
- package/test/{v3-maturity.test.ts → maturity.test.ts} +74 -74
- package/test/modeller-route.test.ts +5 -6
- package/test/{v3-palette-icons.test.ts → palette-icons.test.ts} +1 -1
- package/test/{v3-parts-wiring.test.ts → parts-wiring.test.ts} +5 -5
- package/test/{v3-preview.test.ts → preview.test.ts} +12 -12
- package/test/{v3-product-session.test.ts → product-session.test.ts} +17 -17
- package/test/{v3-product-write.test.ts → product-write.test.ts} +20 -20
- package/test/{v3-properties.test.ts → properties.test.ts} +24 -24
- package/test/{v3-recipes.test.ts → recipes.test.ts} +9 -9
- package/test/{v3-refusals.test.ts → refusals.test.ts} +43 -43
- package/test/{v3-release-panel.test.ts → release-panel.test.ts} +21 -21
- package/test/renderer.test.ts +24 -0
- package/test/resolve-ts-siblings.mjs +1 -1
- package/test/{v3-scene-canvas.test.ts → scene-canvas.test.ts} +6 -6
- package/test/{v3-stage-model.test.ts → stage-model.test.ts} +5 -5
- package/test/{v3-theme.test.ts → theme.test.ts} +4 -4
- package/test/{v3-thumbnail.test.ts → thumbnail.test.ts} +4 -4
- package/test/{v3-verdict.test.ts → verdict.test.ts} +17 -17
- package/test/{v3-view.test.ts → view.test.ts} +2 -2
- package/things-factory.config.js +2 -3
- package/translations/en.json +2 -2
- package/translations/ja.json +2 -2
- package/translations/ko.json +2 -2
- package/translations/ms.json +2 -2
- package/translations/zh.json +2 -2
- package/docs/v3-product-cutover.md +0 -25
- package/test/v3-renderer.test.ts +0 -24
- /package/client/modeller/{v3-fields.ts → fields.ts} +0 -0
- /package/client/modeller/{v3-first-save.ts → first-save.ts} +0 -0
- /package/client/modeller/{v3-help.ts → help.ts} +0 -0
- /package/client/modeller/{v3-keys.ts → keys.ts} +0 -0
- /package/client/modeller/{v3-refusals.ts → refusals.ts} +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type {
|
|
3
|
-
import type {
|
|
1
|
+
import { FIGURE_FACES, figureShapeFaces, figurePartsExtentOf, compileFigureAsset, compileFigureGraph, figureFollowOf, proposeFigureProportion, defaultInstanceSizeRange, foldInstanceSizeRange, figureAttachmentsOf, figureBindingsOf, figureInstanceSizeLimitsOf } from '@hatiolab/figure-model'
|
|
2
|
+
import type { FigureFollow, FigureSizeRange } from '@hatiolab/figure-model'
|
|
3
|
+
import type { FigureAsset, FigureBindingPlace, FigureFace, FigureJoint } from '@hatiolab/figure-model'
|
|
4
4
|
|
|
5
5
|
/*
|
|
6
|
-
* What a
|
|
6
|
+
* What a part is tied to — the readers behind the parts' size response, fastening and motion sections. Kept apart from the element so they can be tested without a browser.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
export type Axis = 'x' | 'y' | 'z'
|
|
@@ -15,11 +15,11 @@ interface Node {
|
|
|
15
15
|
args: string[]
|
|
16
16
|
outputs: Record<string, string>
|
|
17
17
|
}
|
|
18
|
-
const modelOf = (asset:
|
|
18
|
+
const modelOf = (asset: FigureAsset) =>
|
|
19
19
|
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 }> }
|
|
20
20
|
|
|
21
|
-
/** What one dimension of a part is worked out from (figure-model's `
|
|
22
|
-
export type Follow =
|
|
21
|
+
/** What one dimension of a part is worked out from (figure-model's `FigureFollow`). */
|
|
22
|
+
export type Follow = FigureFollow
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* `link-dimension` routes a part's dimension through `<part>.dimension.<axis>` and points its first argument at the
|
|
@@ -27,16 +27,16 @@ export type Follow = V3Follow
|
|
|
27
27
|
* instance size (`size.<axis>`), another part's dimension (`<part>.dimension.<axis>.value`) or a declared input.
|
|
28
28
|
*/
|
|
29
29
|
/** What one measure of a part is worked out from, read back from the graph (figure-model's reader, one copy). */
|
|
30
|
-
export function followOf(asset:
|
|
31
|
-
return
|
|
30
|
+
export function followOf(asset: FigureAsset, part: string, axis: Axis): Follow {
|
|
31
|
+
return figureFollowOf(asset, part, axis)
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
/** Whether the asset declares an instance size on an axis — only then can a dimension follow it. */
|
|
35
|
-
export const hasInstanceSize = (asset:
|
|
35
|
+
export const hasInstanceSize = (asset: FigureAsset, axis: Axis) => modelOf(asset).inputs.some(i => i.id === `size.${axis}` && i.role === 'design')
|
|
36
36
|
|
|
37
37
|
/** The part this one is fastened to, or `null`. The mounting plane counts as fastened (the model names no part). */
|
|
38
|
-
export function fastenedTo(asset:
|
|
39
|
-
return
|
|
38
|
+
export function fastenedTo(asset: FigureAsset, part: string): string | null {
|
|
39
|
+
return figureAttachmentsOf(asset)[part] ?? null
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
/**
|
|
@@ -44,7 +44,7 @@ export function fastenedTo(asset: V3Asset, part: string): string | null {
|
|
|
44
44
|
* it; a part standing on the plane had read as fastened to nothing, so the form offered no way to move it and sent
|
|
45
45
|
* no replace -- the fastening was refused as already made (the door's post, 2026-09-25).
|
|
46
46
|
*/
|
|
47
|
-
export function heldBy(asset:
|
|
47
|
+
export function heldBy(asset: FigureAsset, part: string): { part: string } | { plane: true } | null {
|
|
48
48
|
const record = asset.joints?.find(j => j.body1.part === part)
|
|
49
49
|
if (record) return 'plane' in record.body0 ? { plane: true } : { part: record.body0.part }
|
|
50
50
|
const to = fastenedTo(asset, part)
|
|
@@ -52,7 +52,7 @@ export function heldBy(asset: V3Asset, part: string): { part: string } | { plane
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
/** Whether a dimension of this part can follow something — box-shaped, whose three sizes are X, Y and Z. */
|
|
55
|
-
export function canFollow(asset:
|
|
55
|
+
export function canFollow(asset: FigureAsset, part: string): boolean {
|
|
56
56
|
return followableAxes(asset, part).length > 0
|
|
57
57
|
}
|
|
58
58
|
|
|
@@ -60,7 +60,7 @@ export function canFollow(asset: V3Asset, part: string): boolean {
|
|
|
60
60
|
* The axes whose size can follow something, by the shape's own measures: a box's X · Y · Z; a cylinder's radius (read
|
|
61
61
|
* as X) and length (Y) -- its shape takes those two, so it has no Z of its own.
|
|
62
62
|
*/
|
|
63
|
-
export function followableAxes(asset:
|
|
63
|
+
export function followableAxes(asset: FigureAsset, part: string): Axis[] {
|
|
64
64
|
const shape = modelOf(asset).nodes.find(n => n.id === `${part}.shape`)
|
|
65
65
|
if (!shape) return []
|
|
66
66
|
/* A door frame's first three measures are its width, height and depth, as a box's (figure-model portal-profile@2). */
|
|
@@ -70,12 +70,12 @@ export function followableAxes(asset: V3Asset, part: string): Axis[] {
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
/** 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. */
|
|
73
|
-
export function dimensionNow(asset:
|
|
73
|
+
export function dimensionNow(asset: FigureAsset, part: string, axis: Axis): number | undefined {
|
|
74
74
|
const m = modelOf(asset)
|
|
75
75
|
const ref = m.nodes.find(n => n.id === `${part}.shape`)?.args[AXES.indexOf(axis)]
|
|
76
76
|
if (!ref) return undefined
|
|
77
77
|
try {
|
|
78
|
-
const values =
|
|
78
|
+
const values = compileFigureGraph(asset.document.model as never).evaluate({ ...asset.designInputs, ...asset.stateDefaults }).values as Record<string, number>
|
|
79
79
|
const value = values[ref]
|
|
80
80
|
return typeof value === 'number' && Number.isFinite(value) ? value : undefined
|
|
81
81
|
} catch {
|
|
@@ -87,19 +87,19 @@ export function dimensionNow(asset: V3Asset, part: string, axis: Axis): number |
|
|
|
87
87
|
* The flat faces a part's shape declares, by name -- figure-model's shape definitions are the one place they are
|
|
88
88
|
* written (a portal's opening sides as well as its outer faces). A sphere has none.
|
|
89
89
|
*/
|
|
90
|
-
export function facesOf(asset:
|
|
90
|
+
export function facesOf(asset: FigureAsset, part: string): string[] {
|
|
91
91
|
const shape = modelOf(asset).nodes.find(n => n.id === `${part}.shape`)
|
|
92
|
-
return Object.keys((shape &&
|
|
92
|
+
return Object.keys((shape && figureShapeFaces(shape.op)) ?? {})
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
/** The axis a face of a part faces along, from the same definitions. */
|
|
96
|
-
export function faceAxisOf(asset:
|
|
96
|
+
export function faceAxisOf(asset: FigureAsset, part: string, face: string): Axis | undefined {
|
|
97
97
|
const shape = modelOf(asset).nodes.find(n => n.id === `${part}.shape`)
|
|
98
|
-
return (shape &&
|
|
98
|
+
return (shape && figureShapeFaces(shape.op)?.[face]?.axis) as Axis | undefined
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
/** Whether `attach` can take this part: any part whose shape has a flat face. */
|
|
102
|
-
export function canFasten(asset:
|
|
102
|
+
export function canFasten(asset: FigureAsset, part: string): boolean {
|
|
103
103
|
return facesOf(asset, part).length > 0
|
|
104
104
|
}
|
|
105
105
|
|
|
@@ -110,16 +110,16 @@ const OUTER: Record<Axis, [string, string]> = { x: ['left', 'right'], y: ['botto
|
|
|
110
110
|
* it is fastened fixed to another part and holds nothing. None for a part that is loose, moves, stands on the floor or
|
|
111
111
|
* already repeats -- the command would refuse those, so they are not offered.
|
|
112
112
|
*/
|
|
113
|
-
export function rowAxesOf(asset:
|
|
113
|
+
export function rowAxesOf(asset: FigureAsset, part: string): Axis[] {
|
|
114
114
|
const joints = asset.joints ?? []
|
|
115
115
|
const j = joints.find(o => o.body1.part === part)
|
|
116
116
|
if (!j || j.type !== 'fixed' || j.repeat || !('part' in j.body0)) return []
|
|
117
117
|
if (joints.some(o => 'part' in o.body0 && o.body0.part === part)) return []
|
|
118
|
-
return AXES.filter(axis => axis !==
|
|
118
|
+
return AXES.filter(axis => axis !== FIGURE_FACES[j.body1.face])
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
/** The row a part is made into, as its fastening's record keeps it. */
|
|
122
|
-
export const rowOf = (asset:
|
|
122
|
+
export const rowOf = (asset: FigureAsset, part: string) => asset.joints?.find(o => o.body1.part === part)?.repeat
|
|
123
123
|
|
|
124
124
|
/** Where a part sits across the face it is fastened to, on one axis: centred, level with an edge, or a distance off centre. */
|
|
125
125
|
/** `share`: the distance as a share of the figure's size on that axis, so it keeps its place when the figure is resized. */
|
|
@@ -134,11 +134,11 @@ export type Alignment = 'centre' | 'min' | 'max' | 'mm' | 'share'
|
|
|
134
134
|
* The mounting plane of this figure, by its placement (ADR-0093): the floor or the ceiling is Y = 0 and a part stands
|
|
135
135
|
* on it by its bottom or hangs from it by its top; a wall is Z = 0 and a part is fastened to it by its back.
|
|
136
136
|
*/
|
|
137
|
-
export function mountingPlaneOf(asset:
|
|
137
|
+
export function mountingPlaneOf(asset: FigureAsset): { normal: Axis; faces: FigureFace[] } {
|
|
138
138
|
return asset.occupancy?.placement === 'wall' ? { normal: 'z', faces: ['back'] } : { normal: 'y', faces: ['bottom', 'top'] }
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
-
export function alignAxesOf(asset:
|
|
141
|
+
export function alignAxesOf(asset: FigureAsset, part: string, target: { part: string; face: string } | 'plane', twisted = false): { axis: Axis; edges: boolean }[] {
|
|
142
142
|
/* The plane has no edges, but a figure that declares its size has sides on it (figure-model 0.1.64). */
|
|
143
143
|
if (target === 'plane') {
|
|
144
144
|
const { normal } = mountingPlaneOf(asset)
|
|
@@ -153,7 +153,7 @@ export function alignAxesOf(asset: V3Asset, part: string, target: { part: string
|
|
|
153
153
|
}
|
|
154
154
|
|
|
155
155
|
/** The controls a part's showing can follow: the figure's state inputs, with their range and label. */
|
|
156
|
-
export function visibilityControlsOf(asset:
|
|
156
|
+
export function visibilityControlsOf(asset: FigureAsset): Array<{ id: string; unit: string; min: number; max: number; label: string }> {
|
|
157
157
|
const labels = (asset as unknown as { stateInputs?: Record<string, { label?: string }> }).stateInputs ?? {}
|
|
158
158
|
return modelOf(asset)
|
|
159
159
|
.inputs.filter(input => input.role === 'state')
|
|
@@ -161,7 +161,7 @@ export function visibilityControlsOf(asset: V3Asset): Array<{ id: string; unit:
|
|
|
161
161
|
}
|
|
162
162
|
|
|
163
163
|
/** When the part is drawn (figure-model `set-visibility`): always (null), or while a control is at least a value. */
|
|
164
|
-
export function shownWhenOf(asset:
|
|
164
|
+
export function shownWhenOf(asset: FigureAsset, part: string): { input: string; atLeast: number } | null {
|
|
165
165
|
const look = asset.appearance.find(one => one.target === part) as { visibility?: string } | undefined
|
|
166
166
|
if (!look?.visibility) return null
|
|
167
167
|
const m = modelOf(asset)
|
|
@@ -171,11 +171,11 @@ export function shownWhenOf(asset: V3Asset, part: string): { input: string; atLe
|
|
|
171
171
|
}
|
|
172
172
|
|
|
173
173
|
/** Whether `part` hangs from `from`, through any number of fastenings. */
|
|
174
|
-
export function hangsFrom(asset:
|
|
174
|
+
export function hangsFrom(asset: FigureAsset, part: string, from: string): boolean {
|
|
175
175
|
const seen = new Set<string>()
|
|
176
176
|
for (let at: string | undefined = part; at && !seen.has(at); ) {
|
|
177
177
|
seen.add(at)
|
|
178
|
-
const j = ((asset.joints ?? []) as
|
|
178
|
+
const j = ((asset.joints ?? []) as FigureJoint[]).find(o => o.body1.part === at)
|
|
179
179
|
at = j && 'part' in j.body0 ? (j.body0 as { part: string }).part : undefined
|
|
180
180
|
if (at === from) return true
|
|
181
181
|
}
|
|
@@ -189,7 +189,7 @@ export function hangsFrom(asset: V3Asset, part: string, from: string): boolean {
|
|
|
189
189
|
* from there broke its turning in place (walk 1, 2026-09-26). Null for a part fastened to nothing.
|
|
190
190
|
*/
|
|
191
191
|
export function fasteningOf(
|
|
192
|
-
asset:
|
|
192
|
+
asset: FigureAsset,
|
|
193
193
|
part: string
|
|
194
194
|
): {
|
|
195
195
|
face: string
|
|
@@ -238,7 +238,7 @@ export function fasteningOf(
|
|
|
238
238
|
* its frame slid into it (2026-09-26). Left centred where it already is, and where the target stands turned.
|
|
239
239
|
*/
|
|
240
240
|
export function placeKeptOf(
|
|
241
|
-
asset:
|
|
241
|
+
asset: FigureAsset,
|
|
242
242
|
part: string,
|
|
243
243
|
target: { part: string; face: string } | 'plane',
|
|
244
244
|
twisted = false
|
|
@@ -246,7 +246,7 @@ export function placeKeptOf(
|
|
|
246
246
|
const kept = { align: {} as Partial<Record<Axis, Alignment>>, alignMm: {} as Partial<Record<Axis, number>> }
|
|
247
247
|
let geometry
|
|
248
248
|
try {
|
|
249
|
-
geometry =
|
|
249
|
+
geometry = compileFigureAsset(asset).evaluate().geometry as unknown as Array<{ id: string; pose?: { r: number[][]; t: number[] } }>
|
|
250
250
|
} catch {
|
|
251
251
|
return kept
|
|
252
252
|
}
|
|
@@ -272,7 +272,7 @@ export function placeKeptOf(
|
|
|
272
272
|
|
|
273
273
|
/** The `align` of an attach command from the form's choices: only the axes across that face, and centred ones left out. */
|
|
274
274
|
export function alignOf(
|
|
275
|
-
asset:
|
|
275
|
+
asset: FigureAsset,
|
|
276
276
|
part: string,
|
|
277
277
|
target: { part: string; face: string } | 'plane',
|
|
278
278
|
how: Partial<Record<Axis, Alignment>>,
|
|
@@ -294,7 +294,7 @@ export function alignOf(
|
|
|
294
294
|
/**
|
|
295
295
|
* The axes on which the part's position is worked out from other values rather than being its own number. `fix-position` makes one its own number where it is now.
|
|
296
296
|
*/
|
|
297
|
-
export function positionsWorkedOut(asset:
|
|
297
|
+
export function positionsWorkedOut(asset: FigureAsset, part: string): Axis[] {
|
|
298
298
|
const m = modelOf(asset)
|
|
299
299
|
const pose = m.nodes.find(n => n.id === `${part}.pose`)
|
|
300
300
|
if (!pose || pose.op !== 'rigid@1' || fastenedTo(asset, part)) return []
|
|
@@ -307,7 +307,7 @@ export function positionsWorkedOut(asset: V3Asset, part: string): Axis[] {
|
|
|
307
307
|
* Whether the part's position on an axis is a share of the instance size (`scale-position`): `size.<axis> × share`.
|
|
308
308
|
* Such a position is worked out too, but it already grows with the figure, so it is not offered again.
|
|
309
309
|
*/
|
|
310
|
-
export function positionIsShare(asset:
|
|
310
|
+
export function positionIsShare(asset: FigureAsset, part: string, axis: Axis): boolean {
|
|
311
311
|
const m = modelOf(asset)
|
|
312
312
|
const ref = m.nodes.find(n => n.id === `${part}.pose`)?.args[AXES.indexOf(axis)]
|
|
313
313
|
const writer = ref ? m.nodes.find(n => Object.values(n.outputs).includes(ref)) : undefined
|
|
@@ -325,7 +325,7 @@ export interface Motion {
|
|
|
325
325
|
travel?: { part: string; axis: string; plus?: number }
|
|
326
326
|
}
|
|
327
327
|
|
|
328
|
-
export function motionOf(asset:
|
|
328
|
+
export function motionOf(asset: FigureAsset, part: string): Motion | undefined {
|
|
329
329
|
const m = modelOf(asset)
|
|
330
330
|
const node = m.nodes.find(n => n.id === `${part}.motion`)
|
|
331
331
|
if (!node) return undefined
|
|
@@ -377,7 +377,7 @@ export interface MotionForm {
|
|
|
377
377
|
* travels by, what it follows -- rather than taken away and made again. Taking it away is refused while another joint
|
|
378
378
|
* follows it, which left a leader's stroke fixed for good (the telescopic conveyor, 2026-09-25).
|
|
379
379
|
*/
|
|
380
|
-
export function motionFormOf(joint:
|
|
380
|
+
export function motionFormOf(joint: FigureJoint): MotionForm {
|
|
381
381
|
const kind: MotionForm['kind'] = joint.type === 'prismatic' ? 'slide' : joint.lowerLimit === undefined && joint.upperLimit === undefined && !joint.mimic ? 'continuous' : 'turn'
|
|
382
382
|
const travel = joint.travel?.source && 'part' in joint.travel.source ? `${joint.travel.source.part}:${joint.travel.source.axis}` : ''
|
|
383
383
|
return {
|
|
@@ -398,7 +398,7 @@ export function motionFormOf(joint: V3Joint): MotionForm {
|
|
|
398
398
|
* The joints a part's joint can move with (URDF's mimic): a joint that moves the same way — a slide follows a slide, a
|
|
399
399
|
* turn a turn — and has a control of its own, since a follower follows the joint that is driven.
|
|
400
400
|
*/
|
|
401
|
-
export function leadersFor(asset:
|
|
401
|
+
export function leadersFor(asset: FigureAsset, part: string, kind: MotionForm['kind']): FigureJoint[] {
|
|
402
402
|
return (asset.joints ?? []).filter(j => j.body1.part !== part && j.type !== 'fixed' && !j.mimic && (j.type === 'prismatic') === (kind === 'slide'))
|
|
403
403
|
}
|
|
404
404
|
|
|
@@ -407,7 +407,7 @@ export function leadersFor(asset: V3Asset, part: string, kind: MotionForm['kind'
|
|
|
407
407
|
* a turn without end, as in USD) and a control; a follower gets the leader, the multiplier and the offset, and no
|
|
408
408
|
* control or limits of its own — its range is the leader's times the multiplier.
|
|
409
409
|
*/
|
|
410
|
-
export function motionJoint(joint:
|
|
410
|
+
export function motionJoint(joint: FigureJoint, m: MotionForm, part: string): FigureJoint {
|
|
411
411
|
const moving = {
|
|
412
412
|
id: joint.id,
|
|
413
413
|
body0: joint.body0,
|
|
@@ -441,7 +441,7 @@ export function choiceOf(follow: Follow): string {
|
|
|
441
441
|
}
|
|
442
442
|
|
|
443
443
|
/** 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. */
|
|
444
|
-
export function placeFacesOf(asset:
|
|
444
|
+
export function placeFacesOf(asset: FigureAsset, part: string): FigureFace[] {
|
|
445
445
|
const shape = modelOf(asset).nodes.find(n => n.id === `${part}.shape`)
|
|
446
446
|
if (shape?.op === 'box-shape@1') return ['left', 'right', 'bottom', 'top', 'back', 'front']
|
|
447
447
|
if (shape?.op === 'rounded-box@1') return ['bottom', 'top']
|
|
@@ -449,12 +449,12 @@ export function placeFacesOf(asset: V3Asset, part: string): V3Face[] {
|
|
|
449
449
|
}
|
|
450
450
|
|
|
451
451
|
/** The places things are held that sit on this part. */
|
|
452
|
-
export function placesOn(asset:
|
|
453
|
-
return
|
|
452
|
+
export function placesOn(asset: FigureAsset, part: string): FigureBindingPlace[] {
|
|
453
|
+
return figureBindingsOf(asset).filter(one => one.part === part)
|
|
454
454
|
}
|
|
455
455
|
|
|
456
456
|
/** An id for a new place on this part, not yet taken: `<part>-<role>`, then `-2`, `-3`. */
|
|
457
|
-
export function placeIdFor(asset:
|
|
457
|
+
export function placeIdFor(asset: FigureAsset, part: string, role: 'seat' | 'grip'): string {
|
|
458
458
|
const taken = new Set(asset.document.capabilities.bindings.map(b => b.id))
|
|
459
459
|
const base = `${part}-${role}`
|
|
460
460
|
if (!taken.has(base)) return base
|
|
@@ -467,7 +467,7 @@ export function placeIdFor(asset: V3Asset, part: string, role: 'seat' | 'grip'):
|
|
|
467
467
|
* Whether a board will hand this figure anything: a place is a slot only on a figure that declares it holds things
|
|
468
468
|
* (`Capacity`, or `FlowNode`, which includes it). Without it the places are declared and nothing arrives.
|
|
469
469
|
*/
|
|
470
|
-
export const holdsThings = (asset:
|
|
470
|
+
export const holdsThings = (asset: FigureAsset) => (asset.document.capabilities.names ?? []).some(n => n === 'Capacity' || n === 'FlowNode')
|
|
471
471
|
|
|
472
472
|
/** What `accepts` a person typed: names split on commas, trimmed, blanks and repeats dropped. */
|
|
473
473
|
export const acceptsOf = (typed: string) => [...new Set(typed.split(',').map(one => one.trim()).filter(Boolean))]
|
|
@@ -478,9 +478,9 @@ export const acceptsOf = (typed: string) => [...new Set(typed.split(',').map(one
|
|
|
478
478
|
* A range measured when a part filled what the others left (ROBOT_ARM: Y from 1,854 mm) stays narrow after the part is
|
|
479
479
|
* made to grow with the figure; measuring again gives the figure its whole range back. Nothing when no size is declared.
|
|
480
480
|
*/
|
|
481
|
-
export function instanceRangeOf(asset:
|
|
481
|
+
export function instanceRangeOf(asset: FigureAsset): { declared: FigureSizeRange; measured: FigureSizeRange } | undefined {
|
|
482
482
|
const inputs = modelOf(asset).inputs
|
|
483
|
-
const declared = {} as
|
|
483
|
+
const declared = {} as FigureSizeRange
|
|
484
484
|
const authored = {} as Record<Axis, number>
|
|
485
485
|
for (const axis of AXES) {
|
|
486
486
|
const input = inputs.find(i => i.id === `size.${axis}` && i.role === 'design')
|
|
@@ -490,7 +490,7 @@ export function instanceRangeOf(asset: V3Asset): { declared: V3SizeRange; measur
|
|
|
490
490
|
authored[axis] = at
|
|
491
491
|
}
|
|
492
492
|
const start = defaultInstanceSizeRange(authored)
|
|
493
|
-
const measured = foldInstanceSizeRange(start,
|
|
493
|
+
const measured = foldInstanceSizeRange(start, figureInstanceSizeLimitsOf(asset, start)).range
|
|
494
494
|
return { declared, measured }
|
|
495
495
|
}
|
|
496
496
|
|
|
@@ -500,13 +500,13 @@ export function instanceRangeOf(asset: V3Asset): { declared: V3SizeRange; measur
|
|
|
500
500
|
* size or has nothing to measure. Without a declared size a board cannot resize the figure, and no part is offered
|
|
501
501
|
* to follow its size -- every figure made in the modeller was left there (user, 2026-09-25).
|
|
502
502
|
*/
|
|
503
|
-
export function instanceRangeToDeclare(asset:
|
|
503
|
+
export function instanceRangeToDeclare(asset: FigureAsset): { range: FigureSizeRange; at: Record<Axis, number> } | undefined {
|
|
504
504
|
if (instanceRangeOf(asset) || !asset.appearance.length) return undefined
|
|
505
505
|
/* The parts as they rest -- not the room their joints swing them through, which declared a door 8208 mm every way. */
|
|
506
|
-
const at =
|
|
506
|
+
const at = figurePartsExtentOf(asset) as Record<Axis, number>
|
|
507
507
|
if (AXES.some(axis => !(at[axis] > 0))) return undefined
|
|
508
508
|
const start = defaultInstanceSizeRange(at)
|
|
509
|
-
return { range: foldInstanceSizeRange(start,
|
|
509
|
+
return { range: foldInstanceSizeRange(start, figureInstanceSizeLimitsOf(asset, start)).range, at }
|
|
510
510
|
}
|
|
511
511
|
|
|
512
512
|
/** Whether measuring again would change the declared range (to a micrometre). */
|
|
@@ -515,7 +515,7 @@ export function instanceRangeToDeclare(asset: V3Asset): { range: V3SizeRange; at
|
|
|
515
515
|
* product (a gantry spans metres, not 30 cm to 29 m), and measuring only ever widens them. Nothing when the typed
|
|
516
516
|
* value is not a number; whether the figure holds there is the command's to say.
|
|
517
517
|
*/
|
|
518
|
-
export function rangeWith(declared:
|
|
518
|
+
export function rangeWith(declared: FigureSizeRange, axis: Axis, bound: 'min' | 'max', value: number): FigureSizeRange | undefined {
|
|
519
519
|
if (!Number.isFinite(value)) return undefined
|
|
520
520
|
const next = structuredClone(declared)
|
|
521
521
|
next[axis][bound] = value
|
|
@@ -526,10 +526,10 @@ export function rangeWith(declared: V3SizeRange, axis: Axis, bound: 'min' | 'max
|
|
|
526
526
|
* For a figure that grows by one ratio: how many of its lengths would follow the ratio, and which it leaves with their
|
|
527
527
|
* own number (not those that follow another length -- they grow with it). Nothing for a figure of another sizing.
|
|
528
528
|
*/
|
|
529
|
-
export function proportionToFollow(asset:
|
|
529
|
+
export function proportionToFollow(asset: FigureAsset): { count: number; kept: string[] } | undefined {
|
|
530
530
|
if (asset.sizing !== 'uniform') return undefined
|
|
531
531
|
try {
|
|
532
|
-
const plan =
|
|
532
|
+
const plan = proposeFigureProportion(asset)
|
|
533
533
|
return { count: plan.dimensions.length + plan.lengths.length, kept: plan.skipped.filter(s => !s.follows).map(s => s.what) }
|
|
534
534
|
} catch {
|
|
535
535
|
return undefined
|
|
@@ -537,10 +537,10 @@ export function proportionToFollow(asset: V3Asset): { count: number; kept: strin
|
|
|
537
537
|
}
|
|
538
538
|
|
|
539
539
|
/** A value of the figure as it stands, by its graph reference. */
|
|
540
|
-
export function valueNow(asset:
|
|
540
|
+
export function valueNow(asset: FigureAsset, ref: string | undefined): number | undefined {
|
|
541
541
|
if (!ref) return undefined
|
|
542
542
|
try {
|
|
543
|
-
const values =
|
|
543
|
+
const values = compileFigureGraph(asset.document.model as never).evaluate({ ...asset.designInputs, ...asset.stateDefaults }).values as Record<string, number>
|
|
544
544
|
const value = values[ref]
|
|
545
545
|
return typeof value === 'number' && Number.isFinite(value) ? value : undefined
|
|
546
546
|
} catch {
|
|
@@ -548,7 +548,7 @@ export function valueNow(asset: V3Asset, ref: string | undefined): number | unde
|
|
|
548
548
|
}
|
|
549
549
|
}
|
|
550
550
|
|
|
551
|
-
export function rangeDiffers(a:
|
|
551
|
+
export function rangeDiffers(a: FigureSizeRange, b: FigureSizeRange): boolean {
|
|
552
552
|
return AXES.some(axis => Math.abs(a[axis].min - b[axis].min) > 1e-3 || Math.abs(a[axis].max - b[axis].max) > 1e-3)
|
|
553
553
|
}
|
|
554
554
|
|
|
@@ -556,7 +556,7 @@ export function rangeDiffers(a: V3SizeRange, b: V3SizeRange): boolean {
|
|
|
556
556
|
* Whether the figure is a mechanism -- a part turns about a joint. Such a figure should grow by one ratio (chief
|
|
557
557
|
* architect's ruling 2026-09-24); the modeller proposes it, the author declares it.
|
|
558
558
|
*/
|
|
559
|
-
export function turnsAboutAJoint(asset:
|
|
559
|
+
export function turnsAboutAJoint(asset: FigureAsset): boolean {
|
|
560
560
|
return modelOf(asset).nodes.some(n => n.op === 'axis-turn@1')
|
|
561
561
|
}
|
|
562
562
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type {
|
|
1
|
+
import { compileFigureAsset, editFigurePart } from '@hatiolab/figure-model'
|
|
2
|
+
import type { FigureAsset, FigurePartEdit } from '@hatiolab/figure-model'
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { drawableFigureParts, type FigureRenderPart } from './renderer.js'
|
|
5
5
|
|
|
6
6
|
/*
|
|
7
7
|
* The figure while a value is in hand: evaluated with the values being typed or dragged, drawn at once and written
|
|
@@ -18,13 +18,13 @@ export interface LiveValues {
|
|
|
18
18
|
/** Input values in hand, design or state, by input id. */
|
|
19
19
|
values: Record<string, number>
|
|
20
20
|
/** A part edit in hand, not yet committed. */
|
|
21
|
-
edit?:
|
|
21
|
+
edit?: FigurePartEdit
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
export const NOTHING_LIVE: LiveValues = { values: {} }
|
|
25
25
|
|
|
26
26
|
/** Known inputs with a number, split by what they are. An id the figure does not declare is dropped. */
|
|
27
|
-
export function liveOverridesOf(asset:
|
|
27
|
+
export function liveOverridesOf(asset: FigureAsset, values: Record<string, number>): { design: Record<string, number>; state: Record<string, number> } {
|
|
28
28
|
const roles = new Map(asset.document.model.inputs.map(i => [i.id, i.role]))
|
|
29
29
|
const design: Record<string, number> = {},
|
|
30
30
|
state: Record<string, number> = {}
|
|
@@ -48,13 +48,13 @@ export function isLive(live: LiveValues): boolean {
|
|
|
48
48
|
* refuses): the canvas keeps the figure as it stands until the release, and the release reports why through the same
|
|
49
49
|
* refusal every committed edit gets. A preview never invents a figure the commit would not make.
|
|
50
50
|
*/
|
|
51
|
-
export function livePartsOf(asset:
|
|
51
|
+
export function livePartsOf(asset: FigureAsset, live: LiveValues): FigureRenderPart[] | null {
|
|
52
52
|
if (!isLive(live)) return null
|
|
53
53
|
const { design, state } = liveOverridesOf(asset, live.values)
|
|
54
54
|
try {
|
|
55
|
-
let next = live.edit ?
|
|
55
|
+
let next = live.edit ? editFigurePart(asset, live.edit) : asset
|
|
56
56
|
if (Object.keys(design).length) next = { ...next, designInputs: { ...next.designInputs, ...design } }
|
|
57
|
-
return
|
|
57
|
+
return drawableFigureParts(compileFigureAsset(next).evaluate(state).geometry)
|
|
58
58
|
} catch {
|
|
59
59
|
return null
|
|
60
60
|
}
|
|
@@ -83,7 +83,7 @@ export function scrubbed(start: number, pixels: number, step: number): number {
|
|
|
83
83
|
* author has already set with the other controls, and the design values. Drawing over the asset's saved defaults put
|
|
84
84
|
* every other joint back to its saved angle while one slider moved (user, 2026-09-24: the arm jumped on release).
|
|
85
85
|
*/
|
|
86
|
-
export function liveOverSession(asset:
|
|
86
|
+
export function liveOverSession(asset: FigureAsset, current: Record<string, number>, live: LiveValues): FigureRenderPart[] | null {
|
|
87
87
|
if (!isLive(live)) return null
|
|
88
88
|
return livePartsOf(asset, { ...live, values: { ...current, ...live.values } })
|
|
89
89
|
}
|