@metamesh/modeling 0.9.0 → 0.11.0
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 +46 -0
- package/README.md +66 -422
- package/THIRD_PARTY_NOTICES.md +4 -148
- package/dist/api/engine.d.ts +105 -8
- package/dist/api/geometry.d.ts +95 -83
- package/dist/api/index.d.ts +767 -772
- package/dist/engine/construction-operations.js +3 -1
- package/dist/engine/deferred-context.js +182 -0
- package/dist/engine/execution.js +38 -4
- package/dist/engine/finalization.js +65 -133
- package/dist/engine/geometry-recipes.js +12 -0
- package/dist/engine/immutable-generation-data.js +186 -0
- package/dist/engine/index.js +57 -35
- package/dist/engine/kernel/manifold.wasm +0 -0
- package/dist/engine/model-dependencies.js +46 -0
- package/dist/engine/model-summary.js +43 -0
- package/dist/engine/native/ownership.js +11 -5
- package/dist/engine/placement.js +24 -0
- package/dist/engine/recipe-realization.js +119 -0
- package/dist/engine/session-graph.js +442 -0
- package/dist/engine/session-native.js +90 -0
- package/dist/engine/session-snapshot.js +153 -0
- package/dist/engine/session.js +184 -0
- package/dist/engine/shape-anchors.js +5 -0
- package/dist/geometry/index.js +2 -2
- package/dist/geometry/mesh-centroid.js +7 -6
- package/dist/geometry/mesh-planar.js +11 -10
- package/dist/geometry/mesh-quality.js +15 -14
- package/dist/geometry/mesh-signature.js +8 -7
- package/dist/geometry/mesh-solid.js +10 -4
- package/dist/geometry/mesh.js +36 -31
- package/dist/geometry/profiles/{cross-section.js → contour.js} +3 -3
- package/dist/geometry/profiles/{open-profile.js → side-profile.js} +3 -3
- package/dist/geometry/svg-import.js +1 -16
- package/dist/index.js +2 -0
- package/dist/model/execution-binding.js +5 -5
- package/dist/model/generation.js +27 -0
- package/dist/model/immutable-buffer.js +111 -0
- package/dist/model/modeling-context.js +1 -0
- package/dist/model/operations.js +521 -160
- package/dist/model/shape-handles.js +44 -0
- package/package.json +4 -4
|
@@ -5,6 +5,7 @@ import { resolveCircularSegments, resolveGeometryResolution, } from "./resolutio
|
|
|
5
5
|
import { resolveGeometrySamplingLimits } from "./sampling-limits.js";
|
|
6
6
|
import { strokePathMultiPolygon } from "./stroke-path.js";
|
|
7
7
|
import { createBoundedSvgClipping } from "./svg/fill.js";
|
|
8
|
+
import { multiplyMatrices as multiply, transformPoint } from "./svg/matrix.js";
|
|
8
9
|
import { flattenSvgSubpaths, SvgVertexBudget } from "./svg/path.js";
|
|
9
10
|
const MAX_SVG_BYTES = 256 * 1024;
|
|
10
11
|
const MAX_SVG_ELEMENTS = 500;
|
|
@@ -89,22 +90,6 @@ function finite(value, fallback) {
|
|
|
89
90
|
const parsed = Number(value);
|
|
90
91
|
return Number.isFinite(parsed) ? parsed : fail(`Invalid SVG number “${value}”.`);
|
|
91
92
|
}
|
|
92
|
-
function multiply(left, right) {
|
|
93
|
-
return [
|
|
94
|
-
left[0] * right[0] + left[2] * right[1],
|
|
95
|
-
left[1] * right[0] + left[3] * right[1],
|
|
96
|
-
left[0] * right[2] + left[2] * right[3],
|
|
97
|
-
left[1] * right[2] + left[3] * right[3],
|
|
98
|
-
left[0] * right[4] + left[2] * right[5] + left[4],
|
|
99
|
-
left[1] * right[4] + left[3] * right[5] + left[5],
|
|
100
|
-
];
|
|
101
|
-
}
|
|
102
|
-
function transformPoint(matrix, point) {
|
|
103
|
-
return [
|
|
104
|
-
matrix[0] * point[0] + matrix[2] * point[1] + matrix[4],
|
|
105
|
-
matrix[1] * point[0] + matrix[3] * point[1] + matrix[5],
|
|
106
|
-
];
|
|
107
|
-
}
|
|
108
93
|
function parseTransform(value) {
|
|
109
94
|
if (value === undefined || value.trim() === "")
|
|
110
95
|
return [1, 0, 0, 1, 0, 0];
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { GeometryError } from "../geometry/errors.js";
|
|
2
2
|
// One protocol key lets locally bundled operation wrappers resolve the host's execution
|
|
3
3
|
// without depending on package instances, class identity, or initializing a native runtime.
|
|
4
|
-
const executionKey = Symbol.for("@metamesh/modeling/execution/
|
|
4
|
+
const executionKey = Symbol.for("@metamesh/modeling/execution/4");
|
|
5
5
|
const bindings = globalThis;
|
|
6
6
|
/** Internal host binding. Executable callbacks are synchronous and may not nest executions. */
|
|
7
7
|
export function bindModelContext(context, build) {
|
|
@@ -10,7 +10,7 @@ export function bindModelContext(context, build) {
|
|
|
10
10
|
operation: "build",
|
|
11
11
|
});
|
|
12
12
|
}
|
|
13
|
-
bindings[executionKey] = { version:
|
|
13
|
+
bindings[executionKey] = { version: 4, context };
|
|
14
14
|
try {
|
|
15
15
|
return build();
|
|
16
16
|
}
|
|
@@ -18,11 +18,11 @@ export function bindModelContext(context, build) {
|
|
|
18
18
|
delete bindings[executionKey];
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
|
-
/** Resolves
|
|
21
|
+
/** Resolves the private realm protocol; imported functions carry no native state themselves. */
|
|
22
22
|
export function currentModelContext(operation) {
|
|
23
23
|
const binding = bindings[executionKey];
|
|
24
|
-
if (binding?.version !==
|
|
25
|
-
throw new GeometryError("INVALID_ARGUMENT", "Modeling functions can only run inside
|
|
24
|
+
if (binding?.version !== 4) {
|
|
25
|
+
throw new GeometryError("INVALID_ARGUMENT", "Modeling functions can only run inside generate().", {
|
|
26
26
|
operation,
|
|
27
27
|
});
|
|
28
28
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** A user-correctable assertion failure. Successful computation caches never retain failures. */
|
|
2
|
+
export class GenerationError extends Error {
|
|
3
|
+
/** Issues reached in normal JavaScript order, ending with the first failed assertion. */
|
|
4
|
+
diagnostics;
|
|
5
|
+
/** Captures immutable diagnostics; unexpected author errors retain their original type. */
|
|
6
|
+
constructor(diagnostics) {
|
|
7
|
+
super(diagnostics.at(-1)?.message ?? "Generation failed.");
|
|
8
|
+
this.name = "GenerationError";
|
|
9
|
+
this.diagnostics = Object.freeze(diagnostics.map((issue) => Object.freeze({ ...issue })));
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Defines a typed model without executing it. Use `computed` only for work worth reusing.
|
|
14
|
+
*
|
|
15
|
+
* @example A simple model
|
|
16
|
+
* ```ts
|
|
17
|
+
* const model = generate<{ height: number }>(({ params, part, assert }) => {
|
|
18
|
+
* assert(params.height > 0, "Height must be positive.");
|
|
19
|
+
* return [part("body", box({ size: [10, 10, params.height] }))];
|
|
20
|
+
* });
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export function generate(callback) {
|
|
24
|
+
if (typeof callback !== "function")
|
|
25
|
+
throw new TypeError("generate requires a callback.");
|
|
26
|
+
return Object.freeze({ kind: "metamesh-model", generate: callback });
|
|
27
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
const constructors = { Float32Array, Float64Array, Uint32Array, Uint8Array, Int32Array };
|
|
2
|
+
const protocol = Symbol.for("@metamesh/modeling/immutable-buffer/1");
|
|
3
|
+
const storage = new WeakMap();
|
|
4
|
+
const adopted = new WeakMap();
|
|
5
|
+
/**
|
|
6
|
+
* Copies numeric data once into private storage. This pure helper needs no active generation.
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { immutableBuffer } from "@metamesh/modeling";
|
|
10
|
+
* const heights = immutableBuffer(new Float32Array([2, 4, 6]));
|
|
11
|
+
* const count = heights.length;
|
|
12
|
+
* const first = heights.at(0);
|
|
13
|
+
* const editable = heights.copy();
|
|
14
|
+
* editable[0] = 8; // heights still contains 2, 4, 6.
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export function immutableBuffer(source) {
|
|
18
|
+
const entry = Object.entries(constructors).find(([, constructor]) => source instanceof constructor);
|
|
19
|
+
if (entry === undefined)
|
|
20
|
+
throw new TypeError("immutableBuffer requires a supported numeric typed array.");
|
|
21
|
+
const [kind, constructor] = entry;
|
|
22
|
+
const owned = new constructor(source);
|
|
23
|
+
const length = owned.length;
|
|
24
|
+
const result = Object.freeze({
|
|
25
|
+
[protocol]: kind,
|
|
26
|
+
length,
|
|
27
|
+
at: (index) => {
|
|
28
|
+
const offset = index < 0 ? length + index : index;
|
|
29
|
+
return Number.isInteger(offset) && offset >= 0 && offset < length ? owned[offset] : undefined;
|
|
30
|
+
},
|
|
31
|
+
copy: () => new constructor(owned),
|
|
32
|
+
});
|
|
33
|
+
storage.set(result, owned);
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Copies mesh buffers into immutable data; geometric validation still occurs when modeling or finalizing.
|
|
38
|
+
* @example
|
|
39
|
+
* ```ts
|
|
40
|
+
* import { generate, immutableMesh } from "@metamesh/modeling";
|
|
41
|
+
* import { heightfieldMesh } from "@metamesh/modeling/geometry";
|
|
42
|
+
* const model = generate(({ computed, part }) => {
|
|
43
|
+
* const surface = computed("surface", () => immutableMesh(heightfieldMesh({
|
|
44
|
+
* size: [10, 10], segments: [8, 8], height: () => 2,
|
|
45
|
+
* })));
|
|
46
|
+
* return [part("surface", surface)];
|
|
47
|
+
* });
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export function immutableMesh(mesh) {
|
|
51
|
+
const { positions, indices } = mesh;
|
|
52
|
+
if (!(positions instanceof Float32Array) || !(indices instanceof Uint32Array))
|
|
53
|
+
throw new TypeError("immutableMesh requires Float32 positions and Uint32 indices.");
|
|
54
|
+
return Object.freeze({
|
|
55
|
+
positions: immutableBuffer(positions),
|
|
56
|
+
indices: immutableBuffer(indices),
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
/** Recognizes the versioned cross-bundle protocol without invoking foreign callbacks. */
|
|
60
|
+
export function isImmutableBuffer(value) {
|
|
61
|
+
if (typeof value !== "object" || value === null)
|
|
62
|
+
return false;
|
|
63
|
+
if (storage.has(value))
|
|
64
|
+
return true;
|
|
65
|
+
if (!Object.isFrozen(value))
|
|
66
|
+
return false;
|
|
67
|
+
const field = (key) => {
|
|
68
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
69
|
+
return descriptor !== undefined && "value" in descriptor ? descriptor.value : undefined;
|
|
70
|
+
};
|
|
71
|
+
const kind = field(protocol), length = field("length");
|
|
72
|
+
return (typeof kind === "string" &&
|
|
73
|
+
Object.hasOwn(constructors, kind) &&
|
|
74
|
+
typeof length === "number" &&
|
|
75
|
+
Number.isSafeInteger(length) &&
|
|
76
|
+
length >= 0 &&
|
|
77
|
+
typeof field("at") === "function" &&
|
|
78
|
+
typeof field("copy") === "function");
|
|
79
|
+
}
|
|
80
|
+
/** Adopts separately bundled factories once, copying before trusting their storage or methods. */
|
|
81
|
+
export function adoptImmutableBuffer(buffer) {
|
|
82
|
+
if (storage.has(buffer))
|
|
83
|
+
return buffer;
|
|
84
|
+
const previous = adopted.get(buffer);
|
|
85
|
+
if (previous !== undefined)
|
|
86
|
+
return previous;
|
|
87
|
+
if (!isImmutableBuffer(buffer))
|
|
88
|
+
throw new TypeError("Expected an immutableBuffer() value.");
|
|
89
|
+
const source = buffer.copy();
|
|
90
|
+
const kind = Object.getOwnPropertyDescriptor(buffer, protocol)
|
|
91
|
+
.value;
|
|
92
|
+
if (!(source instanceof constructors[kind]))
|
|
93
|
+
throw new TypeError("An immutable buffer copy must retain its numeric type and length.");
|
|
94
|
+
const captured = immutableBuffer(source);
|
|
95
|
+
if (captured.length !== buffer.length)
|
|
96
|
+
throw new TypeError("An immutable buffer copy must retain its numeric type and length.");
|
|
97
|
+
adopted.set(buffer, captured);
|
|
98
|
+
return captured;
|
|
99
|
+
}
|
|
100
|
+
/** Trusted internal read only: never mutate or expose this view or its backing buffer. */
|
|
101
|
+
export function immutableBufferView(buffer) {
|
|
102
|
+
return storage.get(adoptImmutableBuffer(buffer));
|
|
103
|
+
}
|
|
104
|
+
/** Normalizes trusted modeling/finalization inputs without copying or exposing storage publicly. */
|
|
105
|
+
export function immutableMeshView(mesh) {
|
|
106
|
+
const { positions, indices } = mesh;
|
|
107
|
+
return {
|
|
108
|
+
positions: isImmutableBuffer(positions) ? immutableBufferView(positions) : positions,
|
|
109
|
+
indices: isImmutableBuffer(indices) ? immutableBufferView(indices) : indices,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|