@thatopen/services 0.3.1 → 0.3.2
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 +3 -0
- package/dist/built-in/index.d.ts +22 -2
- package/dist/cli.js +3 -3
- package/docs/ai-quickstart.md +86 -0
- package/docs/builtin/paths.json +74 -74
- package/docs/cli/create.md +4 -3
- package/package.json +1 -1
- package/src/built-in/index.ts +22 -2
- package/src/cli/templates/app/src/app.ts +37 -37
- package/src/cli/templates/app/src/assets/tool-placeholder.ts +50 -50
- package/src/cli/templates/app/src/main.ts +223 -340
- package/src/cli/templates/app/src/setups/camera-tools.ts +63 -63
- package/src/cli/templates/app/src/setups/clipper-tool.ts +20 -1
- package/src/cli/templates/app/src/setups/clipper.ts +75 -75
- package/src/cli/templates/app/src/setups/index.ts +23 -33
- package/src/cli/templates/app/src/setups/inspection.ts +136 -136
- package/src/cli/templates/app/src/setups/measurement-tool.ts +19 -1
- package/src/cli/templates/app/src/setups/measurements.ts +133 -133
- package/src/cli/templates/app/src/setups/reality-capture/lib/hidden-tiles-plugin.ts +88 -88
- package/src/cli/templates/app/src/setups/reality-capture/lib/point-tile-plugin.ts +166 -166
- package/src/cli/templates/app/src/setups/reality-capture/lib/render-frame.ts +31 -31
- package/src/cli/templates/app/src/setups/reality-capture/lib/splat-tile-plugin.ts +51 -51
- package/src/cli/templates/app/src/setups/reality-capture-viewer.ts +1383 -1383
- package/src/cli/templates/app/src/setups/right-sidebar.ts +178 -178
- package/src/cli/templates/app/src/setups/styles-panel.ts +110 -110
- package/src/cli/templates/app/src/setups/tool-mode-manager.ts +15 -9
- package/src/cli/templates/app/src/setups/tool-mode.ts +23 -23
- package/src/cli/templates/app/src/setups/active-tool-hud.ts +0 -69
- package/src/cli/templates/app/src/setups/clipper-panel.ts +0 -167
- package/src/cli/templates/app/src/setups/commands-panel.ts +0 -239
- package/src/cli/templates/app/src/setups/data-table-panel.ts +0 -2224
- package/src/cli/templates/app/src/setups/file-format-icons.ts +0 -95
- package/src/cli/templates/app/src/setups/files-panel.ts +0 -1503
- package/src/cli/templates/app/src/setups/fps-indicator.ts +0 -63
- package/src/cli/templates/app/src/setups/graphics-panel.ts +0 -490
- package/src/cli/templates/app/src/setups/measurement-settings-panel.ts +0 -161
- package/src/cli/templates/app/src/setups/model-tree.ts +0 -797
- package/src/cli/templates/app/src/setups/objects-panel.ts +0 -127
- package/src/cli/templates/app/src/setups/properties-panel.ts +0 -1048
- package/src/cli/templates/app/src/setups/settings-panel.ts +0 -94
- package/src/cli/templates/app/src/setups/visibility-toolbar.ts +0 -498
- package/src/cli/templates/app/src/setups/walkthrough.ts +0 -528
|
@@ -1,63 +1,63 @@
|
|
|
1
|
-
import * as THREE from "three";
|
|
2
|
-
import * as OBC from "@thatopen/components";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Camera tools for the viewer toolbar. Returns controllers matching the
|
|
6
|
-
* toolbar's integration contract:
|
|
7
|
-
* - `fitAll` — ACTION: fit the camera to the whole loaded model.
|
|
8
|
-
* - `orthoToggle` — TOGGLE: switch the projection Perspective ⇄ Orthographic.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
12
|
-
const firstWorld = (components: OBC.Components): any =>
|
|
13
|
-
[...components.get(OBC.Worlds).list.values()][0];
|
|
14
|
-
|
|
15
|
-
export interface CameraTools {
|
|
16
|
-
fitAll: { label: string; icon: string; run(): Promise<void> };
|
|
17
|
-
orthoToggle: {
|
|
18
|
-
label: string;
|
|
19
|
-
icon: string;
|
|
20
|
-
active(): boolean;
|
|
21
|
-
activate(): Promise<void>;
|
|
22
|
-
deactivate(): Promise<void>;
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export const cameraTools = (components: OBC.Components): CameraTools => {
|
|
27
|
-
const fragments = components.get(OBC.FragmentsManager);
|
|
28
|
-
|
|
29
|
-
return {
|
|
30
|
-
fitAll: {
|
|
31
|
-
label: "Zoom to fit",
|
|
32
|
-
icon: "mdi:fit-to-page-outline",
|
|
33
|
-
async run() {
|
|
34
|
-
const controls = firstWorld(components)?.camera?.controls;
|
|
35
|
-
if (!controls) return;
|
|
36
|
-
// Union every loaded model's bounding box, then frame its sphere —
|
|
37
|
-
// fitToSphere preserves the current view direction (no rotation snap).
|
|
38
|
-
const box = new THREE.Box3();
|
|
39
|
-
for (const model of fragments.list.values()) {
|
|
40
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
41
|
-
const b = (model as any).box as THREE.Box3 | undefined;
|
|
42
|
-
if (b && !b.isEmpty()) box.union(b);
|
|
43
|
-
}
|
|
44
|
-
if (box.isEmpty()) return;
|
|
45
|
-
const sphere = box.getBoundingSphere(new THREE.Sphere());
|
|
46
|
-
await controls.fitToSphere(sphere, true);
|
|
47
|
-
},
|
|
48
|
-
},
|
|
49
|
-
orthoToggle: {
|
|
50
|
-
label: "Orthographic",
|
|
51
|
-
icon: "mdi:angle-right",
|
|
52
|
-
active() {
|
|
53
|
-
return firstWorld(components)?.camera?.projection?.current === "Orthographic";
|
|
54
|
-
},
|
|
55
|
-
async activate() {
|
|
56
|
-
await firstWorld(components)?.camera?.projection?.set?.("Orthographic");
|
|
57
|
-
},
|
|
58
|
-
async deactivate() {
|
|
59
|
-
await firstWorld(components)?.camera?.projection?.set?.("Perspective");
|
|
60
|
-
},
|
|
61
|
-
},
|
|
62
|
-
};
|
|
63
|
-
};
|
|
1
|
+
import * as THREE from "three";
|
|
2
|
+
import * as OBC from "@thatopen/components";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Camera tools for the viewer toolbar. Returns controllers matching the
|
|
6
|
+
* toolbar's integration contract:
|
|
7
|
+
* - `fitAll` — ACTION: fit the camera to the whole loaded model.
|
|
8
|
+
* - `orthoToggle` — TOGGLE: switch the projection Perspective ⇄ Orthographic.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
12
|
+
const firstWorld = (components: OBC.Components): any =>
|
|
13
|
+
[...components.get(OBC.Worlds).list.values()][0];
|
|
14
|
+
|
|
15
|
+
export interface CameraTools {
|
|
16
|
+
fitAll: { label: string; icon: string; run(): Promise<void> };
|
|
17
|
+
orthoToggle: {
|
|
18
|
+
label: string;
|
|
19
|
+
icon: string;
|
|
20
|
+
active(): boolean;
|
|
21
|
+
activate(): Promise<void>;
|
|
22
|
+
deactivate(): Promise<void>;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const cameraTools = (components: OBC.Components): CameraTools => {
|
|
27
|
+
const fragments = components.get(OBC.FragmentsManager);
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
fitAll: {
|
|
31
|
+
label: "Zoom to fit",
|
|
32
|
+
icon: "mdi:fit-to-page-outline",
|
|
33
|
+
async run() {
|
|
34
|
+
const controls = firstWorld(components)?.camera?.controls;
|
|
35
|
+
if (!controls) return;
|
|
36
|
+
// Union every loaded model's bounding box, then frame its sphere —
|
|
37
|
+
// fitToSphere preserves the current view direction (no rotation snap).
|
|
38
|
+
const box = new THREE.Box3();
|
|
39
|
+
for (const model of fragments.list.values()) {
|
|
40
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
41
|
+
const b = (model as any).box as THREE.Box3 | undefined;
|
|
42
|
+
if (b && !b.isEmpty()) box.union(b);
|
|
43
|
+
}
|
|
44
|
+
if (box.isEmpty()) return;
|
|
45
|
+
const sphere = box.getBoundingSphere(new THREE.Sphere());
|
|
46
|
+
await controls.fitToSphere(sphere, true);
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
orthoToggle: {
|
|
50
|
+
label: "Orthographic",
|
|
51
|
+
icon: "mdi:angle-right",
|
|
52
|
+
active() {
|
|
53
|
+
return firstWorld(components)?.camera?.projection?.current === "Orthographic";
|
|
54
|
+
},
|
|
55
|
+
async activate() {
|
|
56
|
+
await firstWorld(components)?.camera?.projection?.set?.("Orthographic");
|
|
57
|
+
},
|
|
58
|
+
async deactivate() {
|
|
59
|
+
await firstWorld(components)?.camera?.projection?.set?.("Perspective");
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
};
|
|
@@ -89,7 +89,7 @@ export const ensureSectionStyle = (components: OBC.Components) => {
|
|
|
89
89
|
});
|
|
90
90
|
};
|
|
91
91
|
|
|
92
|
-
|
|
92
|
+
const clipperToolImpl = (components: OBC.Components): ClipperTool => {
|
|
93
93
|
const clipper = components.get(OBC.Clipper);
|
|
94
94
|
clipper.localClippingPlanes = true; // before any plane is created
|
|
95
95
|
clipper.enabled = true;
|
|
@@ -752,3 +752,22 @@ export const clipperTool = (components: OBC.Components): ClipperTool => {
|
|
|
752
752
|
isStylingVisible: () => styler.visible,
|
|
753
753
|
};
|
|
754
754
|
};
|
|
755
|
+
|
|
756
|
+
// Real OBC.Component wrapper so the ClipperTool wrapper is a per-`components`
|
|
757
|
+
// singleton, resolved by UUID via `components.get(...)`. Because `components` is
|
|
758
|
+
// a single shared instance across bundle boundaries, the toolbar built-in and
|
|
759
|
+
// the app's side panels (clipper-panel, objects) all get the SAME ClipperTool.
|
|
760
|
+
export class ClipperToolComponent extends OBC.Component {
|
|
761
|
+
static readonly uuid = "9b2c7e10-3a4f-4c21-8b6d-1f0a2c3d4e51" as const;
|
|
762
|
+
enabled = true;
|
|
763
|
+
readonly tool: ClipperTool;
|
|
764
|
+
constructor(components: OBC.Components) {
|
|
765
|
+
super(components);
|
|
766
|
+
components.add(ClipperToolComponent.uuid, this);
|
|
767
|
+
this.tool = clipperToolImpl(components);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
/** Per-Components singleton accessor for the clipping tool. */
|
|
772
|
+
export const clipperTool = (components: OBC.Components): ClipperTool =>
|
|
773
|
+
components.get(ClipperToolComponent).tool;
|
|
@@ -1,75 +1,75 @@
|
|
|
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
|
+
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,33 +1,23 @@
|
|
|
1
|
-
export * from "./ui-manager";
|
|
2
|
-
export * from "./cloud-runner";
|
|
3
|
-
export * from "./
|
|
4
|
-
export * from "./
|
|
5
|
-
export * from "./
|
|
6
|
-
export * from "./
|
|
7
|
-
export * from "./
|
|
8
|
-
export * from "./
|
|
9
|
-
export * from "./
|
|
10
|
-
export * from "./
|
|
11
|
-
export * from "./
|
|
12
|
-
export * from "./
|
|
13
|
-
export * from "./tool
|
|
14
|
-
export * from "./
|
|
15
|
-
export * from "./
|
|
16
|
-
export * from "./
|
|
17
|
-
export * from "./
|
|
18
|
-
export * from "./
|
|
19
|
-
export * from "./
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
export * from "./navigation-gizmo";
|
|
25
|
-
export * from "./exploded-view";
|
|
26
|
-
export * from "./measurement-tool";
|
|
27
|
-
export * from "./measurement-panel";
|
|
28
|
-
export * from "./data-table-panel";
|
|
29
|
-
export * from "./walkthrough";
|
|
30
|
-
export * from "./tool-mode-manager";
|
|
31
|
-
// reality-capture-viewer is intentionally NOT re-exported here: it spins up
|
|
32
|
-
// decode workers at module load, so it must be LAZY-imported (dynamic import in
|
|
33
|
-
// files-panel's .3tz "View" handler), never pulled onto the app boot path.
|
|
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 +1,136 @@
|
|
|
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
|
-
};
|
|
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
|
+
};
|