partforge 0.169.1 → 0.170.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/README.md CHANGED
@@ -204,7 +204,9 @@ and projection and keep the file's own framing; a carried render mode and
204
204
  environment still apply) and `renderMode: "cad" | "realistic"` (the opening
205
205
  mode, over any carried one; it can change afterwards), and `autostart: false`
206
206
  (0.169.0+: the animation or turntable waits at its first frame until
207
- `handle.motion.start()`). An unknown option key
207
+ `handle.motion.start()`), and `edgeWidth` / `edgeOpacity` (0.170.0+: the CAD
208
+ edge lines' width in CSS pixels, default 1, and opacity, default 1 — a small
209
+ card reads the default lines as heavy). An unknown option key
208
210
  throws. A baked animation always plays as authored — it
209
211
  loops, or plays once and holds its end pose. Every handle carries `drawn`, which
210
212
  resolves after the first frame showing the meshes, so a host can cross-fade from
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "partforge",
3
- "version": "0.169.1",
3
+ "version": "0.170.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",
@@ -81,6 +81,10 @@
81
81
  // `drawn` resolves on a frame showing exactly that pose, so a host can
82
82
  // cross-fade from a still of it and then say go. A waiting animation's play
83
83
  // button reads Play.
84
+ // - `edgeWidth` (0.170.0; CSS px, 0 < w <= 8, default 1) and `edgeOpacity`
85
+ // (0.170.0; 0 < o <= 1, default 1) — the CAD feature edge lines' thickness
86
+ // and strength. A small preview card reads the default 1 px lines as heavy;
87
+ // 0.5 px is one device pixel on a 2x screen. Fixed for the mount.
84
88
  // Any other key throws (an option this version does not know is a host bug).
85
89
  //
86
90
  // `handle.motion` (0.169.0; null before ready and once disposed, present for
@@ -136,10 +140,16 @@ function flag(name, value, fallback) {
136
140
  const KNOWN_OPTIONS = new Set([
137
141
  "glb", "elements", "viewerState", "animate",
138
142
  "chrome", "background", "contactShadow", "zoom", "pan", "turntable", "fill", "camera", "renderMode",
139
- "autostart",
143
+ "autostart", "edgeWidth", "edgeOpacity",
140
144
  ]);
141
145
 
142
- function presentation({ chrome, background, contactShadow, zoom, pan, turntable, fill, camera, renderMode, autostart }) {
146
+ function presentation({ chrome, background, contactShadow, zoom, pan, turntable, fill, camera, renderMode, autostart, edgeWidth, edgeOpacity }) {
147
+ if (edgeWidth !== undefined && !(typeof edgeWidth === "number" && edgeWidth > 0 && edgeWidth <= 8)) {
148
+ throw new TypeError("mountPreview: edgeWidth must be a number of CSS pixels above 0 and at most 8");
149
+ }
150
+ if (edgeOpacity !== undefined && !(typeof edgeOpacity === "number" && edgeOpacity > 0 && edgeOpacity <= 1)) {
151
+ throw new TypeError("mountPreview: edgeOpacity must be a number above 0 and at most 1");
152
+ }
143
153
  if (fill !== undefined && !(typeof fill === "number" && fill > 0 && fill <= 1)) {
144
154
  throw new TypeError("mountPreview: fill must be a number above 0 and at most 1");
145
155
  }
@@ -157,6 +167,8 @@ function presentation({ chrome, background, contactShadow, zoom, pan, turntable,
157
167
  defaultCamera: oneOf("camera", camera, ["carry", "default"], "carry") === "default",
158
168
  renderMode: oneOf("renderMode", renderMode, ["cad", "realistic"], null),
159
169
  autostart: flag("autostart", autostart, true),
170
+ edgeWidth: edgeWidth ?? 1,
171
+ edgeOpacity: edgeOpacity ?? 1,
160
172
  };
161
173
  }
162
174
 
@@ -280,6 +292,7 @@ export function mountPreview(container, options = {}) {
280
292
  transparent: opts.transparent, contactShadow: opts.contactShadow, zoom: opts.zoom, pan: opts.pan,
281
293
  // A turning model is framed swept a full turn, so no angle of it clips.
282
294
  frameFill: opts.fill, frameSwept: sweeps,
295
+ edgeWidth: opts.edgeWidth, edgeOpacity: opts.edgeOpacity,
283
296
  });
284
297
  cleanups.push(() => viewer.dispose());
285
298
  let tooltip = null, toolbar = null, viewStyle = null;
@@ -176,8 +176,12 @@ export function captureCurrentFromScene(
176
176
  // vertical instead (a turntable never clips).
177
177
  // Unset keeps the fixed r·2.6 + 6 distance. mountPreview's presentation
178
178
  // options are the caller.
179
+ // `edgeWidth` (CSS px, default 1) and `edgeOpacity` (0 < o <= 1, default 1)
180
+ // style the CAD feature edge lines; a small preview reads them as heavy at the
181
+ // default. Fades multiply into the opacity rather than replacing it.
179
182
  export function createViewer(container, part, {
180
183
  transparent = false, contactShadow = false, zoom = true, pan = true, frameFill = null, frameSwept = false,
184
+ edgeWidth = 1, edgeOpacity = 1,
181
185
  } = {}) {
182
186
  const names = Object.keys(part.parts);
183
187
  // Membership for host-supplied names. subMesh, cadMats and part.parts are
@@ -328,7 +332,9 @@ export function createViewer(container, part, {
328
332
 
329
333
  // CAD-style feature edge lines (anti-aliased "fat" lines), one per sub-part.
330
334
  const EDGE_ANGLE = 35; // deg — last-ditch threshold for payloads with no kernel edge data
331
- const lineMaterial = new LineMaterial({ color: THEME.dark.line, linewidth: 1.0 }); // ~10% lighter, 1 px
335
+ const lineMaterial = new LineMaterial({ // ~10% lighter, 1 px by default
336
+ color: THEME.dark.line, linewidth: edgeWidth, transparent: edgeOpacity < 1, opacity: edgeOpacity,
337
+ });
332
338
  lineMaterial.resolution.set(1, 1); // real size set by resize() below
333
339
  const subLines = Object.fromEntries(
334
340
  names.map((n) => [n, new LineSegments2(new LineSegmentsGeometry(), lineMaterial)])
@@ -466,7 +472,7 @@ export function createViewer(container, part, {
466
472
  mesh.material = fm;
467
473
  const flm = fadeLineMatFor(name);
468
474
  const so = effectiveDisplayOf(name).opacity ?? 1;
469
- flm.opacity = so > 0 ? fade / so : 0; // lines fade by the multiplier alone
475
+ flm.opacity = so > 0 ? (fade / so) * edgeOpacity : 0; // lines fade by the multiplier alone
470
476
  lines.material = flm;
471
477
  }
472
478
  mesh.visible = meshVisible;
@@ -2560,7 +2566,7 @@ export function createViewer(container, part, {
2560
2566
  if (lines.material === fadeLineMats.get(n)) lines.material = lineMaterial;
2561
2567
  } else {
2562
2568
  const f = fadeMatFor(n); f.opacity = fade; mesh.material = f;
2563
- const fl = fadeLineMatFor(n); fl.opacity = staticOpacity > 0 ? fade / staticOpacity : 0; lines.material = fl;
2569
+ const fl = fadeLineMatFor(n); fl.opacity = staticOpacity > 0 ? (fade / staticOpacity) * edgeOpacity : 0; lines.material = fl;
2564
2570
  }
2565
2571
  mesh.visible = meshVisible; lines.visible = linesVisible && meshVisible;
2566
2572
  },
package/types/index.d.ts CHANGED
@@ -830,6 +830,13 @@ export interface MountPreviewOptions {
830
830
  * angle 0) until `motion.start()`. `drawn` resolves on a frame showing that pose.
831
831
  */
832
832
  autostart?: boolean;
833
+ /**
834
+ * 0.170.0+: the CAD feature edge lines' width in CSS pixels, 0 < w <= 8 (default 1).
835
+ * A small preview card reads the default as heavy; 0.5 is one device pixel at 2x.
836
+ */
837
+ edgeWidth?: number;
838
+ /** 0.170.0+: the CAD feature edge lines' opacity, 0 < o <= 1 (default 1). */
839
+ edgeOpacity?: number;
833
840
  /**
834
841
  * 0 < fill <= 1 (0.168.0+): the opening framing (and the viewer's reframe) puts the
835
842
  * camera, along the opening view direction, so the model's bounding box as projected