partforge 0.149.0 → 0.149.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.
@@ -391,7 +391,9 @@ meta: { title: "…", units: "mm", environment: "studio" },
391
391
  treated as declaring one.
392
392
  - `color` — the base colour (`0xRRGGBB`). With a preset it is the TINT. Presets
393
393
  marked tintable are normally coloured this way (anodizing, plastic, paint);
394
- the others have an intrinsic colour (brass) but still accept one.
394
+ the others have an intrinsic colour (brass) but still accept one. (A viewer
395
+ user PICKING an untintable preset drops an authored tint for that pick — see
396
+ the runtime section below.)
395
397
  - Overrides — each clamped to its range: `roughness` (0–1), `metalness` (0–1),
396
398
  `clearcoat` (0–1), `clearcoatRoughness` (0–1), `anisotropy` (0–1), `textureScale` (0.01–1000).
397
399
  Transmission, index of refraction, sheen and iridescence come only from a preset.
@@ -3316,7 +3318,11 @@ drive the same state:
3316
3318
 
3317
3319
  - `runtime.setSubPartDisplay(name, patch)` — merges `patch` over the sub-part's
3318
3320
  override and returns the override now held (`null` when nothing is left, or
3319
- for an unknown `name`). Appearance keys (`material`, `color`, `roughness`,
3321
+ for an unknown `name`). An override `material` that names an untintable
3322
+ preset (brass, oak, …) with no override `color` beside it drops the authored
3323
+ `color` from the effective look — picking brass means brass, not red brass —
3324
+ while a tintable pick keeps the authored colour; a host saving the pick into
3325
+ source should remove `color` alongside writing `material`. Appearance keys (`material`, `color`, `roughness`,
3320
3326
  `metalness`, `clearcoat`, `clearcoatRoughness`, `anisotropy`, `textureScale`)
3321
3327
  merge over the authored `display` for every render — CAD, realistic and the
3322
3328
  `captureView` thumbnail — so a tile picked here is drawn exactly as if it had
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "partforge",
3
- "version": "0.149.0",
3
+ "version": "0.149.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",
@@ -12,6 +12,7 @@
12
12
  // animation opacity already uses), so a ghost can be faded further but not
13
13
  // made solid from here.
14
14
  import { DISPLAY_KEYS, OVERRIDE_RANGES } from "./resolve.js";
15
+ import { PRESETS } from "./presets.js";
15
16
 
16
17
  export const APPEARANCE_OVERRIDE_KEYS = DISPLAY_KEYS.filter((k) => k !== "opacity");
17
18
  export const VIEWING_KEYS = ["hidden", "opacity"];
@@ -65,19 +66,33 @@ export function mergeOverride(current, patch) {
65
66
  return Object.keys(next).length ? next : null;
66
67
  }
67
68
 
69
+ // A preset whose colour is its own (brass, oak): an authored `color` still
70
+ // tints it (resolve.js's contract), but a USER picking such a preset means
71
+ // "make it look like that", so the pick drops the authored tint — red PLA
72
+ // picked as brass is brass, not red brass. A tintable pick keeps the colour
73
+ // (red PLA picked as PETG stays red). An explicit override colour wins either
74
+ // way; the card only offers one for tintable presets.
75
+ const untintable = (material) => typeof material === "string" && Object.hasOwn(PRESETS, material) && PRESETS[material].tintable !== true;
76
+
68
77
  // The display every material read sees: authored, with the override's
69
78
  // APPEARANCE keys on top. Viewing keys are never in it.
70
79
  export function effectiveDisplay(authored, override) {
71
80
  const out = isObject(authored) ? { ...authored } : {};
72
81
  if (!isObject(override)) return out;
73
82
  for (const key of APPEARANCE_OVERRIDE_KEYS) if (key in override) out[key] = override[key];
83
+ if ("material" in override && untintable(override.material) && !("color" in override)) delete out.color;
74
84
  return out;
75
85
  }
76
86
 
87
+ const sameAppearance = (a, b) => APPEARANCE_OVERRIDE_KEYS.every((key) => a[key] === b[key]);
88
+
89
+ // True when the override changes nothing about how the part is drawn — judged
90
+ // on the EFFECTIVE display, not key by key, because an untintable material pick
91
+ // with no colour of its own changes the look (the authored tint goes) while
92
+ // naming the authored material.
77
93
  export function isAppearanceNoop(authored, override) {
78
94
  if (!isObject(override)) return true;
79
- const a = isObject(authored) ? authored : {};
80
- return APPEARANCE_OVERRIDE_KEYS.every((key) => !(key in override) || override[key] === a[key]);
95
+ return sameAppearance(effectiveDisplay(authored, null), effectiveDisplay(authored, override));
81
96
  }
82
97
 
83
98
  // A carried map, made safe for THIS part (spec A.4): names the part no longer
@@ -96,8 +111,17 @@ export function pruneOverrides(map, part) {
96
111
  if (!sp) continue;
97
112
  const clean = mergeOverride(null, sanitizeDisplayPatch(raw));
98
113
  if (!clean) continue;
114
+ // An appearance key goes when dropping it leaves the effective display as
115
+ // it is — which is "equals the authored value", except that an untintable
116
+ // material naming the authored material still earns its keep when it is
117
+ // what drops the authored tint.
99
118
  const authored = isObject(sp.display) ? sp.display : {};
100
- for (const key of APPEARANCE_OVERRIDE_KEYS) if (key in clean && clean[key] === authored[key]) delete clean[key];
119
+ for (const key of APPEARANCE_OVERRIDE_KEYS) {
120
+ if (!(key in clean)) continue;
121
+ const without = { ...clean };
122
+ delete without[key];
123
+ if (sameAppearance(effectiveDisplay(authored, clean), effectiveDisplay(authored, without))) delete clean[key];
124
+ }
101
125
  if (Object.keys(clean).length) out[name] = clean;
102
126
  }
103
127
  return out;