@hatiolab/figure-model 0.1.92 → 0.1.94
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/CHANGELOG.md +12 -12
- package/README.md +25 -25
- package/dist/{v3-asset-types.d.ts → asset-types.d.ts} +73 -67
- package/dist/asset-types.d.ts.map +1 -0
- package/dist/asset-types.js +2 -0
- package/dist/asset-types.js.map +1 -0
- package/dist/asset.d.ts +20 -0
- package/dist/asset.d.ts.map +1 -0
- package/dist/{v3-asset.js → asset.js} +49 -28
- package/dist/asset.js.map +1 -0
- package/dist/{v3-authoring-actions.d.ts → authoring-actions.d.ts} +77 -66
- package/dist/authoring-actions.d.ts.map +1 -0
- package/dist/{v3-authoring-actions.js → authoring-actions.js} +129 -104
- package/dist/authoring-actions.js.map +1 -0
- package/dist/{v3-authoring-session.d.ts → authoring-session.d.ts} +23 -21
- package/dist/authoring-session.d.ts.map +1 -0
- package/dist/{v3-authoring-session.js → authoring-session.js} +4 -4
- package/dist/authoring-session.js.map +1 -0
- package/dist/capabilities.d.ts +3 -0
- package/dist/capabilities.d.ts.map +1 -0
- package/dist/{v3-capabilities.js → capabilities.js} +8 -8
- package/dist/capabilities.js.map +1 -0
- package/dist/clip-keeping.d.ts +30 -0
- package/dist/clip-keeping.d.ts.map +1 -0
- package/dist/clip-keeping.js +77 -0
- package/dist/clip-keeping.js.map +1 -0
- package/dist/command-preview.d.ts +125 -0
- package/dist/command-preview.d.ts.map +1 -0
- package/dist/command-preview.js +246 -0
- package/dist/command-preview.js.map +1 -0
- package/dist/contract.d.ts +7 -0
- package/dist/contract.d.ts.map +1 -0
- package/dist/contract.js +12 -0
- package/dist/contract.js.map +1 -0
- package/dist/{v3-cost.d.ts → cost.d.ts} +5 -5
- package/dist/cost.d.ts.map +1 -0
- package/dist/{v3-cost.js → cost.js} +11 -11
- package/dist/cost.js.map +1 -0
- package/dist/{v3-create.d.ts → create.d.ts} +8 -8
- package/dist/{v3-create.d.ts.map → create.d.ts.map} +1 -1
- package/dist/{v3-create.js → create.js} +26 -26
- package/dist/create.js.map +1 -0
- package/dist/document.d.ts +5 -0
- package/dist/document.d.ts.map +1 -0
- package/dist/document.js +31 -0
- package/dist/document.js.map +1 -0
- package/dist/{v3-driver.d.ts → driver.d.ts} +10 -10
- package/dist/driver.d.ts.map +1 -0
- package/dist/{v3-driver.js → driver.js} +10 -10
- package/dist/driver.js.map +1 -0
- package/dist/{v3-editor-workspace.d.ts → editor-workspace.d.ts} +99 -73
- package/dist/editor-workspace.d.ts.map +1 -0
- package/dist/{v3-editor-workspace.js → editor-workspace.js} +48 -32
- package/dist/editor-workspace.js.map +1 -0
- package/dist/{v3-examples.d.ts → examples.d.ts} +5 -5
- package/dist/examples.d.ts.map +1 -0
- package/dist/{v3-examples.js → examples.js} +11 -11
- package/dist/examples.js.map +1 -0
- package/dist/{v3-follow.d.ts → follow.d.ts} +5 -5
- package/dist/follow.d.ts.map +1 -0
- package/dist/{v3-follow.js → follow.js} +2 -2
- package/dist/follow.js.map +1 -0
- package/dist/{v3-gate.d.ts → gate.d.ts} +36 -28
- package/dist/gate.d.ts.map +1 -0
- package/dist/{v3-gate.js → gate.js} +54 -48
- package/dist/gate.js.map +1 -0
- package/dist/{v3-graph-types.d.ts → graph-types.d.ts} +33 -33
- package/dist/graph-types.d.ts.map +1 -0
- package/dist/graph-types.js +2 -0
- package/dist/graph-types.js.map +1 -0
- package/dist/{v3-graph.d.ts → graph.d.ts} +14 -14
- package/dist/graph.d.ts.map +1 -0
- package/dist/{v3-graph.js → graph.js} +16 -16
- package/dist/graph.js.map +1 -0
- package/dist/index.d.ts +39 -33
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +39 -33
- package/dist/index.js.map +1 -1
- package/dist/{v3-instance-size.d.ts → instance-size.d.ts} +14 -14
- package/dist/instance-size.d.ts.map +1 -0
- package/dist/{v3-instance-size.js → instance-size.js} +13 -13
- package/dist/instance-size.js.map +1 -0
- package/dist/json.d.ts +12 -0
- package/dist/json.d.ts.map +1 -0
- package/dist/{v3-json.js → json.js} +13 -13
- package/dist/json.js.map +1 -0
- package/dist/kernel-version.d.ts +8 -0
- package/dist/kernel-version.d.ts.map +1 -0
- package/dist/{v3-kernel-version.js → kernel-version.js} +4 -4
- package/dist/kernel-version.js.map +1 -0
- package/dist/{v3-part-edit.d.ts → part-edit.d.ts} +12 -11
- package/dist/part-edit.d.ts.map +1 -0
- package/dist/{v3-part-edit.js → part-edit.js} +84 -68
- package/dist/part-edit.js.map +1 -0
- package/dist/proposal.d.ts +55 -0
- package/dist/proposal.d.ts.map +1 -0
- package/dist/{v3-proposal.js → proposal.js} +29 -21
- package/dist/proposal.js.map +1 -0
- package/dist/{v3-public-inputs.d.ts → public-inputs.d.ts} +4 -4
- package/dist/public-inputs.d.ts.map +1 -0
- package/dist/{v3-public-inputs.js → public-inputs.js} +3 -3
- package/dist/public-inputs.js.map +1 -0
- package/dist/recipe-build.d.ts +27 -0
- package/dist/recipe-build.d.ts.map +1 -0
- package/dist/{v3-recipe-build.js → recipe-build.js} +9 -9
- package/dist/recipe-build.js.map +1 -0
- package/dist/recipe-from-parts.d.ts +8 -0
- package/dist/recipe-from-parts.d.ts.map +1 -0
- package/dist/{v3-recipe-from-parts.js → recipe-from-parts.js} +9 -9
- package/dist/recipe-from-parts.js.map +1 -0
- package/dist/{v3-recipe.d.ts → recipe.d.ts} +25 -25
- package/dist/recipe.d.ts.map +1 -0
- package/dist/{v3-recipe.js → recipe.js} +30 -30
- package/dist/recipe.js.map +1 -0
- package/dist/release-contract.d.ts +19 -0
- package/dist/release-contract.d.ts.map +1 -0
- package/dist/{v3-release-contract.js → release-contract.js} +8 -7
- package/dist/release-contract.js.map +1 -0
- package/dist/{v3-render-batches.d.ts → render-batches.d.ts} +8 -8
- package/dist/render-batches.d.ts.map +1 -0
- package/dist/{v3-render-batches.js → render-batches.js} +4 -4
- package/dist/render-batches.js.map +1 -0
- package/dist/resize-plan.d.ts +61 -0
- package/dist/resize-plan.d.ts.map +1 -0
- package/dist/resize-plan.js +189 -0
- package/dist/resize-plan.js.map +1 -0
- package/dist/room.d.ts +53 -0
- package/dist/room.d.ts.map +1 -0
- package/dist/room.js +90 -0
- package/dist/room.js.map +1 -0
- package/dist/{v3-score.d.ts → score.d.ts} +19 -19
- package/dist/score.d.ts.map +1 -0
- package/dist/{v3-score.js → score.js} +32 -32
- package/dist/score.js.map +1 -0
- package/dist/shape-provider.d.ts +37 -0
- package/dist/shape-provider.d.ts.map +1 -0
- package/dist/shape-provider.js +69 -0
- package/dist/shape-provider.js.map +1 -0
- package/dist/{v3-shape-sampling.d.ts → shape-sampling.d.ts} +4 -4
- package/dist/shape-sampling.d.ts.map +1 -0
- package/dist/{v3-shape-sampling.js → shape-sampling.js} +3 -3
- package/dist/shape-sampling.js.map +1 -0
- package/dist/size-edit.d.ts +38 -0
- package/dist/size-edit.d.ts.map +1 -0
- package/dist/size-edit.js +178 -0
- package/dist/size-edit.js.map +1 -0
- package/dist/{v3-surface.d.ts → surface.d.ts} +5 -5
- package/dist/surface.d.ts.map +1 -0
- package/dist/{v3-surface.js → surface.js} +7 -7
- package/dist/surface.js.map +1 -0
- package/dist/{v3-words.d.ts → words.d.ts} +3 -3
- package/dist/words.d.ts.map +1 -0
- package/dist/{v3-words.js → words.js} +3 -3
- package/dist/words.js.map +1 -0
- package/docs/{assembly-constraints-v3.md → assembly-constraints.md} +1 -1
- package/docs/{v3-asset-persistence.md → asset-persistence.md} +8 -8
- package/docs/{v3-authoring-transactions.md → authoring-transactions.md} +2 -2
- package/docs/{v3-capability-bindings.md → capability-bindings.md} +8 -8
- package/docs/{v3-common-kernel-experiment.md → common-kernel-experiment.md} +4 -4
- package/docs/{v3-core-semantics.md → core-semantics.md} +5 -5
- package/docs/{v3-cutover.md → cutover.md} +4 -4
- package/docs/{v3-design.md → design.md} +11 -11
- package/docs/{v3-document-contract.md → document-contract.md} +9 -9
- package/docs/{v3-dryroom-profile-validation.md → dryroom-profile-validation.md} +4 -4
- package/docs/{v3-editor-integration.md → editor-integration.md} +6 -6
- package/docs/{v3-full-conveyor-validation.md → full-conveyor-validation.md} +4 -4
- package/docs/{v3-full-dryroom-validation.md → full-dryroom-validation.md} +4 -4
- package/docs/{v3-full-forklift-validation.md → full-forklift-validation.md} +5 -5
- package/docs/{v3-full-robot-validation.md → full-robot-validation.md} +4 -4
- package/docs/{v3-layout-system.md → layout-system.md} +4 -4
- package/docs/{v3-legacy-risk-audit-2026-09-23.md → legacy-risk-audit-2026-09-23.md} +5 -5
- package/docs/{v3-minimal-model.md → minimal-model.md} +6 -6
- package/docs/{v3-motion-contract.md → motion-contract.md} +3 -3
- package/docs/{v3-openusd-review.md → openusd-review.md} +8 -8
- package/docs/{v3-operator-contracts.md → operator-contracts.md} +6 -6
- package/docs/{v3-payload-collision-validation.md → payload-collision-validation.md} +4 -4
- package/docs/{v3-payload-contact-validation.md → payload-contact-validation.md} +5 -5
- package/docs/prototypes/README.md +10 -10
- package/docs/prototypes/{v3-asset-samples.mjs → asset-samples.mjs} +6 -6
- package/docs/prototypes/{v3-asset-store.mjs → asset-store.mjs} +3 -3
- package/docs/prototypes/{v3-asset-store.test.mjs → asset-store.test.mjs} +6 -6
- package/docs/prototypes/asset.mjs +1 -0
- package/docs/prototypes/{v3-asset.schema.json → asset.schema.json} +2 -2
- package/docs/prototypes/{v3-asset.test.mjs → asset.test.mjs} +10 -10
- package/docs/prototypes/authoring-session.mjs +1 -0
- package/docs/prototypes/{v3-authoring-session.test.mjs → authoring-session.test.mjs} +6 -6
- package/docs/prototypes/capability-plan.mjs +1 -0
- package/docs/prototypes/{v3-capability-plan.test.mjs → capability-plan.test.mjs} +5 -5
- package/docs/prototypes/{v3-capability-samples.mjs → capability-samples.mjs} +2 -2
- package/docs/prototypes/document.mjs +2 -0
- package/docs/prototypes/{v3-document.schema.json → document.schema.json} +1 -1
- package/docs/prototypes/{v3-document.test.mjs → document.test.mjs} +7 -7
- package/docs/prototypes/{v3-dryroom-mesh.mjs → dryroom-mesh.mjs} +1 -1
- package/docs/prototypes/{v3-dryroom-mesh.test.mjs → dryroom-mesh.test.mjs} +1 -1
- package/docs/prototypes/{v3-editor-browser.mjs → editor-browser.mjs} +13 -13
- package/docs/prototypes/editor-workspace.mjs +1 -0
- package/docs/prototypes/{v3-editor-workspace.test.mjs → editor-workspace.test.mjs} +3 -3
- package/docs/prototypes/{v3-feature-pattern.test.mjs → feature-pattern.test.mjs} +1 -1
- package/docs/prototypes/{v3-full-conveyor.mjs → full-conveyor.mjs} +2 -2
- package/docs/prototypes/{v3-full-conveyor.test.mjs → full-conveyor.test.mjs} +3 -3
- package/docs/prototypes/{v3-full-dryroom.mjs → full-dryroom.mjs} +2 -2
- package/docs/prototypes/{v3-full-dryroom.test.mjs → full-dryroom.test.mjs} +3 -3
- package/docs/prototypes/{v3-full-forklift.mjs → full-forklift.mjs} +2 -2
- package/docs/prototypes/{v3-full-forklift.test.mjs → full-forklift.test.mjs} +3 -3
- package/docs/prototypes/{v3-full-robot.mjs → full-robot.mjs} +2 -2
- package/docs/prototypes/{v3-full-robot.test.mjs → full-robot.test.mjs} +4 -4
- package/docs/prototypes/{v3-kernel.test.mjs → kernel.test.mjs} +1 -1
- package/docs/prototypes/{v3-layout.test.mjs → layout.test.mjs} +1 -1
- package/docs/prototypes/{v3-more-samples.test.mjs → more-samples.test.mjs} +2 -2
- package/docs/prototypes/{v3-payload-collision.mjs → payload-collision.mjs} +2 -2
- package/docs/prototypes/{v3-payload-collision.test.mjs → payload-collision.test.mjs} +3 -3
- package/docs/prototypes/{v3-payload-fit.mjs → payload-fit.mjs} +2 -2
- package/docs/prototypes/{v3-payload-fit.test.mjs → payload-fit.test.mjs} +4 -4
- package/docs/prototypes/render-full-dryroom.mjs +12 -12
- package/docs/prototypes/{v3-repeated-binding.mjs → repeated-binding.mjs} +1 -1
- package/docs/prototypes/{v3-repeated-binding.test.mjs → repeated-binding.test.mjs} +6 -6
- package/docs/prototypes/{v3-runtime-browser.mjs → runtime-browser.mjs} +26 -26
- package/docs/prototypes/{v3-samples.test.mjs → samples.test.mjs} +1 -1
- package/docs/prototypes/{v3-spatial-plan.test.mjs → spatial-plan.test.mjs} +1 -1
- package/docs/prototypes/typed-plan.mjs +3 -0
- package/docs/prototypes/{v3-typed-plan.test.mjs → typed-plan.test.mjs} +1 -1
- package/docs/prototypes/{v3-typed-repeat.test.mjs → typed-repeat.test.mjs} +2 -2
- package/docs/{v3-repeated-binding-validation.md → repeated-binding-validation.md} +4 -4
- package/docs/{v3-research-review.md → research-review.md} +2 -2
- package/docs/{v3-review-2026-09-22.md → review-2026-09-22.md} +10 -10
- package/docs/{v3-runtime-extraction.md → runtime-extraction.md} +16 -16
- package/docs/{v3-sample-validation-expanded.md → sample-validation-expanded.md} +5 -5
- package/docs/{v3-sample-validation.md → sample-validation.md} +3 -3
- package/docs/{v3-shape-dimension-contract.md → shape-dimension-contract.md} +12 -12
- package/docs/{v3-shapeassembly-reproduction.md → shapeassembly-reproduction.md} +4 -4
- package/docs/{v3-spatial-contracts.md → spatial-contracts.md} +5 -5
- package/docs/status.md +12 -0
- package/docs/{v3-storage-integration.md → storage-integration.md} +5 -5
- package/docs/{v3-typed-repeat-contracts.md → typed-repeat-contracts.md} +3 -3
- package/package.json +3 -3
- package/dist/v3-asset-types.d.ts.map +0 -1
- package/dist/v3-asset-types.js +0 -2
- package/dist/v3-asset-types.js.map +0 -1
- package/dist/v3-asset.d.ts +0 -8
- package/dist/v3-asset.d.ts.map +0 -1
- package/dist/v3-asset.js.map +0 -1
- package/dist/v3-authoring-actions.d.ts.map +0 -1
- package/dist/v3-authoring-actions.js.map +0 -1
- package/dist/v3-authoring-session.d.ts.map +0 -1
- package/dist/v3-authoring-session.js.map +0 -1
- package/dist/v3-capabilities.d.ts +0 -3
- package/dist/v3-capabilities.d.ts.map +0 -1
- package/dist/v3-capabilities.js.map +0 -1
- package/dist/v3-contract.d.ts +0 -7
- package/dist/v3-contract.d.ts.map +0 -1
- package/dist/v3-contract.js +0 -12
- package/dist/v3-contract.js.map +0 -1
- package/dist/v3-cost.d.ts.map +0 -1
- package/dist/v3-cost.js.map +0 -1
- package/dist/v3-create.js.map +0 -1
- package/dist/v3-document.d.ts +0 -5
- package/dist/v3-document.d.ts.map +0 -1
- package/dist/v3-document.js +0 -31
- package/dist/v3-document.js.map +0 -1
- package/dist/v3-driver.d.ts.map +0 -1
- package/dist/v3-driver.js.map +0 -1
- package/dist/v3-editor-workspace.d.ts.map +0 -1
- package/dist/v3-editor-workspace.js.map +0 -1
- package/dist/v3-examples.d.ts.map +0 -1
- package/dist/v3-examples.js.map +0 -1
- package/dist/v3-follow.d.ts.map +0 -1
- package/dist/v3-follow.js.map +0 -1
- package/dist/v3-gate.d.ts.map +0 -1
- package/dist/v3-gate.js.map +0 -1
- package/dist/v3-graph-types.d.ts.map +0 -1
- package/dist/v3-graph-types.js +0 -2
- package/dist/v3-graph-types.js.map +0 -1
- package/dist/v3-graph.d.ts.map +0 -1
- package/dist/v3-graph.js.map +0 -1
- package/dist/v3-instance-size.d.ts.map +0 -1
- package/dist/v3-instance-size.js.map +0 -1
- package/dist/v3-json.d.ts +0 -12
- package/dist/v3-json.d.ts.map +0 -1
- package/dist/v3-json.js.map +0 -1
- package/dist/v3-kernel-version.d.ts +0 -8
- package/dist/v3-kernel-version.d.ts.map +0 -1
- package/dist/v3-kernel-version.js.map +0 -1
- package/dist/v3-part-edit.d.ts.map +0 -1
- package/dist/v3-part-edit.js.map +0 -1
- package/dist/v3-proposal.d.ts +0 -42
- package/dist/v3-proposal.d.ts.map +0 -1
- package/dist/v3-proposal.js.map +0 -1
- package/dist/v3-public-inputs.d.ts.map +0 -1
- package/dist/v3-public-inputs.js.map +0 -1
- package/dist/v3-recipe-build.d.ts +0 -27
- package/dist/v3-recipe-build.d.ts.map +0 -1
- package/dist/v3-recipe-build.js.map +0 -1
- package/dist/v3-recipe-from-parts.d.ts +0 -8
- package/dist/v3-recipe-from-parts.d.ts.map +0 -1
- package/dist/v3-recipe-from-parts.js.map +0 -1
- package/dist/v3-recipe.d.ts.map +0 -1
- package/dist/v3-recipe.js.map +0 -1
- package/dist/v3-release-contract.d.ts +0 -19
- package/dist/v3-release-contract.d.ts.map +0 -1
- package/dist/v3-release-contract.js.map +0 -1
- package/dist/v3-render-batches.d.ts.map +0 -1
- package/dist/v3-render-batches.js.map +0 -1
- package/dist/v3-score.d.ts.map +0 -1
- package/dist/v3-score.js.map +0 -1
- package/dist/v3-shape-sampling.d.ts.map +0 -1
- package/dist/v3-shape-sampling.js.map +0 -1
- package/dist/v3-surface.d.ts.map +0 -1
- package/dist/v3-surface.js.map +0 -1
- package/dist/v3-words.d.ts.map +0 -1
- package/dist/v3-words.js.map +0 -1
- package/docs/prototypes/v3-asset.mjs +0 -1
- package/docs/prototypes/v3-authoring-session.mjs +0 -1
- package/docs/prototypes/v3-capability-plan.mjs +0 -1
- package/docs/prototypes/v3-document.mjs +0 -2
- package/docs/prototypes/v3-editor-workspace.mjs +0 -1
- package/docs/prototypes/v3-typed-plan.mjs +0 -3
- package/docs/v3-status.md +0 -50
- /package/docs/prototypes/{v3-kernel.mjs → kernel.mjs} +0 -0
- /package/docs/prototypes/{v3-linear-comparison.py → linear-comparison.py} +0 -0
- /package/docs/prototypes/{v3-semantics.test.mjs → semantics.test.mjs} +0 -0
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
/*
|
|
5
5
|
* What a person is trying to say when they build a figure, as commands.
|
|
6
6
|
*
|
|
7
|
-
* The four things the hand-written sliding gate needed and the authoring API had no word for (
|
|
7
|
+
* The four things the hand-written sliding gate needed and the authoring API had no word for (designer's
|
|
8
8
|
* ruling 2026-09-23): a dimension that follows something, a part fastened to a face, a part that moves, and the
|
|
9
9
|
* room the figure takes up. This file carries the first two; the other two follow.
|
|
10
10
|
*
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* a reference an author or a connector holds keeps pointing at the same part after any of these.
|
|
14
14
|
*
|
|
15
15
|
* Frames. A part's placement is built from three nodes that are never merged, so adding one does not erase
|
|
16
|
-
* another (
|
|
16
|
+
* another (designer's ruling: attachment and motion must not overwrite the same pose):
|
|
17
17
|
*
|
|
18
18
|
* <id>.local --<id>.pose--> <id>.seat --<id>.motion--> <parent>.local --<parent>.world--> asset
|
|
19
19
|
*
|
|
@@ -21,25 +21,27 @@
|
|
|
21
21
|
* absent until something makes it move. `<parent>.world` is the parent's own chain, so moving or resizing the
|
|
22
22
|
* parent carries the child with it. A part that is fastened to nothing poses straight into the asset frame.
|
|
23
23
|
*/
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
24
|
+
import { settleFigureClips } from "./clip-keeping.js";
|
|
25
|
+
import { compileFigureAsset, figurePlacementOf, upgradeFigurePlacement } from "./asset.js";
|
|
26
|
+
import { checkPublicInput, keepPublicInputs } from "./public-inputs.js";
|
|
27
|
+
import { figurePartsExtentOf } from "./gate.js";
|
|
28
|
+
import { compileFigureGraph, figureShapeFaces } from "./graph.js";
|
|
29
|
+
import { figureFollowableAxesOf } from "./shape-provider.js";
|
|
30
|
+
import { FigureContractError } from "./contract.js";
|
|
31
|
+
import { AXES, REPEAT_LIMIT } from "./words.js";
|
|
32
|
+
import { defaultInstanceSizeRange, foldInstanceSizeRange, figureInstanceSizeLimitsOf, FIGURE_INSTANCE_SIZE_DEFAULT, FIGURE_INSTANCE_SIZE_INPUTS } from "./instance-size.js";
|
|
33
|
+
import { FIGURE_PLACEMENTS } from "./asset-types.js";
|
|
32
34
|
const fail = (code, path, message) => {
|
|
33
|
-
throw new
|
|
35
|
+
throw new FigureContractError(code, path, message);
|
|
34
36
|
};
|
|
35
37
|
/** The six faces of a box-shaped part, and the axis each one faces along. */
|
|
36
|
-
export const
|
|
38
|
+
export const FIGURE_FACES = { left: 'x', right: 'x', bottom: 'y', top: 'y', back: 'z', front: 'z' };
|
|
37
39
|
/**
|
|
38
40
|
* UsdPhysics's joint types (ADR-0093, amended 2026-09-25: the joint follows USD). A `fixed` joint is a fastening; a
|
|
39
41
|
* `revolute` joint turns -- with no limits it turns without end, its value a phase (-180..180), as USD's revolute
|
|
40
42
|
* joint without limits does; a `prismatic` joint slides.
|
|
41
43
|
*/
|
|
42
|
-
export const
|
|
44
|
+
export const FIGURE_JOINT_TYPES = ['fixed', 'revolute', 'prismatic'];
|
|
43
45
|
const SIGN = { left: -1, right: 1, bottom: -1, top: 1, back: -1, front: 1 };
|
|
44
46
|
const OPPOSITE = { left: 'right', right: 'left', bottom: 'top', top: 'bottom', back: 'front', front: 'back' };
|
|
45
47
|
const clone = (asset) => structuredClone(asset);
|
|
@@ -172,7 +174,7 @@ const OUTER = { x: ['left', 'right'], y: ['bottom', 'top'], z: ['back', 'front']
|
|
|
172
174
|
/** A part's declared faces, from its shape's own definition, or a refusal saying the shape has none. */
|
|
173
175
|
function facesOfPart(m, id, path) {
|
|
174
176
|
const { shape } = partOf(m, id, path);
|
|
175
|
-
const faces =
|
|
177
|
+
const faces = figureShapeFaces(shape.op);
|
|
176
178
|
if (!faces)
|
|
177
179
|
fail('EDIT_TARGET', path, `${id} is a ${shape.op}; it has no flat face to fasten by`);
|
|
178
180
|
return { shape, faces: faces };
|
|
@@ -189,12 +191,12 @@ function childFaceOf(m, j) {
|
|
|
189
191
|
fail('JOINT_SCHEMA', path, 'a part stands on the mounting plane by a face of its own, not by its centre');
|
|
190
192
|
/* One of the six, in the record's words: the face across from it. An opening's face, read from the parent's shape. */
|
|
191
193
|
const named = j.body0.face;
|
|
192
|
-
if (Object.hasOwn(
|
|
193
|
-
return Object.keys(
|
|
194
|
+
if (Object.hasOwn(FIGURE_FACES, named))
|
|
195
|
+
return Object.keys(FIGURE_FACES).find(f => FIGURE_FACES[f] === FIGURE_FACES[named] && SIGN[f] === -SIGN[named]);
|
|
194
196
|
const theirs = facesOfPart(m, j.body0.part, path).faces[j.body0.face];
|
|
195
197
|
if (!theirs)
|
|
196
198
|
fail('JOINT_SCHEMA', path, `${j.body0.part} has no face ${j.body0.face}`);
|
|
197
|
-
return Object.keys(
|
|
199
|
+
return Object.keys(FIGURE_FACES).find(f => FIGURE_FACES[f] === theirs.axis && SIGN[f] === -theirs.normal);
|
|
198
200
|
}
|
|
199
201
|
const planeOf = (shape, face) => face.plane.map(p => ({ ref: shape.args[p.arg], k: p.k }));
|
|
200
202
|
/** The part's outer faces on each axis in its own frame; an axis without a pair of flat faces has none. */
|
|
@@ -328,7 +330,7 @@ function sourceRef(m, asset, source, path) {
|
|
|
328
330
|
*
|
|
329
331
|
* "B is as wide as A" has to keep meaning that when A is told to follow something else. Reading A's shape
|
|
330
332
|
* argument at the moment of linking gave B whatever A happened to read then — A's own control — so relinking A
|
|
331
|
-
* left B on the old value (
|
|
333
|
+
* left B on the old value (designer's counterexample 2026-09-23: A went to 300 and B stayed at 100). Every
|
|
332
334
|
* dimension a command touches gets a node of its own, `<part>.dimension.<axis>`, and that is what the shape
|
|
333
335
|
* reads and what anything following it reads. Relinking rewrites what that node computes, and everyone pointing
|
|
334
336
|
* at it comes along — fastenings included, since they measure the same references.
|
|
@@ -356,7 +358,7 @@ function stableDimension(m, part, axis, path) {
|
|
|
356
358
|
const dimensionSource = (m, part, axis) => nodeById(m, `${part}.dimension.${axis}`)?.args?.[0];
|
|
357
359
|
/** Whether this side of the part already follows something, rather than reading its own control. */
|
|
358
360
|
/**
|
|
359
|
-
* A part's own number for a dimension: a design input named under the part. `
|
|
361
|
+
* A part's own number for a dimension: a design input named under the part. `addFigureBox` names it `<part>.size.<axis>`,
|
|
360
362
|
* the editor's shape palette `<part>.dimension.width` (and height, depth); both are the part's own, and a dimension
|
|
361
363
|
* read from one follows nothing. Reading only the first name made a palette box look linked as soon as any command
|
|
362
364
|
* had routed its dimension, and a link was refused as a replacement (2026-09-24, :3300).
|
|
@@ -415,9 +417,17 @@ function unlinkDimension(asset, a) {
|
|
|
415
417
|
if (!followsAlready(m, a.part, a.axis))
|
|
416
418
|
fail('EDIT_TARGET', path, `${a.part}'s ${a.axis} does not follow anything`);
|
|
417
419
|
const id = `${a.part}.size.${a.axis}`;
|
|
418
|
-
|
|
420
|
+
/*
|
|
421
|
+
The part's own field may still be there: the declared room was measured from it and still reads it, so it stayed
|
|
422
|
+
when the measure began to follow (ADR-0097). Setting the measure back to fixed takes that field back -- it is the
|
|
423
|
+
part's own number -- rather than refusing (found 2026-09-27: 「고정으로 되돌리기」 failed on any figure with a room).
|
|
424
|
+
Only a field of the part's own kind: anything else by that name is refused as before.
|
|
425
|
+
*/
|
|
426
|
+
const kept = m.inputs.find((i) => i.id === id);
|
|
427
|
+
if (kept && !(kept.role === 'design' && kept.unit === 'mm'))
|
|
419
428
|
fail('DUPLICATE_WRITER', path, `${id} already exists`);
|
|
420
|
-
|
|
429
|
+
if (!kept)
|
|
430
|
+
m.inputs.push({ id, unit: 'mm', min: Number.MIN_VALUE, max: Number.MAX_VALUE, role: 'design' });
|
|
421
431
|
asset.designInputs[id] = a.mm;
|
|
422
432
|
const node = nodeById(m, `${a.part}.dimension.${a.axis}`);
|
|
423
433
|
const was = node.args[0];
|
|
@@ -476,7 +486,7 @@ function attach(asset, a, twist) {
|
|
|
476
486
|
if (a.part === to.part)
|
|
477
487
|
fail('ATTACH_CYCLE', path, 'a part cannot be fastened to itself');
|
|
478
488
|
const byCentre = a.face === 'centre';
|
|
479
|
-
if (!byCentre && !Object.hasOwn(
|
|
489
|
+
if (!byCentre && !Object.hasOwn(FIGURE_FACES, String(a.face)))
|
|
480
490
|
fail('SCHEMA', path, `${String(a.face)} is not a face`);
|
|
481
491
|
/* The target's faces are the ones its shape declares: a portal's opening as well as its outer faces. */
|
|
482
492
|
const target = facesOfPart(m, to.part, path);
|
|
@@ -492,10 +502,10 @@ function attach(asset, a, twist) {
|
|
|
492
502
|
fail('ATTACH_FACE', path, `${a.part} fastened by its centre sits on ${to.face}; flush has no meaning for a centre`);
|
|
493
503
|
/* By its centre, the part is measured as the face that would meet the target's; only its offset differs. */
|
|
494
504
|
if (byCentre)
|
|
495
|
-
a = { ...a, face: Object.keys(
|
|
496
|
-
if (
|
|
497
|
-
fail('ATTACH_FACE', path, `${a.face} faces along ${
|
|
498
|
-
const wanted = Object.keys(
|
|
505
|
+
a = { ...a, face: Object.keys(FIGURE_FACES).find(f => FIGURE_FACES[f] === axis && SIGN[f] === -sign) };
|
|
506
|
+
if (FIGURE_FACES[a.face] !== axis)
|
|
507
|
+
fail('ATTACH_FACE', path, `${a.face} faces along ${FIGURE_FACES[a.face]} and ${to.face} along ${axis}; fastening them would need a turn, which this command does not do`);
|
|
508
|
+
const wanted = Object.keys(FIGURE_FACES).find(f => FIGURE_FACES[f] === axis && SIGN[f] === (facing === 'meet' ? -sign : sign));
|
|
499
509
|
if (facing === 'meet' && a.face !== wanted)
|
|
500
510
|
fail('ATTACH_FACE', path, `to meet ${to.face}, ${a.part} offers its ${wanted}; pass flush to lay ${a.face} in the same plane instead`);
|
|
501
511
|
if (facing === 'flush' && a.face !== wanted)
|
|
@@ -640,13 +650,13 @@ function standOnPlane(asset, a, path) {
|
|
|
640
650
|
if (a.facing !== undefined && a.facing !== 'meet')
|
|
641
651
|
fail('SCHEMA', path, 'a part standing on the plane meets it; flush says nothing here');
|
|
642
652
|
/* Which plane it is, the figure's placement says: the floor or the ceiling is Y = 0, a wall is Z = 0 (ADR-0093). */
|
|
643
|
-
const placement = (asset
|
|
653
|
+
const placement = figurePlacementOf(asset);
|
|
644
654
|
const takes = planeFacesOf(placement);
|
|
645
655
|
const normal = placement === 'wall' ? 'z' : 'y';
|
|
646
656
|
if (!takes.includes(a.face))
|
|
647
657
|
fail('ATTACH_FACE', path, placement === 'wall'
|
|
648
|
-
? `${String(a.face)} faces along ${
|
|
649
|
-
: `${String(a.face)} faces along ${
|
|
658
|
+
? `${String(a.face)} faces along ${FIGURE_FACES[a.face] ?? 'no axis'}; the figure hangs on a wall, the plane Z = 0, so a part is fastened to it by its back`
|
|
659
|
+
: `${String(a.face)} faces along ${FIGURE_FACES[a.face] ?? 'no axis'}; the plane faces along y, so a part stands on it by its bottom or hangs from it by its top`);
|
|
650
660
|
if (parentOf(m, a.part) && !a.replace)
|
|
651
661
|
fail('ATTACH_REPLACED', path, `${a.part} is already fastened to ${parentOf(m, a.part)}; pass replace to stand it on the plane instead`);
|
|
652
662
|
const mine = standingSpansOf(m, asset, a.part, path);
|
|
@@ -701,7 +711,7 @@ function detach(asset, a) {
|
|
|
701
711
|
/*
|
|
702
712
|
Detaching keeps the part where it stands, and keeps it moving the way it moved. Taking the world pose and
|
|
703
713
|
writing it in as the part's own pose did neither: the part's motion was already in that pose and was then
|
|
704
|
-
applied a second time, so a part with 20 mm of travel jumped to 40 (
|
|
714
|
+
applied a second time, so a part with 20 mm of travel jumped to 40 (designer's counterexample
|
|
705
715
|
2026-09-23).
|
|
706
716
|
|
|
707
717
|
What the part's pose has to gain is exactly what the parent's chain was contributing — and that is a plain
|
|
@@ -710,7 +720,7 @@ function detach(asset, a) {
|
|
|
710
720
|
*/
|
|
711
721
|
for (let up = parent; up; up = parentOf(m, up))
|
|
712
722
|
requireUnturned(m, asset, up, path);
|
|
713
|
-
const evaluated =
|
|
723
|
+
const evaluated = compileFigureAsset(asset).evaluate();
|
|
714
724
|
const above = evaluated.values?.[nodeById(m, `${parent}.world`)?.outputs?.pose ?? nodeById(m, `${parent}.chain`)?.outputs?.pose ?? nodeById(m, `${parent}.pose`).outputs.pose];
|
|
715
725
|
if (!above || !above.t)
|
|
716
726
|
fail('TARGET_ABSENT', path, `${parent} has no pose to read`);
|
|
@@ -833,8 +843,17 @@ function reseat(asset, id, to, place) {
|
|
|
833
843
|
dropIfUnused(asset, ref, ownInput(m, id, ref) ? ref : undefined);
|
|
834
844
|
}
|
|
835
845
|
/** Apply one authoring command. Throws on any refusal, leaving the asset it was given untouched. */
|
|
836
|
-
export function
|
|
837
|
-
|
|
846
|
+
export function applyFigureAuthoring(source, action, consent) {
|
|
847
|
+
/*
|
|
848
|
+
Writing the joints again strips each moving joint's clip with it; which clips stay, go (only when agreed) or refuse
|
|
849
|
+
the command is settled here, against the figure as it was (clip-keeping.ts).
|
|
850
|
+
*/
|
|
851
|
+
const before = upgradeFigurePlacement(source);
|
|
852
|
+
return settleFigureClips(before, authoring(before, action), consent);
|
|
853
|
+
}
|
|
854
|
+
function authoring(source, action) {
|
|
855
|
+
source = upgradeFigurePlacement(source);
|
|
856
|
+
compileFigureAsset(source);
|
|
838
857
|
if (!action || typeof action !== 'object')
|
|
839
858
|
fail('SCHEMA', 'action', 'an authoring action is required');
|
|
840
859
|
/* Made of the commands it stands for, each checked as it would be alone. */
|
|
@@ -851,6 +870,9 @@ export function applyV3Authoring(source, action) {
|
|
|
851
870
|
case 'attach':
|
|
852
871
|
fastenAsJoint(asset, action);
|
|
853
872
|
break;
|
|
873
|
+
case 'set-placement':
|
|
874
|
+
setPlacement(asset, action);
|
|
875
|
+
break;
|
|
854
876
|
case 'detach': {
|
|
855
877
|
const joint = jointAbove(asset, action.part);
|
|
856
878
|
if (!joint)
|
|
@@ -989,28 +1011,19 @@ export function applyV3Authoring(source, action) {
|
|
|
989
1011
|
rewriteJoints(asset);
|
|
990
1012
|
/* An input a command took away takes its place on the board with it (ADR-0096). */
|
|
991
1013
|
keepPublicInputs(asset);
|
|
992
|
-
|
|
1014
|
+
compileFigureAsset(asset);
|
|
993
1015
|
if ((action.kind === 'link-dimension' || action.kind === 'make-repeat' || action.kind === 'set-joint') && action.narrowRange)
|
|
994
1016
|
narrowDeclaredSize(asset);
|
|
995
1017
|
holdsOverDeclaredSize(asset, source);
|
|
996
1018
|
return asset;
|
|
997
1019
|
}
|
|
998
1020
|
/* ----------------------------------------------------------- instance size */
|
|
999
|
-
/** The shapes whose first measures lie along X, Y and Z, and which axes they have. */
|
|
1000
|
-
const MEASURED_ALONG = {
|
|
1001
|
-
'box-shape@1': ['x', 'y', 'z'],
|
|
1002
|
-
'rounded-box@1': ['x', 'y', 'z'],
|
|
1003
|
-
'portal-profile@2': ['x', 'y', 'z'],
|
|
1004
|
-
'hollow-box@2': ['x', 'y', 'z'],
|
|
1005
|
-
/* A cylinder's radius is read across (X), its length along (Y). */
|
|
1006
|
-
'cylinder-shape@1': ['x', 'y']
|
|
1007
|
-
};
|
|
1008
1021
|
function followSize(source, a) {
|
|
1009
1022
|
const path = `${a.part}.follow-size`;
|
|
1010
1023
|
const shape = source.document.model.nodes.find((n) => n.id === `${a.part}.shape`);
|
|
1011
1024
|
if (!shape)
|
|
1012
1025
|
fail('TARGET_ABSENT', path, `${a.part} is not a part`);
|
|
1013
|
-
const along =
|
|
1026
|
+
const along = figureFollowableAxesOf(shape.op);
|
|
1014
1027
|
if (!along.length)
|
|
1015
1028
|
fail('EDIT_TARGET', path, `${a.part} is a ${shape.op}; its measures do not lie along X, Y and Z`);
|
|
1016
1029
|
const axes = a.axes ?? along;
|
|
@@ -1020,28 +1033,28 @@ function followSize(source, a) {
|
|
|
1020
1033
|
let asset = source;
|
|
1021
1034
|
const m = asset.document.model;
|
|
1022
1035
|
if (!AXES.every(axis => sizeInputOf(m, axis))) {
|
|
1023
|
-
const at =
|
|
1036
|
+
const at = figurePartsExtentOf(asset);
|
|
1024
1037
|
const start = defaultInstanceSizeRange(at);
|
|
1025
|
-
const range = foldInstanceSizeRange(start,
|
|
1026
|
-
asset =
|
|
1038
|
+
const range = foldInstanceSizeRange(start, figureInstanceSizeLimitsOf(asset, start)).range;
|
|
1039
|
+
asset = applyFigureAuthoring(asset, { kind: 'declare-instance-size', range, at, replace: AXES.some(axis => sizeInputOf(m, axis)) });
|
|
1027
1040
|
}
|
|
1028
|
-
const values =
|
|
1041
|
+
const values = compileFigureGraph(asset.document.model).evaluate({ ...asset.designInputs, ...asset.stateDefaults }).values;
|
|
1029
1042
|
const argsNow = (asset.document.model.nodes.find((n) => n.id === `${a.part}.shape`)).args;
|
|
1030
1043
|
for (const axis of axes) {
|
|
1031
1044
|
const measure = values[argsNow[AXES.indexOf(axis)]];
|
|
1032
|
-
const size = asset.designInputs[
|
|
1045
|
+
const size = asset.designInputs[FIGURE_INSTANCE_SIZE_INPUTS[axis]];
|
|
1033
1046
|
if (!(typeof measure === 'number' && measure > 0 && typeof size === 'number' && size > 0))
|
|
1034
1047
|
fail('EDIT_TARGET', `${path}.${axis}`, `${a.part}'s ${axis} or the figure's size is not a length to follow`);
|
|
1035
|
-
asset =
|
|
1048
|
+
asset = applyFigureAuthoring(asset, { kind: 'link-dimension', part: a.part, axis, source: { of: 'instance', axis }, times: measure / size, replace: true, narrowRange: true });
|
|
1036
1049
|
}
|
|
1037
1050
|
return asset;
|
|
1038
1051
|
}
|
|
1039
|
-
const sizeInputOf = (m, axis) => m.inputs.find((i) => i.id ===
|
|
1052
|
+
const sizeInputOf = (m, axis) => m.inputs.find((i) => i.id === FIGURE_INSTANCE_SIZE_INPUTS[axis]);
|
|
1040
1053
|
function declareInstanceSize(asset, a) {
|
|
1041
1054
|
const m = asset.document.model;
|
|
1042
1055
|
const declared = AXES.filter(axis => sizeInputOf(m, axis));
|
|
1043
1056
|
if (declared.length && !a.replace)
|
|
1044
|
-
fail('INSTANCE_SIZE_DECLARED', 'size', `the instance size is already declared (${declared.map(x =>
|
|
1057
|
+
fail('INSTANCE_SIZE_DECLARED', 'size', `the instance size is already declared (${declared.map(x => FIGURE_INSTANCE_SIZE_INPUTS[x]).join(', ')}); pass replace to change its range`);
|
|
1045
1058
|
if (!a.range || typeof a.range !== 'object')
|
|
1046
1059
|
fail('SCHEMA', 'size', 'a range per axis is required');
|
|
1047
1060
|
let measured;
|
|
@@ -1049,18 +1062,18 @@ function declareInstanceSize(asset, a) {
|
|
|
1049
1062
|
const given = a.at?.[axis];
|
|
1050
1063
|
if (given !== undefined)
|
|
1051
1064
|
return given;
|
|
1052
|
-
const existing = asset.designInputs[
|
|
1065
|
+
const existing = asset.designInputs[FIGURE_INSTANCE_SIZE_INPUTS[axis]];
|
|
1053
1066
|
if (typeof existing === 'number')
|
|
1054
1067
|
return existing;
|
|
1055
1068
|
/* The parts as they rest, not the room their joints carry them through (a door's swing). */
|
|
1056
1069
|
if (!measured)
|
|
1057
|
-
measured =
|
|
1070
|
+
measured = figurePartsExtentOf(asset);
|
|
1058
1071
|
return measured[axis];
|
|
1059
1072
|
};
|
|
1060
1073
|
for (const axis of AXES) {
|
|
1061
1074
|
const r = a.range[axis];
|
|
1062
1075
|
const at = authoredOf(axis);
|
|
1063
|
-
const path =
|
|
1076
|
+
const path = FIGURE_INSTANCE_SIZE_INPUTS[axis];
|
|
1064
1077
|
if (!r || ![r.min, r.max, at].every(Number.isFinite))
|
|
1065
1078
|
fail('SCHEMA', path, 'finite min, max and size required');
|
|
1066
1079
|
if (!(r.min > 0))
|
|
@@ -1088,7 +1101,7 @@ function sizeRangeGaps(asset) {
|
|
|
1088
1101
|
if (!AXES.every(axis => sizeInputOf(m, axis)))
|
|
1089
1102
|
return [];
|
|
1090
1103
|
const range = Object.fromEntries(AXES.map(axis => [axis, { min: sizeInputOf(m, axis).min, max: sizeInputOf(m, axis).max }]));
|
|
1091
|
-
const limits =
|
|
1104
|
+
const limits = figureInstanceSizeLimitsOf(asset, range);
|
|
1092
1105
|
const { range: holds } = foldInstanceSizeRange(range, limits);
|
|
1093
1106
|
const gaps = [];
|
|
1094
1107
|
for (const axis of AXES) {
|
|
@@ -1108,9 +1121,9 @@ function narrowDeclaredSize(asset) {
|
|
|
1108
1121
|
return;
|
|
1109
1122
|
for (const gap of sizeRangeGaps(asset)) {
|
|
1110
1123
|
const input = sizeInputOf(m, gap.axis);
|
|
1111
|
-
const at = asset.designInputs[
|
|
1124
|
+
const at = asset.designInputs[FIGURE_INSTANCE_SIZE_INPUTS[gap.axis]];
|
|
1112
1125
|
if (gap.bound === 'min' ? at < gap.need : at > gap.need)
|
|
1113
|
-
fail('INSTANCE_SIZE_RANGE',
|
|
1126
|
+
fail('INSTANCE_SIZE_RANGE', FIGURE_INSTANCE_SIZE_INPUTS[gap.axis], `the figure is authored at ${at} mm, where a length is already gone; change the link or the size`);
|
|
1114
1127
|
if (gap.bound === 'min')
|
|
1115
1128
|
input.min = gap.need;
|
|
1116
1129
|
else
|
|
@@ -1133,7 +1146,7 @@ function holdsOverDeclaredSize(asset, before) {
|
|
|
1133
1146
|
const worse = !prior || (gap.bound === 'min' ? gap.need > prior.need + 1e-9 || gap.declared < prior.declared : gap.need < prior.need - 1e-9 || gap.declared > prior.declared);
|
|
1134
1147
|
if (!worse)
|
|
1135
1148
|
continue;
|
|
1136
|
-
const path =
|
|
1149
|
+
const path = FIGURE_INSTANCE_SIZE_INPUTS[gap.axis];
|
|
1137
1150
|
const by = gap.who ? ` (${gap.who})` : '';
|
|
1138
1151
|
if (gap.bound === 'min')
|
|
1139
1152
|
fail('INSTANCE_SIZE_RANGE', path, `below ${round6(gap.need)} mm a length goes to zero${by}; declare the smallest size at ${round6(gap.need)} mm or more`);
|
|
@@ -1141,7 +1154,7 @@ function holdsOverDeclaredSize(asset, before) {
|
|
|
1141
1154
|
}
|
|
1142
1155
|
}
|
|
1143
1156
|
/** What each part is fastened to, for a reader that wants the structure rather than the graph. */
|
|
1144
|
-
export function
|
|
1157
|
+
export function figureAttachmentsOf(asset) {
|
|
1145
1158
|
const m = asset.document.model;
|
|
1146
1159
|
const out = {};
|
|
1147
1160
|
for (const n of m.nodes)
|
|
@@ -1161,8 +1174,8 @@ function checkJoint(asset, j) {
|
|
|
1161
1174
|
const path = `joints.${String(j?.id)}`;
|
|
1162
1175
|
if (!j || typeof j.id !== 'string' || !j.id.trim())
|
|
1163
1176
|
fail('JOINT_SCHEMA', 'joints', 'a joint has an id');
|
|
1164
|
-
if (!
|
|
1165
|
-
fail('JOINT_SCHEMA', path, `${String(j.type)} is not a joint type; ${
|
|
1177
|
+
if (!FIGURE_JOINT_TYPES.includes(j.type))
|
|
1178
|
+
fail('JOINT_SCHEMA', path, `${String(j.type)} is not a joint type; ${FIGURE_JOINT_TYPES.join(', ')}`);
|
|
1166
1179
|
if (!j.body1 || typeof j.body1.part !== 'string')
|
|
1167
1180
|
fail('JOINT_SCHEMA', path, 'a joint carries a part (body1)');
|
|
1168
1181
|
partOf(m, j.body1.part, path);
|
|
@@ -1247,7 +1260,7 @@ function checkJoint(asset, j) {
|
|
|
1247
1260
|
fail('JOINT_SCHEMA', row, 'a row fills the part it is fastened to; the mounting plane has no length to fill');
|
|
1248
1261
|
if (!['X', 'Y', 'Z'].includes(r.axis))
|
|
1249
1262
|
fail('JOINT_SCHEMA', row, `a row runs along X, Y or Z, not ${String(r.axis)}`);
|
|
1250
|
-
if (r.axis.toLowerCase() ===
|
|
1263
|
+
if (r.axis.toLowerCase() === FIGURE_FACES[childFaceOf(asset.document.model, j)])
|
|
1251
1264
|
fail('JOINT_SCHEMA', row, `a row runs across the face it is fastened to; ${r.axis} is out of that face`);
|
|
1252
1265
|
if (typeof r.pitch === 'number' ? !(Number.isFinite(r.pitch) && r.pitch > 0) : !(r.pitch && typeof r.pitch === 'object' && r.pitch.from))
|
|
1253
1266
|
fail('GEOMETRY_DOMAIN', row, 'a row has a positive pitch, or one that follows a length');
|
|
@@ -1282,7 +1295,7 @@ function writeJoint(asset, j) {
|
|
|
1282
1295
|
const own = AXES.map(a => j.origin?.turn?.[a] ?? 0);
|
|
1283
1296
|
/* The child as it stands: its quarter turn, then its twist about the normal of the face it is fastened by. */
|
|
1284
1297
|
const childFace = childFaceOf(m, j);
|
|
1285
|
-
const normal =
|
|
1298
|
+
const normal = FIGURE_FACES[childFace];
|
|
1286
1299
|
const standing = j.origin?.twist ? mul3(axisTurn(normal, j.origin.twist), turnMatrix(own)) : turnMatrix(own);
|
|
1287
1300
|
/* In the parent's frame: its twist about the face's normal there, and the quarter turn that is left. */
|
|
1288
1301
|
const inParent = mul3(back, standing);
|
|
@@ -1373,7 +1386,7 @@ function standingTurnOf(asset, part, path) {
|
|
|
1373
1386
|
fail('EDIT_TARGET', path, `${part} is turned (${saidTurn(turn)}); fastening to a turned part by its faces is covered only for quarter turns`);
|
|
1374
1387
|
const quarter = turn.every(t => t === 0) ? IDENTITY : turnMatrix(turn);
|
|
1375
1388
|
/* A twisted part stands twisted about the normal of the face it is fastened by. */
|
|
1376
|
-
return record?.origin?.twist ? mul3(axisTurn(
|
|
1389
|
+
return record?.origin?.twist ? mul3(axisTurn(FIGURE_FACES[childFaceOf(asset.document.model, record)], record.origin.twist), quarter) : quarter;
|
|
1377
1390
|
}
|
|
1378
1391
|
/** How a part stands before its twist: its quarter turn only. */
|
|
1379
1392
|
function standingQuarterOf(asset, part, path) {
|
|
@@ -1689,7 +1702,7 @@ function rewriteJoints(asset) {
|
|
|
1689
1702
|
* was no longer there, and the author could not save (TARGET_ABSENT on joints.*, 2026-09-25).
|
|
1690
1703
|
*/
|
|
1691
1704
|
/** The records follow a part's new name -- as a joint's parent, as its child, and as the joint a follower follows -- and the nodes are made again from them. */
|
|
1692
|
-
export function
|
|
1705
|
+
export function renameFigurePartInJoints(asset, from, to) {
|
|
1693
1706
|
const joints = asset.joints;
|
|
1694
1707
|
if (!joints?.length)
|
|
1695
1708
|
return;
|
|
@@ -1746,7 +1759,7 @@ function refuseIfRead(asset, part, spaceFittedAgain = false) {
|
|
|
1746
1759
|
* A copied part is fastened as the original is: its own record, the same faces and place, so it is not held by nodes
|
|
1747
1760
|
* no record makes. A moving joint gets its own control -- a copy is a second thing, not the original's shadow.
|
|
1748
1761
|
*/
|
|
1749
|
-
export function
|
|
1762
|
+
export function copyFigurePartJoint(asset, from, to) {
|
|
1750
1763
|
const joints = (asset.joints ?? []);
|
|
1751
1764
|
const j = joints.find(o => o.body1.part === from);
|
|
1752
1765
|
if (!j)
|
|
@@ -1799,7 +1812,7 @@ function baseUnitOf(type) {
|
|
|
1799
1812
|
* size or place becomes the number it is now. One thing is refused, by name: a row laid along it, or hanging in it --
|
|
1800
1813
|
* a row is made from its record, and a record needs the part (ADR-0093).
|
|
1801
1814
|
*/
|
|
1802
|
-
export function
|
|
1815
|
+
export function releaseFigurePartJoints(asset, part, options = {}) {
|
|
1803
1816
|
const a = asset;
|
|
1804
1817
|
const m = a.document.model;
|
|
1805
1818
|
const notice = { released: [], fixed: [], controls: [] };
|
|
@@ -1815,7 +1828,7 @@ export function releaseV3PartJoints(asset, part, options = {}) {
|
|
|
1815
1828
|
const inputs = (over = {}) => ({ ...a.designInputs, ...a.stateDefaults, ...over });
|
|
1816
1829
|
const controls = m.inputs.filter((i) => i.role === 'state');
|
|
1817
1830
|
const rest = Object.fromEntries(controls.map((i) => [i.id, Math.min(i.max, Math.max(i.min, 0))]));
|
|
1818
|
-
const graph =
|
|
1831
|
+
const graph = compileFigureGraph(m);
|
|
1819
1832
|
const atRest = graph.evaluate(inputs(rest)).values;
|
|
1820
1833
|
const now = graph.evaluate(inputs()).values;
|
|
1821
1834
|
/* A part moving with a joint that goes: its own control, over what the leader's distance or angle came to. */
|
|
@@ -1848,7 +1861,7 @@ export function releaseV3PartJoints(asset, part, options = {}) {
|
|
|
1848
1861
|
return measure;
|
|
1849
1862
|
const probe = structuredClone(m);
|
|
1850
1863
|
const ref = stableDimension(probe, part, measure.from.axis, where);
|
|
1851
|
-
const value = round6(Number(
|
|
1864
|
+
const value = round6(Number(compileFigureGraph(probe).evaluate(inputs()).values[ref]) * (measure.times ?? 1) + (measure.plus ?? 0));
|
|
1852
1865
|
notice.fixed.push({ part: where, value, unit: 'mm' });
|
|
1853
1866
|
return value;
|
|
1854
1867
|
};
|
|
@@ -2066,7 +2079,7 @@ function setJoint(asset, joint) {
|
|
|
2066
2079
|
if (!jointAbove(asset, j.body1.part) && !j.origin?.turn) {
|
|
2067
2080
|
const turn = turnOf(asset.document.model, asset, j.body1.part, j.body1.part);
|
|
2068
2081
|
/* A part turned by any angle about the normal of the face it is fastened by keeps that angle as its twist. */
|
|
2069
|
-
const n = AXES.indexOf(
|
|
2082
|
+
const n = AXES.indexOf(FIGURE_FACES[childFaceOf(asset.document.model, j)]);
|
|
2070
2083
|
const quarter = (t) => t !== null && Number.isFinite(t) && Math.abs(t / 90 - Math.round(t / 90)) < 1e-9;
|
|
2071
2084
|
const twist = !quarter(turn[n]) && turn.every((t, i) => i === n || t === 0) ? turn[n] : undefined;
|
|
2072
2085
|
if (twist !== undefined && !j.origin?.twist)
|
|
@@ -2092,7 +2105,7 @@ function removeJoint(asset, id) {
|
|
|
2092
2105
|
fail('JOINT_SCHEMA', `joints.${id}`, `${followers.map(f => f.id).join(', ')} follow ${id}; remove them first`);
|
|
2093
2106
|
const m = asset.document.model;
|
|
2094
2107
|
const child = j.body1.part;
|
|
2095
|
-
const evaluated =
|
|
2108
|
+
const evaluated = compileFigureAsset(asset).evaluate();
|
|
2096
2109
|
const placed = nodeById(m, child);
|
|
2097
2110
|
const at = evaluated.values?.[placed.args[1]];
|
|
2098
2111
|
if (!at?.t)
|
|
@@ -2147,7 +2160,7 @@ function fastenAsJoint(asset, a) {
|
|
|
2147
2160
|
* The records are the canon: made again from them, the joints' nodes must be the nodes the asset holds. An asset
|
|
2148
2161
|
* whose nodes or records were edited apart is refused, not quietly rebuilt (ADR-0093, chief architect's condition).
|
|
2149
2162
|
*/
|
|
2150
|
-
export function
|
|
2163
|
+
export function checkFigureJoints(asset) {
|
|
2151
2164
|
const joints = asset.joints;
|
|
2152
2165
|
if (!joints?.length)
|
|
2153
2166
|
return;
|
|
@@ -2306,7 +2319,8 @@ function removeMotion(asset, a) {
|
|
|
2306
2319
|
}
|
|
2307
2320
|
/** A figure as one value, for saying "this is the figure I read". The declaration itself is left out of it. */
|
|
2308
2321
|
function fingerprintOf(asset) {
|
|
2309
|
-
|
|
2322
|
+
/* The room and the placement are left out: neither changes what the room would measure (ADR-0097). */
|
|
2323
|
+
const text = JSON.stringify({ ...asset, placement: undefined, occupancy: asset.occupancy ? { ...asset.occupancy, bounds: null } : undefined });
|
|
2310
2324
|
let h1 = 0x811c9dc5;
|
|
2311
2325
|
let h2 = 0x01000193;
|
|
2312
2326
|
for (let i = 0; i < text.length; i++) {
|
|
@@ -2699,7 +2713,7 @@ function occupancyLins(asset, over) {
|
|
|
2699
2713
|
const own = occupiedExtentsOf(m, id, id, turns);
|
|
2700
2714
|
const c = turns ? IDENTITY : worldTurnOf(m, asset, id);
|
|
2701
2715
|
if (!c)
|
|
2702
|
-
throw new
|
|
2716
|
+
throw new FigureContractError('EDIT_TARGET', id, `${id} is turned by other than quarter turns`);
|
|
2703
2717
|
return Object.fromEntries(AXES.map((axis, i) => [axis, own[AXES[c[i].findIndex(v => v !== 0)]]]));
|
|
2704
2718
|
};
|
|
2705
2719
|
/*
|
|
@@ -2749,7 +2763,7 @@ function occupancyLins(asset, over) {
|
|
|
2749
2763
|
sizes = sizesOf(place.id, turns);
|
|
2750
2764
|
}
|
|
2751
2765
|
catch (e) {
|
|
2752
|
-
skipped.push({ part: place.id, reason: e instanceof
|
|
2766
|
+
skipped.push({ part: place.id, reason: e instanceof FigureContractError ? e.message : 'not a part whose size this command can read' });
|
|
2753
2767
|
continue;
|
|
2754
2768
|
}
|
|
2755
2769
|
/*
|
|
@@ -2818,7 +2832,7 @@ function occupancyLins(asset, over) {
|
|
|
2818
2832
|
}
|
|
2819
2833
|
/*
|
|
2820
2834
|
Both ends of every control are candidates, and which one is the low end is not decided here: a travel of
|
|
2821
|
-
−40 mm puts the low end at the control's maximum (
|
|
2835
|
+
−40 mm puts the low end at the control's maximum (designer's counterexample 2026-09-23, where the
|
|
2822
2836
|
proposal read −50..10 for a part that needs −90..50). Both candidates go into the same min and the same
|
|
2823
2837
|
max, so the answer holds whichever way the travel runs, and holds at every design size, because the
|
|
2824
2838
|
choice is made when the graph is evaluated rather than when the proposal is written.
|
|
@@ -2890,7 +2904,8 @@ function occupancyLins(asset, over) {
|
|
|
2890
2904
|
}
|
|
2891
2905
|
const valueOfLin = (values, l) => l.terms.reduce((s, t) => s + t.k * (values[t.ref] ?? NaN), l.c);
|
|
2892
2906
|
/** What the parts come to, and what a person is being asked to confirm. Reads the asset; changes nothing. */
|
|
2893
|
-
export function
|
|
2907
|
+
export function proposeFigureOccupancy(asset, options = {}) {
|
|
2908
|
+
asset = upgradeFigurePlacement(asset);
|
|
2894
2909
|
const over = options.over ?? 'range';
|
|
2895
2910
|
if (over !== 'rest' && over !== 'range')
|
|
2896
2911
|
fail('SCHEMA', 'over', `${String(over)} is neither rest nor range`);
|
|
@@ -2903,28 +2918,35 @@ export function proposeV3Occupancy(asset, options = {}) {
|
|
|
2903
2918
|
}
|
|
2904
2919
|
if (!parts.length)
|
|
2905
2920
|
fail('OCCUPANCY_NO_PARTS', 'occupancy', `no part could be measured${skipped.length ? `: ${skipped.map(s => `${s.part} — ${s.reason}`).join('; ')}` : ''}`);
|
|
2906
|
-
const values =
|
|
2921
|
+
const values = compileFigureGraph(source.document.model).evaluate({ ...source.designInputs, ...source.stateDefaults }).values;
|
|
2907
2922
|
const at = Object.fromEntries(AXES.map(axis => [axis, { min: round6(Math.min(...low[axis].map(l => valueOfLin(values, l)))), max: round6(Math.max(...high[axis].map(l => valueOfLin(values, l)))) }]));
|
|
2908
2923
|
return { over, at, parts, skipped, basis: fingerprintOf(source) };
|
|
2909
2924
|
}
|
|
2925
|
+
/**
|
|
2926
|
+
* The placement says which plane the mounting plane is. A part already fastened to it by a face the new plane does not
|
|
2927
|
+
* take -- a bottom on the floor, when the figure is to hang on a wall -- is named, not seated again without a word
|
|
2928
|
+
* (chief architect's condition, 2026-09-25): fasten it again by the face the new plane takes.
|
|
2929
|
+
*/
|
|
2930
|
+
function setPlacement(asset, a) {
|
|
2931
|
+
if (!FIGURE_PLACEMENTS.includes(a.placement))
|
|
2932
|
+
fail('SCHEMA', 'placement', `${String(a.placement)} is not a placement`);
|
|
2933
|
+
const takes = planeFacesOf(a.placement);
|
|
2934
|
+
const misfit = (asset.joints ?? []).filter(j => 'plane' in j.body0 && !takes.includes(j.body1.face));
|
|
2935
|
+
if (misfit.length)
|
|
2936
|
+
fail('PLACEMENT_PLANE', 'placement', `${misfit.map(j => `${j.body1.part} (${j.body1.face})`).join(', ')} ${misfit.length > 1 ? 'are' : 'is'} fastened to the mounting plane by a face a ${a.placement} does not take (${takes.join(' or ')}); detach ${misfit.length > 1 ? 'them' : 'it'} or fasten ${misfit.length > 1 ? 'them' : 'it'} by ${takes.join(' or ')} first`);
|
|
2937
|
+
asset.placement = a.placement;
|
|
2938
|
+
return asset;
|
|
2939
|
+
}
|
|
2910
2940
|
function declareOccupancy(asset, a) {
|
|
2911
2941
|
const m = asset.document.model;
|
|
2912
2942
|
if (!a.proposal || (a.proposal.over !== 'rest' && a.proposal.over !== 'range'))
|
|
2913
2943
|
fail('SCHEMA', 'occupancy', 'a proposal is required; propose first, then confirm');
|
|
2914
|
-
if (!V3_PLACEMENTS.includes(a.placement))
|
|
2915
|
-
fail('SCHEMA', 'occupancy', `${String(a.placement)} is not a placement`);
|
|
2916
|
-
/*
|
|
2917
|
-
The placement says which plane the mounting plane is. A part already fastened to it by a face the new plane does
|
|
2918
|
-
not take -- a bottom on the floor, when the figure is to hang on a wall -- is named, not seated again without a
|
|
2919
|
-
word (chief architect's condition, 2026-09-25): fasten it again by the face the new plane takes.
|
|
2920
|
-
*/
|
|
2921
|
-
const takes = planeFacesOf(a.placement);
|
|
2922
|
-
const misfit = (asset.joints ?? []).filter(j => 'plane' in j.body0 && !takes.includes(j.body1.face));
|
|
2923
|
-
if (misfit.length)
|
|
2924
|
-
fail('PLACEMENT_PLANE', 'occupancy.placement', `${misfit.map(j => `${j.body1.part} (${j.body1.face})`).join(', ')} ${misfit.length > 1 ? 'are' : 'is'} fastened to the mounting plane by a face a ${a.placement} does not take (${takes.join(' or ')}); detach ${misfit.length > 1 ? 'them' : 'it'} or fasten ${misfit.length > 1 ? 'them' : 'it'} by ${takes.join(' or ')} first`);
|
|
2925
2944
|
const now = fingerprintOf(asset);
|
|
2926
2945
|
if (a.proposal.basis !== now)
|
|
2927
2946
|
fail('OCCUPANCY_STALE', 'occupancy', 'the figure changed since this was proposed, so the volume it would declare is not the one that was looked at; propose again and confirm that');
|
|
2947
|
+
/* Declared with a placement, the placement is set by the same command as on its own: one value, one check. */
|
|
2948
|
+
if (a.placement !== undefined)
|
|
2949
|
+
setPlacement(asset, { kind: 'set-placement', placement: a.placement });
|
|
2928
2950
|
const padding = {};
|
|
2929
2951
|
for (const axis of AXES) {
|
|
2930
2952
|
const pad = a.padding?.[axis] ?? 0;
|
|
@@ -2963,7 +2985,7 @@ function declareOccupancy(asset, a) {
|
|
|
2963
2985
|
m.nodes.push({ id, op: 'affine-bounds@1', args, params: { layout: `L${layout.join('.')}` }, outputs: Object.fromEntries(['xmin', 'xmax', 'ymin', 'ymax', 'zmin', 'zmax'].map(p => [p, port(p)])) });
|
|
2964
2986
|
const bounds = Object.fromEntries(AXES.map(axis => [axis, { min: port(`${axis}min`), max: port(`${axis}max`) }]));
|
|
2965
2987
|
const was = stringsIn(asset.occupancy?.bounds);
|
|
2966
|
-
asset.occupancy = { ...(asset.occupancy?.contact ? { contact: asset.occupancy.contact } : {}), bounds
|
|
2988
|
+
asset.occupancy = { ...(asset.occupancy?.contact ? { contact: asset.occupancy.contact } : {}), bounds };
|
|
2967
2989
|
/*
|
|
2968
2990
|
The room declared before goes with what fed only it. Left in the graph, it kept reading where parts had been
|
|
2969
2991
|
fastened, and the next fastening was refused over a room no one read (the gantry, 2026-09-25).
|
|
@@ -2972,11 +2994,13 @@ function declareOccupancy(asset, a) {
|
|
|
2972
2994
|
dropIfUnused(asset, ref);
|
|
2973
2995
|
/*
|
|
2974
2996
|
A part's own measure the old room alone still read -- the part follows something else now -- goes too: kept, it
|
|
2975
|
-
was a dial that changed nothing (the turntable after every part followed the one ratio, 2026-09-25).
|
|
2997
|
+
was a dial that changed nothing (the turntable after every part followed the one ratio, 2026-09-25). So does its
|
|
2998
|
+
own position or turn, once a fastening places it: a floor part's height position stayed as a field that moved
|
|
2999
|
+
nothing (browser check, ADR-0097, 2026-09-27). The room's refit names them before it is confirmed (figureRoomRefitOf).
|
|
2976
3000
|
*/
|
|
2977
3001
|
const placed = new Set(m.nodes.filter((n) => n.op === 'place@1' || n.op === 'member@1').map((n) => n.id));
|
|
2978
3002
|
for (const input of [...m.inputs])
|
|
2979
|
-
if (input.role === 'design' && placed.has(String(input.id).split('.')[0]) && /\.(dimension|size)\.[a-z]+$/.test(input.id))
|
|
3003
|
+
if (input.role === 'design' && placed.has(String(input.id).split('.')[0]) && /\.(dimension|size|position|rotation)\.[a-z]+$/.test(input.id))
|
|
2980
3004
|
dropIfUnused(asset, input.id, input.id);
|
|
2981
3005
|
return asset;
|
|
2982
3006
|
}
|
|
@@ -2985,7 +3009,7 @@ function setMountingFace(asset, a) {
|
|
|
2985
3009
|
const m = asset.document.model;
|
|
2986
3010
|
if (!asset.occupancy)
|
|
2987
3011
|
fail('OCCUPANCY_UNDECLARED', 'occupancy', 'declare the room the figure takes up before naming the face it stands on');
|
|
2988
|
-
if (!Object.hasOwn(
|
|
3012
|
+
if (!Object.hasOwn(FIGURE_FACES, String(a.face)))
|
|
2989
3013
|
fail('SCHEMA', a.part, `${String(a.face)} is not a face`);
|
|
2990
3014
|
const { place } = partOf(m, a.part, a.part);
|
|
2991
3015
|
const id = `${a.part}.mount.${a.face}`;
|
|
@@ -3028,7 +3052,7 @@ function addBinding(asset, a) {
|
|
|
3028
3052
|
fail('DUPLICATE_BINDING', id, 'a place with this id exists; replace it explicitly');
|
|
3029
3053
|
if (a.role !== 'seat' && a.role !== 'grip')
|
|
3030
3054
|
fail('ROLE', id, 'a place is a seat or a grip');
|
|
3031
|
-
if (!Object.hasOwn(
|
|
3055
|
+
if (!Object.hasOwn(FIGURE_FACES, String(a.face)))
|
|
3032
3056
|
fail('SCHEMA', id, `${String(a.face)} is not a face`);
|
|
3033
3057
|
const place = nodeById(m, a.part);
|
|
3034
3058
|
if (!place || place.op !== 'place@1')
|
|
@@ -3083,7 +3107,7 @@ function dropBindingRefs(asset, binding) {
|
|
|
3083
3107
|
if (!declared.has(ref) && !m.nodes.some((n) => n.args.includes(ref)))
|
|
3084
3108
|
m.constants = m.constants.filter((c) => c.id !== ref);
|
|
3085
3109
|
}
|
|
3086
|
-
export function
|
|
3110
|
+
export function figureBindingsOf(asset) {
|
|
3087
3111
|
const m = asset.document.model;
|
|
3088
3112
|
const constant = (ref) => m.constants.find((c) => c.id === ref)?.value;
|
|
3089
3113
|
return asset.document.capabilities.bindings.map((b) => {
|
|
@@ -3213,7 +3237,7 @@ function setRepeat(asset, a) {
|
|
|
3213
3237
|
So the longest row the figure may be placed at is limit x pitch. A new pitch moves that bound with it; the pitch is refused only when the length the figure
|
|
3214
3238
|
is authored at would already hold more copies than the cap.
|
|
3215
3239
|
*/
|
|
3216
|
-
const values =
|
|
3240
|
+
const values = compileFigureGraph(m).evaluate({ ...asset.designInputs, ...asset.stateDefaults }).values;
|
|
3217
3241
|
const lengthRef = layout.args[0];
|
|
3218
3242
|
const authored = values[lengthRef];
|
|
3219
3243
|
const limit = (values[layout.args[6]] ?? m.constants.find((c) => c.id === layout.args[6])?.value);
|
|
@@ -3234,7 +3258,7 @@ function setRepeat(asset, a) {
|
|
|
3234
3258
|
/* The longest length follows the pitch, within the default range (ten times the authored length). */
|
|
3235
3259
|
const length = m.inputs.find((i) => i.id === lengthRef && i.role === 'design');
|
|
3236
3260
|
if (length) {
|
|
3237
|
-
const longest = Math.min(round6(limit * pitch), round6(authored *
|
|
3261
|
+
const longest = Math.min(round6(limit * pitch), round6(authored * FIGURE_INSTANCE_SIZE_DEFAULT.times));
|
|
3238
3262
|
length.max = Math.max(longest, authored);
|
|
3239
3263
|
if (length.min > length.max)
|
|
3240
3264
|
length.min = Math.min(length.min, authored);
|
|
@@ -3295,14 +3319,15 @@ function scaleUniformly(asset) {
|
|
|
3295
3319
|
return asset;
|
|
3296
3320
|
}
|
|
3297
3321
|
/** Reads the asset; changes nothing. Refused for a figure that does not grow by one ratio. */
|
|
3298
|
-
export function
|
|
3322
|
+
export function proposeFigureProportion(asset) {
|
|
3323
|
+
asset = upgradeFigurePlacement(asset);
|
|
3299
3324
|
return proportionPlan(asset);
|
|
3300
3325
|
}
|
|
3301
3326
|
function proportionPlan(asset) {
|
|
3302
3327
|
const m = asset.document.model;
|
|
3303
3328
|
if (asset.sizing !== 'uniform')
|
|
3304
3329
|
fail('SIZING_UNDECLARED', 'sizing', 'the figure does not grow by one ratio; declare that first (scale-uniformly), then every length can follow it');
|
|
3305
|
-
const values =
|
|
3330
|
+
const values = compileFigureGraph(m).evaluate({ ...asset.designInputs, ...asset.stateDefaults }).values;
|
|
3306
3331
|
const sizeNow = Object.fromEntries(AXES.map(axis => [axis, values[uniformSizeOf(m, axis, 'sizing')]]));
|
|
3307
3332
|
const plan = { dimensions: [], lengths: [], skipped: [] };
|
|
3308
3333
|
/* To nine places: the ratio's own mean is kept to six, and its noise is not the author's (0.1, not 0.0999999999787). */
|
|
@@ -3339,7 +3364,7 @@ function proportionPlan(asset) {
|
|
|
3339
3364
|
plan.skipped.push({ what: `${part}.place`, reason: 'stands by no fastening; fasten it for its place to follow' });
|
|
3340
3365
|
}
|
|
3341
3366
|
for (const j of (asset.joints ?? [])) {
|
|
3342
|
-
const faceAxis =
|
|
3367
|
+
const faceAxis = FIGURE_FACES[childFaceOf(m, j)];
|
|
3343
3368
|
const gap = j.origin?.gap;
|
|
3344
3369
|
if (typeof gap === 'number' && gap !== 0)
|
|
3345
3370
|
plan.lengths.push({ joint: j.id, at: 'gap', axis: faceAxis, times: share(gap, faceAxis) });
|
|
@@ -3401,7 +3426,7 @@ function scalePosition(asset, a) {
|
|
|
3401
3426
|
fail('TARGET_ABSENT', path, `the instance has no ${size}; declare it before keeping a place in proportion to it`);
|
|
3402
3427
|
const i = AXES.indexOf(a.axis);
|
|
3403
3428
|
const was = pose.args[i];
|
|
3404
|
-
const values =
|
|
3429
|
+
const values = compileFigureGraph(m).evaluate({ ...asset.designInputs, ...asset.stateDefaults }).values;
|
|
3405
3430
|
const value = values[was];
|
|
3406
3431
|
const of = values[size];
|
|
3407
3432
|
if (!Number.isFinite(value))
|
|
@@ -3430,7 +3455,7 @@ function fixPosition(asset, a) {
|
|
|
3430
3455
|
const was = pose.args[i];
|
|
3431
3456
|
if (ownInput(m, a.part, was))
|
|
3432
3457
|
return asset;
|
|
3433
|
-
const values =
|
|
3458
|
+
const values = compileFigureGraph(m).evaluate({ ...asset.designInputs, ...asset.stateDefaults }).values;
|
|
3434
3459
|
const value = round6(values[was]);
|
|
3435
3460
|
if (!Number.isFinite(value))
|
|
3436
3461
|
fail('TARGET_ABSENT', path, `${was} has no value to keep`);
|
|
@@ -3451,4 +3476,4 @@ function fixPosition(asset, a) {
|
|
|
3451
3476
|
dropIfUnused(asset, was);
|
|
3452
3477
|
return asset;
|
|
3453
3478
|
}
|
|
3454
|
-
//# sourceMappingURL=
|
|
3479
|
+
//# sourceMappingURL=authoring-actions.js.map
|