@hatiolab/figure-model 0.1.91 → 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} +107 -78
- 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
|
};
|
|
@@ -1930,7 +1930,36 @@ export function releaseV3PartJoints(asset, part, options = {}) {
|
|
|
1930
1930
|
for (const ref of old)
|
|
1931
1931
|
if (!pose.args.includes(ref))
|
|
1932
1932
|
dropIfUnused(a, ref, ownInput(m, child, ref) ? ref : undefined);
|
|
1933
|
-
notice.released.push({ part: child, ...(control
|
|
1933
|
+
notice.released.push({ part: child, ...(control ? { control } : {}) });
|
|
1934
|
+
}
|
|
1935
|
+
/*
|
|
1936
|
+
A control goes with its joint once nothing reads it -- read only after every part is let go of: a lamp and the shade
|
|
1937
|
+
that followed it, both released, left the lamp's control behind while the shade still read it (2026-09-27).
|
|
1938
|
+
*/
|
|
1939
|
+
const readAnywhere = (id) => m.nodes.some((n) => n.args.includes(id) || stringsIn(n.params).includes(id)) || stringsIn(a.occupancy).includes(id) || stringsIn(a.document.capabilities).includes(id);
|
|
1940
|
+
for (const r of notice.released) {
|
|
1941
|
+
if (!r.control)
|
|
1942
|
+
continue;
|
|
1943
|
+
if (readAnywhere(r.control)) {
|
|
1944
|
+
delete r.control;
|
|
1945
|
+
continue;
|
|
1946
|
+
}
|
|
1947
|
+
const id = r.control;
|
|
1948
|
+
m.inputs = m.inputs.filter((i) => i.id !== id);
|
|
1949
|
+
delete a.stateDefaults[id];
|
|
1950
|
+
delete a.designInputs[id];
|
|
1951
|
+
/* Its clip and its label go with it, as a motion's do (removeMotion). */
|
|
1952
|
+
if (a.drivers)
|
|
1953
|
+
a.drivers = a.drivers.filter((d) => d.state !== id);
|
|
1954
|
+
if (a.drivers && !a.drivers.length)
|
|
1955
|
+
delete a.drivers;
|
|
1956
|
+
if (a.stateInputs?.[id]) {
|
|
1957
|
+
const { [id]: _gone, ...rest } = a.stateInputs;
|
|
1958
|
+
if (Object.keys(rest).length)
|
|
1959
|
+
a.stateInputs = rest;
|
|
1960
|
+
else
|
|
1961
|
+
delete a.stateInputs;
|
|
1962
|
+
}
|
|
1934
1963
|
}
|
|
1935
1964
|
if (own)
|
|
1936
1965
|
stripJoint(a, own);
|
|
@@ -2037,7 +2066,7 @@ function setJoint(asset, joint) {
|
|
|
2037
2066
|
if (!jointAbove(asset, j.body1.part) && !j.origin?.turn) {
|
|
2038
2067
|
const turn = turnOf(asset.document.model, asset, j.body1.part, j.body1.part);
|
|
2039
2068
|
/* A part turned by any angle about the normal of the face it is fastened by keeps that angle as its twist. */
|
|
2040
|
-
const n = AXES.indexOf(
|
|
2069
|
+
const n = AXES.indexOf(FIGURE_FACES[childFaceOf(asset.document.model, j)]);
|
|
2041
2070
|
const quarter = (t) => t !== null && Number.isFinite(t) && Math.abs(t / 90 - Math.round(t / 90)) < 1e-9;
|
|
2042
2071
|
const twist = !quarter(turn[n]) && turn.every((t, i) => i === n || t === 0) ? turn[n] : undefined;
|
|
2043
2072
|
if (twist !== undefined && !j.origin?.twist)
|
|
@@ -2063,7 +2092,7 @@ function removeJoint(asset, id) {
|
|
|
2063
2092
|
fail('JOINT_SCHEMA', `joints.${id}`, `${followers.map(f => f.id).join(', ')} follow ${id}; remove them first`);
|
|
2064
2093
|
const m = asset.document.model;
|
|
2065
2094
|
const child = j.body1.part;
|
|
2066
|
-
const evaluated =
|
|
2095
|
+
const evaluated = compileFigureAsset(asset).evaluate();
|
|
2067
2096
|
const placed = nodeById(m, child);
|
|
2068
2097
|
const at = evaluated.values?.[placed.args[1]];
|
|
2069
2098
|
if (!at?.t)
|
|
@@ -2118,7 +2147,7 @@ function fastenAsJoint(asset, a) {
|
|
|
2118
2147
|
* The records are the canon: made again from them, the joints' nodes must be the nodes the asset holds. An asset
|
|
2119
2148
|
* whose nodes or records were edited apart is refused, not quietly rebuilt (ADR-0093, chief architect's condition).
|
|
2120
2149
|
*/
|
|
2121
|
-
export function
|
|
2150
|
+
export function checkFigureJoints(asset) {
|
|
2122
2151
|
const joints = asset.joints;
|
|
2123
2152
|
if (!joints?.length)
|
|
2124
2153
|
return;
|
|
@@ -2670,7 +2699,7 @@ function occupancyLins(asset, over) {
|
|
|
2670
2699
|
const own = occupiedExtentsOf(m, id, id, turns);
|
|
2671
2700
|
const c = turns ? IDENTITY : worldTurnOf(m, asset, id);
|
|
2672
2701
|
if (!c)
|
|
2673
|
-
throw new
|
|
2702
|
+
throw new FigureContractError('EDIT_TARGET', id, `${id} is turned by other than quarter turns`);
|
|
2674
2703
|
return Object.fromEntries(AXES.map((axis, i) => [axis, own[AXES[c[i].findIndex(v => v !== 0)]]]));
|
|
2675
2704
|
};
|
|
2676
2705
|
/*
|
|
@@ -2720,7 +2749,7 @@ function occupancyLins(asset, over) {
|
|
|
2720
2749
|
sizes = sizesOf(place.id, turns);
|
|
2721
2750
|
}
|
|
2722
2751
|
catch (e) {
|
|
2723
|
-
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' });
|
|
2724
2753
|
continue;
|
|
2725
2754
|
}
|
|
2726
2755
|
/*
|
|
@@ -2789,7 +2818,7 @@ function occupancyLins(asset, over) {
|
|
|
2789
2818
|
}
|
|
2790
2819
|
/*
|
|
2791
2820
|
Both ends of every control are candidates, and which one is the low end is not decided here: a travel of
|
|
2792
|
-
−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
|
|
2793
2822
|
proposal read −50..10 for a part that needs −90..50). Both candidates go into the same min and the same
|
|
2794
2823
|
max, so the answer holds whichever way the travel runs, and holds at every design size, because the
|
|
2795
2824
|
choice is made when the graph is evaluated rather than when the proposal is written.
|
|
@@ -2861,7 +2890,7 @@ function occupancyLins(asset, over) {
|
|
|
2861
2890
|
}
|
|
2862
2891
|
const valueOfLin = (values, l) => l.terms.reduce((s, t) => s + t.k * (values[t.ref] ?? NaN), l.c);
|
|
2863
2892
|
/** What the parts come to, and what a person is being asked to confirm. Reads the asset; changes nothing. */
|
|
2864
|
-
export function
|
|
2893
|
+
export function proposeFigureOccupancy(asset, options = {}) {
|
|
2865
2894
|
const over = options.over ?? 'range';
|
|
2866
2895
|
if (over !== 'rest' && over !== 'range')
|
|
2867
2896
|
fail('SCHEMA', 'over', `${String(over)} is neither rest nor range`);
|
|
@@ -2874,7 +2903,7 @@ export function proposeV3Occupancy(asset, options = {}) {
|
|
|
2874
2903
|
}
|
|
2875
2904
|
if (!parts.length)
|
|
2876
2905
|
fail('OCCUPANCY_NO_PARTS', 'occupancy', `no part could be measured${skipped.length ? `: ${skipped.map(s => `${s.part} — ${s.reason}`).join('; ')}` : ''}`);
|
|
2877
|
-
const values =
|
|
2906
|
+
const values = compileFigureGraph(source.document.model).evaluate({ ...source.designInputs, ...source.stateDefaults }).values;
|
|
2878
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)))) }]));
|
|
2879
2908
|
return { over, at, parts, skipped, basis: fingerprintOf(source) };
|
|
2880
2909
|
}
|
|
@@ -2882,7 +2911,7 @@ function declareOccupancy(asset, a) {
|
|
|
2882
2911
|
const m = asset.document.model;
|
|
2883
2912
|
if (!a.proposal || (a.proposal.over !== 'rest' && a.proposal.over !== 'range'))
|
|
2884
2913
|
fail('SCHEMA', 'occupancy', 'a proposal is required; propose first, then confirm');
|
|
2885
|
-
if (!
|
|
2914
|
+
if (!FIGURE_PLACEMENTS.includes(a.placement))
|
|
2886
2915
|
fail('SCHEMA', 'occupancy', `${String(a.placement)} is not a placement`);
|
|
2887
2916
|
/*
|
|
2888
2917
|
The placement says which plane the mounting plane is. A part already fastened to it by a face the new plane does
|
|
@@ -2956,7 +2985,7 @@ function setMountingFace(asset, a) {
|
|
|
2956
2985
|
const m = asset.document.model;
|
|
2957
2986
|
if (!asset.occupancy)
|
|
2958
2987
|
fail('OCCUPANCY_UNDECLARED', 'occupancy', 'declare the room the figure takes up before naming the face it stands on');
|
|
2959
|
-
if (!Object.hasOwn(
|
|
2988
|
+
if (!Object.hasOwn(FIGURE_FACES, String(a.face)))
|
|
2960
2989
|
fail('SCHEMA', a.part, `${String(a.face)} is not a face`);
|
|
2961
2990
|
const { place } = partOf(m, a.part, a.part);
|
|
2962
2991
|
const id = `${a.part}.mount.${a.face}`;
|
|
@@ -2999,7 +3028,7 @@ function addBinding(asset, a) {
|
|
|
2999
3028
|
fail('DUPLICATE_BINDING', id, 'a place with this id exists; replace it explicitly');
|
|
3000
3029
|
if (a.role !== 'seat' && a.role !== 'grip')
|
|
3001
3030
|
fail('ROLE', id, 'a place is a seat or a grip');
|
|
3002
|
-
if (!Object.hasOwn(
|
|
3031
|
+
if (!Object.hasOwn(FIGURE_FACES, String(a.face)))
|
|
3003
3032
|
fail('SCHEMA', id, `${String(a.face)} is not a face`);
|
|
3004
3033
|
const place = nodeById(m, a.part);
|
|
3005
3034
|
if (!place || place.op !== 'place@1')
|
|
@@ -3054,7 +3083,7 @@ function dropBindingRefs(asset, binding) {
|
|
|
3054
3083
|
if (!declared.has(ref) && !m.nodes.some((n) => n.args.includes(ref)))
|
|
3055
3084
|
m.constants = m.constants.filter((c) => c.id !== ref);
|
|
3056
3085
|
}
|
|
3057
|
-
export function
|
|
3086
|
+
export function figureBindingsOf(asset) {
|
|
3058
3087
|
const m = asset.document.model;
|
|
3059
3088
|
const constant = (ref) => m.constants.find((c) => c.id === ref)?.value;
|
|
3060
3089
|
return asset.document.capabilities.bindings.map((b) => {
|
|
@@ -3184,7 +3213,7 @@ function setRepeat(asset, a) {
|
|
|
3184
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
|
|
3185
3214
|
is authored at would already hold more copies than the cap.
|
|
3186
3215
|
*/
|
|
3187
|
-
const values =
|
|
3216
|
+
const values = compileFigureGraph(m).evaluate({ ...asset.designInputs, ...asset.stateDefaults }).values;
|
|
3188
3217
|
const lengthRef = layout.args[0];
|
|
3189
3218
|
const authored = values[lengthRef];
|
|
3190
3219
|
const limit = (values[layout.args[6]] ?? m.constants.find((c) => c.id === layout.args[6])?.value);
|
|
@@ -3205,7 +3234,7 @@ function setRepeat(asset, a) {
|
|
|
3205
3234
|
/* The longest length follows the pitch, within the default range (ten times the authored length). */
|
|
3206
3235
|
const length = m.inputs.find((i) => i.id === lengthRef && i.role === 'design');
|
|
3207
3236
|
if (length) {
|
|
3208
|
-
const longest = Math.min(round6(limit * pitch), round6(authored *
|
|
3237
|
+
const longest = Math.min(round6(limit * pitch), round6(authored * FIGURE_INSTANCE_SIZE_DEFAULT.times));
|
|
3209
3238
|
length.max = Math.max(longest, authored);
|
|
3210
3239
|
if (length.min > length.max)
|
|
3211
3240
|
length.min = Math.min(length.min, authored);
|
|
@@ -3266,14 +3295,14 @@ function scaleUniformly(asset) {
|
|
|
3266
3295
|
return asset;
|
|
3267
3296
|
}
|
|
3268
3297
|
/** Reads the asset; changes nothing. Refused for a figure that does not grow by one ratio. */
|
|
3269
|
-
export function
|
|
3298
|
+
export function proposeFigureProportion(asset) {
|
|
3270
3299
|
return proportionPlan(asset);
|
|
3271
3300
|
}
|
|
3272
3301
|
function proportionPlan(asset) {
|
|
3273
3302
|
const m = asset.document.model;
|
|
3274
3303
|
if (asset.sizing !== 'uniform')
|
|
3275
3304
|
fail('SIZING_UNDECLARED', 'sizing', 'the figure does not grow by one ratio; declare that first (scale-uniformly), then every length can follow it');
|
|
3276
|
-
const values =
|
|
3305
|
+
const values = compileFigureGraph(m).evaluate({ ...asset.designInputs, ...asset.stateDefaults }).values;
|
|
3277
3306
|
const sizeNow = Object.fromEntries(AXES.map(axis => [axis, values[uniformSizeOf(m, axis, 'sizing')]]));
|
|
3278
3307
|
const plan = { dimensions: [], lengths: [], skipped: [] };
|
|
3279
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). */
|
|
@@ -3310,7 +3339,7 @@ function proportionPlan(asset) {
|
|
|
3310
3339
|
plan.skipped.push({ what: `${part}.place`, reason: 'stands by no fastening; fasten it for its place to follow' });
|
|
3311
3340
|
}
|
|
3312
3341
|
for (const j of (asset.joints ?? [])) {
|
|
3313
|
-
const faceAxis =
|
|
3342
|
+
const faceAxis = FIGURE_FACES[childFaceOf(m, j)];
|
|
3314
3343
|
const gap = j.origin?.gap;
|
|
3315
3344
|
if (typeof gap === 'number' && gap !== 0)
|
|
3316
3345
|
plan.lengths.push({ joint: j.id, at: 'gap', axis: faceAxis, times: share(gap, faceAxis) });
|
|
@@ -3372,7 +3401,7 @@ function scalePosition(asset, a) {
|
|
|
3372
3401
|
fail('TARGET_ABSENT', path, `the instance has no ${size}; declare it before keeping a place in proportion to it`);
|
|
3373
3402
|
const i = AXES.indexOf(a.axis);
|
|
3374
3403
|
const was = pose.args[i];
|
|
3375
|
-
const values =
|
|
3404
|
+
const values = compileFigureGraph(m).evaluate({ ...asset.designInputs, ...asset.stateDefaults }).values;
|
|
3376
3405
|
const value = values[was];
|
|
3377
3406
|
const of = values[size];
|
|
3378
3407
|
if (!Number.isFinite(value))
|
|
@@ -3401,7 +3430,7 @@ function fixPosition(asset, a) {
|
|
|
3401
3430
|
const was = pose.args[i];
|
|
3402
3431
|
if (ownInput(m, a.part, was))
|
|
3403
3432
|
return asset;
|
|
3404
|
-
const values =
|
|
3433
|
+
const values = compileFigureGraph(m).evaluate({ ...asset.designInputs, ...asset.stateDefaults }).values;
|
|
3405
3434
|
const value = round6(values[was]);
|
|
3406
3435
|
if (!Number.isFinite(value))
|
|
3407
3436
|
fail('TARGET_ABSENT', path, `${was} has no value to keep`);
|
|
@@ -3422,4 +3451,4 @@ function fixPosition(asset, a) {
|
|
|
3422
3451
|
dropIfUnused(asset, was);
|
|
3423
3452
|
return asset;
|
|
3424
3453
|
}
|
|
3425
|
-
//# sourceMappingURL=
|
|
3454
|
+
//# sourceMappingURL=authoring-actions.js.map
|