partforge 0.143.0 → 0.143.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "partforge",
3
- "version": "0.143.0",
3
+ "version": "0.143.1",
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",
@@ -51,6 +51,29 @@ function makeProbe(onCall) {
51
51
  toSTL: () => new ArrayBuffer(0),
52
52
  toIndexedMesh: () => ({ positions: new Float32Array(9), indices: new Uint32Array(3) }),
53
53
  };
54
+ // Solid's optional mesh-topology queries return numbers/booleans, not a handle.
55
+ Object.assign(solidQueries, { genus: () => 0, isEmpty: () => false });
56
+ // Shape2D ops that return DATA rather than another Shape2D. Without an entry here an
57
+ // op falls through to the chaining branch and hands back the Shape2D handle itself,
58
+ // so ordinary array/number use of the result (`.corners().map(…)`, `.area() * 2`)
59
+ // was recorded as an unknown Shape2D op and failed lint as `unknown-solid-op`.
60
+ // `regions()` returns live Shape2Ds, so its dummy is an array holding the handle.
61
+ // The dummy region is a unit square in the contour IR (closed: last point = first).
62
+ const square = [[0, 0], [1, 0], [1, 1], [0, 1], [0, 0]];
63
+ const shape2dQueries = {
64
+ ...solidQueries,
65
+ isEmpty: () => false,
66
+ area: () => 1,
67
+ boundingBox: () => ({ min: [0, 0], max: [1, 1], center: [0.5, 0.5], size: [1, 1] }),
68
+ toRegions: () => [{ outer: square.map((p) => [...p]), holes: [] }],
69
+ simple: () => ({ outer: square.map((p) => [...p]), holes: [] }),
70
+ toContours: () => [{ outer: { start: [0, 0], segments: square.slice(1).map((p) => ({ to: [...p] })) }, holes: [] }],
71
+ corners: () => [0, 1, 2, 3].map((i) => ({
72
+ index: i, position: i, point: [...square[i]], interiorAngleDeg: 90, convex: true, segTypes: ["line", "line"],
73
+ })),
74
+ contains: () => false,
75
+ regions: () => [shape2d],
76
+ };
54
77
  const kernelQueries = {
55
78
  toSTEP: () => Promise.resolve(new ArrayBuffer(0)),
56
79
  cleanup: () => {},
@@ -82,10 +105,8 @@ function makeProbe(onCall) {
82
105
  },
83
106
  });
84
107
 
85
- // Shape2D handles reuse solidQueries: boundingBox/volume/… dummies chain the same
86
- // either way, and a 3-component bbox is as good a dummy as a 2-component one.
87
108
  const proxy = opProxy(solidQueries, "solid", () => proxy); // a solid handle: every op chains back to itself
88
- const shape2d = opProxy(solidQueries, "shape2d",
109
+ const shape2d = opProxy(shape2dQueries, "shape2d",
89
110
  (key) => (SOLID_YIELDING_SHAPE2D_OPS.has(key) ? proxy : shape2d));
90
111
  const kernel = opProxy(kernelQueries, "kernel", // factory ops (cylinder/box/prism/…) return a solid
91
112
  (key) => (SHAPE2D_YIELDING_KERNEL_OPS.has(key) ? shape2d : proxy));
@@ -10,11 +10,21 @@ import { parse3MF } from "./geometry/threemf-parse.js";
10
10
 
11
11
  const EXT = { step: "step", stp: "step", stl: "stl", "3mf": "3mf" };
12
12
 
13
+ // Font files declared as an import. Without this a TTF fell through to the STL
14
+ // fallback below and failed as "binary STL truncated: header says 3848966777
15
+ // triangles" — an error about a mesh the author never meant (cloud feedback #148).
16
+ const FONT_EXT = new Set(["ttf", "otf", "woff", "woff2"]);
17
+ const FONT_MAGIC = ["\x00\x01\x00\x00", "OTTO", "true", "ttcf", "wOFF", "wOF2"];
18
+
13
19
  export function detectFormat(source, bytes) {
14
20
  const path = source instanceof URL ? source.pathname : typeof source === "string" ? source.split("?")[0] : null;
15
21
  const ext = path?.match(/\.([A-Za-z0-9]+)$/)?.[1]?.toLowerCase();
16
- if (ext && EXT[ext]) return EXT[ext];
17
22
  const u8 = bytes instanceof ArrayBuffer ? new Uint8Array(bytes) : bytes;
23
+ const magic = u8 && u8.length >= 4 ? String.fromCharCode(...u8.slice(0, 4)) : "";
24
+ if ((ext && FONT_EXT.has(ext)) || FONT_MAGIC.includes(magic)) {
25
+ throw new Error(`a font file${path ? ` ("${path}")` : ""} is declared under \`imports\` — declare it under \`fonts\` and use it with text2d`);
26
+ }
27
+ if (ext && EXT[ext]) return EXT[ext];
18
28
  if (u8 && u8.length > 0) {
19
29
  const head = String.fromCharCode(...u8.slice(0, 64));
20
30
  if (head.startsWith("ISO-10303-21")) return "step";