@metamesh/modeling 0.0.0-stage → 0.7.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.
Files changed (131) hide show
  1. package/CHANGELOG.md +111 -0
  2. package/LICENSE +200 -0
  3. package/README.md +376 -2
  4. package/THIRD_PARTY_NOTICES.md +153 -0
  5. package/dist/api/engine.d.ts +71 -0
  6. package/dist/api/geometry.d.ts +3136 -0
  7. package/dist/api/index.d.ts +2087 -0
  8. package/dist/api/text.d.ts +93 -0
  9. package/dist/engine/construction-operations.js +189 -0
  10. package/dist/engine/execution.js +227 -0
  11. package/dist/engine/export-3mf.js +100 -0
  12. package/dist/engine/extrusion-finishing.js +82 -0
  13. package/dist/engine/finalization.js +214 -0
  14. package/dist/engine/index.js +83 -0
  15. package/dist/engine/inspection-operations.js +52 -0
  16. package/dist/engine/kernel/LICENSE +201 -0
  17. package/dist/engine/kernel/manifold.js +2 -0
  18. package/dist/engine/kernel/manifold.wasm +0 -0
  19. package/dist/engine/loft-operations.js +156 -0
  20. package/dist/engine/mechanical-operations.js +51 -0
  21. package/dist/engine/modeling-context.js +38 -0
  22. package/dist/engine/native/adapter.js +20 -0
  23. package/dist/engine/native/context.js +209 -0
  24. package/dist/engine/native/copied-views.js +46 -0
  25. package/dist/engine/native/index.js +13 -0
  26. package/dist/engine/native/operations.js +115 -0
  27. package/dist/engine/native/ownership.js +98 -0
  28. package/dist/engine/native/regions.js +138 -0
  29. package/dist/engine/native/scoped-primitives.js +127 -0
  30. package/dist/engine/native/scoped-threads.js +34 -0
  31. package/dist/engine/native/threads.js +52 -0
  32. package/dist/engine/native/transforms.js +111 -0
  33. package/dist/engine/operation-environment.js +20 -0
  34. package/dist/engine/pattern-operations.js +36 -0
  35. package/dist/engine/pattern-placements.js +176 -0
  36. package/dist/engine/primitive-anchors.js +70 -0
  37. package/dist/engine/primitive-correspondence.js +74 -0
  38. package/dist/engine/primitive-operations.js +151 -0
  39. package/dist/engine/primitive-profile-types.js +1 -0
  40. package/dist/engine/primitive-profiles.js +250 -0
  41. package/dist/engine/profile-mesh.js +279 -0
  42. package/dist/engine/section-operations.js +93 -0
  43. package/dist/engine/shape-anchors.js +88 -0
  44. package/dist/engine/transform-operations.js +225 -0
  45. package/dist/engine/types.js +1 -0
  46. package/dist/geometry/anchors.js +226 -0
  47. package/dist/geometry/curves/evaluation.js +162 -0
  48. package/dist/geometry/curves/sampling.js +266 -0
  49. package/dist/geometry/curves/types.js +1 -0
  50. package/dist/geometry/cylinder.js +117 -0
  51. package/dist/geometry/edge-finishing.js +160 -0
  52. package/dist/geometry/errors.js +76 -0
  53. package/dist/geometry/extrusion.js +150 -0
  54. package/dist/geometry/heightfield.js +115 -0
  55. package/dist/geometry/index.js +42 -0
  56. package/dist/geometry/loft-correspondence.js +204 -0
  57. package/dist/geometry/loft-geometry.js +184 -0
  58. package/dist/geometry/loft-options.js +56 -0
  59. package/dist/geometry/loft-simple-mesh.js +92 -0
  60. package/dist/geometry/loft-triangulation.js +64 -0
  61. package/dist/geometry/loft.js +95 -0
  62. package/dist/geometry/math/vector3.js +71 -0
  63. package/dist/geometry/mechanical/gear-motion.js +94 -0
  64. package/dist/geometry/mechanical/gears.js +484 -0
  65. package/dist/geometry/mechanical/index.js +13 -0
  66. package/dist/geometry/mechanical/standards.js +67 -0
  67. package/dist/geometry/mechanical/thread-helices.js +44 -0
  68. package/dist/geometry/mechanical/thread-mesh-plan.js +358 -0
  69. package/dist/geometry/mechanical/thread-mesh.js +142 -0
  70. package/dist/geometry/mechanical/thread-profiles.js +373 -0
  71. package/dist/geometry/mechanical/thread-solids.js +230 -0
  72. package/dist/geometry/mechanical/thread-surface.js +433 -0
  73. package/dist/geometry/mechanical/threads.js +138 -0
  74. package/dist/geometry/mesh-edge-table.js +126 -0
  75. package/dist/geometry/mesh-planar.js +408 -0
  76. package/dist/geometry/mesh-quality.js +396 -0
  77. package/dist/geometry/mesh-signature.js +137 -0
  78. package/dist/geometry/mesh.js +283 -0
  79. package/dist/geometry/model-limits.js +50 -0
  80. package/dist/geometry/numeric.js +11 -0
  81. package/dist/geometry/paths/frames.js +118 -0
  82. package/dist/geometry/paths/helix.js +49 -0
  83. package/dist/geometry/paths/path.js +32 -0
  84. package/dist/geometry/paths/planar.js +135 -0
  85. package/dist/geometry/paths/resampling.js +125 -0
  86. package/dist/geometry/polygon-clipping.js +52 -0
  87. package/dist/geometry/primitive-options.js +1 -0
  88. package/dist/geometry/primitive-resolution.js +17 -0
  89. package/dist/geometry/primitive-resolvers.js +235 -0
  90. package/dist/geometry/profiles/matched.js +192 -0
  91. package/dist/geometry/profiles/open-profile.js +137 -0
  92. package/dist/geometry/profiles/profiles.js +91 -0
  93. package/dist/geometry/profiles/rectangle.js +249 -0
  94. package/dist/geometry/profiles/shapes.js +41 -0
  95. package/dist/geometry/profiles/span-sampling.js +28 -0
  96. package/dist/geometry/profiles/types.js +1 -0
  97. package/dist/geometry/profiles/validation.js +20 -0
  98. package/dist/geometry/regions.js +371 -0
  99. package/dist/geometry/resolution.js +236 -0
  100. package/dist/geometry/revolve/revolve-mesh.js +129 -0
  101. package/dist/geometry/sampling-limits.js +29 -0
  102. package/dist/geometry/stroke-path.js +181 -0
  103. package/dist/geometry/svg/errors.js +23 -0
  104. package/dist/geometry/svg/fill.js +172 -0
  105. package/dist/geometry/svg/index.js +1 -0
  106. package/dist/geometry/svg/matrix.js +130 -0
  107. package/dist/geometry/svg/parser.js +536 -0
  108. package/dist/geometry/svg/path.js +349 -0
  109. package/dist/geometry/svg/types.js +1 -0
  110. package/dist/geometry/svg-import.js +662 -0
  111. package/dist/geometry/sweeps/options.js +28 -0
  112. package/dist/geometry/sweeps/orientation.js +106 -0
  113. package/dist/geometry/sweeps/stations.js +161 -0
  114. package/dist/geometry/sweeps/sweep-closed.js +96 -0
  115. package/dist/geometry/sweeps/sweep-mesh.js +30 -0
  116. package/dist/geometry/sweeps/sweep-open.js +76 -0
  117. package/dist/geometry/sweeps/validation.js +116 -0
  118. package/dist/geometry/text-contours.js +32 -0
  119. package/dist/geometry/text-layout.js +1 -0
  120. package/dist/geometry/tolerance.js +53 -0
  121. package/dist/geometry/transform-geometry.js +41 -0
  122. package/dist/geometry/transform3.js +340 -0
  123. package/dist/geometry/types.js +1 -0
  124. package/dist/index.js +1 -0
  125. package/dist/model/execution-binding.js +30 -0
  126. package/dist/model/operations.js +154 -0
  127. package/dist/model/options.js +1 -0
  128. package/dist/model/types.js +1 -0
  129. package/dist/text/index.js +58 -0
  130. package/dist/text/vector-text-layout.js +279 -0
  131. package/package.json +100 -3
@@ -0,0 +1,172 @@
1
+ import { polygonClipping, unionPolygonGeometry, xorPolygonGeometry } from "../polygon-clipping.js";
2
+ import { normalizeRegion2, signedAreaLoop2 } from "../regions.js";
3
+ import { assertSamplingCount } from "../sampling-limits.js";
4
+ /** Private clipping stage with the selected execution's immutable allocation ceiling. */
5
+ export function createBoundedSvgClipping(maximumVertices) {
6
+ function ring(points) {
7
+ const result = points.map(([x, y]) => [x, y]);
8
+ const first = result[0];
9
+ const last = result.at(-1);
10
+ if (first !== undefined && last !== undefined && (first[0] !== last[0] || first[1] !== last[1]))
11
+ result.push([...first]);
12
+ return result;
13
+ }
14
+ function ringVertexCount(value) {
15
+ const first = value[0];
16
+ const last = value.at(-1);
17
+ const repeatedSeam = first !== undefined && last !== undefined && first[0] === last[0] && first[1] === last[1];
18
+ return value.length - (repeatedSeam ? 1 : 0);
19
+ }
20
+ function multiPolygonVertexCount(value) {
21
+ return value.reduce((total, polygon) => total + polygon.reduce((polygonTotal, path) => polygonTotal + ringVertexCount(path), 0), 0);
22
+ }
23
+ function assertExpandedVertexCount(vertices, stage) {
24
+ assertSamplingCount(vertices, "preflightSvgRegion", maximumVertices, `SVG ${stage} exceeds ${maximumVertices} expanded vertices (requires ${vertices}).`);
25
+ }
26
+ function boundedMultiPolygon(value, stage) {
27
+ assertExpandedVertexCount(multiPolygonVertexCount(value), stage);
28
+ return value;
29
+ }
30
+ function boundedUnion(values, stage) {
31
+ const nonEmpty = values.filter((value) => value.length > 0);
32
+ assertExpandedVertexCount(nonEmpty.reduce((total, value) => total + multiPolygonVertexCount(value), 0), `${stage} input`);
33
+ return boundedMultiPolygon(unionPolygonGeometry(nonEmpty), stage);
34
+ }
35
+ function boundedXor(values, stage) {
36
+ const nonEmpty = values.filter((value) => value.length > 0);
37
+ assertExpandedVertexCount(nonEmpty.reduce((total, value) => total + multiPolygonVertexCount(value), 0), `${stage} input`);
38
+ return boundedMultiPolygon(xorPolygonGeometry(nonEmpty), stage);
39
+ }
40
+ function unionAll(polygons) {
41
+ return boundedUnion(polygons, "union output");
42
+ }
43
+ function intersectGeometry(left, right) {
44
+ if (left.length === 0 || right.length === 0)
45
+ return [];
46
+ assertExpandedVertexCount(multiPolygonVertexCount(left) + multiPolygonVertexCount(right), "intersection input");
47
+ return boundedMultiPolygon(polygonClipping.intersection(left, right), "intersection output");
48
+ }
49
+ function multiPolygonArea(value) {
50
+ return value.reduce((total, polygon) => {
51
+ const [outer, ...holes] = polygon;
52
+ if (outer === undefined)
53
+ return total;
54
+ return (total +
55
+ Math.abs(signedAreaLoop2(outer)) -
56
+ holes.reduce((holeTotal, hole) => holeTotal + Math.abs(signedAreaLoop2(hole)), 0));
57
+ }, 0);
58
+ }
59
+ function coverageHasMultiplicity(rings, union) {
60
+ const summedArea = rings.reduce((total, points) => total + Math.abs(signedAreaLoop2(points)), 0);
61
+ const tolerance = Math.max(1, summedArea) * 1e-9;
62
+ return Math.abs(summedArea - multiPolygonArea(union)) > tolerance;
63
+ }
64
+ function addCoverageBits(left, right) {
65
+ const output = [];
66
+ let carry = [];
67
+ const inputBits = Math.max(left.length, right.length);
68
+ for (let index = 0; index < inputBits || carry.length > 0; index += 1) {
69
+ const inputs = [left[index] ?? [], right[index] ?? [], carry].filter((geometry) => geometry.length > 0);
70
+ if (inputs.length === 0) {
71
+ output.push([]);
72
+ carry = [];
73
+ }
74
+ else if (inputs.length === 1) {
75
+ output.push(inputs[0]);
76
+ carry = [];
77
+ }
78
+ else if (inputs.length === 2) {
79
+ output.push(boundedXor(inputs, "nonzero coverage XOR"));
80
+ carry = intersectGeometry(inputs[0], inputs[1]);
81
+ }
82
+ else {
83
+ output.push(boundedXor(inputs, "nonzero coverage XOR"));
84
+ carry = boundedUnion([
85
+ intersectGeometry(inputs[0], inputs[1]),
86
+ intersectGeometry(inputs[0], inputs[2]),
87
+ intersectGeometry(inputs[1], inputs[2]),
88
+ ], "nonzero coverage carry");
89
+ }
90
+ }
91
+ return output;
92
+ }
93
+ function coverageBits(polygons) {
94
+ let level = polygons.map((polygon) => [
95
+ boundedUnion([[polygon]], "nonzero coverage seed"),
96
+ ]);
97
+ while (level.length > 1) {
98
+ const next = [];
99
+ for (let index = 0; index < level.length; index += 2) {
100
+ const left = level[index];
101
+ const right = level[index + 1];
102
+ next.push(right === undefined ? left : addCoverageBits(left, right));
103
+ }
104
+ level = next;
105
+ }
106
+ return level[0] ?? [];
107
+ }
108
+ function differingCoverage(positive, negative) {
109
+ const positiveUnion = boundedUnion([positive.slice()], "positive winding union");
110
+ const negativeUnion = boundedUnion([negative.slice()], "negative winding union");
111
+ if (intersectGeometry(positiveUnion, negativeUnion).length === 0) {
112
+ return boundedUnion([positiveUnion, negativeUnion], "disconnected winding union");
113
+ }
114
+ const positiveBits = coverageHasMultiplicity(positive.map((polygon) => polygon[0] ?? []), positiveUnion)
115
+ ? coverageBits(positive)
116
+ : [positiveUnion];
117
+ const negativeBits = coverageHasMultiplicity(negative.map((polygon) => polygon[0] ?? []), negativeUnion)
118
+ ? coverageBits(negative)
119
+ : [negativeUnion];
120
+ const differingBits = [];
121
+ for (let index = 0; index < Math.max(positiveBits.length, negativeBits.length); index += 1) {
122
+ const positiveBit = positiveBits[index] ?? [];
123
+ const negativeBit = negativeBits[index] ?? [];
124
+ if (positiveBit.length === 0)
125
+ differingBits.push(negativeBit);
126
+ else if (negativeBit.length === 0)
127
+ differingBits.push(positiveBit);
128
+ else
129
+ differingBits.push(boundedXor([positiveBit, negativeBit], "winding difference XOR"));
130
+ }
131
+ return boundedUnion(differingBits, "winding difference union");
132
+ }
133
+ function fillGeometry(subpaths, fillRule) {
134
+ const rings = subpaths
135
+ .filter((subpath) => subpath.points.length >= 3)
136
+ .map((subpath) => subpath.points);
137
+ if (rings.length === 0)
138
+ return [];
139
+ const polygons = rings.map((points) => [ring(points)]);
140
+ if (fillRule === "evenodd") {
141
+ return boundedXor(polygons.map((polygon) => [polygon]), "even-odd fill output");
142
+ }
143
+ const positive = rings
144
+ .filter((points) => signedAreaLoop2(points) >= 0)
145
+ .map((points) => [ring(points)]);
146
+ const negative = rings
147
+ .filter((points) => signedAreaLoop2(points) < 0)
148
+ .map((points) => [ring(points)]);
149
+ if (positive.length === 0)
150
+ return boundedUnion([negative], "negative fill output");
151
+ if (negative.length === 0)
152
+ return boundedUnion([positive], "positive fill output");
153
+ // Compare the binary coverage counts instead of choosing one global exterior winding. This
154
+ // preserves disconnected opposite-winding components and nested islands while still cancelling
155
+ // precisely where positive and negative nonzero winding counts match.
156
+ return differingCoverage(positive, negative);
157
+ }
158
+ function regionFromMultiPolygon(value) {
159
+ return normalizeRegion2(value.map((polygon) => ({
160
+ outer: polygon[0].slice(0, -1).map(([x, y]) => [x, y]),
161
+ holes: polygon.slice(1).map((hole) => hole.slice(0, -1).map(([x, y]) => [x, y])),
162
+ })));
163
+ }
164
+ return {
165
+ assertExpandedVertexCount,
166
+ boundedMultiPolygon,
167
+ fillGeometry,
168
+ multiPolygonVertexCount,
169
+ regionFromMultiPolygon,
170
+ unionAll,
171
+ };
172
+ }
@@ -0,0 +1 @@
1
+ export { parseSvgDocument, parseSvgPath } from "./parser.js";
@@ -0,0 +1,130 @@
1
+ import { invalid } from "./errors.js";
2
+ export const IDENTITY_MATRIX = [1, 0, 0, 1, 0, 0];
3
+ export function multiplyMatrices(left, right) {
4
+ return [
5
+ left[0] * right[0] + left[2] * right[1],
6
+ left[1] * right[0] + left[3] * right[1],
7
+ left[0] * right[2] + left[2] * right[3],
8
+ left[1] * right[2] + left[3] * right[3],
9
+ left[0] * right[4] + left[2] * right[5] + left[4],
10
+ left[1] * right[4] + left[3] * right[5] + left[5],
11
+ ];
12
+ }
13
+ export function transformPoint(matrix, point) {
14
+ return [
15
+ matrix[0] * point[0] + matrix[2] * point[1] + matrix[4],
16
+ matrix[1] * point[0] + matrix[3] * point[1] + matrix[5],
17
+ ];
18
+ }
19
+ const NUMBER_PATTERN = /[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?/g;
20
+ /** Parses compact SVG number sequences while rejecting unconsumed syntax. */
21
+ export function parseNumberSequence(value, operation) {
22
+ const numbers = [];
23
+ let end = 0;
24
+ NUMBER_PATTERN.lastIndex = 0;
25
+ for (let match = NUMBER_PATTERN.exec(value); match !== null; match = NUMBER_PATTERN.exec(value)) {
26
+ const separator = value.slice(end, match.index);
27
+ if (!/^[\s,]*$/.test(separator) ||
28
+ (separator.match(/,/g)?.length ?? 0) > 1 ||
29
+ (numbers.length === 0 && separator.includes(","))) {
30
+ invalid(operation, `Invalid numeric syntax near "${separator}".`);
31
+ }
32
+ const parsed = Number(match[0]);
33
+ if (!Number.isFinite(parsed)) {
34
+ invalid(operation, "SVG numeric values must be finite.");
35
+ }
36
+ numbers.push(parsed);
37
+ end = match.index + match[0].length;
38
+ }
39
+ if (!/^\s*$/.test(value.slice(end))) {
40
+ invalid(operation, "Invalid trailing numeric syntax.");
41
+ }
42
+ if (numbers.length === 0 && value.trim() !== "") {
43
+ invalid(operation, `Expected a numeric value, received "${value}".`);
44
+ }
45
+ return numbers;
46
+ }
47
+ function transformFor(name, values, operation) {
48
+ switch (name) {
49
+ case "matrix":
50
+ if (values.length !== 6)
51
+ invalid(operation, "matrix() requires six numbers.");
52
+ return values;
53
+ case "translate":
54
+ if (values.length < 1 || values.length > 2) {
55
+ invalid(operation, "translate() requires one or two numbers.");
56
+ }
57
+ return [1, 0, 0, 1, values[0] ?? 0, values[1] ?? 0];
58
+ case "scale": {
59
+ if (values.length < 1 || values.length > 2) {
60
+ invalid(operation, "scale() requires one or two numbers.");
61
+ }
62
+ const sx = values[0] ?? 1;
63
+ return [sx, 0, 0, values[1] ?? sx, 0, 0];
64
+ }
65
+ case "rotate": {
66
+ if (values.length !== 1 && values.length !== 3) {
67
+ invalid(operation, "rotate() requires an angle, optionally followed by a center.");
68
+ }
69
+ const radians = ((values[0] ?? 0) * Math.PI) / 180;
70
+ const rotation = [
71
+ Math.cos(radians),
72
+ Math.sin(radians),
73
+ -Math.sin(radians),
74
+ Math.cos(radians),
75
+ 0,
76
+ 0,
77
+ ];
78
+ if (values.length === 1)
79
+ return rotation;
80
+ const cx = values[1] ?? 0;
81
+ const cy = values[2] ?? 0;
82
+ return multiplyMatrices(multiplyMatrices([1, 0, 0, 1, cx, cy], rotation), [
83
+ 1,
84
+ 0,
85
+ 0,
86
+ 1,
87
+ -cx,
88
+ -cy,
89
+ ]);
90
+ }
91
+ case "skewX": {
92
+ if (values.length !== 1)
93
+ invalid(operation, "skewX() requires one angle.");
94
+ const tangent = Math.tan(((values[0] ?? 0) * Math.PI) / 180);
95
+ if (!Number.isFinite(tangent))
96
+ invalid(operation, "skewX() produced a non-finite matrix.");
97
+ return [1, 0, tangent, 1, 0, 0];
98
+ }
99
+ case "skewY": {
100
+ if (values.length !== 1)
101
+ invalid(operation, "skewY() requires one angle.");
102
+ const tangent = Math.tan(((values[0] ?? 0) * Math.PI) / 180);
103
+ if (!Number.isFinite(tangent))
104
+ invalid(operation, "skewY() produced a non-finite matrix.");
105
+ return [1, tangent, 0, 1, 0, 0];
106
+ }
107
+ default:
108
+ invalid(operation, `Unsupported SVG transform "${name}".`);
109
+ }
110
+ }
111
+ /** Parses an SVG transform list using standard post-multiplication order. */
112
+ export function parseTransform(value, operation) {
113
+ if (value === undefined || value.trim() === "")
114
+ return IDENTITY_MATRIX;
115
+ const callPattern = /([A-Za-z]+)\s*\(([^()]*)\)/g;
116
+ let result = IDENTITY_MATRIX;
117
+ let end = 0;
118
+ for (let match = callPattern.exec(value); match !== null; match = callPattern.exec(value)) {
119
+ if (!/^[\s,]*$/.test(value.slice(end, match.index))) {
120
+ invalid(operation, "Malformed SVG transform list.");
121
+ }
122
+ const values = parseNumberSequence(match[2] ?? "", operation);
123
+ result = multiplyMatrices(result, transformFor(match[1] ?? "", values, operation));
124
+ end = match.index + match[0].length;
125
+ }
126
+ if (end === 0 || !/^\s*$/.test(value.slice(end))) {
127
+ invalid(operation, "Malformed SVG transform list.");
128
+ }
129
+ return result;
130
+ }