partforge 0.121.0 → 0.122.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.
Files changed (63) hide show
  1. package/docs/AUTHORING-PARTS.md +164 -3
  2. package/package.json +3 -1
  3. package/src/app-materials.js +23 -0
  4. package/src/framework/app.css +16 -1
  5. package/src/framework/cutaway.js +12 -0
  6. package/src/framework/geometry/arc-math.js +27 -0
  7. package/src/framework/geometry/contour-corners.js +130 -0
  8. package/src/framework/geometry/contour-ops.js +11 -125
  9. package/src/framework/geometry/paper-bridge.js +4 -21
  10. package/src/framework/geometry/threemf.js +32 -3
  11. package/src/framework/jobs.js +2 -1
  12. package/src/framework/lint/index.js +2 -1
  13. package/src/framework/lint/rules-materials.js +56 -0
  14. package/src/framework/materials/assets/SOURCES.md +90 -0
  15. package/src/framework/materials/assets/env-outdoor.jpg +0 -0
  16. package/src/framework/materials/assets/env-print-bed.jpg +0 -0
  17. package/src/framework/materials/assets/env-studio.jpg +0 -0
  18. package/src/framework/materials/assets/env-workshop.jpg +0 -0
  19. package/src/framework/materials/assets/ground-concrete-normal.jpg +0 -0
  20. package/src/framework/materials/assets/ground-concrete-rough.jpg +0 -0
  21. package/src/framework/materials/assets/ground-concrete.jpg +0 -0
  22. package/src/framework/materials/assets/ground-maple-color.jpg +0 -0
  23. package/src/framework/materials/assets/ground-maple-normal.jpg +0 -0
  24. package/src/framework/materials/assets/ground-paper-normal.jpg +0 -0
  25. package/src/framework/materials/assets/ground-paper-rough.jpg +0 -0
  26. package/src/framework/materials/assets/ground-paper.jpg +0 -0
  27. package/src/framework/materials/assets/ground-pei-rough.jpg +0 -0
  28. package/src/framework/materials/assets/ground-pei.jpg +0 -0
  29. package/src/framework/materials/assets/pattern-carbon.jpg +0 -0
  30. package/src/framework/materials/assets/pattern-oak-color.jpg +0 -0
  31. package/src/framework/materials/assets/pattern-oak-normal.jpg +0 -0
  32. package/src/framework/materials/assets/pattern-oak-rough.jpg +0 -0
  33. package/src/framework/materials/assets/pattern-walnut-color.jpg +0 -0
  34. package/src/framework/materials/assets/pattern-walnut-normal.jpg +0 -0
  35. package/src/framework/materials/assets/pattern-walnut-rough.jpg +0 -0
  36. package/src/framework/materials/assets.js +39 -0
  37. package/src/framework/materials/contact-shadow.js +155 -0
  38. package/src/framework/materials/environment.js +157 -0
  39. package/src/framework/materials/environments.js +82 -0
  40. package/src/framework/materials/patterns.js +195 -0
  41. package/src/framework/materials/physical.js +82 -0
  42. package/src/framework/materials/presets.js +89 -0
  43. package/src/framework/materials/print-bed.js +169 -0
  44. package/src/framework/materials/print-frame.js +18 -0
  45. package/src/framework/materials/resolve.js +117 -0
  46. package/src/framework/materials/tonemap-readback.js +36 -0
  47. package/src/framework/materials/uv.js +23 -0
  48. package/src/framework/mount.js +121 -4
  49. package/src/framework/oracle/shape-probe.js +2 -2
  50. package/src/framework/realistic-controls.js +78 -0
  51. package/src/framework/selection/feature-highlight.js +3 -0
  52. package/src/framework/view-state.js +27 -1
  53. package/src/framework/viewer.js +623 -34
  54. package/src/materials-worker.js +3 -0
  55. package/src/parts/bracket.js +2 -1
  56. package/src/parts/hinged-box.js +3 -1
  57. package/src/parts/material-swatches.js +40 -0
  58. package/src/parts/planter.js +2 -1
  59. package/src/parts/propeller.js +2 -1
  60. package/src/parts/scott-label.js +3 -2
  61. package/src/parts/screw.js +2 -1
  62. package/types/index.d.ts +48 -3
  63. package/types/part.d.ts +15 -2
@@ -0,0 +1,78 @@
1
+ // Viewbar chrome for realistic mode: a toggle button and an environment <select>.
2
+ // Both optional (a host without the elements simply has no built-in controls and
3
+ // drives runtime.renderMode / runtime.environment itself). Same shape as
4
+ // viewer-controls.js. The state shown is always the viewer's: the toggle is
5
+ // pressed only once realistic has actually landed, and the menu shows the
6
+ // environment in effect (a failed switch snaps it back).
7
+ import { ENVIRONMENTS } from "./materials/environments.js";
8
+ import { saveRenderMode, saveEnvironment } from "./view-state.js";
9
+ import { attachButtonTooltips } from "./tooltip.js";
10
+
11
+ const LABEL = "Realistic view";
12
+ const ERROR_LABEL = "Couldn't load realistic view";
13
+
14
+ export function attachRealisticControls(viewer, { toggle, envMenu } = {}, { tooltip } = {}) {
15
+ const binding = tooltip && toggle ? attachButtonTooltips(tooltip, [{ element: toggle }]) : null;
16
+ if (envMenu && !envMenu.options.length) {
17
+ for (const env of Object.values(ENVIRONMENTS)) {
18
+ const o = document.createElement("option");
19
+ o.value = env.id;
20
+ o.textContent = env.label;
21
+ envMenu.append(o);
22
+ }
23
+ envMenu.setAttribute("aria-label", "Environment");
24
+ }
25
+
26
+ // The last mode event's busy/error, so an environment event or sync() —
27
+ // which carry neither — re-render without clearing them.
28
+ let busy = false;
29
+ let error = null;
30
+
31
+ function render() {
32
+ const mode = viewer.getRenderMode();
33
+ if (toggle) {
34
+ const on = mode === "realistic";
35
+ toggle.classList.toggle("on", on);
36
+ toggle.setAttribute("aria-pressed", String(on));
37
+ if (busy) toggle.dataset.busy = "true"; else delete toggle.dataset.busy;
38
+ const label = error ? ERROR_LABEL : LABEL;
39
+ toggle.setAttribute("aria-label", label);
40
+ if (!tooltip) toggle.title = label;
41
+ }
42
+ if (envMenu) {
43
+ envMenu.hidden = mode !== "realistic";
44
+ envMenu.value = viewer.getEnvironment();
45
+ }
46
+ binding?.sync();
47
+ }
48
+
49
+ // A click while realistic is still loading is a change of mind: back to CAD.
50
+ // What persists is what the viewer says took effect, not what was asked for.
51
+ const onToggle = () => {
52
+ const next = viewer.getRenderMode() === "realistic" || busy ? "cad" : "realistic";
53
+ viewer.setRenderMode(next).then(saveRenderMode, () => {});
54
+ };
55
+ const onEnv = () => {
56
+ viewer.setEnvironment(envMenu.value).then(saveEnvironment, () => {});
57
+ };
58
+ toggle?.addEventListener("click", onToggle);
59
+ envMenu?.addEventListener("change", onEnv);
60
+ const offMode = viewer.onRenderModeChange((e) => {
61
+ busy = !!e?.busy;
62
+ error = e?.error ?? null;
63
+ render();
64
+ });
65
+ const offEnv = viewer.onEnvironmentChange(() => render());
66
+ render();
67
+
68
+ return {
69
+ sync: render,
70
+ detach() {
71
+ toggle?.removeEventListener("click", onToggle);
72
+ envMenu?.removeEventListener("change", onEnv);
73
+ offMode();
74
+ offEnv();
75
+ binding?.detach();
76
+ },
77
+ };
78
+ }
@@ -40,6 +40,8 @@ export function createFeatureHighlight(viewer) {
40
40
  const overlay = new THREE.Mesh(emptyOverlayGeometry, material);
41
41
  overlay.visible = false;
42
42
  overlay.renderOrder = CUTAWAY_OVERLAY_RENDER_ORDER;
43
+ // Pointer feedback, never part of a capture (it tints what it covers).
44
+ const unregisterCapture = viewer.registerCaptureHidden?.(overlay) ?? (() => {});
43
45
  let overlayParent = null;
44
46
  // Subset cache per sub-part: rebuilt when the sub-part's geometry object
45
47
  // changes (i.e. after a regenerate) — keyed on the geometry instance.
@@ -91,6 +93,7 @@ export function createFeatureHighlight(viewer) {
91
93
  () => subsets.clear(),
92
94
  () => emptyOverlayGeometry?.dispose(),
93
95
  unregisterCutaway,
96
+ unregisterCapture,
94
97
  () => material.dispose(),
95
98
  );
96
99
  runCleanupSteps(steps, "feature highlight cleanup failed");
@@ -6,12 +6,17 @@
6
6
  // and a fresh session opens on the part's own default (see default-view.js).
7
7
  // Reads/writes are guarded: if storage is unavailable (private mode, disabled) or a
8
8
  // value is corrupt, reads return the documented default and writes are no-ops —
9
- // persistence never throws.
9
+ // persistence never throws. `renderMode` and `environment` are viewer
10
+ // preferences like `theme`; in the cloud's opaque-origin sandbox storage throws,
11
+ // so there the host carries them in viewerState instead.
12
+ import { ENVIRONMENTS } from "./materials/environments.js";
10
13
 
11
14
  const KEY = {
12
15
  camera: "partforge:camera",
13
16
  theme: "partforge:theme",
14
17
  projection: "partforge:projection",
18
+ renderMode: "partforge:renderMode",
19
+ environment: "partforge:environment",
15
20
  };
16
21
 
17
22
  const viewKey = (partKey) => `partforge:view:${partKey}`;
@@ -64,6 +69,27 @@ export function saveProjection(mode) {
64
69
  if (mode === "perspective" || mode === "orthographic") write(KEY.projection, mode);
65
70
  }
66
71
 
72
+ // null = nothing (valid) stored — the caller falls back to "cad".
73
+ export function loadRenderMode() {
74
+ const raw = read(KEY.renderMode);
75
+ return raw === "realistic" || raw === "cad" ? raw : null;
76
+ }
77
+
78
+ export function saveRenderMode(mode) {
79
+ if (mode === "cad" || mode === "realistic") write(KEY.renderMode, mode);
80
+ }
81
+
82
+ // null = nothing stored, or an id this version doesn't know — the caller falls
83
+ // back to the part's own meta.environment.
84
+ export function loadEnvironment() {
85
+ const raw = read(KEY.environment);
86
+ return raw && Object.hasOwn(ENVIRONMENTS, raw) ? raw : null;
87
+ }
88
+
89
+ export function saveEnvironment(id) {
90
+ if (typeof id === "string" && Object.hasOwn(ENVIRONMENTS, id)) write(KEY.environment, id);
91
+ }
92
+
67
93
  // `partKey` identifies the part — createViewTabs passes `meta.title`. Without one
68
94
  // there is nothing safe to key on, so both calls no-op rather than falling back to a
69
95
  // shared key (the cross-part bleed this scoping exists to remove).