@thatopen/services 0.3.10 → 0.3.12

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 (39) 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 +101 -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 +151 -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/README.md +217 -241
  16. package/src/cli/templates/app/package.json +33 -33
  17. package/src/cli/templates/app/src/setups/camera-tools.ts +0 -63
  18. package/src/cli/templates/app/src/setups/camera-views.ts +0 -201
  19. package/src/cli/templates/app/src/setups/card-header.ts +0 -35
  20. package/src/cli/templates/app/src/setups/clipper-tool.ts +0 -773
  21. package/src/cli/templates/app/src/setups/clipper.ts +0 -75
  22. package/src/cli/templates/app/src/setups/cloud-runner.ts +0 -13
  23. package/src/cli/templates/app/src/setups/exploded-view.ts +0 -246
  24. package/src/cli/templates/app/src/setups/helper-panel.ts +0 -88
  25. package/src/cli/templates/app/src/setups/hider.ts +0 -98
  26. package/src/cli/templates/app/src/setups/index.ts +0 -23
  27. package/src/cli/templates/app/src/setups/inspection.ts +0 -136
  28. package/src/cli/templates/app/src/setups/measurement-panel.ts +0 -188
  29. package/src/cli/templates/app/src/setups/measurement-tool.ts +0 -588
  30. package/src/cli/templates/app/src/setups/measurements.ts +0 -133
  31. package/src/cli/templates/app/src/setups/navigation-gizmo.ts +0 -316
  32. package/src/cli/templates/app/src/setups/plans-panel.ts +0 -619
  33. package/src/cli/templates/app/src/setups/right-sidebar.ts +0 -178
  34. package/src/cli/templates/app/src/setups/styles-panel.ts +0 -110
  35. package/src/cli/templates/app/src/setups/styles.ts +0 -375
  36. package/src/cli/templates/app/src/setups/tool-mode-manager.ts +0 -156
  37. package/src/cli/templates/app/src/setups/tool-mode.ts +0 -23
  38. package/src/cli/templates/app/src/setups/toolbar.ts +0 -229
  39. package/src/cli/templates/app/src/setups/ui-manager.ts +0 -27
@@ -1,75 +0,0 @@
1
- import * as OBC from "@thatopen/components";
2
- import * as OBF from "@thatopen/components-front";
3
- import type { ModeTool } from "./tool-mode";
4
-
5
- /**
6
- * Section / clipping-planes tool. Wraps `OBC.Clipper` as a toolbar mode tool:
7
- *
8
- * - double-click on the model → create a clip plane on the surface under the
9
- * pointer (raycast; the plane's normal is the hit face normal),
10
- * - drag the plane's arrow gizmo to move it along its normal (handled by the
11
- * Clipper's built-in TransformControls while the mode is active),
12
- * - Delete / Backspace → delete the plane under the cursor,
13
- * - Shift + Delete / Shift + Backspace → clear ALL section planes.
14
- *
15
- * Clipping is applied through `renderer.three.clippingPlanes` with
16
- * `localClippingEnabled = true` (set by the SimpleRenderer), so the cut is
17
- * honored during the deferred pipeline's single capture render — no
18
- * deferred-lib change is needed. (Worth an explicit live check that the section
19
- * actually cuts geometry in deferred mode.)
20
- *
21
- * Leaving the mode keeps the existing section planes cutting the model (so a
22
- * section persists while navigating); it only stops creating/dragging new ones.
23
- */
24
- export const clipper = (
25
- components: OBC.Components,
26
- world: OBC.World,
27
- ): ModeTool => {
28
- const clipperComp = components.get(OBC.Clipper);
29
-
30
- const canvas = (world.renderer as OBF.PostproductionRenderer | null)?.three
31
- .domElement;
32
- if (!canvas) {
33
- throw new Error("clipper setup: world has no renderer with a canvas");
34
- }
35
-
36
- const onDblClick = () => {
37
- void clipperComp.create(world);
38
- };
39
-
40
- const onKeyDown = (event: KeyboardEvent) => {
41
- if (event.code !== "Delete" && event.code !== "Backspace") return;
42
- if (event.shiftKey) {
43
- clipperComp.deleteAll();
44
- } else {
45
- // No-op when nothing is hovered (the Clipper raycasts for a plane).
46
- void clipperComp.delete(world);
47
- }
48
- };
49
-
50
- let active = false;
51
-
52
- return {
53
- label: "Section",
54
- icon: "mdi:scissors-cutting",
55
- activate() {
56
- if (active) return;
57
- active = true;
58
- clipperComp.enabled = true; // allow create + drag-to-move
59
- clipperComp.visible = true;
60
- canvas.addEventListener("dblclick", onDblClick);
61
- window.addEventListener("keydown", onKeyDown);
62
- canvas.style.cursor = "crosshair";
63
- },
64
- deactivate() {
65
- if (!active) return;
66
- active = false;
67
- // Stop creating / dragging, but keep the planes in the renderer's
68
- // clippingPlanes so the section stays cut while navigating.
69
- clipperComp.enabled = false;
70
- canvas.removeEventListener("dblclick", onDblClick);
71
- window.removeEventListener("keydown", onKeyDown);
72
- canvas.style.cursor = "";
73
- },
74
- };
75
- };
@@ -1,13 +0,0 @@
1
- import * as OBC from "@thatopen/components";
2
- import { CloudRunner } from "../bim-components";
3
- import { getUIManager } from "./ui-manager";
4
-
5
- export const cloudRunner = (components: OBC.Components) => {
6
- const runner = components.get(CloudRunner);
7
- const uis = getUIManager(components);
8
-
9
- // Re-render all appInfoSection instances whenever execution state changes.
10
- runner.onExecutionUpdated.add(() => {
11
- uis.custom.get("cloudRunnerSection").updateInstances();
12
- });
13
- };
@@ -1,246 +0,0 @@
1
- import * as THREE from "three";
2
- import * as OBC from "@thatopen/components";
3
- import * as FRAGS from "@thatopen/fragments";
4
-
5
- /**
6
- * EXPLODED VIEW — a HEADLESS controller (no UI). The toolbar wires a single
7
- * `mdi:arrow-expand-vertical` button to `toggle()` / `isActive()` / `onChange()`.
8
- *
9
- * ── Technique ──────────────────────────────────────────────────────────────
10
- * Fragments has no runtime "translate these items" call; the supported path is
11
- * the Edit API's GLOBAL TRANSFORMS (each item placement is a global transform
12
- * with a `position`). So per model we:
13
- * 1. Group element localIds by IFCBUILDINGSTOREY (from the spatial structure)
14
- * and order storeys by elevation (min-Y of each group's bbox).
15
- * 2. Map items → their global-transform ids; a transform shared across MORE
16
- * than one storey is skipped (it can't be moved per-storey cleanly).
17
- * 3. SNAPSHOT each transform's original position ONCE.
18
- * 4. To explode at magnitude m: set each transform's Y to
19
- * `originalY + storeyIndex × step × m` — ALWAYS absolute from the snapshot,
20
- * never accumulated, so repeated explode/reset never drifts. m = 0 restores
21
- * exactly.
22
- *
23
- * Edits are applied in the background (the Edit API can't do 60/s). Visibility &
24
- * selection are untouched (only transforms change).
25
- *
26
- * const explode = explodedView(components);
27
- * // toolbar: button @click=explode.toggle(); active styling = explode.isActive()
28
- *
29
- * @param components engine components
30
- */
31
-
32
- const STOREY = "IFCBUILDINGSTOREY";
33
- const DEFAULT_MAGNITUDE = 1.0;
34
-
35
- export interface ExplodedViewController {
36
- /** Off→on (applies the default magnitude) / on→off (restores exactly). */
37
- toggle(): void;
38
- /** Whether the model is currently exploded (magnitude > 0). */
39
- isActive(): boolean;
40
- /** Subscribe to active changes (toggle / auto-reset). Returns an unsubscribe. */
41
- onChange(cb: (active: boolean) => void): () => void;
42
- /** Graded control (0→1). Kept for a future popover slider; 0 restores exactly. */
43
- setMagnitude(v: number): void;
44
- }
45
-
46
- interface ModelExplode {
47
- // global-transform localId → its storey index (0 = lowest). Only single-storey
48
- // transforms are included.
49
- gtToStorey: Map<number, number>;
50
- // global-transform localId → original position [x,y,z] (the snapshot).
51
- snapshot: Map<number, [number, number, number]>;
52
- // current global-transform objects (other fields preserved on UPDATE).
53
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
54
- gts: Map<number, any>;
55
- step: number; // vertical units per storey index at magnitude 1
56
- }
57
-
58
- export const explodedView = (
59
- components: OBC.Components,
60
- ): ExplodedViewController => {
61
- const fragments = components.get(OBC.FragmentsManager);
62
- // The Edit API; opt into the in-place incremental edit path (cheaper than a
63
- // per-edit delta-model rebuild). Harmless if already enabled.
64
- try {
65
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
66
- (fragments.core as any).settings.incrementalEdit = true;
67
- } catch {
68
- /* setting may not exist on older cores — edits still work, just slower */
69
- }
70
-
71
- let magnitude = 0;
72
- let active = false;
73
- let busy = false;
74
- const prepared = new Map<string, ModelExplode>();
75
- const listeners = new Set<(active: boolean) => void>();
76
-
77
- const setActive = (a: boolean) => {
78
- if (a === active) return;
79
- active = a;
80
- for (const cb of listeners) cb(a);
81
- };
82
-
83
- // Collect, per storey node, all descendant element localIds.
84
- const collectStoreys = (root: FRAGS.SpatialTreeItem) => {
85
- const storeys: number[][] = [];
86
- const descendants = (node: FRAGS.SpatialTreeItem, acc: number[]) => {
87
- if (node.localId !== null && node.localId !== undefined && node.localId >= 0) {
88
- acc.push(node.localId);
89
- }
90
- node.children?.forEach((c) => descendants(c, acc));
91
- };
92
- const walk = (node: FRAGS.SpatialTreeItem) => {
93
- if ((node.category ?? "").toUpperCase() === STOREY) {
94
- const ids: number[] = [];
95
- descendants(node, ids);
96
- if (ids.length) storeys.push(ids);
97
- } else {
98
- node.children?.forEach(walk);
99
- }
100
- };
101
- walk(root);
102
- return storeys;
103
- };
104
-
105
- // Build the explode plan for one model (snapshot + storey→transform map).
106
- const prepareModel = async (
107
- model: FRAGS.FragmentsModel,
108
- ): Promise<ModelExplode | null> => {
109
- const structure = await model.getSpatialStructure();
110
- if (!structure) return null;
111
- let storeys = collectStoreys(structure);
112
- if (storeys.length < 2) return null; // nothing to separate
113
-
114
- // Order storeys by elevation (min-Y of the group's merged bbox).
115
- const withY = await Promise.all(
116
- storeys.map(async (ids) => {
117
- let minY = Number.POSITIVE_INFINITY;
118
- try {
119
- const boxes = (await fragments.getBBoxes({
120
- [model.modelId]: new Set(ids),
121
- })) as THREE.Box3[];
122
- for (const b of boxes) if (!b.isEmpty()) minY = Math.min(minY, b.min.y);
123
- } catch {
124
- /* leave at +Inf → sorts last; harmless */
125
- }
126
- return { ids, minY };
127
- }),
128
- );
129
- withY.sort((a, b) => a.minY - b.minY);
130
- storeys = withY.map((s) => s.ids);
131
-
132
- // Map global-transform ids → storey index; mark cross-storey ones for skip.
133
- const gtToStorey = new Map<number, number>();
134
- const skip = new Set<number>();
135
- for (let i = 0; i < storeys.length; i += 1) {
136
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
137
- const gtIds: Iterable<number> = await (model as any).getGlobalTranformsIdsOfItems(
138
- storeys[i],
139
- );
140
- for (const gt of gtIds) {
141
- if (skip.has(gt)) continue;
142
- if (gtToStorey.has(gt) && gtToStorey.get(gt) !== i) {
143
- gtToStorey.delete(gt); // shared across storeys → can't move cleanly
144
- skip.add(gt);
145
- } else {
146
- gtToStorey.set(gt, i);
147
- }
148
- }
149
- }
150
- if (gtToStorey.size === 0) return null;
151
-
152
- // Snapshot original positions.
153
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
154
- const gts: Map<number, any> = await model.getGlobalTransforms();
155
- const snapshot = new Map<number, [number, number, number]>();
156
- for (const [id, gt] of gts) {
157
- if (!gtToStorey.has(id)) continue;
158
- const p = gt.position as number[];
159
- snapshot.set(id, [p[0], p[1], p[2]]);
160
- }
161
-
162
- // Vertical step per storey index: a fraction of the model's height so the
163
- // explode reads clearly regardless of model scale.
164
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
165
- const box = (model as any).box as THREE.Box3 | undefined;
166
- const height = box && !box.isEmpty() ? box.max.y - box.min.y : 10;
167
- const step = (height * 0.45) / Math.max(1, storeys.length - 1);
168
-
169
- return { gtToStorey, snapshot, gts, step };
170
- };
171
-
172
- const rebuild = async () => {
173
- prepared.clear();
174
- for (const model of fragments.list.values()) {
175
- try {
176
- const plan = await prepareModel(model);
177
- if (plan) prepared.set(model.modelId, plan);
178
- } catch (error) {
179
- console.warn("[exploded-view] prepare failed", model.modelId, error);
180
- }
181
- }
182
- // Re-apply the current magnitude to freshly-loaded models.
183
- if (magnitude > 0) void apply();
184
- };
185
-
186
- // Apply the current magnitude across all prepared models. Always sets the
187
- // absolute position from the snapshot (no accumulation → no drift).
188
- const apply = async () => {
189
- if (busy) return;
190
- busy = true;
191
- try {
192
- for (const [modelId, plan] of prepared) {
193
- const model = fragments.list.get(modelId);
194
- if (!model) continue;
195
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
196
- const requests: any[] = [];
197
- for (const [id, orig] of plan.snapshot) {
198
- const i = plan.gtToStorey.get(id) ?? 0;
199
- const gt = plan.gts.get(id);
200
- if (!gt) continue;
201
- requests.push({
202
- type: FRAGS.EditRequestType.UPDATE_GLOBAL_TRANSFORM,
203
- localId: id,
204
- data: {
205
- ...gt,
206
- position: [orig[0], orig[1] + i * plan.step * magnitude, orig[2]],
207
- },
208
- });
209
- }
210
- if (requests.length === 0) continue;
211
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
212
- await (fragments.core as any).editor.edit(modelId, requests);
213
- }
214
- await fragments.core.update(true);
215
- } catch (error) {
216
- console.warn("[exploded-view] apply failed", error);
217
- } finally {
218
- busy = false;
219
- }
220
- };
221
-
222
- const setMagnitude = (v: number) => {
223
- magnitude = Math.max(0, Math.min(1, v));
224
- void apply();
225
- setActive(magnitude > 0);
226
- };
227
-
228
- // Recompute the plan whenever the loaded models change.
229
- fragments.core.onModelLoaded.add(() => void rebuild());
230
- fragments.list.onItemDeleted.add(() => void rebuild());
231
- void rebuild();
232
-
233
- return {
234
- toggle() {
235
- setMagnitude(active ? 0 : DEFAULT_MAGNITUDE);
236
- },
237
- isActive() {
238
- return active;
239
- },
240
- onChange(cb) {
241
- listeners.add(cb);
242
- return () => listeners.delete(cb);
243
- },
244
- setMagnitude,
245
- };
246
- };
@@ -1,88 +0,0 @@
1
- import * as BUI from "@thatopen/ui";
2
- import { cardHeader } from "./card-header";
3
- import { toolPlaceholderUri } from "../assets/tool-placeholder";
4
-
5
- /**
6
- * A contextual helper card — ALWAYS present in the left stack (see left-sidebar).
7
- * It renders the options of whichever panel-tool is active (e.g. Styles); when
8
- * no tool is active it shows an empty-state illustration + hint. Mirrors the
9
- * standardized card chrome: dark `bg-base`, `cardHeader` (with divider) fixed,
10
- * inset `.helper-scroll` `overflow-y:auto` (same scrollbar as the others).
11
- *
12
- * Returns the panel ELEMENT (mounted by the left stack) plus a controller:
13
- * show({ title, icon, render }) — show a tool's content (+ retitle the header)
14
- * clear() — revert to the empty state
15
- * refresh() — re-run the current `render`
16
- */
17
- export interface HelperContent {
18
- title: string;
19
- icon: string;
20
- render: () => unknown;
21
- }
22
-
23
- export interface HelperPanelController {
24
- element: HTMLElement;
25
- show(content: HelperContent): void;
26
- clear(): void;
27
- refresh(): void;
28
- }
29
-
30
- const DEFAULT = { title: "Tools", icon: "mdi:tune" };
31
-
32
- const emptyState = () => BUI.html`
33
- <div style="
34
- display: flex; flex-direction: column; align-items: center; justify-content: center;
35
- gap: 0.75rem; height: 100%; min-height: 12rem; padding: 1rem; text-align: center;
36
- ">
37
- <img src=${toolPlaceholderUri} alt="" style="width: 7.5rem; height: auto; opacity: 0.9;" />
38
- <bim-label style="opacity: 0.55; white-space: normal;">Select a tool to see it here.</bim-label>
39
- </div>
40
- `;
41
-
42
- export const helperPanel = (): HelperPanelController => {
43
- let renderContent: (() => unknown) | null = null;
44
-
45
- const [panel, update] = BUI.Component.create<BUI.Panel, { title: string; icon: string }>(
46
- (state) => BUI.html`
47
- <bim-panel
48
- label=${state.title}
49
- icon=${state.icon}
50
- style="width: 100%; height: 100%; pointer-events: auto;"
51
- >
52
- <style>
53
- .helper-scroll::-webkit-scrollbar { width: 0.4rem; height: 0.4rem; }
54
- .helper-scroll::-webkit-scrollbar-thumb { border-radius: 0.25rem; background-color: var(--bim-scrollbar--c, #3C3C41); }
55
- .helper-scroll::-webkit-scrollbar-track { background-color: var(--bim-scrollbar--bgc, var(--bim-ui_bg-contrast-10)); }
56
- </style>
57
- <div style="display: flex; flex-direction: column; height: 100%;">
58
- <div style="flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; padding: 0.4rem 0.4rem 0.4rem 1.1rem;">
59
- <!-- Scroll container: scrollbar sits inset from the card (left
60
- wrapper pad). Inner content has right padding so it clears the
61
- scrollbar (gap on both sides), mirroring the tree. -->
62
- <div class="helper-scroll" style="flex: 1 1 auto; min-height: 0; overflow-y: auto;">
63
- <div style="padding-right: 0.6rem;">
64
- ${renderContent ? renderContent() : emptyState()}
65
- </div>
66
- </div>
67
- </div>
68
- </div>
69
- </bim-panel>
70
- `,
71
- { ...DEFAULT },
72
- );
73
-
74
- return {
75
- element: panel,
76
- show({ title, icon, render }) {
77
- renderContent = render;
78
- update({ title, icon });
79
- },
80
- clear() {
81
- renderContent = null;
82
- update({ ...DEFAULT });
83
- },
84
- refresh() {
85
- update({}); // re-runs the template → re-calls renderContent()
86
- },
87
- };
88
- };
@@ -1,98 +0,0 @@
1
- import * as OBC from "@thatopen/components";
2
- import * as OBF from "@thatopen/components-front";
3
-
4
- /**
5
- * Visibility actions driven by the CURRENT selection (the Highlighter "select"
6
- * set that the tree/viewport populate). Returns a controller for the toolbar to
7
- * wire to buttons — it does not render any UI itself.
8
- *
9
- * Suggested icons (ACTION buttons):
10
- * - hideSelected → "mdi:eye-off-outline"
11
- * - isolateSelected → "mdi:select-search" (alt: "mdi:eye")
12
- * - showAll → "mdi:eye-outline" (alt: "mdi:restore")
13
- */
14
- export interface HiderController {
15
- /** Hide the currently selected items. No-op if nothing is selected. */
16
- hideSelected(): void;
17
- /** Hide everything except the current selection. No-op if nothing is selected. */
18
- isolateSelected(): void;
19
- /** Reset: make every item visible again. */
20
- showAll(): void;
21
- /**
22
- * Ghost (x-ray) the current selection via the scalable per-element GPU state
23
- * texture (`model.setGhostItems`) — NO per-item recolor, so it scales to
24
- * millions of elements. No-op if nothing is selected.
25
- */
26
- ghostSelected(): void;
27
- /** Clear the ghost (x-ray) overlay on all loaded models. */
28
- clearGhost(): void;
29
- }
30
-
31
- export const hider = (components: OBC.Components): HiderController => {
32
- const hiderComp = components.get(OBC.Hider);
33
- const highlighter = components.get(OBF.Highlighter);
34
- const fragments = components.get(OBC.FragmentsManager);
35
-
36
- const currentSelection = (): OBC.ModelIdMap | undefined =>
37
- highlighter.selection.select;
38
- const hasItems = (map?: OBC.ModelIdMap) =>
39
- !!map && Object.values(map).some((set) => set.size > 0);
40
-
41
- // Making items visible must also un-ghost them — a shown element should never
42
- // linger as a ghost. unsetGhostItems is async (localId→itemId worker
43
- // conversion), so collect per-model tasks and update once they all land.
44
- const unghost = (map: OBC.ModelIdMap) => {
45
- const tasks: Promise<unknown>[] = [];
46
- for (const [modelId, set] of Object.entries(map)) {
47
- const model = fragments.list.get(modelId);
48
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
49
- if (model && set.size > 0) tasks.push((model as any).unsetGhostItems?.([...set]));
50
- }
51
- if (tasks.length) void Promise.all(tasks).then(() => fragments.core.update(true));
52
- };
53
-
54
- const clearGhostAll = () => {
55
- for (const model of fragments.list.values()) {
56
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
57
- (model as any).clearGhost?.();
58
- }
59
- void fragments.core.update(true);
60
- };
61
-
62
- return {
63
- hideSelected() {
64
- const sel = currentSelection();
65
- if (!sel || !hasItems(sel)) return;
66
- void hiderComp.set(false, sel);
67
- },
68
- isolateSelected() {
69
- const sel = currentSelection();
70
- if (!sel || !hasItems(sel)) return;
71
- void hiderComp.isolate(sel);
72
- // The selection is the only thing visible now → un-ghost it so an isolated
73
- // element is never left ghosted.
74
- unghost(sel);
75
- },
76
- showAll() {
77
- void hiderComp.set(true);
78
- // Everything is visible again → nothing should stay ghosted.
79
- clearGhostAll();
80
- },
81
- ghostSelected() {
82
- const sel = currentSelection();
83
- if (!sel || !hasItems(sel)) return;
84
- const tasks: Promise<unknown>[] = [];
85
- for (const [modelId, set] of Object.entries(sel)) {
86
- const model = fragments.list.get(modelId);
87
- // setGhostItems is async (localId→itemId worker conversion) — update
88
- // only after every model's ghost state is written.
89
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
90
- if (model && set.size > 0) tasks.push((model as any).setGhostItems([...set], false));
91
- }
92
- void Promise.all(tasks).then(() => fragments.core.update(true));
93
- },
94
- clearGhost() {
95
- clearGhostAll();
96
- },
97
- };
98
- };
@@ -1,23 +0,0 @@
1
- export * from "./ui-manager";
2
- export * from "./cloud-runner";
3
- export * from "./right-sidebar";
4
- export * from "./card-header";
5
- export * from "./toolbar";
6
- export * from "./hider";
7
- export * from "./tool-mode";
8
- export * from "./clipper";
9
- export * from "./measurements";
10
- export * from "./styles";
11
- export * from "./helper-panel";
12
- export * from "./styles-panel";
13
- export * from "./clipper-tool";
14
- export * from "./plans-panel";
15
- export * from "./navigation-gizmo";
16
- export * from "./exploded-view";
17
- export * from "./measurement-tool";
18
- export * from "./measurement-panel";
19
- export * from "./tool-mode-manager";
20
- // reality-capture-viewer is intentionally NOT re-exported here: it spins up
21
- // decode workers at module load, so it must be LAZY-imported (dynamic import in
22
- // main.ts's app-registered .3tz loader for <top-models-list>), never pulled onto
23
- // the app boot path.
@@ -1,136 +0,0 @@
1
- import * as OBC from "@thatopen/components";
2
- import type { ClipperTool } from "./clipper-tool";
3
- import type { MeasurementTool } from "./measurement-tool";
4
-
5
- /**
6
- * Unified "Objects" outliner API (W2's new panel). Enumerates every created clip
7
- * plane and every measurement as a flat list of {@link InstanceRow}s with
8
- * per-instance HIDE / DISABLE / DELETE, so W2 can render rows without knowing
9
- * about the individual Clipping / Measurement tools.
10
- *
11
- * Shapes are owned here and produced by each tool's `instances()`; this module
12
- * just merges them and fans the two tools' change events into one.
13
- */
14
- export type InstanceKind = "clip" | "measurement";
15
-
16
- export interface InstanceRow {
17
- /** Stable id (plane id for clips; per-measurement id) — use as the row key. */
18
- id: string;
19
- kind: InstanceKind;
20
- /** "Clip plane" | "Length" | "Area" | "Angle". */
21
- type: string;
22
- /** User-facing row label (includes the measured value for measurements). */
23
- label: string;
24
- /** Currently shown in the scene. */
25
- visible: boolean;
26
- /** Clip planes only: whether the plane is actively cutting. `undefined` for measurements. */
27
- enabled?: boolean;
28
- /** Show/hide this instance without deleting it. */
29
- setVisible(on: boolean): void;
30
- /** Clip planes only: toggle the cut on/off (present only when `enabled` is defined). */
31
- setEnabled?(on: boolean): void;
32
- /** Permanently remove this instance. */
33
- remove(): void;
34
- }
35
-
36
- export interface InspectionInstances {
37
- /** Snapshot of all clip planes + measurements, in that order. */
38
- list(): InstanceRow[];
39
- /** Fires whenever the set or state of instances changes (re-query `list()`). */
40
- readonly onChanged: OBC.Event<void>;
41
- }
42
-
43
- /**
44
- * A toolbar action W2 wires into the new "Inspection" tab. Each `activate()`
45
- * routes through the toolModeManager (exclusive — entering one exits any other
46
- * tool). W2 drives the Select button + active highlighting via
47
- * `toolModeManager.selectMode()` / `getActiveId()` / `onActiveChanged`; each
48
- * action's own `isActive()` is provided for convenience.
49
- */
50
- export interface InspectionAction {
51
- id: string;
52
- label: string;
53
- /** mdi icon name (e.g. "mdi:ruler"). */
54
- icon: string;
55
- activate(): void;
56
- isActive(): boolean;
57
- }
58
-
59
- /**
60
- * Inspection toolbar actions: clip plane + the measurement types/modes. Order is
61
- * the toolbar order. measure-edge/face reuse Length/Area with a forced sub-mode;
62
- * measure-length/area are the plain "free" modes (isActive distinguishes them by
63
- * sub-mode). Volume is a distinct measurer.
64
- */
65
- export const inspectionActions = (
66
- clipperTool: ClipperTool,
67
- measurementTool: MeasurementTool,
68
- ): InspectionAction[] => {
69
- const m = measurementTool;
70
- const isMeasure = (mode: string, sub: string) =>
71
- m.getMode() === mode && m.getSubMode() === sub;
72
- return [
73
- {
74
- id: "clip-plane",
75
- label: "Clip plane",
76
- icon: "mdi:scissors-cutting",
77
- activate: () => clipperTool.setPlacing(true),
78
- isActive: () => clipperTool.isPlacing(),
79
- },
80
- {
81
- id: "measure-length",
82
- label: "Length",
83
- icon: "mdi:ruler",
84
- activate: () => m.setMode("length", "free"),
85
- isActive: () => isMeasure("length", "free"),
86
- },
87
- {
88
- id: "measure-area",
89
- label: "Area",
90
- icon: "mdi:vector-polygon",
91
- activate: () => m.setMode("area", "free"),
92
- isActive: () => isMeasure("area", "free"),
93
- },
94
- {
95
- id: "measure-angle",
96
- label: "Angle",
97
- icon: "mdi:angle-acute",
98
- activate: () => m.setMode("angle"),
99
- isActive: () => m.getMode() === "angle",
100
- },
101
- {
102
- id: "measure-edge",
103
- label: "Edge",
104
- icon: "mdi:vector-line",
105
- activate: () => m.setMode("length", "edge"),
106
- isActive: () => isMeasure("length", "edge"),
107
- },
108
- {
109
- id: "measure-face",
110
- label: "Face",
111
- icon: "mdi:vector-square",
112
- activate: () => m.setMode("area", "face"),
113
- isActive: () => isMeasure("area", "face"),
114
- },
115
- {
116
- id: "volume",
117
- label: "Volume",
118
- icon: "mdi:cube-outline",
119
- activate: () => m.setMode("volume"),
120
- isActive: () => m.getMode() === "volume",
121
- },
122
- ];
123
- };
124
-
125
- export const inspectionInstances = (
126
- clipperTool: ClipperTool,
127
- measurementTool: MeasurementTool,
128
- ): InspectionInstances => {
129
- const onChanged = new OBC.Event<void>();
130
- clipperTool.onChanged.add(() => onChanged.trigger());
131
- measurementTool.onChanged.add(() => onChanged.trigger());
132
- return {
133
- list: () => [...clipperTool.instances(), ...measurementTool.instances()],
134
- onChanged,
135
- };
136
- };