@hatiolab/figure-model 0.1.92 → 0.1.93
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} +67 -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 +8 -0
- package/dist/asset.d.ts.map +1 -0
- package/dist/{v3-asset.js → asset.js} +23 -23
- package/dist/asset.js.map +1 -0
- package/dist/{v3-authoring-actions.d.ts → authoring-actions.d.ts} +66 -66
- package/dist/authoring-actions.d.ts.map +1 -0
- package/dist/{v3-authoring-actions.js → authoring-actions.js} +77 -77
- package/dist/authoring-actions.js.map +1 -0
- package/dist/{v3-authoring-session.d.ts → authoring-session.d.ts} +21 -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/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} +73 -73
- package/dist/editor-workspace.d.ts.map +1 -0
- package/dist/{v3-editor-workspace.js → editor-workspace.js} +29 -29
- 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} +28 -28
- package/dist/gate.d.ts.map +1 -0
- package/dist/{v3-gate.js → gate.js} +42 -42
- 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 +33 -33
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +33 -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} +10 -10
- package/dist/part-edit.d.ts.map +1 -0
- package/dist/{v3-part-edit.js → part-edit.js} +65 -65
- package/dist/part-edit.js.map +1 -0
- package/dist/proposal.d.ts +42 -0
- package/dist/proposal.d.ts.map +1 -0
- package/dist/{v3-proposal.js → proposal.js} +21 -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} +5 -5
- 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/{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/{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/{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,25 @@
|
|
|
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 { checkPublicInput, keepPublicInputs } from "./
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
-
import { AXES, REPEAT_LIMIT } from "./
|
|
30
|
-
import { defaultInstanceSizeRange, foldInstanceSizeRange,
|
|
31
|
-
import {
|
|
24
|
+
import { compileFigureAsset } from "./asset.js";
|
|
25
|
+
import { checkPublicInput, keepPublicInputs } from "./public-inputs.js";
|
|
26
|
+
import { figurePartsExtentOf } from "./gate.js";
|
|
27
|
+
import { compileFigureGraph, figureShapeFaces } from "./graph.js";
|
|
28
|
+
import { FigureContractError } from "./contract.js";
|
|
29
|
+
import { AXES, REPEAT_LIMIT } from "./words.js";
|
|
30
|
+
import { defaultInstanceSizeRange, foldInstanceSizeRange, figureInstanceSizeLimitsOf, FIGURE_INSTANCE_SIZE_DEFAULT, FIGURE_INSTANCE_SIZE_INPUTS } from "./instance-size.js";
|
|
31
|
+
import { FIGURE_PLACEMENTS } from "./asset-types.js";
|
|
32
32
|
const fail = (code, path, message) => {
|
|
33
|
-
throw new
|
|
33
|
+
throw new FigureContractError(code, path, message);
|
|
34
34
|
};
|
|
35
35
|
/** The six faces of a box-shaped part, and the axis each one faces along. */
|
|
36
|
-
export const
|
|
36
|
+
export const FIGURE_FACES = { left: 'x', right: 'x', bottom: 'y', top: 'y', back: 'z', front: 'z' };
|
|
37
37
|
/**
|
|
38
38
|
* UsdPhysics's joint types (ADR-0093, amended 2026-09-25: the joint follows USD). A `fixed` joint is a fastening; a
|
|
39
39
|
* `revolute` joint turns -- with no limits it turns without end, its value a phase (-180..180), as USD's revolute
|
|
40
40
|
* joint without limits does; a `prismatic` joint slides.
|
|
41
41
|
*/
|
|
42
|
-
export const
|
|
42
|
+
export const FIGURE_JOINT_TYPES = ['fixed', 'revolute', 'prismatic'];
|
|
43
43
|
const SIGN = { left: -1, right: 1, bottom: -1, top: 1, back: -1, front: 1 };
|
|
44
44
|
const OPPOSITE = { left: 'right', right: 'left', bottom: 'top', top: 'bottom', back: 'front', front: 'back' };
|
|
45
45
|
const clone = (asset) => structuredClone(asset);
|
|
@@ -172,7 +172,7 @@ const OUTER = { x: ['left', 'right'], y: ['bottom', 'top'], z: ['back', 'front']
|
|
|
172
172
|
/** A part's declared faces, from its shape's own definition, or a refusal saying the shape has none. */
|
|
173
173
|
function facesOfPart(m, id, path) {
|
|
174
174
|
const { shape } = partOf(m, id, path);
|
|
175
|
-
const faces =
|
|
175
|
+
const faces = figureShapeFaces(shape.op);
|
|
176
176
|
if (!faces)
|
|
177
177
|
fail('EDIT_TARGET', path, `${id} is a ${shape.op}; it has no flat face to fasten by`);
|
|
178
178
|
return { shape, faces: faces };
|
|
@@ -189,12 +189,12 @@ function childFaceOf(m, j) {
|
|
|
189
189
|
fail('JOINT_SCHEMA', path, 'a part stands on the mounting plane by a face of its own, not by its centre');
|
|
190
190
|
/* 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
191
|
const named = j.body0.face;
|
|
192
|
-
if (Object.hasOwn(
|
|
193
|
-
return Object.keys(
|
|
192
|
+
if (Object.hasOwn(FIGURE_FACES, named))
|
|
193
|
+
return Object.keys(FIGURE_FACES).find(f => FIGURE_FACES[f] === FIGURE_FACES[named] && SIGN[f] === -SIGN[named]);
|
|
194
194
|
const theirs = facesOfPart(m, j.body0.part, path).faces[j.body0.face];
|
|
195
195
|
if (!theirs)
|
|
196
196
|
fail('JOINT_SCHEMA', path, `${j.body0.part} has no face ${j.body0.face}`);
|
|
197
|
-
return Object.keys(
|
|
197
|
+
return Object.keys(FIGURE_FACES).find(f => FIGURE_FACES[f] === theirs.axis && SIGN[f] === -theirs.normal);
|
|
198
198
|
}
|
|
199
199
|
const planeOf = (shape, face) => face.plane.map(p => ({ ref: shape.args[p.arg], k: p.k }));
|
|
200
200
|
/** The part's outer faces on each axis in its own frame; an axis without a pair of flat faces has none. */
|
|
@@ -328,7 +328,7 @@ function sourceRef(m, asset, source, path) {
|
|
|
328
328
|
*
|
|
329
329
|
* "B is as wide as A" has to keep meaning that when A is told to follow something else. Reading A's shape
|
|
330
330
|
* 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 (
|
|
331
|
+
* left B on the old value (designer's counterexample 2026-09-23: A went to 300 and B stayed at 100). Every
|
|
332
332
|
* dimension a command touches gets a node of its own, `<part>.dimension.<axis>`, and that is what the shape
|
|
333
333
|
* reads and what anything following it reads. Relinking rewrites what that node computes, and everyone pointing
|
|
334
334
|
* at it comes along — fastenings included, since they measure the same references.
|
|
@@ -356,7 +356,7 @@ function stableDimension(m, part, axis, path) {
|
|
|
356
356
|
const dimensionSource = (m, part, axis) => nodeById(m, `${part}.dimension.${axis}`)?.args?.[0];
|
|
357
357
|
/** Whether this side of the part already follows something, rather than reading its own control. */
|
|
358
358
|
/**
|
|
359
|
-
* A part's own number for a dimension: a design input named under the part. `
|
|
359
|
+
* A part's own number for a dimension: a design input named under the part. `addFigureBox` names it `<part>.size.<axis>`,
|
|
360
360
|
* the editor's shape palette `<part>.dimension.width` (and height, depth); both are the part's own, and a dimension
|
|
361
361
|
* read from one follows nothing. Reading only the first name made a palette box look linked as soon as any command
|
|
362
362
|
* had routed its dimension, and a link was refused as a replacement (2026-09-24, :3300).
|
|
@@ -476,7 +476,7 @@ function attach(asset, a, twist) {
|
|
|
476
476
|
if (a.part === to.part)
|
|
477
477
|
fail('ATTACH_CYCLE', path, 'a part cannot be fastened to itself');
|
|
478
478
|
const byCentre = a.face === 'centre';
|
|
479
|
-
if (!byCentre && !Object.hasOwn(
|
|
479
|
+
if (!byCentre && !Object.hasOwn(FIGURE_FACES, String(a.face)))
|
|
480
480
|
fail('SCHEMA', path, `${String(a.face)} is not a face`);
|
|
481
481
|
/* The target's faces are the ones its shape declares: a portal's opening as well as its outer faces. */
|
|
482
482
|
const target = facesOfPart(m, to.part, path);
|
|
@@ -492,10 +492,10 @@ function attach(asset, a, twist) {
|
|
|
492
492
|
fail('ATTACH_FACE', path, `${a.part} fastened by its centre sits on ${to.face}; flush has no meaning for a centre`);
|
|
493
493
|
/* By its centre, the part is measured as the face that would meet the target's; only its offset differs. */
|
|
494
494
|
if (byCentre)
|
|
495
|
-
a = { ...a, face: Object.keys(
|
|
496
|
-
if (
|
|
497
|
-
fail('ATTACH_FACE', path, `${a.face} faces along ${
|
|
498
|
-
const wanted = Object.keys(
|
|
495
|
+
a = { ...a, face: Object.keys(FIGURE_FACES).find(f => FIGURE_FACES[f] === axis && SIGN[f] === -sign) };
|
|
496
|
+
if (FIGURE_FACES[a.face] !== axis)
|
|
497
|
+
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`);
|
|
498
|
+
const wanted = Object.keys(FIGURE_FACES).find(f => FIGURE_FACES[f] === axis && SIGN[f] === (facing === 'meet' ? -sign : sign));
|
|
499
499
|
if (facing === 'meet' && a.face !== wanted)
|
|
500
500
|
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
501
|
if (facing === 'flush' && a.face !== wanted)
|
|
@@ -645,8 +645,8 @@ function standOnPlane(asset, a, path) {
|
|
|
645
645
|
const normal = placement === 'wall' ? 'z' : 'y';
|
|
646
646
|
if (!takes.includes(a.face))
|
|
647
647
|
fail('ATTACH_FACE', path, placement === 'wall'
|
|
648
|
-
? `${String(a.face)} faces along ${
|
|
649
|
-
: `${String(a.face)} faces along ${
|
|
648
|
+
? `${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`
|
|
649
|
+
: `${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
650
|
if (parentOf(m, a.part) && !a.replace)
|
|
651
651
|
fail('ATTACH_REPLACED', path, `${a.part} is already fastened to ${parentOf(m, a.part)}; pass replace to stand it on the plane instead`);
|
|
652
652
|
const mine = standingSpansOf(m, asset, a.part, path);
|
|
@@ -701,7 +701,7 @@ function detach(asset, a) {
|
|
|
701
701
|
/*
|
|
702
702
|
Detaching keeps the part where it stands, and keeps it moving the way it moved. Taking the world pose and
|
|
703
703
|
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 (
|
|
704
|
+
applied a second time, so a part with 20 mm of travel jumped to 40 (designer's counterexample
|
|
705
705
|
2026-09-23).
|
|
706
706
|
|
|
707
707
|
What the part's pose has to gain is exactly what the parent's chain was contributing — and that is a plain
|
|
@@ -710,7 +710,7 @@ function detach(asset, a) {
|
|
|
710
710
|
*/
|
|
711
711
|
for (let up = parent; up; up = parentOf(m, up))
|
|
712
712
|
requireUnturned(m, asset, up, path);
|
|
713
|
-
const evaluated =
|
|
713
|
+
const evaluated = compileFigureAsset(asset).evaluate();
|
|
714
714
|
const above = evaluated.values?.[nodeById(m, `${parent}.world`)?.outputs?.pose ?? nodeById(m, `${parent}.chain`)?.outputs?.pose ?? nodeById(m, `${parent}.pose`).outputs.pose];
|
|
715
715
|
if (!above || !above.t)
|
|
716
716
|
fail('TARGET_ABSENT', path, `${parent} has no pose to read`);
|
|
@@ -833,8 +833,8 @@ function reseat(asset, id, to, place) {
|
|
|
833
833
|
dropIfUnused(asset, ref, ownInput(m, id, ref) ? ref : undefined);
|
|
834
834
|
}
|
|
835
835
|
/** Apply one authoring command. Throws on any refusal, leaving the asset it was given untouched. */
|
|
836
|
-
export function
|
|
837
|
-
|
|
836
|
+
export function applyFigureAuthoring(source, action) {
|
|
837
|
+
compileFigureAsset(source);
|
|
838
838
|
if (!action || typeof action !== 'object')
|
|
839
839
|
fail('SCHEMA', 'action', 'an authoring action is required');
|
|
840
840
|
/* Made of the commands it stands for, each checked as it would be alone. */
|
|
@@ -989,7 +989,7 @@ export function applyV3Authoring(source, action) {
|
|
|
989
989
|
rewriteJoints(asset);
|
|
990
990
|
/* An input a command took away takes its place on the board with it (ADR-0096). */
|
|
991
991
|
keepPublicInputs(asset);
|
|
992
|
-
|
|
992
|
+
compileFigureAsset(asset);
|
|
993
993
|
if ((action.kind === 'link-dimension' || action.kind === 'make-repeat' || action.kind === 'set-joint') && action.narrowRange)
|
|
994
994
|
narrowDeclaredSize(asset);
|
|
995
995
|
holdsOverDeclaredSize(asset, source);
|
|
@@ -1020,28 +1020,28 @@ function followSize(source, a) {
|
|
|
1020
1020
|
let asset = source;
|
|
1021
1021
|
const m = asset.document.model;
|
|
1022
1022
|
if (!AXES.every(axis => sizeInputOf(m, axis))) {
|
|
1023
|
-
const at =
|
|
1023
|
+
const at = figurePartsExtentOf(asset);
|
|
1024
1024
|
const start = defaultInstanceSizeRange(at);
|
|
1025
|
-
const range = foldInstanceSizeRange(start,
|
|
1026
|
-
asset =
|
|
1025
|
+
const range = foldInstanceSizeRange(start, figureInstanceSizeLimitsOf(asset, start)).range;
|
|
1026
|
+
asset = applyFigureAuthoring(asset, { kind: 'declare-instance-size', range, at, replace: AXES.some(axis => sizeInputOf(m, axis)) });
|
|
1027
1027
|
}
|
|
1028
|
-
const values =
|
|
1028
|
+
const values = compileFigureGraph(asset.document.model).evaluate({ ...asset.designInputs, ...asset.stateDefaults }).values;
|
|
1029
1029
|
const argsNow = (asset.document.model.nodes.find((n) => n.id === `${a.part}.shape`)).args;
|
|
1030
1030
|
for (const axis of axes) {
|
|
1031
1031
|
const measure = values[argsNow[AXES.indexOf(axis)]];
|
|
1032
|
-
const size = asset.designInputs[
|
|
1032
|
+
const size = asset.designInputs[FIGURE_INSTANCE_SIZE_INPUTS[axis]];
|
|
1033
1033
|
if (!(typeof measure === 'number' && measure > 0 && typeof size === 'number' && size > 0))
|
|
1034
1034
|
fail('EDIT_TARGET', `${path}.${axis}`, `${a.part}'s ${axis} or the figure's size is not a length to follow`);
|
|
1035
|
-
asset =
|
|
1035
|
+
asset = applyFigureAuthoring(asset, { kind: 'link-dimension', part: a.part, axis, source: { of: 'instance', axis }, times: measure / size, replace: true, narrowRange: true });
|
|
1036
1036
|
}
|
|
1037
1037
|
return asset;
|
|
1038
1038
|
}
|
|
1039
|
-
const sizeInputOf = (m, axis) => m.inputs.find((i) => i.id ===
|
|
1039
|
+
const sizeInputOf = (m, axis) => m.inputs.find((i) => i.id === FIGURE_INSTANCE_SIZE_INPUTS[axis]);
|
|
1040
1040
|
function declareInstanceSize(asset, a) {
|
|
1041
1041
|
const m = asset.document.model;
|
|
1042
1042
|
const declared = AXES.filter(axis => sizeInputOf(m, axis));
|
|
1043
1043
|
if (declared.length && !a.replace)
|
|
1044
|
-
fail('INSTANCE_SIZE_DECLARED', 'size', `the instance size is already declared (${declared.map(x =>
|
|
1044
|
+
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
1045
|
if (!a.range || typeof a.range !== 'object')
|
|
1046
1046
|
fail('SCHEMA', 'size', 'a range per axis is required');
|
|
1047
1047
|
let measured;
|
|
@@ -1049,18 +1049,18 @@ function declareInstanceSize(asset, a) {
|
|
|
1049
1049
|
const given = a.at?.[axis];
|
|
1050
1050
|
if (given !== undefined)
|
|
1051
1051
|
return given;
|
|
1052
|
-
const existing = asset.designInputs[
|
|
1052
|
+
const existing = asset.designInputs[FIGURE_INSTANCE_SIZE_INPUTS[axis]];
|
|
1053
1053
|
if (typeof existing === 'number')
|
|
1054
1054
|
return existing;
|
|
1055
1055
|
/* The parts as they rest, not the room their joints carry them through (a door's swing). */
|
|
1056
1056
|
if (!measured)
|
|
1057
|
-
measured =
|
|
1057
|
+
measured = figurePartsExtentOf(asset);
|
|
1058
1058
|
return measured[axis];
|
|
1059
1059
|
};
|
|
1060
1060
|
for (const axis of AXES) {
|
|
1061
1061
|
const r = a.range[axis];
|
|
1062
1062
|
const at = authoredOf(axis);
|
|
1063
|
-
const path =
|
|
1063
|
+
const path = FIGURE_INSTANCE_SIZE_INPUTS[axis];
|
|
1064
1064
|
if (!r || ![r.min, r.max, at].every(Number.isFinite))
|
|
1065
1065
|
fail('SCHEMA', path, 'finite min, max and size required');
|
|
1066
1066
|
if (!(r.min > 0))
|
|
@@ -1088,7 +1088,7 @@ function sizeRangeGaps(asset) {
|
|
|
1088
1088
|
if (!AXES.every(axis => sizeInputOf(m, axis)))
|
|
1089
1089
|
return [];
|
|
1090
1090
|
const range = Object.fromEntries(AXES.map(axis => [axis, { min: sizeInputOf(m, axis).min, max: sizeInputOf(m, axis).max }]));
|
|
1091
|
-
const limits =
|
|
1091
|
+
const limits = figureInstanceSizeLimitsOf(asset, range);
|
|
1092
1092
|
const { range: holds } = foldInstanceSizeRange(range, limits);
|
|
1093
1093
|
const gaps = [];
|
|
1094
1094
|
for (const axis of AXES) {
|
|
@@ -1108,9 +1108,9 @@ function narrowDeclaredSize(asset) {
|
|
|
1108
1108
|
return;
|
|
1109
1109
|
for (const gap of sizeRangeGaps(asset)) {
|
|
1110
1110
|
const input = sizeInputOf(m, gap.axis);
|
|
1111
|
-
const at = asset.designInputs[
|
|
1111
|
+
const at = asset.designInputs[FIGURE_INSTANCE_SIZE_INPUTS[gap.axis]];
|
|
1112
1112
|
if (gap.bound === 'min' ? at < gap.need : at > gap.need)
|
|
1113
|
-
fail('INSTANCE_SIZE_RANGE',
|
|
1113
|
+
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
1114
|
if (gap.bound === 'min')
|
|
1115
1115
|
input.min = gap.need;
|
|
1116
1116
|
else
|
|
@@ -1133,7 +1133,7 @@ function holdsOverDeclaredSize(asset, before) {
|
|
|
1133
1133
|
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
1134
|
if (!worse)
|
|
1135
1135
|
continue;
|
|
1136
|
-
const path =
|
|
1136
|
+
const path = FIGURE_INSTANCE_SIZE_INPUTS[gap.axis];
|
|
1137
1137
|
const by = gap.who ? ` (${gap.who})` : '';
|
|
1138
1138
|
if (gap.bound === 'min')
|
|
1139
1139
|
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 +1141,7 @@ function holdsOverDeclaredSize(asset, before) {
|
|
|
1141
1141
|
}
|
|
1142
1142
|
}
|
|
1143
1143
|
/** What each part is fastened to, for a reader that wants the structure rather than the graph. */
|
|
1144
|
-
export function
|
|
1144
|
+
export function figureAttachmentsOf(asset) {
|
|
1145
1145
|
const m = asset.document.model;
|
|
1146
1146
|
const out = {};
|
|
1147
1147
|
for (const n of m.nodes)
|
|
@@ -1161,8 +1161,8 @@ function checkJoint(asset, j) {
|
|
|
1161
1161
|
const path = `joints.${String(j?.id)}`;
|
|
1162
1162
|
if (!j || typeof j.id !== 'string' || !j.id.trim())
|
|
1163
1163
|
fail('JOINT_SCHEMA', 'joints', 'a joint has an id');
|
|
1164
|
-
if (!
|
|
1165
|
-
fail('JOINT_SCHEMA', path, `${String(j.type)} is not a joint type; ${
|
|
1164
|
+
if (!FIGURE_JOINT_TYPES.includes(j.type))
|
|
1165
|
+
fail('JOINT_SCHEMA', path, `${String(j.type)} is not a joint type; ${FIGURE_JOINT_TYPES.join(', ')}`);
|
|
1166
1166
|
if (!j.body1 || typeof j.body1.part !== 'string')
|
|
1167
1167
|
fail('JOINT_SCHEMA', path, 'a joint carries a part (body1)');
|
|
1168
1168
|
partOf(m, j.body1.part, path);
|
|
@@ -1247,7 +1247,7 @@ function checkJoint(asset, j) {
|
|
|
1247
1247
|
fail('JOINT_SCHEMA', row, 'a row fills the part it is fastened to; the mounting plane has no length to fill');
|
|
1248
1248
|
if (!['X', 'Y', 'Z'].includes(r.axis))
|
|
1249
1249
|
fail('JOINT_SCHEMA', row, `a row runs along X, Y or Z, not ${String(r.axis)}`);
|
|
1250
|
-
if (r.axis.toLowerCase() ===
|
|
1250
|
+
if (r.axis.toLowerCase() === FIGURE_FACES[childFaceOf(asset.document.model, j)])
|
|
1251
1251
|
fail('JOINT_SCHEMA', row, `a row runs across the face it is fastened to; ${r.axis} is out of that face`);
|
|
1252
1252
|
if (typeof r.pitch === 'number' ? !(Number.isFinite(r.pitch) && r.pitch > 0) : !(r.pitch && typeof r.pitch === 'object' && r.pitch.from))
|
|
1253
1253
|
fail('GEOMETRY_DOMAIN', row, 'a row has a positive pitch, or one that follows a length');
|
|
@@ -1282,7 +1282,7 @@ function writeJoint(asset, j) {
|
|
|
1282
1282
|
const own = AXES.map(a => j.origin?.turn?.[a] ?? 0);
|
|
1283
1283
|
/* The child as it stands: its quarter turn, then its twist about the normal of the face it is fastened by. */
|
|
1284
1284
|
const childFace = childFaceOf(m, j);
|
|
1285
|
-
const normal =
|
|
1285
|
+
const normal = FIGURE_FACES[childFace];
|
|
1286
1286
|
const standing = j.origin?.twist ? mul3(axisTurn(normal, j.origin.twist), turnMatrix(own)) : turnMatrix(own);
|
|
1287
1287
|
/* In the parent's frame: its twist about the face's normal there, and the quarter turn that is left. */
|
|
1288
1288
|
const inParent = mul3(back, standing);
|
|
@@ -1373,7 +1373,7 @@ function standingTurnOf(asset, part, path) {
|
|
|
1373
1373
|
fail('EDIT_TARGET', path, `${part} is turned (${saidTurn(turn)}); fastening to a turned part by its faces is covered only for quarter turns`);
|
|
1374
1374
|
const quarter = turn.every(t => t === 0) ? IDENTITY : turnMatrix(turn);
|
|
1375
1375
|
/* A twisted part stands twisted about the normal of the face it is fastened by. */
|
|
1376
|
-
return record?.origin?.twist ? mul3(axisTurn(
|
|
1376
|
+
return record?.origin?.twist ? mul3(axisTurn(FIGURE_FACES[childFaceOf(asset.document.model, record)], record.origin.twist), quarter) : quarter;
|
|
1377
1377
|
}
|
|
1378
1378
|
/** How a part stands before its twist: its quarter turn only. */
|
|
1379
1379
|
function standingQuarterOf(asset, part, path) {
|
|
@@ -1689,7 +1689,7 @@ function rewriteJoints(asset) {
|
|
|
1689
1689
|
* was no longer there, and the author could not save (TARGET_ABSENT on joints.*, 2026-09-25).
|
|
1690
1690
|
*/
|
|
1691
1691
|
/** 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
|
|
1692
|
+
export function renameFigurePartInJoints(asset, from, to) {
|
|
1693
1693
|
const joints = asset.joints;
|
|
1694
1694
|
if (!joints?.length)
|
|
1695
1695
|
return;
|
|
@@ -1746,7 +1746,7 @@ function refuseIfRead(asset, part, spaceFittedAgain = false) {
|
|
|
1746
1746
|
* 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
1747
|
* no record makes. A moving joint gets its own control -- a copy is a second thing, not the original's shadow.
|
|
1748
1748
|
*/
|
|
1749
|
-
export function
|
|
1749
|
+
export function copyFigurePartJoint(asset, from, to) {
|
|
1750
1750
|
const joints = (asset.joints ?? []);
|
|
1751
1751
|
const j = joints.find(o => o.body1.part === from);
|
|
1752
1752
|
if (!j)
|
|
@@ -1799,7 +1799,7 @@ function baseUnitOf(type) {
|
|
|
1799
1799
|
* 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
1800
|
* a row is made from its record, and a record needs the part (ADR-0093).
|
|
1801
1801
|
*/
|
|
1802
|
-
export function
|
|
1802
|
+
export function releaseFigurePartJoints(asset, part, options = {}) {
|
|
1803
1803
|
const a = asset;
|
|
1804
1804
|
const m = a.document.model;
|
|
1805
1805
|
const notice = { released: [], fixed: [], controls: [] };
|
|
@@ -1815,7 +1815,7 @@ export function releaseV3PartJoints(asset, part, options = {}) {
|
|
|
1815
1815
|
const inputs = (over = {}) => ({ ...a.designInputs, ...a.stateDefaults, ...over });
|
|
1816
1816
|
const controls = m.inputs.filter((i) => i.role === 'state');
|
|
1817
1817
|
const rest = Object.fromEntries(controls.map((i) => [i.id, Math.min(i.max, Math.max(i.min, 0))]));
|
|
1818
|
-
const graph =
|
|
1818
|
+
const graph = compileFigureGraph(m);
|
|
1819
1819
|
const atRest = graph.evaluate(inputs(rest)).values;
|
|
1820
1820
|
const now = graph.evaluate(inputs()).values;
|
|
1821
1821
|
/* A part moving with a joint that goes: its own control, over what the leader's distance or angle came to. */
|
|
@@ -1848,7 +1848,7 @@ export function releaseV3PartJoints(asset, part, options = {}) {
|
|
|
1848
1848
|
return measure;
|
|
1849
1849
|
const probe = structuredClone(m);
|
|
1850
1850
|
const ref = stableDimension(probe, part, measure.from.axis, where);
|
|
1851
|
-
const value = round6(Number(
|
|
1851
|
+
const value = round6(Number(compileFigureGraph(probe).evaluate(inputs()).values[ref]) * (measure.times ?? 1) + (measure.plus ?? 0));
|
|
1852
1852
|
notice.fixed.push({ part: where, value, unit: 'mm' });
|
|
1853
1853
|
return value;
|
|
1854
1854
|
};
|
|
@@ -2066,7 +2066,7 @@ function setJoint(asset, joint) {
|
|
|
2066
2066
|
if (!jointAbove(asset, j.body1.part) && !j.origin?.turn) {
|
|
2067
2067
|
const turn = turnOf(asset.document.model, asset, j.body1.part, j.body1.part);
|
|
2068
2068
|
/* 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(
|
|
2069
|
+
const n = AXES.indexOf(FIGURE_FACES[childFaceOf(asset.document.model, j)]);
|
|
2070
2070
|
const quarter = (t) => t !== null && Number.isFinite(t) && Math.abs(t / 90 - Math.round(t / 90)) < 1e-9;
|
|
2071
2071
|
const twist = !quarter(turn[n]) && turn.every((t, i) => i === n || t === 0) ? turn[n] : undefined;
|
|
2072
2072
|
if (twist !== undefined && !j.origin?.twist)
|
|
@@ -2092,7 +2092,7 @@ function removeJoint(asset, id) {
|
|
|
2092
2092
|
fail('JOINT_SCHEMA', `joints.${id}`, `${followers.map(f => f.id).join(', ')} follow ${id}; remove them first`);
|
|
2093
2093
|
const m = asset.document.model;
|
|
2094
2094
|
const child = j.body1.part;
|
|
2095
|
-
const evaluated =
|
|
2095
|
+
const evaluated = compileFigureAsset(asset).evaluate();
|
|
2096
2096
|
const placed = nodeById(m, child);
|
|
2097
2097
|
const at = evaluated.values?.[placed.args[1]];
|
|
2098
2098
|
if (!at?.t)
|
|
@@ -2147,7 +2147,7 @@ function fastenAsJoint(asset, a) {
|
|
|
2147
2147
|
* The records are the canon: made again from them, the joints' nodes must be the nodes the asset holds. An asset
|
|
2148
2148
|
* whose nodes or records were edited apart is refused, not quietly rebuilt (ADR-0093, chief architect's condition).
|
|
2149
2149
|
*/
|
|
2150
|
-
export function
|
|
2150
|
+
export function checkFigureJoints(asset) {
|
|
2151
2151
|
const joints = asset.joints;
|
|
2152
2152
|
if (!joints?.length)
|
|
2153
2153
|
return;
|
|
@@ -2699,7 +2699,7 @@ function occupancyLins(asset, over) {
|
|
|
2699
2699
|
const own = occupiedExtentsOf(m, id, id, turns);
|
|
2700
2700
|
const c = turns ? IDENTITY : worldTurnOf(m, asset, id);
|
|
2701
2701
|
if (!c)
|
|
2702
|
-
throw new
|
|
2702
|
+
throw new FigureContractError('EDIT_TARGET', id, `${id} is turned by other than quarter turns`);
|
|
2703
2703
|
return Object.fromEntries(AXES.map((axis, i) => [axis, own[AXES[c[i].findIndex(v => v !== 0)]]]));
|
|
2704
2704
|
};
|
|
2705
2705
|
/*
|
|
@@ -2749,7 +2749,7 @@ function occupancyLins(asset, over) {
|
|
|
2749
2749
|
sizes = sizesOf(place.id, turns);
|
|
2750
2750
|
}
|
|
2751
2751
|
catch (e) {
|
|
2752
|
-
skipped.push({ part: place.id, reason: e instanceof
|
|
2752
|
+
skipped.push({ part: place.id, reason: e instanceof FigureContractError ? e.message : 'not a part whose size this command can read' });
|
|
2753
2753
|
continue;
|
|
2754
2754
|
}
|
|
2755
2755
|
/*
|
|
@@ -2818,7 +2818,7 @@ function occupancyLins(asset, over) {
|
|
|
2818
2818
|
}
|
|
2819
2819
|
/*
|
|
2820
2820
|
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 (
|
|
2821
|
+
−40 mm puts the low end at the control's maximum (designer's counterexample 2026-09-23, where the
|
|
2822
2822
|
proposal read −50..10 for a part that needs −90..50). Both candidates go into the same min and the same
|
|
2823
2823
|
max, so the answer holds whichever way the travel runs, and holds at every design size, because the
|
|
2824
2824
|
choice is made when the graph is evaluated rather than when the proposal is written.
|
|
@@ -2890,7 +2890,7 @@ function occupancyLins(asset, over) {
|
|
|
2890
2890
|
}
|
|
2891
2891
|
const valueOfLin = (values, l) => l.terms.reduce((s, t) => s + t.k * (values[t.ref] ?? NaN), l.c);
|
|
2892
2892
|
/** What the parts come to, and what a person is being asked to confirm. Reads the asset; changes nothing. */
|
|
2893
|
-
export function
|
|
2893
|
+
export function proposeFigureOccupancy(asset, options = {}) {
|
|
2894
2894
|
const over = options.over ?? 'range';
|
|
2895
2895
|
if (over !== 'rest' && over !== 'range')
|
|
2896
2896
|
fail('SCHEMA', 'over', `${String(over)} is neither rest nor range`);
|
|
@@ -2903,7 +2903,7 @@ export function proposeV3Occupancy(asset, options = {}) {
|
|
|
2903
2903
|
}
|
|
2904
2904
|
if (!parts.length)
|
|
2905
2905
|
fail('OCCUPANCY_NO_PARTS', 'occupancy', `no part could be measured${skipped.length ? `: ${skipped.map(s => `${s.part} — ${s.reason}`).join('; ')}` : ''}`);
|
|
2906
|
-
const values =
|
|
2906
|
+
const values = compileFigureGraph(source.document.model).evaluate({ ...source.designInputs, ...source.stateDefaults }).values;
|
|
2907
2907
|
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
2908
|
return { over, at, parts, skipped, basis: fingerprintOf(source) };
|
|
2909
2909
|
}
|
|
@@ -2911,7 +2911,7 @@ function declareOccupancy(asset, a) {
|
|
|
2911
2911
|
const m = asset.document.model;
|
|
2912
2912
|
if (!a.proposal || (a.proposal.over !== 'rest' && a.proposal.over !== 'range'))
|
|
2913
2913
|
fail('SCHEMA', 'occupancy', 'a proposal is required; propose first, then confirm');
|
|
2914
|
-
if (!
|
|
2914
|
+
if (!FIGURE_PLACEMENTS.includes(a.placement))
|
|
2915
2915
|
fail('SCHEMA', 'occupancy', `${String(a.placement)} is not a placement`);
|
|
2916
2916
|
/*
|
|
2917
2917
|
The placement says which plane the mounting plane is. A part already fastened to it by a face the new plane does
|
|
@@ -2985,7 +2985,7 @@ function setMountingFace(asset, a) {
|
|
|
2985
2985
|
const m = asset.document.model;
|
|
2986
2986
|
if (!asset.occupancy)
|
|
2987
2987
|
fail('OCCUPANCY_UNDECLARED', 'occupancy', 'declare the room the figure takes up before naming the face it stands on');
|
|
2988
|
-
if (!Object.hasOwn(
|
|
2988
|
+
if (!Object.hasOwn(FIGURE_FACES, String(a.face)))
|
|
2989
2989
|
fail('SCHEMA', a.part, `${String(a.face)} is not a face`);
|
|
2990
2990
|
const { place } = partOf(m, a.part, a.part);
|
|
2991
2991
|
const id = `${a.part}.mount.${a.face}`;
|
|
@@ -3028,7 +3028,7 @@ function addBinding(asset, a) {
|
|
|
3028
3028
|
fail('DUPLICATE_BINDING', id, 'a place with this id exists; replace it explicitly');
|
|
3029
3029
|
if (a.role !== 'seat' && a.role !== 'grip')
|
|
3030
3030
|
fail('ROLE', id, 'a place is a seat or a grip');
|
|
3031
|
-
if (!Object.hasOwn(
|
|
3031
|
+
if (!Object.hasOwn(FIGURE_FACES, String(a.face)))
|
|
3032
3032
|
fail('SCHEMA', id, `${String(a.face)} is not a face`);
|
|
3033
3033
|
const place = nodeById(m, a.part);
|
|
3034
3034
|
if (!place || place.op !== 'place@1')
|
|
@@ -3083,7 +3083,7 @@ function dropBindingRefs(asset, binding) {
|
|
|
3083
3083
|
if (!declared.has(ref) && !m.nodes.some((n) => n.args.includes(ref)))
|
|
3084
3084
|
m.constants = m.constants.filter((c) => c.id !== ref);
|
|
3085
3085
|
}
|
|
3086
|
-
export function
|
|
3086
|
+
export function figureBindingsOf(asset) {
|
|
3087
3087
|
const m = asset.document.model;
|
|
3088
3088
|
const constant = (ref) => m.constants.find((c) => c.id === ref)?.value;
|
|
3089
3089
|
return asset.document.capabilities.bindings.map((b) => {
|
|
@@ -3213,7 +3213,7 @@ function setRepeat(asset, a) {
|
|
|
3213
3213
|
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
3214
|
is authored at would already hold more copies than the cap.
|
|
3215
3215
|
*/
|
|
3216
|
-
const values =
|
|
3216
|
+
const values = compileFigureGraph(m).evaluate({ ...asset.designInputs, ...asset.stateDefaults }).values;
|
|
3217
3217
|
const lengthRef = layout.args[0];
|
|
3218
3218
|
const authored = values[lengthRef];
|
|
3219
3219
|
const limit = (values[layout.args[6]] ?? m.constants.find((c) => c.id === layout.args[6])?.value);
|
|
@@ -3234,7 +3234,7 @@ function setRepeat(asset, a) {
|
|
|
3234
3234
|
/* The longest length follows the pitch, within the default range (ten times the authored length). */
|
|
3235
3235
|
const length = m.inputs.find((i) => i.id === lengthRef && i.role === 'design');
|
|
3236
3236
|
if (length) {
|
|
3237
|
-
const longest = Math.min(round6(limit * pitch), round6(authored *
|
|
3237
|
+
const longest = Math.min(round6(limit * pitch), round6(authored * FIGURE_INSTANCE_SIZE_DEFAULT.times));
|
|
3238
3238
|
length.max = Math.max(longest, authored);
|
|
3239
3239
|
if (length.min > length.max)
|
|
3240
3240
|
length.min = Math.min(length.min, authored);
|
|
@@ -3295,14 +3295,14 @@ function scaleUniformly(asset) {
|
|
|
3295
3295
|
return asset;
|
|
3296
3296
|
}
|
|
3297
3297
|
/** Reads the asset; changes nothing. Refused for a figure that does not grow by one ratio. */
|
|
3298
|
-
export function
|
|
3298
|
+
export function proposeFigureProportion(asset) {
|
|
3299
3299
|
return proportionPlan(asset);
|
|
3300
3300
|
}
|
|
3301
3301
|
function proportionPlan(asset) {
|
|
3302
3302
|
const m = asset.document.model;
|
|
3303
3303
|
if (asset.sizing !== 'uniform')
|
|
3304
3304
|
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 =
|
|
3305
|
+
const values = compileFigureGraph(m).evaluate({ ...asset.designInputs, ...asset.stateDefaults }).values;
|
|
3306
3306
|
const sizeNow = Object.fromEntries(AXES.map(axis => [axis, values[uniformSizeOf(m, axis, 'sizing')]]));
|
|
3307
3307
|
const plan = { dimensions: [], lengths: [], skipped: [] };
|
|
3308
3308
|
/* 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 +3339,7 @@ function proportionPlan(asset) {
|
|
|
3339
3339
|
plan.skipped.push({ what: `${part}.place`, reason: 'stands by no fastening; fasten it for its place to follow' });
|
|
3340
3340
|
}
|
|
3341
3341
|
for (const j of (asset.joints ?? [])) {
|
|
3342
|
-
const faceAxis =
|
|
3342
|
+
const faceAxis = FIGURE_FACES[childFaceOf(m, j)];
|
|
3343
3343
|
const gap = j.origin?.gap;
|
|
3344
3344
|
if (typeof gap === 'number' && gap !== 0)
|
|
3345
3345
|
plan.lengths.push({ joint: j.id, at: 'gap', axis: faceAxis, times: share(gap, faceAxis) });
|
|
@@ -3401,7 +3401,7 @@ function scalePosition(asset, a) {
|
|
|
3401
3401
|
fail('TARGET_ABSENT', path, `the instance has no ${size}; declare it before keeping a place in proportion to it`);
|
|
3402
3402
|
const i = AXES.indexOf(a.axis);
|
|
3403
3403
|
const was = pose.args[i];
|
|
3404
|
-
const values =
|
|
3404
|
+
const values = compileFigureGraph(m).evaluate({ ...asset.designInputs, ...asset.stateDefaults }).values;
|
|
3405
3405
|
const value = values[was];
|
|
3406
3406
|
const of = values[size];
|
|
3407
3407
|
if (!Number.isFinite(value))
|
|
@@ -3430,7 +3430,7 @@ function fixPosition(asset, a) {
|
|
|
3430
3430
|
const was = pose.args[i];
|
|
3431
3431
|
if (ownInput(m, a.part, was))
|
|
3432
3432
|
return asset;
|
|
3433
|
-
const values =
|
|
3433
|
+
const values = compileFigureGraph(m).evaluate({ ...asset.designInputs, ...asset.stateDefaults }).values;
|
|
3434
3434
|
const value = round6(values[was]);
|
|
3435
3435
|
if (!Number.isFinite(value))
|
|
3436
3436
|
fail('TARGET_ABSENT', path, `${was} has no value to keep`);
|
|
@@ -3451,4 +3451,4 @@ function fixPosition(asset, a) {
|
|
|
3451
3451
|
dropIfUnused(asset, was);
|
|
3452
3452
|
return asset;
|
|
3453
3453
|
}
|
|
3454
|
-
//# sourceMappingURL=
|
|
3454
|
+
//# sourceMappingURL=authoring-actions.js.map
|