partforge 0.131.0 → 0.132.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.
@@ -1501,7 +1501,8 @@ in STEP. Use these for every round outline that shows or fits:
1501
1501
  ring is a region with a hole: extrude `{ outer, holes }` or cut an inner cylinder from an outer
1502
1502
  one), `pieProfile(tipR,arcDeg)` (a sector from the origin), `slotProfile(length,r)` (overall
1503
1503
  length = `length + 2r`; `length` 0 is a circle), `roundedRectProfile(w,h,r)` (r clamped to
1504
- min(w,h)/2), `roundedProfile(points,r)` (round any polygon's corners), and `pathProfile()`.
1504
+ min(w,h)/2), `circleProfile(r, center?)` (a circle), `roundedProfile(points,r)` (round any
1505
+ polygon's corners), and `pathProfile()`.
1505
1506
 
1506
1507
  *Straight edges* — CCW point arrays, built and exported exactly as written:
1507
1508
  `regularPolygon(n,r,{flat})`, `hexPolygon(r)`, `starPolygon(points,outerR,innerR)`,
@@ -1509,14 +1510,13 @@ min(w,h)/2), `roundedProfile(points,r)` (round any polygon's corners), and `path
1509
1510
  `circlePolygon(r, center?, segs = 48)` — a circle as a fixed point list, for a deliberately
1510
1511
  faceted circle or point math of your own (mapping, indexing, spreading its points).
1511
1512
 
1512
- *Circles.* `circleProfile(r, center?)` is the circle to hand to an op — `prism`, an `extrude`
1513
- outline or hole, `k.shape2d`, `revolve`, `hull`, `loft`, `sweep`. **It is in transition**:
1514
- today it returns the same 48 points as `circlePolygon`, and a coming release turns it into an
1515
- exact curve like the other `*Profile` helpers. So pass it straight to an op and **never map,
1516
- index or spread its result — use `circlePolygon` for that**. Until then, an exact circle in a
1517
- 2-D profile is `slotProfile(0, r)`. For a round SOLID use `k.cylinder` (a prism of 48 points is
1518
- a 48-sided prism, never refined at export), and **use `k.torus({ rMajor, rMinor })` for a
1519
- torus** — the primitive keeps real TORUS faces in STEP.
1513
+ *Circles.* `circleProfile(r, center?)` is an exact circle, like the other `*Profile` helpers:
1514
+ hand it to any op — `prism`, an `extrude` outline or hole, `k.shape2d`, `revolve`, `hull`,
1515
+ `loft`, `sweep` (sweep and `offsetPolygon` sample it at 48 points). It is a path contour, not
1516
+ an array, so for point math of your own use `circlePolygon`. (Before partforge 0.132 it
1517
+ returned `circlePolygon`'s points; it takes no `segs`.) For a round SOLID use `k.cylinder`,
1518
+ and **use `k.torus({ rMajor, rMinor })` for a torus** — the primitive keeps real TORUS faces
1519
+ in STEP.
1520
1520
 
1521
1521
  **Patterns** (return `Solid[]` — feed to `k.union(...)` for features or `s.cutAll(...)` for holes):
1522
1522
  `linearPattern(solid, count, [dx,dy,dz])`, `circularPattern(solid, count, { center, axis, angle, rotateCopies })`.
@@ -826,8 +826,8 @@ between the Manifold preview and the OCCT STEP export.
826
826
  ## profile-sampled-arc
827
827
 
828
828
  - **Symptom:** The build succeeds and its result carries a warning like `prism: profile traces an arc in straight facets (radius ≈ 30.0 mm, 9.0° per facet, up to 0.09 mm inside the true curve) — a point list is built and exported exactly as written, so a print shows those facets. …` (also `extrude: profile …`, `revolve: profile …`, `shape2d: profile …`). A printed part with that outline shows flat spots, and a fit built on it (a bayonet lug in its track, a pin in a slot) binds or rattles. Identical text on both backends.
829
- - **Cause:** *(partforge ≥ 0.131.)* A point list is built and exported exactly as written: only curves the kernel knows about — primitives, a revolve's sweep, and the arcs of a path contour or `Shape2D` — are faceted per quality tier and refined at export. An arc sampled into a point list keeps the facets it was sampled with, however fine the export. The round `*Polygon` helpers sample at 32 per circle (`ringSectorPolygon`, `slotPolygon`, `piePolygon`, and `roundedRectPolygon`'s corners), `filletPolygon` at 8 segments per corner whatever its angle, `circlePolygon` (and, until it becomes an exact curve, `circleProfile`) at 48, and a hand-written `Math.cos` loop at whatever it was given. The kernel reports the worst run in a profile: three or more facets turning by the same amount, at most 20° each, whose chord sits more than 0.05 mm inside the true arc. The end facets of a run may be shorter (an offset trims them), and coordinates may carry about 0.01 mm of rounding. Hexagons and other deliberate polygons, small holes, and finely sampled loops stay quiet, as does an arc of only two facets, which can't be told from a bend. It checks the outlines the author hands to `prism`, `extrude`, `revolve` and `k.shape2d`, including a bevelled `extrude`'s. It doesn't check what the kernel samples itself (a bevel's rim, a `screwSweep`'s section, a `hull`), or `loft` and `sweep`, which sample curve rings at their own fixed detail. At most three lines are reported per build.
830
- - **Fix:** Rebuild the outline from exact curves: `ringSectorProfile`, `slotProfile`, `pieProfile` and `roundedRectProfile` trace the same outlines as their `*Polygon` namesakes, `roundedProfile` rounds any polygon's corners, and `pathProfile(start).arcTo(to, via)` builds anything else — all are path contours the kernel facets per tier and refines at export (and OCCT keeps as true circles in STEP). There is no export-resolution setting to raise instead. For an exact circle in a 2-D profile use `slotProfile(0, r)`; for a round solid, `k.cylinder`. Sampling more points by hand only moves the facets, and it keeps them frozen.
829
+ - **Cause:** *(partforge ≥ 0.131.)* A point list is built and exported exactly as written: only curves the kernel knows about — primitives, a revolve's sweep, and the arcs of a path contour or `Shape2D` — are faceted per quality tier and refined at export. An arc sampled into a point list keeps the facets it was sampled with, however fine the export. The round `*Polygon` helpers sample at 32 per circle (`ringSectorPolygon`, `slotPolygon`, `piePolygon`, and `roundedRectPolygon`'s corners), `filletPolygon` at 8 segments per corner whatever its angle, `circlePolygon` at 48 (and `circleProfile`, before partforge 0.132), and a hand-written `Math.cos` loop at whatever it was given. The kernel reports the worst run in a profile: three or more facets turning by the same amount, at most 20° each, whose chord sits more than 0.05 mm inside the true arc. The end facets of a run may be shorter (an offset trims them), and coordinates may carry about 0.01 mm of rounding. Hexagons and other deliberate polygons, small holes, and finely sampled loops stay quiet, as does an arc of only two facets, which can't be told from a bend. It checks the outlines the author hands to `prism`, `extrude`, `revolve` and `k.shape2d`, including a bevelled `extrude`'s. It doesn't check what the kernel samples itself (a bevel's rim, a `screwSweep`'s section, a `hull`), or `loft` and `sweep`, which sample curve rings at their own fixed detail. At most three lines are reported per build.
830
+ - **Fix:** Rebuild the outline from exact curves: `ringSectorProfile`, `slotProfile`, `pieProfile`, `roundedRectProfile` and `circleProfile` trace the same outlines as their `*Polygon` namesakes, `roundedProfile` rounds any polygon's corners, and `pathProfile(start).arcTo(to, via)` builds anything else — all are path contours the kernel facets per tier and refines at export (and OCCT keeps as true circles in STEP). There is no export-resolution setting to raise instead. For an exact circle use `circleProfile`; for a round solid, `k.cylinder`. Sampling more points by hand only moves the facets, and it keeps them frozen.
831
831
 
832
832
  ## export-kernel-out-of-memory
833
833
 
@@ -722,9 +722,8 @@ case appears; add its deterministic fixture and establish the source-domain trut
722
722
  `roundedRectProfile`, `circlePolygon`) are pure functions from numbers to plain CCW point
723
723
  lists or arc profiles — *data already in this contract's input format*, with no kernel
724
724
  dependency at all. The naming rule: `*Profile` builders return path contours with symbolic
725
- arcs, `*Polygon` builders return point lists. `circleProfile` is in transition: it still
726
- returns `circlePolygon`'s 48 points, and becomes a path contour in 0.132, once stored parts
727
- that do point math on it have been migrated to `circlePolygon`. The **solid patterns** (`linearPattern`, `circularPattern`) take a
725
+ arcs, `*Polygon` builders return point lists. (`circleProfile` returned `circlePolygon`'s 48
726
+ points until 0.132.) The **solid patterns** (`linearPattern`, `circularPattern`) take a
728
727
  `Solid` and call only ops from the tables above (`clone`/`translate`/`rotate`/
729
728
  `boundingBox`). The **profile transform** (`offsetPolygon`) takes a point list or
730
729
  `{outer, holes}` region and grows or shrinks it by a delta in mm — printer-clearance
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "partforge",
3
- "version": "0.131.0",
3
+ "version": "0.132.0",
4
4
  "description": "Turn a declarative part definition into a parametric-CAD web app (three.js + Manifold/Replicad). Requires a Vite-based consumer.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -412,15 +412,24 @@ export function circlePolygon(r, center = [0, 0], segs = 48) {
412
412
  return pts;
413
413
  }
414
414
 
415
- // A circle for passing to a kernel op (prism, extrude outline or hole, shape2d, revolve,
416
- // hull, loft, sweep). TRANSITIONAL: today it returns exactly circlePolygon's 48 points;
417
- // partforge 0.132 turns it into an exact curve like the other *Profile helpers. Stored
418
- // parts that do point math on it are migrated to circlePolygon before that release
419
- // (see docs/superpowers/specs/2026-09-28-curve-profile-helpers-design.md), so new code
420
- // should never map, index or spread its result — use circlePolygon for that.
421
- export function circleProfile(r, center = [0, 0], segs = 48) {
415
+ // An exact circle of radius r centred at [cx, cy]: a path contour of two three-point
416
+ // semicircles, starting at angle 0 like circlePolygon's points. The kernel facets it per
417
+ // quality tier (finer at export) and OCCT keeps a true CIRCLE; sweep and offsetPolygon,
418
+ // which take point lists, sample it at 48 per circle (contourToPoints) — the density it
419
+ // had as a point list. Until partforge 0.132 this returned circlePolygon's 48 points;
420
+ // stored parts that did point math on it were migrated to circlePolygon first. A third
421
+ // `segs` argument belonged to the point list, so it is refused by name rather than
422
+ // silently ignored.
423
+ export function circleProfile(r, center = [0, 0], ...rest) {
422
424
  if (!(r > 0)) throw new Error("circleProfile: r must be > 0");
423
- return circlePolygon(r, center, segs);
425
+ if (rest.length > 0 && rest[0] !== undefined)
426
+ throw new Error("circleProfile: an exact circle takes no segs — use circlePolygon(r, center, segs) for a faceted circle");
427
+ const [cx, cy] = center;
428
+ const start = [cx + r, cy];
429
+ return contourFrom(start)
430
+ .arc([cx - r, cy], [cx, cy + r])
431
+ .arc([cx + r, cy], [cx, cy - r])
432
+ .close();
424
433
  }
425
434
 
426
435
  // --- offsetPolygon ---------------------------------------------------------
@@ -100,7 +100,7 @@ const NOISE_MM = 0.01;
100
100
  const SAMPLED_ARC_COACH =
101
101
  "a point list is built and exported exactly as written, so a print shows those facets. " +
102
102
  "Build curves with the *Profile helpers (ringSectorProfile, slotProfile, pieProfile, " +
103
- "roundedRectProfile, roundedProfile) or pathProfile().arcTo(…) — the kernel facets those, finer at export.";
103
+ "roundedRectProfile, circleProfile, roundedProfile) or pathProfile().arcTo(…) — the kernel facets those, finer at export.";
104
104
 
105
105
  // The worst sampled-arc run in one closed point ring, or null.
106
106
  // → { r, stepDeg, sag } for the run with the largest chord sagitta.
@@ -4,11 +4,10 @@
4
4
  // • cutAll — four corner bolt holes drilled in one batch
5
5
  // • cut — a central bore
6
6
  // • offset — an optional print-clearance grow with rounded corners
7
- // The rounded corners and the clip disc are exact arcs (*Profile helpers; slotProfile(0, r)
8
- // is an exact circle), which the kernel refines at export. The small bolt holes use
9
- // circleProfile's fixed 48-gon, within 0.05 mm of round at this size.
7
+ // The rounded corners, the clip disc and the holes are exact arcs (*Profile helpers), so
8
+ // the whole outline stays curve-exact and the kernel refines it at export.
10
9
  // Open /bracket.html after `npm run dev`.
11
- import { roundedRectProfile, slotProfile, circleProfile } from "partforge/geometry";
10
+ import { roundedRectProfile, circleProfile } from "partforge/geometry";
12
11
 
13
12
  export default {
14
13
  meta: { title: "Cross bracket", units: "mm", background: 0x15181d, environment: "workshop" },
@@ -65,7 +64,7 @@ export default {
65
64
  const barH = k.shape2d(roundedRectProfile(p.span, p.bar, p.corner));
66
65
  const barV = k.shape2d(roundedRectProfile(p.bar, p.span, p.corner));
67
66
  let plate = barH.union(barV); // union
68
- if (p.clip) plate = plate.intersect(k.shape2d(slotProfile(0, p.span / 2))); // intersect (an exact disc)
67
+ if (p.clip) plate = plate.intersect(k.shape2d(circleProfile(p.span / 2))); // intersect
69
68
  const d = p.span / 2 - p.inset;
70
69
  const holes = [[d, d], [-d, d], [d, -d], [-d, -d]].map((c) => circleProfile(p.hole_d / 2, c));
71
70
  plate = plate.cutAll(holes); // batch cut
@@ -71,12 +71,12 @@ export function ringSectorPolygon(innerR: number, outerR: number, arcDeg: number
71
71
  export function circlePolygon(r: number, center?: Point2, segs?: number): PointsContour;
72
72
 
73
73
  /**
74
- * A circle for passing to a kernel op (prism, extrude outline or hole, shape2d, revolve,
75
- * hull, loft, sweep). Transitional: today it returns exactly `circlePolygon`'s 48 points,
76
- * and partforge 0.132 turns it into an exact curve like the other `*Profile` helpers —
77
- * so never map, index or spread its result; use `circlePolygon` for point math.
74
+ * An exact circle of radius `r` centred at `center`: a path contour of two semicircles
75
+ * starting at angle 0, which the kernel facets per tier (finer at export) and OCCT keeps as
76
+ * a true circle. Not an array — use `circlePolygon` for point math. (Until 0.132 this
77
+ * returned `circlePolygon`'s 48 points; it takes no `segs`.)
78
78
  */
79
- export function circleProfile(r: number, center?: Point2, segs?: number): PointsContour;
79
+ export function circleProfile(r: number, center?: Point2): ArcContour;
80
80
 
81
81
  /**
82
82
  * Per-corner rounding geometry shared by `filletPolygon` and `roundedProfile`: