@thatopen/services 0.3.11 → 0.3.13

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 (38) hide show
  1. package/dist/cli/commands/revit.d.ts +9 -0
  2. package/dist/cli/lib/revit-addin.d.ts +20 -0
  3. package/dist/cli.js +128 -0
  4. package/dist/cloud/LasToPotree.d.ts +59 -0
  5. package/dist/cloud/SplatToFq.d.ts +38 -0
  6. package/dist/cloud/index.d.ts +2 -0
  7. package/docs/builtin/paths.json +86 -86
  8. package/docs/cloud/GaussianSplatConverter.md +43 -0
  9. package/docs/cloud/PointCloudConverter.md +53 -0
  10. package/docs/revit-collab-quickstart.md +205 -0
  11. package/package.json +95 -95
  12. package/src/built-in/ClashesManager/example.ts +283 -283
  13. package/src/built-in/UIManager/src/clashes-panel/example.ts +160 -160
  14. package/src/built-in/UIManager/src/models-list/example.ts +122 -118
  15. package/src/cli/templates/app/package.json +33 -33
  16. package/src/cli/templates/app/src/setups/camera-tools.ts +0 -63
  17. package/src/cli/templates/app/src/setups/camera-views.ts +0 -201
  18. package/src/cli/templates/app/src/setups/card-header.ts +0 -35
  19. package/src/cli/templates/app/src/setups/clipper-tool.ts +0 -773
  20. package/src/cli/templates/app/src/setups/clipper.ts +0 -75
  21. package/src/cli/templates/app/src/setups/cloud-runner.ts +0 -13
  22. package/src/cli/templates/app/src/setups/exploded-view.ts +0 -246
  23. package/src/cli/templates/app/src/setups/helper-panel.ts +0 -88
  24. package/src/cli/templates/app/src/setups/hider.ts +0 -98
  25. package/src/cli/templates/app/src/setups/index.ts +0 -23
  26. package/src/cli/templates/app/src/setups/inspection.ts +0 -136
  27. package/src/cli/templates/app/src/setups/measurement-panel.ts +0 -188
  28. package/src/cli/templates/app/src/setups/measurement-tool.ts +0 -588
  29. package/src/cli/templates/app/src/setups/measurements.ts +0 -133
  30. package/src/cli/templates/app/src/setups/navigation-gizmo.ts +0 -316
  31. package/src/cli/templates/app/src/setups/plans-panel.ts +0 -619
  32. package/src/cli/templates/app/src/setups/right-sidebar.ts +0 -178
  33. package/src/cli/templates/app/src/setups/styles-panel.ts +0 -110
  34. package/src/cli/templates/app/src/setups/styles.ts +0 -375
  35. package/src/cli/templates/app/src/setups/tool-mode-manager.ts +0 -156
  36. package/src/cli/templates/app/src/setups/tool-mode.ts +0 -23
  37. package/src/cli/templates/app/src/setups/toolbar.ts +0 -229
  38. package/src/cli/templates/app/src/setups/ui-manager.ts +0 -27
@@ -1,178 +0,0 @@
1
- import * as BUI from "@thatopen/ui";
2
-
3
- /**
4
- * Mounts several panels as SEPARATE cards stacked vertically on the RIGHT,
5
- * together spanning the full viewport height. One right-docked `bim-grid
6
- * floating` holds one row per region (heights split by `grow`); the floating
7
- * grid's built-in 1rem gap shows between the cards, and its empty "rest" column
8
- * clicks through to the model. Each region is its own `bim-panel` (own
9
- * header/border), so they read as distinct cards — not one card with a divider.
10
- *
11
- * Each panel element should be `height: 100%` so it fills its row and scrolls
12
- * internally when its content is tall.
13
- *
14
- * @param container the viewport element to overlay
15
- * @param regions the panels to stack, top to bottom
16
- * @returns the floating grid element (already appended to the container)
17
- */
18
- export interface StackRegion {
19
- /** The panel element for this region. */
20
- element: HTMLElement;
21
- /** Relative height share (CSS grid `fr`). Defaults to 1 (equal split). */
22
- grow?: number;
23
- }
24
-
25
- export const rightStack = (container: HTMLElement, regions: StackRegion[]) => {
26
- // ── Collapse toggle (mirror of the left files panel's) ─────────
27
- // A bare chevron in its own thin grid column in the gutter, just OUTSIDE the
28
- // cards (to their left). Collapsing swaps the grid layout to drop the card
29
- // columns entirely; the chevron column stays. When collapsed the chevron sits
30
- // inset from the right edge (the grid's 1rem padding), so it stays in view.
31
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
32
- let gridEl: any = null;
33
- let collapsed = false;
34
-
35
- const [toggle, toggleUpdate] = BUI.Component.create<
36
- HTMLElement,
37
- { collapsed: boolean }
38
- >(
39
- (s) => BUI.html`
40
- <div
41
- style="
42
- height: 100%; display: flex; align-items: flex-start;
43
- pointer-events: none; padding-top: 0.5rem; padding-right: 0.1rem;
44
- "
45
- >
46
- <span
47
- role="button"
48
- title=${s.collapsed ? "Show panel" : "Hide panel"}
49
- @click=${() => setCollapsed(!s.collapsed)}
50
- style="
51
- pointer-events: auto; cursor: pointer; display: inline-flex;
52
- color: var(--bim-ui_bg-contrast-80, #c9c9c9);
53
- "
54
- ><bim-icon
55
- icon=${s.collapsed ? "mdi:chevron-left" : "mdi:chevron-right"}
56
- style="font-size: 1.2rem;"
57
- ></bim-icon></span>
58
- </div>
59
- `,
60
- { collapsed: false },
61
- );
62
-
63
- const setCollapsed = (v: boolean) => {
64
- collapsed = v;
65
- if (gridEl) gridEl.layout = v ? "collapsed" : "main";
66
- toggleUpdate({ collapsed: v });
67
- };
68
-
69
- // ── Floating grid: cards docked full-height right, chevron in the gutter ──
70
- const grid = BUI.Component.create(() => {
71
- const onCreated = (element?: Element) => {
72
- if (!element) return;
73
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
74
- const g = element as any;
75
- gridEl = g;
76
- const names = regions.map((_, i) => `r${i}`);
77
- g.elements = {
78
- toggle,
79
- ...Object.fromEntries(regions.map((r, i) => [names[i], r.element])),
80
- };
81
- // One row per region; columns: rest (1fr, click-through) | toggle | cards.
82
- // The "toggle" area repeats across rows → one full-height gutter column.
83
- const rows = regions
84
- .map((r, i) => `"rest toggle ${names[i]}" ${r.grow ?? 1}fr`)
85
- .join("\n");
86
- g.layouts = {
87
- main: { template: `${rows}\n/ 1fr auto auto` },
88
- // Collapsed: drop the card columns; keep rest + toggle (toggle ends up
89
- // near the right edge, inset by the grid's 1rem padding).
90
- collapsed: { template: `"rest toggle" 1fr\n/ 1fr auto` },
91
- };
92
- g.layout = collapsed ? "collapsed" : "main";
93
- };
94
- return BUI.html`
95
- <bim-grid style="padding: 1rem;" ${BUI.ref(onCreated)} floating></bim-grid>
96
- `;
97
- });
98
- container.append(grid);
99
-
100
- return { grid };
101
- };
102
-
103
- /**
104
- * Mirror of {@link rightStack} for the LEFT side: cards stacked vertically on
105
- * the left (full height), same 1rem gap + outer margins, with a collapse chevron
106
- * in the gutter to the RIGHT of the cards (facing the viewport). Used for the
107
- * Files (top) + helper (bottom) stack.
108
- */
109
- export const leftStack = (container: HTMLElement, regions: StackRegion[]) => {
110
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
111
- let gridEl: any = null;
112
- let collapsed = false;
113
-
114
- const [toggle, toggleUpdate] = BUI.Component.create<
115
- HTMLElement,
116
- { collapsed: boolean }
117
- >(
118
- (s) => BUI.html`
119
- <div
120
- style="
121
- height: 100%; display: flex; align-items: flex-start;
122
- pointer-events: none; padding-top: 0.5rem; padding-left: 0.1rem;
123
- "
124
- >
125
- <span
126
- role="button"
127
- title=${s.collapsed ? "Show panel" : "Hide panel"}
128
- @click=${() => setCollapsed(!s.collapsed)}
129
- style="
130
- pointer-events: auto; cursor: pointer; display: inline-flex;
131
- color: var(--bim-ui_bg-contrast-80, #c9c9c9);
132
- "
133
- ><bim-icon
134
- icon=${s.collapsed ? "mdi:chevron-right" : "mdi:chevron-left"}
135
- style="font-size: 1.2rem;"
136
- ></bim-icon></span>
137
- </div>
138
- `,
139
- { collapsed: false },
140
- );
141
-
142
- const setCollapsed = (v: boolean) => {
143
- collapsed = v;
144
- if (gridEl) gridEl.layout = v ? "collapsed" : "main";
145
- toggleUpdate({ collapsed: v });
146
- };
147
-
148
- const grid = BUI.Component.create(() => {
149
- const onCreated = (element?: Element) => {
150
- if (!element) return;
151
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
152
- const g = element as any;
153
- gridEl = g;
154
- const names = regions.map((_, i) => `r${i}`);
155
- g.elements = {
156
- toggle,
157
- ...Object.fromEntries(regions.map((r, i) => [names[i], r.element])),
158
- };
159
- // Columns: cards | toggle (gutter, facing viewport) | rest (click-through).
160
- const rows = regions
161
- .map((r, i) => `"${names[i]} toggle rest" ${r.grow ?? 1}fr`)
162
- .join("\n");
163
- g.layouts = {
164
- main: { template: `${rows}\n/ auto auto 1fr` },
165
- // Collapsed: drop the card columns; toggle stays at the left edge (inset
166
- // by the grid's 1rem padding).
167
- collapsed: { template: `"toggle rest" 1fr\n/ auto 1fr` },
168
- };
169
- g.layout = collapsed ? "collapsed" : "main";
170
- };
171
- return BUI.html`
172
- <bim-grid style="padding: 1rem;" ${BUI.ref(onCreated)} floating></bim-grid>
173
- `;
174
- });
175
- container.append(grid);
176
-
177
- return { grid };
178
- };
@@ -1,110 +0,0 @@
1
- import * as OBC from "@thatopen/components";
2
- import * as BUI from "@thatopen/ui";
3
- import { styles, StyleSetting } from "./styles";
4
-
5
- /**
6
- * UI side of the Styles tool. Consumes Worker 1's style DESCRIPTOR
7
- * (`styles(components, world).settings`) and renders it generically in the
8
- * bottom-left helper panel: checkbox (bool), slider (number), color picker
9
- * (color), dropdown (enum) — each wired live to the setting's get/set. Settings
10
- * are grouped by their `group` field.
11
- *
12
- * Returned as a "panel tool" `{ label, icon, render }` for the toolbar; `render`
13
- * receives a `refresh` callback to re-read all controls after a change (needed
14
- * because e.g. the preset enum rewrites several other settings).
15
- */
16
- const control = (s: StyleSetting, refresh: () => void) => {
17
- switch (s.type) {
18
- case "bool":
19
- return BUI.html`<bim-checkbox
20
- label=${s.label}
21
- ?checked=${s.get()}
22
- @change=${(e: Event) => {
23
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
24
- s.set(!!(e.target as any).value);
25
- refresh();
26
- }}
27
- ></bim-checkbox>`;
28
- case "number":
29
- return BUI.html`<bim-number-input
30
- label=${s.label}
31
- slider
32
- value=${s.get()}
33
- min=${s.min}
34
- max=${s.max}
35
- step=${s.step}
36
- @change=${(e: Event) => {
37
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
38
- s.set(Number((e.target as any).value));
39
- refresh();
40
- }}
41
- ></bim-number-input>`;
42
- case "color":
43
- return BUI.html`<bim-color-input
44
- label=${s.label}
45
- color=${s.get()}
46
- @input=${(e: Event) => {
47
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
48
- const t = e.target as any;
49
- s.set(String(t.color ?? t.value));
50
- refresh();
51
- }}
52
- ></bim-color-input>`;
53
- case "enum":
54
- return BUI.html`<bim-dropdown
55
- label=${s.label}
56
- @change=${(e: Event) => {
57
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
58
- const picked = (e.target as any).value?.[0];
59
- const match = s.options.find((o) => o.label === picked);
60
- if (match) {
61
- s.set(match.value);
62
- refresh();
63
- }
64
- }}
65
- >
66
- ${s.options.map(
67
- (o) => BUI.html`<bim-option label=${o.label} ?checked=${o.value === s.get()}></bim-option>`,
68
- )}
69
- </bim-dropdown>`;
70
- }
71
- };
72
-
73
- export const stylesTool = (components: OBC.Components, world: OBC.World) => ({
74
- label: "Styles",
75
- icon: "mdi:palette",
76
- render: (refresh: () => void) => {
77
- const { settings } = styles(components, world);
78
- // Group settings by their `group` field, preserving first-seen order.
79
- const groups: { name: string; items: StyleSetting[] }[] = [];
80
- for (const s of settings) {
81
- let g = groups.find((x) => x.name === s.group);
82
- if (!g) {
83
- g = { name: s.group, items: [] };
84
- groups.push(g);
85
- }
86
- g.items.push(s);
87
- }
88
- return BUI.html`
89
- <div style="display: flex; flex-direction: column; gap: 0.6rem;">
90
- ${groups.map(
91
- (g) => BUI.html`
92
- <div>
93
- <div style="
94
- font-size: 0.72rem; font-weight: 600; letter-spacing: 0.04em;
95
- text-transform: uppercase; opacity: 0.6;
96
- color: var(--bim-ui_bg-contrast-100, #e3e3e3);
97
- padding: 0.1rem 0 0.3rem; border-bottom: 1px solid var(--bim-ui_bg-contrast-20, rgba(255,255,255,0.1));
98
- ">${g.name}</div>
99
- <div style="display: flex; flex-direction: column; padding-top: 0.25rem;">
100
- ${g.items.map(
101
- (s) => BUI.html`<div style="padding: 0.3rem 0; border-bottom: 1px solid var(--bim-ui_bg-contrast-20, rgba(255, 255, 255, 0.1));">${control(s, refresh)}</div>`,
102
- )}
103
- </div>
104
- </div>
105
- `,
106
- )}
107
- </div>
108
- `;
109
- },
110
- });
@@ -1,375 +0,0 @@
1
- import * as THREE from "three";
2
- import * as OBC from "@thatopen/components";
3
- import * as OBF from "@thatopen/components-front";
4
-
5
- /**
6
- * STYLES tool — a typed, generic descriptor over the viewer's visual settings so
7
- * a UI (the bottom-left helper panel) can render controls without poking the
8
- * postproduction renderer internals.
9
- *
10
- * Every entry reads/writes LIVE: the deferred composite re-reads its `settings`
11
- * each frame, the AO/FXAA flags are read per-frame, and `renderScale` /
12
- * `adaptiveResolution` apply on assignment — so no rebuild or lib change is
13
- * needed. Mutating a setting takes effect on the next rendered frame.
14
- *
15
- * NOTE on presets: setting `Style preset` runs the engine's
16
- * `_applyDeferredStyle`, which overwrites `Surface color`, `Edges` and
17
- * `Ambient occlusion` to the preset's combination. After changing the preset,
18
- * re-read all settings to refresh the panel.
19
- */
20
- export type StyleSetting =
21
- | {
22
- key: string;
23
- label: string;
24
- group: string;
25
- type: "bool";
26
- default: boolean;
27
- get(): boolean;
28
- set(v: boolean): void;
29
- }
30
- | {
31
- key: string;
32
- label: string;
33
- group: string;
34
- type: "number";
35
- default: number;
36
- min: number;
37
- max: number;
38
- step: number;
39
- get(): number;
40
- set(v: number): void;
41
- }
42
- | {
43
- key: string;
44
- label: string;
45
- group: string;
46
- type: "color"; // value is a "#rrggbb" hex string
47
- default: string;
48
- get(): string;
49
- set(v: string): void;
50
- }
51
- | {
52
- key: string;
53
- label: string;
54
- group: string;
55
- type: "enum";
56
- default: string | number;
57
- options: { label: string; value: string | number }[];
58
- get(): string | number;
59
- set(v: string | number): void;
60
- };
61
-
62
- const hex = (c: THREE.Color) => `#${c.getHexString()}`;
63
-
64
- // Background state is MODULE-scoped so it survives styles() re-creation (the
65
- // graphics panel rebuilds the descriptor on every refresh — a per-instance
66
- // `lastBg` re-clobbered a user-picked background to 0x202020 one frame after the
67
- // pick, #30). `stylesLastBg` is the remembered OPAQUE colour: it persists even
68
- // while transparent mode zeroes the renderer's clear colour, so toggling back to
69
- // opaque restores the picked hue. The 0x202020 default is applied to the
70
- // renderer exactly once.
71
- let stylesBgInitialized = false;
72
- const stylesLastBg = new THREE.Color(0x202020);
73
-
74
- export const styles = (
75
- components: OBC.Components,
76
- world: OBC.World,
77
- ): { settings: StyleSetting[] } => {
78
- const renderer = world.renderer as OBF.PostproductionRenderer;
79
-
80
- // Lazy accessors — resolved at call time so they always hit the live objects
81
- // (the deferred pipeline is allocated once postproduction is up).
82
- const pp = () => renderer.postproduction;
83
- const deferred = () => renderer.postproduction.deferred;
84
- const composite = () => renderer.postproduction.deferred.composite.settings;
85
- const grid = () => components.get(OBC.Grids).list.get(world.uuid);
86
- const scene = world.scene.three as THREE.Scene;
87
- const three = () => renderer.three as THREE.WebGLRenderer;
88
-
89
- // Background is driven through the RENDERER CLEAR colour/alpha, not
90
- // scene.background. Under the deferred pen pipeline the composite passes the
91
- // captured clear straight through for background pixels, so the clear colour IS
92
- // the viewport background; setting scene.background to a THREE.Color instead
93
- // makes three force-clear the capture G-buffer mid-pass and the whole model
94
- // vanishes (#30). Keep scene.background null and own the clear here.
95
- scene.background = null;
96
- // Apply the default background ONCE (never on a rebuild — that was the #30
97
- // reset). Default is TRANSPARENT (clear alpha 0) to match the
98
- // `transparentBackground` setting's default:true, so on load the viewport shows
99
- // whatever is behind it instead of an opaque black. The remembered hue lives in
100
- // the module-scoped `stylesLastBg` for when transparency is toggled off.
101
- if (!stylesBgInitialized) {
102
- stylesBgInitialized = true;
103
- three().setClearColor(0x000000, 0);
104
- }
105
-
106
- const A = OBF.PostproductionAspect;
107
-
108
- const settings: StyleSetting[] = [
109
- // ── Preset ──────────────────────────────────────────────────────
110
- {
111
- key: "preset",
112
- label: "Style preset",
113
- group: "Preset",
114
- type: "enum",
115
- default: A.COLOR_PEN_SHADOWS,
116
- options: [
117
- { label: "Color + edges + shadows", value: A.COLOR_PEN_SHADOWS },
118
- { label: "Color", value: A.COLOR },
119
- { label: "Color + edges", value: A.COLOR_PEN },
120
- { label: "Color + shadows", value: A.COLOR_SHADOWS },
121
- { label: "Pen (edges only)", value: A.PEN },
122
- { label: "Pen + shadows", value: A.PEN_SHADOWS },
123
- ],
124
- get: () => pp().style,
125
- set: (v) => {
126
- pp().style = v as OBF.PostproductionAspect;
127
- },
128
- },
129
- {
130
- key: "postproductionEnabled",
131
- label: "Postproduction",
132
- group: "Preset",
133
- type: "bool",
134
- default: true,
135
- get: () => pp().enabled,
136
- set: (v) => {
137
- pp().enabled = v;
138
- },
139
- },
140
-
141
- // ── Edges (contour) ─────────────────────────────────────────────
142
- {
143
- key: "edges",
144
- label: "Edges",
145
- group: "Edges",
146
- type: "bool",
147
- default: true,
148
- get: () => composite().contourEnabled,
149
- set: (v) => {
150
- composite().contourEnabled = v;
151
- },
152
- },
153
- {
154
- key: "edgeColor",
155
- label: "Edge color",
156
- group: "Edges",
157
- type: "color",
158
- default: "#000000",
159
- get: () => hex(composite().edgeColor),
160
- set: (v) => composite().edgeColor.set(v),
161
- },
162
- {
163
- key: "edgeStrength",
164
- label: "Edge strength",
165
- group: "Edges",
166
- type: "number",
167
- default: 0.8,
168
- min: 0,
169
- max: 1,
170
- step: 0.05,
171
- get: () => composite().edgeStrength,
172
- set: (v) => {
173
- composite().edgeStrength = v;
174
- },
175
- },
176
-
177
- // ── Shading ─────────────────────────────────────────────────────
178
- {
179
- key: "surfaceColor",
180
- label: "Surface color (off = paper)",
181
- group: "Shading",
182
- type: "bool",
183
- default: true,
184
- get: () => composite().colorEnabled,
185
- set: (v) => {
186
- composite().colorEnabled = v;
187
- },
188
- },
189
- {
190
- key: "ao",
191
- label: "Ambient occlusion",
192
- group: "Shading",
193
- type: "bool",
194
- default: true,
195
- get: () => deferred().settings.aoEnabled,
196
- set: (v) => {
197
- deferred().settings.aoEnabled = v;
198
- },
199
- },
200
- {
201
- key: "aoStrength",
202
- label: "AO strength",
203
- group: "Shading",
204
- type: "number",
205
- default: 1.0,
206
- min: 0,
207
- max: 2,
208
- step: 0.05,
209
- get: () => composite().aoStrength,
210
- set: (v) => {
211
- composite().aoStrength = v;
212
- },
213
- },
214
- {
215
- key: "tonalShading",
216
- label: "Tonal shading",
217
- group: "Shading",
218
- type: "bool",
219
- default: true,
220
- get: () => composite().tonalShadingEnabled,
221
- set: (v) => {
222
- composite().tonalShadingEnabled = v;
223
- },
224
- },
225
- {
226
- key: "tonalFloor",
227
- label: "Tonal floor",
228
- group: "Shading",
229
- type: "number",
230
- default: 0.7,
231
- min: 0,
232
- max: 1,
233
- step: 0.05,
234
- get: () => composite().tonalFloor,
235
- set: (v) => {
236
- composite().tonalFloor = v;
237
- },
238
- },
239
-
240
- // ── Scene ───────────────────────────────────────────────────────
241
- {
242
- key: "transparentBackground",
243
- label: "Transparent background",
244
- group: "Scene",
245
- type: "bool",
246
- default: true,
247
- // Transparent === clear alpha 0 (page/scene behind shows through).
248
- get: () => three().getClearAlpha() === 0,
249
- set: (v) => {
250
- scene.background = null;
251
- if (v) {
252
- // TRUE transparency: zero the RGB too, not just alpha. The composite
253
- // passes the clear straight through and the canvas composites
254
- // premultiplied, so a non-zero rgb at alpha 0 ADDS that colour over the
255
- // page (the picked colour bled through as a tint). (0,0,0,0) = clean
256
- // page-through with no tint. The hue is remembered in stylesLastBg.
257
- three().setClearColor(0x000000, 0);
258
- } else {
259
- three().setClearColor(stylesLastBg, 1);
260
- }
261
- },
262
- },
263
- {
264
- key: "backgroundColor",
265
- label: "Background color",
266
- group: "Scene",
267
- type: "color",
268
- // Reflect the remembered hue even while transparent (clear rgb is zeroed).
269
- default: `#${stylesLastBg.getHexString()}`,
270
- get: () => hex(stylesLastBg),
271
- set: (v) => {
272
- stylesLastBg.set(v);
273
- scene.background = null;
274
- // Choosing a colour means you want to SEE it → make the background
275
- // opaque (alpha 1). The Transparent toggle re-reads alpha on refresh and
276
- // flips off to match.
277
- three().setClearColor(stylesLastBg, 1);
278
- },
279
- },
280
- {
281
- key: "grid",
282
- label: "Grid",
283
- group: "Scene",
284
- type: "bool",
285
- default: true,
286
- get: () => grid()?.config.visible ?? false,
287
- set: (v) => {
288
- const g = grid();
289
- if (g) g.config.visible = v;
290
- },
291
- },
292
- {
293
- key: "gridColor",
294
- label: "Grid color",
295
- group: "Scene",
296
- type: "color",
297
- default: "#d3d3d3",
298
- get: () => {
299
- const g = grid();
300
- return g ? hex(g.config.color) : "#d3d3d3";
301
- },
302
- set: (v) => {
303
- const g = grid();
304
- if (g) g.config.color = new THREE.Color(v);
305
- },
306
- },
307
-
308
- // ── Quality / performance ───────────────────────────────────────
309
- {
310
- key: "fxaa",
311
- label: "Anti-aliasing (FXAA)",
312
- group: "Quality",
313
- type: "bool",
314
- default: true,
315
- get: () => deferred().settings.fxaaEnabled,
316
- set: (v) => {
317
- deferred().settings.fxaaEnabled = v;
318
- },
319
- },
320
- {
321
- key: "renderScale",
322
- label: "Render scale",
323
- group: "Quality",
324
- type: "number",
325
- default: 1.0,
326
- min: 0.25,
327
- max: 1,
328
- step: 0.05,
329
- get: () => deferred().renderScale,
330
- set: (v) => {
331
- deferred().renderScale = v;
332
- },
333
- },
334
- {
335
- key: "adaptiveResolution",
336
- label: "Adaptive resolution",
337
- group: "Quality",
338
- type: "bool",
339
- default: true,
340
- get: () => renderer.adaptiveResolution,
341
- set: (v) => {
342
- renderer.adaptiveResolution = v;
343
- },
344
- },
345
- {
346
- key: "targetFps",
347
- label: "Target FPS",
348
- group: "Quality",
349
- type: "number",
350
- default: 60,
351
- min: 24,
352
- max: 120,
353
- step: 5,
354
- get: () => renderer.adaptiveTargetFps,
355
- set: (v) => {
356
- renderer.adaptiveTargetFps = v;
357
- },
358
- },
359
- {
360
- key: "highResOutline",
361
- label: "High-res selection outline",
362
- group: "Quality",
363
- type: "bool",
364
- default: true,
365
- // false → render the selection outline at half resolution (cheaper when a
366
- // selection covers much of the screen, slightly softer); true → full res.
367
- get: () => components.get(OBF.Outliner).resolutionScale >= 1,
368
- set: (v) => {
369
- components.get(OBF.Outliner).resolutionScale = v ? 1 : 0.5;
370
- },
371
- },
372
- ];
373
-
374
- return { settings };
375
- };