@metamesh/modeling 0.9.0 → 0.10.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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # @metamesh/modeling
2
2
 
3
+ ## 0.10.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 9d7052c: Separate parameter values from local UI metadata, add strict parameter-document diagnostics and a generated JSON Schema, and rename the side-profile and contour APIs. Generator packages and modules now require format 3 and runtime 3.0.0.
8
+
3
9
  ## 0.9.0
4
10
 
5
11
  ### Minor Changes
package/README.md CHANGED
@@ -1,35 +1,19 @@
1
1
  # `@metamesh/modeling`
2
2
 
3
- Standalone geometry foundations and a modeling engine for Node applications, browser Workers, and MetaMesh Generators.
3
+ Build parametric 3D parts with TypeScript and export the finished meshes to 3MF. Use Modeling in
4
+ Node applications, browser Workers, or MetaMesh Generators, with no SDK or Testkit required.
4
5
 
5
- > **Preview:** `0.x` minor releases may break compatibility. Patches remain compatible within the same minor line. Stable support requires an explicit `1.0.0` review.
6
+ > **Preview:** `0.x` minor releases may break compatibility. Patches remain compatible within the
7
+ > same minor line. Stable support requires an explicit `1.0.0` review.
6
8
 
7
- Applications initialize one engine and build models with imported functions. Generator hosts supply
8
- the same execution scope and own WASM initialization and native cleanup. Modeling requires no SDK
9
- or Testkit.
10
-
11
- ## API discovery and ownership
12
-
13
- | Entry point | Responsibility |
14
- | ----------------------------- | ------------------------------------------------------------------------------------------- |
15
- | `@metamesh/modeling` | Imported modeling functions, opaque handles, output descriptors, and finalized results |
16
- | `@metamesh/modeling/geometry` | Pure math, paths, profiles, regions, transforms, meshes, mechanical kernels, and SVG import |
17
- | `@metamesh/modeling/engine` | Host initialization, scoped modeling, finalization, resource limits, and snapshot export |
18
- | `@metamesh/modeling/text` | Optional font-byte preparation and synchronous vector layout |
19
-
20
- These four entry points are the complete workspace and published API. Pure helpers cannot import
21
- the engine, native kernel, browser APIs, Studio, or Generators. Fontkit and WOFF2 decoding are optional
22
- peers used only by `/text`. Hosts own acquisition, worker lifecycle, and loading. API reports define
23
- supported symbols; consumer fixtures verify published use.
24
-
25
- ## Modeling and lifecycle
26
-
27
- Install the package and create an engine in the application:
9
+ ## Start modeling
28
10
 
29
11
  ```bash
30
12
  pnpm add @metamesh/modeling
31
13
  ```
32
14
 
15
+ Initialize an engine, then build and export a plate with a hole:
16
+
33
17
  ```ts
34
18
  import { box, cylinder, subtract } from "@metamesh/modeling";
35
19
  import { createModelEngine } from "@metamesh/modeling/engine";
@@ -46,414 +30,35 @@ const snapshot = engine.build(
46
30
  const bytes = await engine.export3mf(snapshot.parts, { name: "Plate" });
47
31
  ```
48
32
 
49
- The package includes the pinned Manifold JS/WASM kernel and its license, without Manifold's CLI or glTF dependency tree. Node and unbundled browser modules resolve the installed WASM automatically. Browser bundlers must retain or emit the relative asset and can pass their emitted URL explicitly:
50
-
51
- ```ts
52
- import { box } from "@metamesh/modeling";
53
- import { createModelEngine, modelingWasmUrl } from "@metamesh/modeling/engine";
54
-
55
- const engine = await createModelEngine({ locateFile: () => modelingWasmUrl });
56
- const snapshot = engine.build(() => box({ size: [10, 20, 30] }));
57
- ```
58
-
59
- Imported functions and `ModelingContext` methods execute the same operations. Imports do not
60
- initialize the kernel. The host binds the active context through a versioned symbol shared across
61
- bundles, then clears it in `finally`. Calls outside `build` throw.
62
-
63
- Builds are synchronous and cannot nest, including across engines in one JavaScript realm.
64
- Hosts finish asynchronous loading and service preparation before `build`; separate Workers have
65
- independent execution scopes.
66
- An engine rejects builds or exports while its asynchronous exporter is busy.
67
-
68
- `Solid` and `Section` are opaque immutable handles belonging to one synchronous build. Sections are
69
- native filled XY shapes, including holes and disconnected components. `union`, `subtract`, and
70
- `intersect` accept either Solid or Section operands and reject mixed dimensions. Offsets and Booleans
71
- retain native sections; `fromRegion` and `toRegion` are explicit conversion boundaries. Repeated
72
- sweeps and loft stations extract each Section once per execution; `toRegion` returns fresh immutable
73
- snapshots.
74
-
75
- Handles cannot cross builds or survive disposal. All native values dispose in reverse construction
76
- order from `finally`, including on failure. Installed bundles call host-owned methods, avoiding
77
- package-local native class or module identity.
78
-
79
- Builds return a bare solid (part ID `model`) or named `{ id, solid, color? }` / `{ id, mesh, color? }`
80
- descriptors. IDs are unique lowercase kebab-case strings of at most 64 characters; colors are
81
- six-digit hex RGB. Direct meshes are copied and checked for finite, nondegenerate, oriented closed
82
- topology and kernel reconstructability without copying them back from the kernel. Native extraction
83
- allows one bounded retry for collapsed Float32 stitches.
84
-
85
- Descriptors for the same Solid share one extraction, copied manufacturing buffers, and immutable
86
- facts. `toTriangleMesh` inspection returns independent copies; mutable mesh inputs are captured and
87
- validated afresh for every build. Finalized parts retain shared bounds, counts, signed volume, area,
88
- and topology facts. Render normals remain outside manufacturing data.
89
-
90
- Default limits are 64 parts, 1,000,000 vertices, 2,000,000 triangles, and 96 MiB of unique backing storage. Trusted applications may configure higher or lower ceilings through `createModelEngine({ limits })`. Per-build limits may only lower those configured ceilings, and snapshot export uses them too. Generator hosts own ceilings; generators receive no engine. Predictable allocations preflight safe-integer counts. Algorithm-specific numeric limits remain documented by their operations. Complexity fails explicitly instead of reducing fidelity.
91
-
92
- The same `limits` policy includes `maxPathPoints` (100,000), `maxProfilePoints` (100,000),
93
- `maxSweepProfilePoints` (4,096), and `maxCircularSegments` (512). Pure helpers accept explicit
94
- limits and use these defaults when omitted. Mesh constructors also accept the selected output
95
- budgets. Engines supply their configured policy to bound operations; operation limits may lower
96
- it. Exact samples override physical sampling without raising an allocation ceiling. Excessive
97
- counts fail before predictable array or mesh-buffer allocation, with required and allowed counts.
98
-
99
- SVG, text, strokes and gear outlines also honor these selected sampling ceilings. Separate SVG
100
- source, XML and recursion bounds, region intersection work limits, and native implicit-field
101
- work limits remain fixed operation safeguards.
102
-
103
- `engine.export3mf` consumes selected finalized buffers without preparation or generation. Its lazy ZIP/XML serializer preserves exact selected vertices, triangle indices, semantic part names, and optional RGB display colors without a native or glTF conversion. Hosts own revisions, appearance overrides, filenames, delivery, and persistence.
104
-
105
- Build and 3MF export accept `meshValidation: "solid"` for bounded geometric validation in addition
106
- to their default indexed topology checks. This rejects intersecting/touching separate shells and
107
- incorrectly oriented nested boundaries while preserving inward cavity shells. Checks apply to each
108
- part independently; separate semantic parts may intentionally overlap. It uses the same pure
109
- `validateSolidMesh` capability described below. Finalization reuses fresh topology facts for its
110
- privately owned selected buffers; export captures mutable input before fresh analysis. Within one
111
- operation, identical buffer ranges share checks. No caller-supplied or cross-operation facts are
112
- trusted. Its additional work and failure ceilings are explicit;
113
- the default `"topology"` mode retains the existing large-model workflow.
114
-
115
- ## Resolution and conventions
116
-
117
- Primitive and pure loop factories share named options and these conventions:
118
-
119
- | Concept | Convention |
120
- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
121
- | Units | Lengths are millimetres; modeling rotations use degrees. Pure transform constructors require explicit `{ degrees }` or `{ radians }`. |
122
- | Dimensions | `rectangle` / `rectangleLoop` take `size: number \| [width, depth]`. `circle` / `circleLoop` and `sphere` take exactly one `radius` or `diameter`. `ellipse` / `ellipseLoop` take exactly one XY `radius` tuple or full `size` tuple. |
123
- | Cylinders | +Z height and exactly one `radius` or `diameter`; either a uniform scalar or `[bottom, top]` tuple. One tuple end may be zero for a cone. |
124
- | Origins | Shapes are centered by default. `center: false` places the box/rectangle minimum corner or cylinder/tube bottom at zero. Circles, ellipses, spheres and regular polygons remain centered. |
125
- | Radial sizing | Circle, cylinder and regular polygon `sizing` defaults to `"vertex"`; `"face"` compensates polygon faceting to reach the nominal dimension at faces. Physical clearance is separate from Boolean overtravel. |
126
- | Treatments | Corners and supported rims share `round` (radius), `chamfer` (distance), `smooth` (tangent setbacks), and `profile` (explicit millimetre points). Offsets use lowercase `join: "round" \| "miter" \| "square"`. |
127
- | Sampling | Exact local `segments` override physical `resolution`; selected allocation ceilings still apply. Pure circle/ellipse loops use physical defaults when `segments` is omitted. |
128
-
129
- Applicable edges, corners and supported rims share `EdgeTreatment`. `{ kind: "round", radius }`
130
- uses an actual circular radius; `{ kind: "chamfer", distance }` uses equal setbacks, or
131
- `distance: [first, second]` for unequal setbacks. `{ kind: "smooth", distance }` uses the same
132
- setbacks with a cubic transition whose curvature reaches zero at both straight joins.
133
- Its size is a setback, rather than a circular radius. Round `segments` describes a complete
134
- circle and must divide by four for rectangle corners; smooth `segments` is an exact cubic
135
- interval count. Physical cubic sampling uses chord error and segment length.
136
-
137
- Rectangles select a uniform treatment or a tuple ordered top-right, top-left, bottom-left,
138
- bottom-right; the tuple accepts `{ kind: "square" }`. Boxes select a uniform treatment or a map
139
- of named edges and groups, such as `treatment: { top: { kind: "round", radius: 1 }, bottom:
140
- { kind: "chamfer", distance: 1 } }`. Groups select the four top/bottom rim edges or `vertical`
141
- edges; individual names include `"top-front"` and `"front-left"`. Overlapping selections fail.
142
- Named faces use +Z top, +Y front
143
- and +X right. Selected edge masks meet by intersection; they do not promise spherical three-edge
144
- blends. Omitted selection retains the spherical all-edge circular box treatment.
145
-
146
- `ends: { bottom, top }` independently finishes cylinder/frustum rims and straight convex
147
- extrusions. Cylinder rounds account for the actual sloped side's angle. Extrusion finishing
148
- requires one strictly convex contour without holes, twist, top scaling or divisions. Inward
149
- parallel offsets preserve its original edge correspondence; disappearing/reversed edges fail.
150
- Use explicit loft profiles for concave or holed transitions.
151
-
152
- Custom `{ kind: "profile", profile }` treatments use exact sampled points from `[positive, 0]`
153
- to `[0, positive]`, with decreasing first and increasing second coordinates. These are setbacks
154
- along the two inward surface directions, in millimetres. Cylinder/extrusion ends use cap inset
155
- first and side setback second. Rectangle corners follow the incoming and outgoing CCW edges.
156
- For a named box edge, the first coordinate lies on the first named face and the second on the
157
- second named face. Samples are captured per call, checked for fit and bounded before allocation;
158
- no analytic curve or outward moulding is inferred. Ordinary curve helpers can prepare the points.
159
-
160
- Primitive `placement: { source, target, ... }` maps a named or explicit local anchor onto a world
161
- anchor, with optional alignment, gap and degree spin. Placement follows local construction,
162
- including `center`; Section placement must preserve the XY plane.
163
-
164
- Rectangle round radii may vary from zero (square corners) through half the smallest dimension
165
- (a capsule or circle). Saturated tangent endpoints are emitted once. Uniform rectangle chamfers
166
- and all-edge box rounds/chamfers require positive sizes smaller than half every dimension.
167
- Selected box treatments must leave positive flat space between opposing setbacks.
168
-
169
- Authored box, cylinder/frustum, tube, sphere, torus, rectangle, circle and ellipse handles know
170
- `center`, `left`, `right`, `back`, `front`, `bottom` and `top` anchors. `left/right` follow −X/+X,
171
- `back/front` follow −Y/+Y and `bottom/top` follow −Z/+Z. Regular polygons and stars provide `center`.
172
- These are nominal construction datums: sampling and finishing do not move them; tube cap and torus
173
- axial centers may lie over an opening. A cone omits the cap anchor at its zero-radius apex.
174
- `anchor(shape, name)` reads its immutable frame, and `attach(pin, "bottom", anchor(base, "top"))`
175
- mates opposed directions by default. Affine transforms and patterns carry positions, outward
176
- normals and roll tangents with the shape, including nonuniform scaling and reflection.
177
-
178
- `withAnchors(shape, { mount: createAnchor3(position, direction, up) })` returns a new same-kind handle
179
- with copied definitions, retaining existing names unless replaced. Each shape supports at most 128
180
- lowercase kebab-case names of at most 64 characters. Looking up an absent name reports the available
181
- names. Shape lookup requires the exact active build; the returned `Anchor3` is copied plain data.
182
- Boolean and other geometry construction results require deliberate definitions rather than
183
- inheriting anchors that may no longer describe their surfaces. Custom rigid frames remain useful;
184
- pure `boxAnchor3` and `cylinderAnchor3` calculate dimension-based frames without a native shape.
185
-
186
- `GeometryResolution` defaults to 0.05 mm maximum chord error and 16–512 segments per complete circle.
187
- Precedence is exact local segments, operation resolution, context-derived resolution, then engine
188
- defaults. Exact counts express deliberate faceting or fixed correspondence; adaptive curves retain
189
- physical deviation and edge-length constraints.
190
-
191
- `model.withResolution(policy)` and imported `modelingFunctions(policy)` return a local method set
192
- for the same build. Destructure those methods to apply the local policy; other imports retain the
193
- original build policy. There is no global resolution or font state.
194
-
195
- Revolution `segments` describes a complete circle; partial revolutions scale that count to the
196
- angular span and round up. Native filled revolutions need at least three intervals: adaptive
197
- sampling refines to that minimum, while exact counts below it fail explicitly. Pure `revolveMesh`
198
- supports one interval.
199
-
200
- `resamplePolyline2` and `resamplePolyline3` share physical sampling options and return immutable
201
- points with directed unit tangents. Open paths retain both endpoints; `closed: true` includes the
202
- closing edge and omits a repeated seam vertex. Exact closed counts require at least three segments.
203
-
204
- `regularPolygonLoop({ radius, sides, rotationDegrees, sizing })` creates a centered CCW point loop
205
- with an implicit seam. `RadialSizing` defaults to `"vertex"`; `"face"` compensates polygon faceting
206
- so the requested radius reaches the faces. `CylinderOptions` uses the same sizing option.
207
-
208
- `normalizeLoop2`, `normalizePolygon2`, and `normalizeRegion2` return immutable geometry and may reuse internally validated results. External objects are validated even when frozen. Mutable mesh buffers are analyzed freshly at external boundaries; engine facts are reused inside their owner.
209
-
210
- ## Profiles and mechanical construction
211
-
212
- `sampleOpenProfile` samples upward radius/height line and cubic chains. Consumers own closure and
213
- product constraints. `sampleCrossSection` samples 2–32 explicitly closed XY line/cubic segments
214
- after positive width/height scaling, using the shared physical curve policy. It returns one
215
- immutable CCW loop without a repeated seam, rejecting sampled self-intersections, zero area,
216
- nonfinite coordinates, and exhausted budgets. Its default unique vertex ceiling is the smaller of
217
- 4096 and `limits.maxProfilePoints`; each segment also obeys `limits.maxPathPoints`. It accepts plain
218
- curve coordinates without an SDK dependency, editor modes, or a normalized-range requirement.
219
- `circleLoop`, `rectangleLoop`, and other point factories return ordered loops.
220
- `Profile` owns identified components, contours, and landmarks; `CorrespondingProfile` retains their
221
- shared sampled slots.
222
-
223
- `rectangle`, `rectangleLoop`, and `rectangleProfile` share `size`, `center`, and `treatment` options.
224
- Use root primitives for ordinary modeling and pure loops/profiles for custom data and explicit
225
- correspondence. Native filled sections do not promise stable vertex ordering.
226
-
227
- For a closed planar XYZ path, map the canonical loop with
228
- `rectangleLoop(options).map(([x, y]) => [x, y, 0] as const)`. Before sampling, cap its
229
- `limits.maxProfilePoints` at the smaller selected profile and path point ceilings. This keeps
230
- path allocation bounded and preserves all corner tangent endpoints and the implicit seam.
231
-
232
- ### Extrusion and sweep
233
-
234
- `extrude(shape, { height, ... })`, `revolve(shape, options)` and `sweep(shape, path, options)` accept a filled Section or CorrespondingProfile, including holes and disconnected components. Open sweeps have watertight end caps; `closed: true` joins the path back to its beginning. Sweep options share degree twist, positive uniform or XY scale, and an XY scale pair per normalized path station. Closed scales interpolate along the implicit closing edge. A rotational symmetry seam uses `seamProfileOffset`, a counterclockwise logical point advance that reverses for clockwise hole contours; every contour must geometrically match the total twist. Incompatible seams fail explicitly.
235
-
236
- Adaptive extrusion sampling bounds the entire side triangles, including the interaction between
237
- profile edges, twist and unequal top scaling. `resolveExtrusionDivisions(profile, height, options)`
238
- accepts a `Loop2` or filled `Region2` and measures every outer and hole edge; it is an optional estimate
239
- using the same policy as construction. Exact `divisions` and deliberate angular counts retain their
240
- precedence. Tight physical requests reject at selected sampling or output ceilings.
241
-
242
- Nonzero sweep twist inserts stations according to `resolution`, including the deviation of side
243
- triangles across profile edges. A full turn on a two-point path remains a full turn. Original
244
- path stations retain their scales, twist progression and orientation-guide indices; `twistBy`
245
- defaults to `"length"`, so angular progression follows physical path travel, including the implicit
246
- closing edge. Choose `"stations"` for a deliberately authored station schedule. Inserted
247
- stations interpolate centers, scales and shortest frame rotations. Guides run once per original
248
- normalized station. Zero twist preserves the supplied path's existing mesh sampling. Explicit
249
- `resolution.circularSegments` selects deliberate angular faceting; allocation ceilings still
250
- apply. Adaptive scale changes can require subdivisions independently of angular travel; those
251
- gaps use the selected full-curve interval ceiling. Refinement checks path and mesh budgets before
252
- allocating the added station arrays.
253
-
254
- Sweep `orientation` is one explicit policy shared by pure, filled, open and closed construction:
255
- `{ kind: "transport", initialNormal? }` (default), `{ kind: "fixed-normal", normal }`,
256
- `{ kind: "guide", normal: (point, index) => vector }`, or `{ kind: "frenet" }`. Transport minimizes
257
- rotation and initially projects +X, then +Y, then +Z when no preference is supplied. A preferred
258
- normal sets profile X; fixed and guide normals remain directed and project perpendicular to each
259
- local tangent. Guide signs are preserved. Frenet uses projected neighboring sampled tangent changes
260
- as curvature normals. Zero curvature and parallel explicit normals reject instead of switching
261
- policies. For a planar closed path whose profile X should stay vertical, request
262
- `orientation: { kind: "transport", initialNormal: [0, 0, 1] }`. Closed policies use cyclic tangents
263
- and the same first frame on their closing gap.
264
-
265
- ### Loft correspondence
266
-
267
- `loft(stations, options)` accepts a `profile` and exactly one horizontal `z` height or explicit
268
- right-handed `frame` per station. Stations stay in input order. Ordinary primitives keep the
269
- simple case at the root entry point:
270
-
271
- ```ts
272
- import { circle, loft, rectangle } from "@metamesh/modeling";
273
-
274
- export function build() {
275
- return loft([
276
- { profile: circle({ diameter: 20 }), z: 0 },
277
- { profile: rectangle({ size: [30, 15], treatment: { kind: "round", radius: 3 } }), z: 40 },
278
- ]);
279
- }
280
- ```
281
-
282
- With omitted `correspondence`, compatible rectangle stations, circle/ellipse rings, equal-sided
283
- regular polygons and equal-pointed stars use their retained authored landmarks. Rectangle corners
284
- can change among square, round and chamfer treatments, including collapsed or saturated rounds.
285
- Circle/ellipse and rectangle stations also share those quadrant/corner landmarks: a circular
286
- inlet can become a rounded rectangle directly.
287
- Exact circular counts for this mixed family must divide by four; other deliberately faceted counts
288
- need an explicit geometric method. Adaptive counts align upward within the selected ceilings.
289
- XY affine transforms preserve these identities; this semantic path requires consistent handedness.
290
- Other unlike retained primitive families use direct perimeter subdivision from their construction-local
291
- +X ray seam. Every physical input vertex remains, including corner tangent endpoints. Retained
292
- curves use the selected loft resolution under every method, accounting for transform magnification.
293
- Exact primitive samples preserve deliberate faceting; `refine` can deliberately add collinear density.
294
- When an exact primitive is paired with a finer member, a shared union of parameter knots retains
295
- its original polygon and inserts only collinear points on its facets. Adaptive members retain
296
- physical curve sampling. Circle face sizing uses the actual minimum facet support from the local
297
- origin, so collapsed slots, unequal quarter counts and collinear refinements preserve nominal clearance.
298
-
299
- Boolean, offset and import operations discard primitive identity. Their Sections and plain point
300
- loops require an explicitly selected correspondence method. This choice associates ordered
301
- contours by component and hole index; it does not identify arbitrary components or repair changed
302
- topology. Use identified profiles when those identities matter. Explicitly choosing a method on
303
- known primitives also runs that geometric method, rather than substituting semantic matching.
304
-
305
- | `correspondence` | Association |
306
- | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
307
- | `"direct"` | Preserve each seam and subdivide edges to a common count without dropping vertices. |
308
- | `"reindex"` | Direct subdivision, then choose the next contour's cyclic phase with the least total world-space connection length. |
309
- | `"fast-distance"` | Keep the supplied seams and minimize connection length over ordered closed vertex associations, repeating vertices for triangular fans. |
310
- | `"distance"` | The same ordered optimization over every cyclic phase of the next contour. |
311
-
312
- One method applies to every gap; an array supplies one per gap. `distribution` defaults to
313
- `"perimeter"` for direct/reindex and `"segments"` when any distance method is present. It controls
314
- where extra subdivisions go; distance methods require segments. `refine` is a positive integer
315
- multiplier, uniformly or per input profile. `slices` is a nonnegative intermediate-profile count,
316
- uniformly or per gap. Slices interpolate the corresponding placed world vertices, never frame axes.
317
- Insertions propagate across a shared grid, so mixed methods retain each middle profile without
318
- T-junctions. Phase ties keep the earliest phase; ordered optimization prefers diagonal, previous-only,
319
- then next-only advances. Its objective sums connection lengths with the closing seam counted once.
320
- No tangent/supporting-plane association is exposed: general concave and spatial stations do not
321
- provide the convexity contract that such a method needs.
322
-
323
- Identified `ProfileSpan2` supports lines, arcs, and cubic Béziers with `start`, `control1`, `control2`,
324
- and `end`. Cubics reuse the curve evaluator and physical subdivision policy, selecting one uniform
325
- parameter grid for corresponding spans. Fully collapsed spans retain logical slots without moving
326
- endpoints. Sampled contours and components use `CorrespondingContour` and `CorrespondingComponent`.
327
- Pass the complete family to `matchProfiles` with optional `limits.maxProfilePoints`; its default is
328
- 100,000 points per profile, while a separate one-million-slot family work bound remains in force.
329
-
330
- For precise custom correspondence, prepare `matchProfiles` over explicit `Profile` specifications.
331
- It chooses the maximum physical interval count for each paired span across the family, including
332
- span-local `resolution`; identified line, arc and cubic kinds may change while IDs, order and
333
- connected endpoints remain valid. `CorrespondingProfile` landmarks are authoritative, so geometric
334
- remapping methods are rejected; direct refinement preserves every paired slot. `rectangleProfile`
335
- is the canonical identified rectangle factory. Changed IDs, winding or filled topology fail.
336
- Lofting merges coincident world vertices, omits degenerate faces and triangulates hole caps natively.
337
-
338
- All loft routes share `LoftOptions`. Pure `loftMesh` requires an explicit method and returns a
339
- closed simple-loop mesh without an engine. Selected sampling/output budgets apply before expanded
340
- grids and mesh buffers; fixed aggregate safeguards bound correspondence at eight million comparisons
341
- and the complete grid at one million logical slots. Simple-loop cap triangulation also has an
342
- eight-million-comparison work ceiling. Caps whose minimum comparison count exceeds that ceiling
343
- reject before ear clipping; other caps retain the running work check. Complexity rejects instead of
344
- reducing fidelity or substituting another method. Pure counterparts also include `sweepMesh`,
345
- `extrudeMesh` and `revolveMesh`.
346
-
347
- ### Patterns and bounded surfaces
33
+ Modeling calls run inside a synchronous build; handles belong to that build. Keep the finalized
34
+ snapshot for rendering or export. For browser WASM setup and host lifecycle, follow
35
+ [Run Modeling in your application](https://github.com/JasonKoolman/metamesh/blob/main/apps/web/content/docs/engine/hosting.mdx).
348
36
 
349
- `linearPattern(shape, { count, length })` determines equal intervals from exactly two of `count`,
350
- signed `spacing`, and signed total `length`; its direction defaults to +X. Length and spacing must
351
- fit a whole interval count. `gridPattern(shape, { count: [3, 2], spacing: [10, 8] })` supports XY
352
- or XYZ tuples, optional centering and an explicit frame; X varies fastest. `pathPattern(shape,
353
- path, { count })` distributes copies by physical arc length, including both endpoints for open
354
- paths and omitting a repeated closed seam. Its optional `orientation: "tangent"` aligns local +Z
355
- to the containing segment by the shortest rotation from world +Z (a -Z tangent uses a +X half turn);
356
- the default preserves source orientation. These placements transform the source's local origin.
357
- All four pattern families, including `radialPattern`, accept a same-build Solid or an `(index) =>
358
- Solid` factory for per-copy variation. Factories run once in ascending index order. The 100000-copy
359
- algorithm ceiling and selected output budgets reject before predictable native copy allocation;
360
- varying factories are checked cumulatively before each transform. Path input and placement counts
361
- also obey `maxPathPoints`. Copies use the shared transform and lifetime checks.
362
-
363
- `heightfield` returns a Solid for transforms and booleans; `heightfieldMesh` returns the same
364
- closed construction as pure indexed data. Supply `size`, `segments`, and optional `origin` for a
365
- uniform grid, or `coordinates: { x, y }` for absolute, unevenly spaced XY samples. Explicit axes
366
- must increase strictly and remain distinct in Float32. This lets callers refine narrow borders
367
- without remapping mesh vertices. Heights remain absolute Z values strictly above `baseZ` (default
368
- zero); sampling never rescales the field.
369
-
370
- `triangulation` defaults to `"fixed"`, preserving the lower-left to upper-right diagonal.
371
- `"min-edge"` chooses the shorter 3D diagonal without extra sampling. `"center-error"` samples the
372
- physical cell centre once and chooses the diagonal whose interpolated height is closer there.
373
- Ties retain the fixed diagonal. Every policy uses two top triangles per cell and the same closed
374
- walls and flat base; centre agreement does not guarantee accuracy elsewhere. Allocation and
375
- coordinate precision are checked before height callbacks. Each grid point is sampled once, with
376
- one additional callback per cell for `"center-error"`.
377
-
378
- ```ts
379
- heightfield({
380
- coordinates: { x: [-15, -14.5, 0, 14.5, 15], y: [-10, -9.5, 0, 9.5, 10] },
381
- triangulation: "min-edge",
382
- height: ([x, y]) => 3 + Math.cos(x / 5) * Math.cos(y / 5),
383
- });
384
- ```
385
-
386
- Pure mechanical modules own thread profiles, sampling, printable fit, meshes, and allocation-free `preflightThreadedRod`, `preflightInternalThreadCutter`, and `preflightThreadedNut`. Engine methods own native construction. `pitch` is adjacent axial spacing; `lead = pitch × starts` is advance per turn. Internal cutters extend beyond requested faces and compensate faceting while preserving end treatments. Printable modeling forms are not certified thread classes or structural ratings.
387
-
388
- `split(solid, cutter)` returns intersection followed by subtraction as two same-build handles.
389
- `splitByPlane(solid, { normal, offset })` returns the positive-normal side followed by the negative
390
- side. Destructure `[positive]` or `[, negative]` to retain just one side. The nonzero normal is
391
- normalized and `offset` is the signed distance from the world origin in millimetres (default zero).
392
- Empty results are valid handles
393
- for `isEmpty`, inspection and further operations, but cannot be finalized as manufacturing parts.
394
-
395
- `hull` accepts Solid inputs/XYZ points or Section inputs/XY points. `components` and `simplify`
396
- also preserve either dimension. Solid components are disconnected surface shells: inward cavity
397
- shells count separately in `inspectSolid().parts` and cannot be finalized alone. Section components
398
- are filled islands that retain their holes. `inspectSolid` includes surface area, native tolerance and emptiness;
399
- `inspectSection` includes emptiness. Both return immutable native facts shared by repeated
400
- inspections of the same shape within one build, including aliases and derived resolution contexts.
401
- Transforms, Booleans and tolerance changes have their own facts. The execution clears these caches
402
- on disposal; retained facts contain no live native values.
403
-
404
- `distance(a, b)` measures the minimum separation between two nonempty solids in millimetres.
405
- It returns zero for contact or overlapping material. The kernel's search range is managed internally;
406
- there is no caller-supplied distance cutoff. Queries use native geometry and precision without
407
- extracting a triangle mesh; empty, expired and foreign-build handles reject.
408
-
409
- Installed Generators using these added host operations or Section overloads declare minimum runtime
410
- 2.1.0. Imported operations require the current host context; package metadata declares the runtime
411
- capabilities the Generator uses.
412
-
413
- `implicit` samples a finite positive-inside field within explicit bounds and grid spacing. Grid allocations and field evaluations are bounded. `components` returns same-build handles. `simplify` requires explicit millimetre tolerance and preserves native original-surface cleanup semantics.
414
-
415
- ## Prepared text and SVG
416
-
417
- Preparation shapes text once with the host's exact font service and retains immutable `VectorTextLayout`. Imported `textSection(layout, options)` consumes vector paths directly, without font shaping, fetching, or ambient providers. Paths and metrics use Y-up millimetres and a baseline origin. Empty layouts are measurable but cannot create sections. Font preparation remains optional and outside synchronous builds.
418
-
419
- `baseline: { kind: "arc", radius }` places one line on a circle centered at the XY origin, in millimetres. Alignment defaults to `"center"` at `centerAngleDegrees` (90° from +X by default); `"left"` and `"right"` place the logical start and end at that datum. Direction defaults to `"clockwise"`, giving upright top lettering at 90°; `"counterclockwise"` at 270° gives upright bottom lettering. Shaped glyphs and attached zero-advance marks rotate rigidly, preserving their proportions, kerning and ligatures. Exact ink bounds describe the placed curves; `logicalWidth` and `advance[0]` retain the unwrapped arc length including spaces. Radius must be positive and at most 10000 mm. Arcs require baseline vertical alignment and reject multiline text, logical spans of a full revolution, and glyph outlines crossing the circle's center axis. Adjacent glyph collision and fit within a particular face remain consumer policy.
420
-
421
- Install `fontkit` to use `prepareVectorFont` from `/text`, and `woff2-encoder` when decoding WOFF2. These optional peers are loaded only when preparing fonts. `sampleTextContours` from `/geometry` and `textSection` share bounded path sampling and need no font dependencies for an already prepared layout.
422
-
423
- `svgSection` imports bounded painted SVG documents; `svgPathSection` imports filled path data. Y-down coordinates convert to Y-up; nonzero/even-odd filling preserves counters. Sampling applies after transforms and unit conversion. Basic shapes, groups, affine transforms, physical units, viewBox, fills, and strokes are supported. Text must be outlined. External resources, scripts, clipping, masks, filters, and unsupported content fail contextually. Pure parsers are shared with validation and the vector editor.
424
-
425
- Zero-alpha colors and zero-opacity paint channels contribute no geometry. Positive alpha contributes
426
- the full painted region; presentation transparency does not reduce its physical area.
427
-
428
- ## Final triangle topology
429
-
430
- `analyzeTriangleMesh` validates and supplies bounds, counts, area, and signed volume in one traversal, with optional edge/component facts. Measurements, bounds, and signatures are independent immutable snapshots; external mutable buffers are inspected freshly on each call. Closed-edge checks require two oppositely wound faces per edge; they do not detect every geometric self-intersection. `createTriangleMeshSignature` adds quantized bounds, volume, topology, and a regression fingerprint, not a cryptographic digest.
37
+ ## API discovery and ownership
431
38
 
432
- Pure `validateSolidMesh(mesh, options)` adds spatial triangle tests, a single closed incident fan at
433
- every vertex, and shell containment/orientation checks, including cavity boundaries and islands.
434
- It never repairs or changes input buffers.
435
- Floating-point contacts within the selected `tolerance` reject conservatively; this is not an exact
436
- predicate or a manufacturing certification. Its fixed ceilings are 200,000 triangles and 2,000,000
437
- spatial/containment tests. `maximumTests` may lower the work ceiling. Exhaustion throws
438
- `COMPLEXITY_LIMIT` and never returns an unchecked result. All position vertices must be referenced.
39
+ | Import | Use it for |
40
+ | ------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------- |
41
+ | [`@metamesh/modeling`](https://github.com/JasonKoolman/metamesh/blob/main/apps/web/content/reference/generated/model/index.mdx) | Solids, sections, transforms, and modeling operations |
42
+ | [`@metamesh/modeling/geometry`](https://github.com/JasonKoolman/metamesh/blob/main/apps/web/content/reference/generated/geometry/index.mdx) | Pure paths, profiles, meshes, and calculations |
43
+ | [`@metamesh/modeling/engine`](https://github.com/JasonKoolman/metamesh/blob/main/apps/web/content/reference/generated/engine/index.mdx) | Initialization, builds, limits, and 3MF export |
44
+ | [`@metamesh/modeling/text`](https://github.com/JasonKoolman/metamesh/blob/main/apps/web/content/reference/generated/text/index.mdx) | Optional font preparation and vector layouts |
439
45
 
440
- `computeTriangleMeshCentroid(mesh)` returns an immutable uniform-density volume centroid in
441
- millimetres for consistently oriented closed geometry, accounting for inward cavity shells.
442
- It checks indexed closure and positive aggregate volume; use solid validation first for untrusted
443
- geometric shell arrangements.
46
+ ## Documentation
444
47
 
445
- The optional `meshTopologyMode: "optimized"` build setting runs bounded planar edge flips for either
446
- execution lane. It preserves vertices, boundaries, winding, closure, reconstructability, area, and
447
- volume. This is local connectivity cleanup, not general remeshing; source buffers never change in place.
48
+ - [Geometry concepts](https://github.com/JasonKoolman/metamesh/blob/main/apps/web/content/docs/develop/geometry-package.mdx) — modules, coordinates, resolution, and supported modeling paths.
49
+ - [Geometry recipes](https://github.com/JasonKoolman/metamesh/blob/main/apps/web/content/docs/develop/geometry-recipes.mdx) — worked examples for primitives, sweeps, lofts, and repeated parts.
50
+ - [Font preparation](https://github.com/JasonKoolman/metamesh/blob/main/apps/web/content/docs/engine/fonts.mdx) — supply exact font bytes outside Studio.
448
51
 
449
- Models above 200,000 triangles skip optimization. Unsupported or failed candidates retain their
450
- original part, as do models whose optimized buffers would exceed an execution ceiling. Equal shared
451
- mesh views reuse one optimized candidate.
52
+ The public docs are being prepared for `https://metamesh.studio/docs`. Until launch, use the
53
+ repository links above or [run Web locally](https://github.com/JasonKoolman/metamesh/blob/main/apps/web/README.md#local-use). Exact options,
54
+ constraints, and examples live in source docblocks and the API reference generated from them.
452
55
 
453
- ## Quality gates
56
+ ## Contributing
454
57
 
455
- Follow the [workspace instructions](https://github.com/JasonKoolman/metamesh/blob/main/packages/modeling/AGENTS.md#verification) for required checks. Timing and sampled process memory are diagnostics, not deterministic assertions or WASM heap measurements. The [publication contract](https://github.com/JasonKoolman/metamesh/blob/main/docs/continuous-delivery.md#public-library-publication) owns tarballs, clean standalone Node/Worker consumers, and release identity. Installed dependency size and browser bundles are measured separately; ZIP serialization and optional font dependencies load only through their capabilities.
58
+ Start with [Update the Modeling API](https://github.com/JasonKoolman/metamesh/blob/main/docs/guides/update-the-geometry-api.md) and the
59
+ [workspace instructions](https://github.com/JasonKoolman/metamesh/blob/main/packages/modeling/AGENTS.md) for checks and reference generation. Internal dependency and
60
+ execution boundaries live in [architecture](https://github.com/JasonKoolman/metamesh/blob/main/docs/architecture.md#modeling-library-and-execution).
456
61
 
457
62
  ## License
458
63
 
459
- Apache-2.0. Bundled offline artifacts also carry `THIRD_PARTY_NOTICES.md`.
64
+ Apache-2.0; see [LICENSE](https://github.com/JasonKoolman/metamesh/blob/main/packages/modeling/LICENSE) and [THIRD_PARTY_NOTICES.md](https://github.com/JasonKoolman/metamesh/blob/main/packages/modeling/THIRD_PARTY_NOTICES.md).
@@ -1,153 +1,9 @@
1
1
  # Third-party notices
2
2
 
3
- `dist/geometry-module.js` includes source from the packages listed below. The private
4
- `dist/engine/kernel/` contains exact Manifold 3.5.4 JS/WASM bytes, verified by SHA-256 during the
5
- build. Its complete Apache-2.0 license is distributed at `dist/engine/kernel/LICENSE`.
6
-
7
- ## Manifold 3.5.4 (Apache-2.0)
3
+ Modeling redistributes the Manifold kernel loader and WASM under Apache-2.0.
4
+ The complete upstream license is included at `dist/engine/kernel/LICENSE`; the build verifies
5
+ these files against the pinned upstream package.
8
6
 
9
7
  Source: https://github.com/elalish/manifold
10
8
 
11
- The package incorporates only the kernel loader and WASM, with their license. Upstream CLI,
12
- glTF utilities, examples, and their dependencies are excluded.
13
-
14
- ## polygon-clipping 0.15.7 (MIT)
15
-
16
- Copyright (c) 2018 Mike Fogel <mike@fogel.ca> - covers everything not specially attributed to
17
- others below.
18
-
19
- Copyright (c) 2016 Alexander Milevski <info@w8r.name> - covers all portions originally part of
20
- github:w8r/martinez, from which this project was forked on February 2, 2018.
21
-
22
- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
23
- associated documentation files (the "Software"), to deal in the Software without restriction,
24
- including without limitation the rights to use, copy, modify, merge, publish, distribute,
25
- sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
26
- furnished to do so, subject to the following conditions:
27
-
28
- The above copyright notice and this permission notice shall be included in all copies or
29
- substantial portions of the Software.
30
-
31
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
32
- NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
33
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
34
- DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
35
- OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
36
-
37
- ## splaytree 3.1.2 (MIT)
38
-
39
- Copyright (c) 2019 Alexander Milevski <info@w8r.name>
40
-
41
- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
42
- associated documentation files (the "Software"), to deal in the Software without restriction,
43
- including without limitation the rights to use, copy, modify, merge, publish, distribute,
44
- sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
45
- furnished to do so, subject to the following conditions:
46
-
47
- The above copyright notice and this permission notice shall be included in all copies or
48
- substantial portions of the Software.
49
-
50
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
51
- NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
52
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
53
- DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
54
- OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
55
-
56
- ## TypeScript runtime helpers in polygon-clipping 0.15.7 (Apache-2.0)
57
-
58
- Copyright (c) Microsoft Corporation. All rights reserved.
59
-
60
- Licensed under the Apache License, Version 2.0. A copy of that license is included in `LICENSE`.
61
-
62
- ## svg-pathdata 9.0.0 (MIT)
63
-
64
- Copyright © 2017 Nicolas Froidure
65
-
66
- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
67
- associated documentation files (the "Software"), to deal in the Software without restriction,
68
- including without limitation the rights to use, copy, modify, merge, publish, distribute,
69
- sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
70
- furnished to do so, subject to the following conditions:
71
-
72
- The above copyright notice and this permission notice shall be included in all copies or
73
- substantial portions of the Software.
74
-
75
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
76
- NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
77
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
78
- DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
79
- OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
80
-
81
- ## saxes 6.0.0 (ISC)
82
-
83
- Copyright (c) Contributors
84
-
85
- Permission to use, copy, modify, and/or distribute this software for any purpose with or without
86
- fee is hereby granted, provided that the above copyright notice and this permission notice appear
87
- in all copies.
88
-
89
- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
90
- SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
91
- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
92
- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
93
- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
94
- OF THIS SOFTWARE.
95
-
96
- Saxes is forked from sax, which carries the following notice:
97
-
98
- Copyright (c) Isaac Z. Schlueter and Contributors
99
-
100
- Permission to use, copy, modify, and/or distribute this software for any purpose with or without
101
- fee is hereby granted, provided that the above copyright notice and this permission notice appear
102
- in all copies.
103
-
104
- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
105
- SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
106
- AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
107
- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
108
- NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
109
- OF THIS SOFTWARE.
110
-
111
- ## xmlchars 2.2.0 (MIT)
112
-
113
- Copyright Louis-Dominique Dubeau and contributors to xmlchars
114
-
115
- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
116
- associated documentation files (the "Software"), to deal in the Software without restriction,
117
- including without limitation the rights to use, copy, modify, merge, publish, distribute,
118
- sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
119
- furnished to do so, subject to the following conditions:
120
-
121
- The above copyright notice and this permission notice shall be included in all copies or
122
- substantial portions of the Software.
123
-
124
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
125
- NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
126
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
127
- DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
128
- OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
129
-
130
- ## robust-predicates (Unlicense)
131
-
132
- The browser distribution bundled by polygon-clipping 0.15.7 contains robust-predicates code but
133
- does not identify a more specific embedded version.
134
-
135
- This is free and unencumbered software released into the public domain.
136
-
137
- Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in
138
- source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by
139
- any means.
140
-
141
- In jurisdictions that recognize copyright laws, the author or authors of this software dedicate
142
- any and all copyright interest in the software to the public domain. We make this dedication for
143
- the benefit of the public at large and to the detriment of our heirs and successors. We intend this
144
- dedication to be an overt act of relinquishment in perpetuity of all present and future rights to
145
- this software under copyright law.
146
-
147
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
148
- NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
149
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
150
- WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
151
- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
152
-
153
- For more information, please refer to <http://unlicense.org/>.
9
+ Other runtime dependencies are installed separately and retain their own licenses.
@@ -436,6 +436,39 @@ export declare interface ArcCurve3 {
436
436
  */
437
437
  export declare function computeTriangleMeshCentroid(mesh: TriangleMesh): Vec3;
438
438
 
439
+ /** One explicitly closed planar contour, independent of editor metadata and parameter contracts. */
440
+ export declare interface ContourCurve {
441
+ /** Initial anchor; coordinates may be any finite value. */
442
+ readonly start: ContourPoint;
443
+ /** Ordered chain of 2–32 segments; the final end must exactly match start. */
444
+ readonly segments: readonly ContourSegment[];
445
+ }
446
+
447
+ /** Planar X and upward Y in millimetres, or in units converted by sampling scales. */
448
+ export declare interface ContourPoint {
449
+ /** Horizontal coordinate; physical scaling does not center the contour. */
450
+ readonly x: number;
451
+ /** Upward planar coordinate. */
452
+ readonly y: number;
453
+ }
454
+
455
+ /** Straight or cubic Bézier segment in one closed planar contour. */
456
+ export declare type ContourSegment = {
457
+ /** Straight segment discriminator. */
458
+ readonly type: "line";
459
+ /** Next anchor; the final anchor repeats the curve start. */
460
+ readonly end: ContourPoint;
461
+ } | {
462
+ /** Cubic Bézier segment discriminator. */
463
+ readonly type: "cubic";
464
+ /** Outgoing control from the previous anchor. */
465
+ readonly control1: ContourPoint;
466
+ /** Incoming control at the next anchor. */
467
+ readonly control2: ContourPoint;
468
+ /** Next anchor; the final anchor repeats the curve start. */
469
+ readonly end: ContourPoint;
470
+ };
471
+
439
472
  /** One sampled component retaining contour identities and logical point correspondence. */
440
473
  export declare interface CorrespondingComponent {
441
474
  /** Stable canonical lowercase identity; unique within the containing topology or output. */
@@ -520,39 +553,6 @@ export declare interface ArcCurve3 {
520
553
  */
521
554
  export declare const cross3: (a: Vec3, b: Vec3) => Vec3;
522
555
 
523
- /** One explicitly closed planar contour, independent of editor metadata and parameter contracts. */
524
- export declare interface CrossSectionCurve {
525
- /** Initial anchor; coordinates may be any finite value. */
526
- readonly start: CrossSectionPoint;
527
- /** Ordered chain of 2–32 segments; the final end must exactly match start. */
528
- readonly segments: readonly CrossSectionSegment[];
529
- }
530
-
531
- /** Planar X and upward Y in millimetres, or in units converted by sampling scales. */
532
- export declare interface CrossSectionPoint {
533
- /** Horizontal coordinate; physical scaling does not center the contour. */
534
- readonly x: number;
535
- /** Upward planar coordinate. */
536
- readonly y: number;
537
- }
538
-
539
- /** Straight or cubic Bézier segment in one closed planar contour. */
540
- export declare type CrossSectionSegment = {
541
- /** Straight segment discriminator. */
542
- readonly type: "line";
543
- /** Next anchor; the final anchor repeats the curve start. */
544
- readonly end: CrossSectionPoint;
545
- } | {
546
- /** Cubic Bézier segment discriminator. */
547
- readonly type: "cubic";
548
- /** Outgoing control from the previous anchor. */
549
- readonly control1: CrossSectionPoint;
550
- /** Incoming control at the next anchor. */
551
- readonly control2: CrossSectionPoint;
552
- /** Next anchor; the final anchor repeats the curve start. */
553
- readonly end: CrossSectionPoint;
554
- };
555
-
556
556
  /** Planar cubic Bézier with explicit endpoint and control coordinates. */
557
557
  export declare interface CubicBezierCurve2 {
558
558
  /** Selects a cubic Bézier through two endpoints and two controls. */
@@ -1226,7 +1226,19 @@ export declare interface ArcCurve3 {
1226
1226
  readonly end: Vec3;
1227
1227
  }
1228
1228
 
1229
- /** Deterministic ordered edge subdivision or bounded geometric vertex association. */
1229
+ /**
1230
+ * Associates ordered loft contours without inferring component identities or repairing topology.
1231
+ *
1232
+ * - `direct` preserves seams and subdivides edges to a common count without dropping vertices.
1233
+ * - `reindex` applies direct subdivision, then selects the next contour's cyclic phase with the
1234
+ * least total world-space connection length.
1235
+ * - `fast-distance` preserves supplied seams and minimizes connection length over ordered closed
1236
+ * vertex associations, repeating vertices for triangular fans.
1237
+ * - `distance` runs the same ordered optimization over every cyclic phase of the next contour.
1238
+ *
1239
+ * Components and holes are paired by their existing order. Use identified profiles when semantic
1240
+ * correspondence must remain authoritative.
1241
+ */
1230
1242
  export declare type LoftCorrespondence = "direct" | "reindex" | "fast-distance" | "distance";
1231
1243
 
1232
1244
  /**
@@ -1475,39 +1487,6 @@ export declare interface ArcCurve3 {
1475
1487
  */
1476
1488
  export declare function normalizeRegion2(region: Region2, tolerance?: GeometryToleranceOptions): Region2;
1477
1489
 
1478
- /** An open radius/height outline; consumers own closure, mounting geometry, and wall policy. */
1479
- export declare interface OpenProfileCurve {
1480
- /** Initial radius/height anchor. */
1481
- readonly start: OpenProfilePoint;
1482
- /** Ordered chain of 1–31 straight or cubic segments. */
1483
- readonly segments: readonly OpenProfileSegment[];
1484
- }
1485
-
1486
- /** Radius and upward height in millimetres, or in units converted by sampling scales. */
1487
- export declare interface OpenProfilePoint {
1488
- /** Nonnegative distance from the revolution axis. */
1489
- readonly radius: number;
1490
- /** Upward axial coordinate; anchors progress strictly upward. */
1491
- readonly height: number;
1492
- }
1493
-
1494
- /** One straight or cubic segment of an open, upward-progressing radius/height outline. */
1495
- export declare type OpenProfileSegment = {
1496
- /** Straight segment discriminator. */
1497
- readonly type: "line";
1498
- /** Next strictly higher anchor. */
1499
- readonly end: OpenProfilePoint;
1500
- } | {
1501
- /** Cubic Bézier segment discriminator. */
1502
- readonly type: "cubic";
1503
- /** Outgoing control, with height between the start and second control. */
1504
- readonly control1: OpenProfilePoint;
1505
- /** Incoming control, with height between the first control and end. */
1506
- readonly control2: OpenProfilePoint;
1507
- /** Next strictly higher anchor. */
1508
- readonly end: OpenProfilePoint;
1509
- };
1510
-
1511
1490
  /**
1512
1491
  * Improves pathological planar triangulation with bounded legal diagonal flips.
1513
1492
  *
@@ -2243,7 +2222,7 @@ export declare interface ArcCurve3 {
2243
2222
  *
2244
2223
  * @example
2245
2224
  * ```ts
2246
- * const section = sampleCrossSection({
2225
+ * const section = sampleContour({
2247
2226
  * start: { x: 0, y: 0 },
2248
2227
  * segments: [
2249
2228
  * { type: "line", end: { x: 1, y: 0 } },
@@ -2253,10 +2232,10 @@ export declare interface ArcCurve3 {
2253
2232
  * }, { widthScale: 20, heightScale: 12 });
2254
2233
  * ```
2255
2234
  */
2256
- export declare function sampleCrossSection(curve: CrossSectionCurve, options?: SampleCrossSectionOptions): Loop2;
2235
+ export declare function sampleContour(curve: ContourCurve, options?: SampleContourOptions): Loop2;
2257
2236
 
2258
2237
  /** Physical scaling, subdivision policy, and output ceilings for one closed contour. */
2259
- export declare interface SampleCrossSectionOptions {
2238
+ export declare interface SampleContourOptions {
2260
2239
  /** Positive horizontal scale; normalized inputs span this width envelope. Defaults to 1. */
2261
2240
  readonly widthScale?: number;
2262
2241
  /** Positive vertical scale; normalized inputs span this height envelope. Defaults to 1. */
@@ -2365,6 +2344,16 @@ export declare interface ArcCurve3 {
2365
2344
  */
2366
2345
  export declare function sampleInternalCylindricalGearSpaceOutline(spec: CylindricalGearSpec, options?: CylindricalGearOutlineOptions): Loop2;
2367
2346
 
2347
+ /**
2348
+ * Samples the open top boundary of a finite rack in its XY pitch plane.
2349
+ *
2350
+ * X follows rack travel, Y is tooth height, and the pitch line is `Y = 0`.
2351
+ * The path begins and ends on the root line, omits duplicate adjacent points,
2352
+ * and leaves base thickness, mounting features, extrusion, and helical sweep to
2353
+ * the owning generator.
2354
+ */
2355
+ export declare function sampleRackToothPath(spec: RackPitchSpec, limits?: Partial<GeometrySamplingLimits>): Path2;
2356
+
2368
2357
  /**
2369
2358
  * Samples 1–31 line/cubic segments into a deeply immutable open [radius,height] polyline.
2370
2359
  * Anchors must progress strictly upward; nonnegative-radius cubic controls must have ordered
@@ -2380,16 +2369,16 @@ export declare interface ArcCurve3 {
2380
2369
  *
2381
2370
  * @example
2382
2371
  * ```ts
2383
- * const side = sampleOpenProfile({
2372
+ * const side = sampleSideProfile({
2384
2373
  * start: { radius: 0.5, height: 0 },
2385
2374
  * segments: [{ type: "line", end: { radius: 1, height: 1 } }],
2386
2375
  * }, { radiusScale: 12, heightScale: 30 });
2387
2376
  * ```
2388
2377
  */
2389
- export declare function sampleOpenProfile(curve: OpenProfileCurve, options?: SampleOpenProfileOptions): readonly Vec2[];
2378
+ export declare function sampleSideProfile(curve: SideProfileCurve, options?: SampleSideProfileOptions): readonly Vec2[];
2390
2379
 
2391
2380
  /** Physical scaling and bounded adaptive subdivision for an open profile. */
2392
- export declare interface SampleOpenProfileOptions {
2381
+ export declare interface SampleSideProfileOptions {
2393
2382
  /** Positive radius scale; use the desired maximum radius for normalized coordinates. Defaults to 1. */
2394
2383
  readonly radiusScale?: number;
2395
2384
  /** Positive height scale; use the desired total height for normalized coordinates. Defaults to 1. */
@@ -2402,16 +2391,6 @@ export declare interface ArcCurve3 {
2402
2391
  readonly limits?: Partial<GeometrySamplingLimits>;
2403
2392
  }
2404
2393
 
2405
- /**
2406
- * Samples the open top boundary of a finite rack in its XY pitch plane.
2407
- *
2408
- * X follows rack travel, Y is tooth height, and the pitch line is `Y = 0`.
2409
- * The path begins and ends on the root line, omits duplicate adjacent points,
2410
- * and leaves base thickness, mounting features, extrusion, and helical sweep to
2411
- * the owning generator.
2412
- */
2413
- export declare function sampleRackToothPath(spec: RackPitchSpec, limits?: Partial<GeometrySamplingLimits>): Path2;
2414
-
2415
2394
  /**
2416
2395
  * Samples a prepared text layout without shaping text again or loading a font.
2417
2396
  *
@@ -2449,6 +2428,39 @@ export declare interface ArcCurve3 {
2449
2428
  */
2450
2429
  export declare function scaleTransform3(factors: Vec3): Transform3;
2451
2430
 
2431
+ /** An open radius/height outline; consumers own closure, mounting geometry, and wall policy. */
2432
+ export declare interface SideProfileCurve {
2433
+ /** Initial radius/height anchor. */
2434
+ readonly start: SideProfilePoint;
2435
+ /** Ordered chain of 1–31 straight or cubic segments. */
2436
+ readonly segments: readonly SideProfileSegment[];
2437
+ }
2438
+
2439
+ /** Radius and upward height in millimetres, or in units converted by sampling scales. */
2440
+ export declare interface SideProfilePoint {
2441
+ /** Nonnegative distance from the revolution axis. */
2442
+ readonly radius: number;
2443
+ /** Upward axial coordinate; anchors progress strictly upward. */
2444
+ readonly height: number;
2445
+ }
2446
+
2447
+ /** One straight or cubic segment of an open, upward-progressing radius/height outline. */
2448
+ export declare type SideProfileSegment = {
2449
+ /** Straight segment discriminator. */
2450
+ readonly type: "line";
2451
+ /** Next strictly higher anchor. */
2452
+ readonly end: SideProfilePoint;
2453
+ } | {
2454
+ /** Cubic Bézier segment discriminator. */
2455
+ readonly type: "cubic";
2456
+ /** Outgoing control, with height between the start and second control. */
2457
+ readonly control1: SideProfilePoint;
2458
+ /** Incoming control, with height between the first control and end. */
2459
+ readonly control2: SideProfilePoint;
2460
+ /** Next strictly higher anchor. */
2461
+ readonly end: SideProfilePoint;
2462
+ };
2463
+
2452
2464
  /** Returns the signed area of a non-duplicated loop; positive means CCW. */
2453
2465
  export declare function signedAreaLoop2(loop: Loop2): number;
2454
2466
 
@@ -588,7 +588,19 @@ export declare type LinearPatternOptions = {
588
588
  /** Host-bound loft operation. Valid only during a synchronous build. */
589
589
  export declare const loft: ModelingContext["loft"];
590
590
 
591
- /** Deterministic ordered edge subdivision or bounded geometric vertex association. */
591
+ /**
592
+ * Associates ordered loft contours without inferring component identities or repairing topology.
593
+ *
594
+ * - `direct` preserves seams and subdivides edges to a common count without dropping vertices.
595
+ * - `reindex` applies direct subdivision, then selects the next contour's cyclic phase with the
596
+ * least total world-space connection length.
597
+ * - `fast-distance` preserves supplied seams and minimizes connection length over ordered closed
598
+ * vertex associations, repeating vertices for triangular fans.
599
+ * - `distance` runs the same ordered optimization over every cyclic phase of the next contour.
600
+ *
601
+ * Components and holes are paired by their existing order. Use identified profiles when semantic
602
+ * correspondence must remain authoritative.
603
+ */
592
604
  export declare type LoftCorrespondence = "direct" | "reindex" | "fast-distance" | "distance";
593
605
 
594
606
  /**
@@ -27,8 +27,8 @@ export { sampleHelix } from "./paths/helix.js";
27
27
  export { cumulativePathLengths, pathLength } from "./paths/path.js";
28
28
  export { distributeAlongPath, roundClosedPolylinePath, } from "./paths/planar.js";
29
29
  export { circleLoop, rectangleLoop, regularPolygonLoop, starLoop, } from "./profiles/profiles.js";
30
- export { sampleOpenProfile, } from "./profiles/open-profile.js";
31
- export { sampleCrossSection, } from "./profiles/cross-section.js";
30
+ export { sampleSideProfile, } from "./profiles/side-profile.js";
31
+ export { sampleContour, } from "./profiles/contour.js";
32
32
  export { ellipseLoop } from "./profiles/shapes.js";
33
33
  export { rectangleProfile, } from "./profiles/rectangle.js";
34
34
  export { loopContainsPoint, normalizeLoop2, normalizePolygon2, normalizeRegion2, regionArea, regionBounds, signedAreaLoop2, } from "./regions.js";
@@ -3,7 +3,7 @@ import { GeometryError } from "../errors.js";
3
3
  import { normalizeLoop2 } from "../regions.js";
4
4
  import { resolveGeometrySamplingLimits } from "../sampling-limits.js";
5
5
  import { requireProfileVertexCount } from "./validation.js";
6
- const OPERATION = "sampleCrossSection";
6
+ const OPERATION = "sampleContour";
7
7
  function invalid(message) {
8
8
  throw new GeometryError("INVALID_ARGUMENT", message, { operation: OPERATION });
9
9
  }
@@ -22,7 +22,7 @@ function invalid(message) {
22
22
  *
23
23
  * @example
24
24
  * ```ts
25
- * const section = sampleCrossSection({
25
+ * const section = sampleContour({
26
26
  * start: { x: 0, y: 0 },
27
27
  * segments: [
28
28
  * { type: "line", end: { x: 1, y: 0 } },
@@ -32,7 +32,7 @@ function invalid(message) {
32
32
  * }, { widthScale: 20, heightScale: 12 });
33
33
  * ```
34
34
  */
35
- export function sampleCrossSection(curve, options = {}) {
35
+ export function sampleContour(curve, options = {}) {
36
36
  const widthScale = options.widthScale ?? 1;
37
37
  const heightScale = options.heightScale ?? 1;
38
38
  if (![widthScale, heightScale].every((scale) => Number.isFinite(scale) && scale > 0))
@@ -2,7 +2,7 @@ import { GeometryError } from "../errors.js";
2
2
  import { resolveGeometryResolution } from "../resolution.js";
3
3
  import { resolveGeometrySamplingLimits } from "../sampling-limits.js";
4
4
  import { requireProfileVertexCount } from "./validation.js";
5
- const OPERATION = "sampleOpenProfile";
5
+ const OPERATION = "sampleSideProfile";
6
6
  const MAX_SEGMENTS = 31;
7
7
  const MAX_DEPTH = 24;
8
8
  function invalid(message) {
@@ -35,13 +35,13 @@ function distanceToChord(point, start, end) {
35
35
  *
36
36
  * @example
37
37
  * ```ts
38
- * const side = sampleOpenProfile({
38
+ * const side = sampleSideProfile({
39
39
  * start: { radius: 0.5, height: 0 },
40
40
  * segments: [{ type: "line", end: { radius: 1, height: 1 } }],
41
41
  * }, { radiusScale: 12, heightScale: 30 });
42
42
  * ```
43
43
  */
44
- export function sampleOpenProfile(curve, options = {}) {
44
+ export function sampleSideProfile(curve, options = {}) {
45
45
  const radiusScale = options.radiusScale ?? 1;
46
46
  const heightScale = options.heightScale ?? 1;
47
47
  if (![radiusScale, heightScale].every((scale) => Number.isFinite(scale) && scale > 0)) {
@@ -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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metamesh/modeling",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "Scoped solid modeling and pure geometry foundations for Node and browsers.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {