@ifc-lite/viewer 1.19.0 → 1.19.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +15 -14
- package/.turbo/turbo-typecheck.log +1 -1
- package/CHANGELOG.md +8 -0
- package/dist/assets/basketViewActivator-CA2CTcVo.js +71 -0
- package/dist/assets/{bcf-DOG9_WPX.js → bcf-4K724hw0.js} +18 -18
- package/dist/assets/{exporters-BraHBeoi.js → exporters-xbXqEDlO.js} +53 -46
- package/dist/assets/ids-2WdONLlu.js +2033 -0
- package/dist/assets/index-BXeEKqJG.css +1 -0
- package/dist/assets/{index-BOi3BuUI.js → index-D8Epw-e7.js} +48072 -30928
- package/dist/assets/{native-bridge-CpBeOPQa.js → native-bridge-DKmx1z95.js} +2 -2
- package/dist/assets/{sandbox-Baez7n-t.js → sandbox-tccwm5Bo.js} +547 -529
- package/dist/assets/{server-client-BB6cMAXE.js → server-client-LoWPK1N2.js} +1 -1
- package/dist/assets/three-CDRZThFA.js +4057 -0
- package/dist/assets/{wasm-bridge-CAYCUHbE.js → wasm-bridge-BsJGgPMs.js} +1 -1
- package/dist/index.html +8 -7
- package/dist/samples/building-architecture.ifc +453 -0
- package/dist/samples/hello-wall.ifc +1054 -0
- package/dist/samples/infra-bridge.ifc +962 -0
- package/package.json +7 -2
- package/public/samples/building-architecture.ifc +453 -0
- package/public/samples/hello-wall.ifc +1054 -0
- package/public/samples/infra-bridge.ifc +962 -0
- package/src/App.tsx +37 -3
- package/src/components/mcp/HeroScene.tsx +876 -0
- package/src/components/mcp/McpLanding.tsx +1318 -0
- package/src/components/mcp/McpPlayground.tsx +524 -0
- package/src/components/mcp/PlaygroundChat.tsx +1097 -0
- package/src/components/mcp/PlaygroundViewer.tsx +815 -0
- package/src/components/mcp/README.md +171 -0
- package/src/components/mcp/data.ts +659 -0
- package/src/components/mcp/playground-dispatcher.ts +1649 -0
- package/src/components/mcp/playground-files.ts +107 -0
- package/src/components/mcp/playground-uploads.ts +122 -0
- package/src/components/mcp/types.ts +65 -0
- package/src/components/mcp/use-mcp-page.ts +109 -0
- package/src/components/viewer/MainToolbar.tsx +19 -0
- package/src/components/viewer/ViewportContainer.tsx +35 -4
- package/src/generated/mcp-catalog.json +82 -0
- package/vite.config.ts +6 -0
- package/dist/assets/basketViewActivator-RZy5c3Td.js +0 -1
- package/dist/assets/ids-DQ5jY0E8.js +0 -1
- package/dist/assets/index-0XpVr_S5.css +0 -1
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* playground-files.ts — virtual file store for the playground.
|
|
7
|
+
*
|
|
8
|
+
* Tools that "write a file" (bcf_export, model_save, export_ifc / csv /
|
|
9
|
+
* json) DON'T trigger a browser download — that would be a surprising
|
|
10
|
+
* privacy issue and against the user's explicit "never auto-download"
|
|
11
|
+
* rule. Instead they push the artifact into this store, which a
|
|
12
|
+
* Downloads panel in the playground sidebar lists with a per-row
|
|
13
|
+
* "Download" button. The actual `Blob` → `<a download>` click only
|
|
14
|
+
* happens when the user presses that button.
|
|
15
|
+
*/
|
|
16
|
+
import { useEffect, useState } from 'react';
|
|
17
|
+
|
|
18
|
+
export interface PlaygroundFile {
|
|
19
|
+
/** Stable id used by tools to refer back to a written artifact. */
|
|
20
|
+
id: string;
|
|
21
|
+
/** Suggested filename used when the user clicks Download. */
|
|
22
|
+
filename: string;
|
|
23
|
+
/** MIME type for the download Blob. */
|
|
24
|
+
mimeType: string;
|
|
25
|
+
/** Bytes — read once, cheap. */
|
|
26
|
+
size: number;
|
|
27
|
+
/** The data. */
|
|
28
|
+
blob: Blob;
|
|
29
|
+
/** ms since epoch. */
|
|
30
|
+
createdAt: number;
|
|
31
|
+
/** Tool that produced it (`bcf_export`, `model_save`, …). */
|
|
32
|
+
source: string;
|
|
33
|
+
/** Free-form line shown under the filename in the UI. */
|
|
34
|
+
description?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
class FileStore {
|
|
38
|
+
private files: PlaygroundFile[] = [];
|
|
39
|
+
private listeners = new Set<() => void>();
|
|
40
|
+
private nextId = 1;
|
|
41
|
+
|
|
42
|
+
add(input: Omit<PlaygroundFile, 'id' | 'createdAt'>): PlaygroundFile {
|
|
43
|
+
const file: PlaygroundFile = {
|
|
44
|
+
...input,
|
|
45
|
+
id: `pg-file-${this.nextId++}`,
|
|
46
|
+
createdAt: Date.now(),
|
|
47
|
+
};
|
|
48
|
+
this.files = [file, ...this.files];
|
|
49
|
+
this.notify();
|
|
50
|
+
return file;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
list(): PlaygroundFile[] {
|
|
54
|
+
return this.files;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
remove(id: string): void {
|
|
58
|
+
this.files = this.files.filter((f) => f.id !== id);
|
|
59
|
+
this.notify();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
clear(): void {
|
|
63
|
+
this.files = [];
|
|
64
|
+
this.notify();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** User-triggered. Synthesises an <a download> click — never called by
|
|
68
|
+
* tool code, only by the explicit Download button. */
|
|
69
|
+
download(id: string): void {
|
|
70
|
+
const file = this.files.find((f) => f.id === id);
|
|
71
|
+
if (!file) return;
|
|
72
|
+
const url = URL.createObjectURL(file.blob);
|
|
73
|
+
const a = document.createElement('a');
|
|
74
|
+
a.href = url;
|
|
75
|
+
a.download = file.filename;
|
|
76
|
+
a.style.display = 'none';
|
|
77
|
+
document.body.appendChild(a);
|
|
78
|
+
a.click();
|
|
79
|
+
a.remove();
|
|
80
|
+
// Revoke after a tick so the browser actually fetched the blob.
|
|
81
|
+
setTimeout(() => URL.revokeObjectURL(url), 1000);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
subscribe(listener: () => void): () => void {
|
|
85
|
+
this.listeners.add(listener);
|
|
86
|
+
return () => this.listeners.delete(listener);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
private notify(): void {
|
|
90
|
+
for (const l of this.listeners) l();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export const playgroundFiles = new FileStore();
|
|
95
|
+
|
|
96
|
+
/** React hook for components that want to render the file list reactively. */
|
|
97
|
+
export function usePlaygroundFiles(): PlaygroundFile[] {
|
|
98
|
+
const [files, setFiles] = useState<PlaygroundFile[]>(() => playgroundFiles.list());
|
|
99
|
+
useEffect(() => playgroundFiles.subscribe(() => setFiles(playgroundFiles.list())), []);
|
|
100
|
+
return files;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function formatBytes(bytes: number): string {
|
|
104
|
+
if (bytes >= 1024 * 1024) return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
|
|
105
|
+
if (bytes >= 1024) return (bytes / 1024).toFixed(0) + ' KB';
|
|
106
|
+
return bytes + ' B';
|
|
107
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* playground-uploads.ts — virtual file system for INPUTS the user attaches
|
|
7
|
+
* to a chat turn (IDS specs, side IFC files for diff, BCF imports later).
|
|
8
|
+
*
|
|
9
|
+
* Mirrors the playground-files.ts shape but for the other direction: the
|
|
10
|
+
* user drops a `.ids` (or any text file) onto the chat textarea, we cache
|
|
11
|
+
* it here, and tools that take a `*_path` argument (ids_validate,
|
|
12
|
+
* ids_explain) resolve the path through this store BEFORE asking the agent
|
|
13
|
+
* to inline the XML. That lifts the "the playground can't read disk" wart
|
|
14
|
+
* and makes the chat experience continuous.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { useEffect, useState } from 'react';
|
|
18
|
+
|
|
19
|
+
export interface UploadedFile {
|
|
20
|
+
/** The original filename, used as the lookup key (paths are normalised
|
|
21
|
+
* to the basename so the agent can pass `./foo.ids` or just `foo.ids`). */
|
|
22
|
+
name: string;
|
|
23
|
+
/** MIME type as the browser saw it. May be empty for `.ids`. */
|
|
24
|
+
mimeType: string;
|
|
25
|
+
/** Bytes — for sizing the chip and bounding what we accept. */
|
|
26
|
+
size: number;
|
|
27
|
+
/** Text content if the file is text/* — this is the path we use for
|
|
28
|
+
* ids_validate. Binaries store an empty string here and put bytes in
|
|
29
|
+
* `bytes` (future use; v1 only handles text). */
|
|
30
|
+
text: string;
|
|
31
|
+
/** Wall-clock when the user attached it. */
|
|
32
|
+
uploadedAt: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
class UploadStore {
|
|
36
|
+
private uploads: UploadedFile[] = [];
|
|
37
|
+
private listeners = new Set<() => void>();
|
|
38
|
+
|
|
39
|
+
/** Read the file as text and stash it. Returns the entry.
|
|
40
|
+
*
|
|
41
|
+
* We only decode known-text formats — `.bcfzip` and other binaries
|
|
42
|
+
* are zip archives whose .text() decode would chew through tens of
|
|
43
|
+
* megabytes on the main thread for no user benefit (the playground
|
|
44
|
+
* has no read path for binary attachments yet). */
|
|
45
|
+
async add(file: File): Promise<UploadedFile> {
|
|
46
|
+
const name = file.name.split(/[\\/]/).pop() ?? file.name;
|
|
47
|
+
const mimeType = file.type || guessMimeType(name);
|
|
48
|
+
const text = isTextLike(name, mimeType) ? await file.text() : '';
|
|
49
|
+
const entry: UploadedFile = {
|
|
50
|
+
name,
|
|
51
|
+
mimeType,
|
|
52
|
+
size: file.size,
|
|
53
|
+
text,
|
|
54
|
+
uploadedAt: Date.now(),
|
|
55
|
+
};
|
|
56
|
+
// De-dup by basename — re-attaching with the same name overwrites.
|
|
57
|
+
this.uploads = [entry, ...this.uploads.filter((u) => u.name !== name)];
|
|
58
|
+
this.notify();
|
|
59
|
+
return entry;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Resolve a path-ish string to an upload. Tolerates absolute paths,
|
|
63
|
+
* ./relative, and bare filenames. */
|
|
64
|
+
resolve(pathOrName: string): UploadedFile | null {
|
|
65
|
+
if (!pathOrName) return null;
|
|
66
|
+
const base = pathOrName.split(/[\\/]/).pop() ?? pathOrName;
|
|
67
|
+
return this.uploads.find((u) => u.name === base) ?? null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
list(): UploadedFile[] {
|
|
71
|
+
return this.uploads;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
remove(name: string): void {
|
|
75
|
+
this.uploads = this.uploads.filter((u) => u.name !== name);
|
|
76
|
+
this.notify();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
clear(): void {
|
|
80
|
+
this.uploads = [];
|
|
81
|
+
this.notify();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
subscribe(listener: () => void): () => void {
|
|
85
|
+
this.listeners.add(listener);
|
|
86
|
+
return () => this.listeners.delete(listener);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
private notify(): void {
|
|
90
|
+
for (const l of this.listeners) l();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export const playgroundUploads = new UploadStore();
|
|
95
|
+
|
|
96
|
+
export function usePlaygroundUploads(): UploadedFile[] {
|
|
97
|
+
const [uploads, setUploads] = useState<UploadedFile[]>(() => playgroundUploads.list());
|
|
98
|
+
useEffect(() => playgroundUploads.subscribe(() => setUploads(playgroundUploads.list())), []);
|
|
99
|
+
return uploads;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Whether we should decode this attachment as text. STEP files (.ifc) and
|
|
103
|
+
* IDS specs are text under the hood; .bcfzip / arbitrary octet-stream
|
|
104
|
+
* attachments are zip archives we only ever surface as references. */
|
|
105
|
+
function isTextLike(name: string, mimeType: string): boolean {
|
|
106
|
+
if (mimeType.startsWith('text/')) return true;
|
|
107
|
+
if (mimeType === 'application/json' || mimeType === 'application/xml' || mimeType === 'application/xhtml+xml') return true;
|
|
108
|
+
const ext = name.toLowerCase().split('.').pop();
|
|
109
|
+
return ext === 'ifc' || ext === 'ids' || ext === 'csv' || ext === 'tsv' || ext === 'xml' || ext === 'json' || ext === 'txt' || ext === 'md';
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function guessMimeType(name: string): string {
|
|
113
|
+
const ext = name.toLowerCase().split('.').pop() ?? '';
|
|
114
|
+
switch (ext) {
|
|
115
|
+
case 'ids': return 'application/xml';
|
|
116
|
+
case 'xml': return 'application/xml';
|
|
117
|
+
case 'json': return 'application/json';
|
|
118
|
+
case 'csv': return 'text/csv';
|
|
119
|
+
case 'txt': return 'text/plain';
|
|
120
|
+
default: return 'application/octet-stream';
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Shared types for the /mcp landing page variants.
|
|
7
|
+
*
|
|
8
|
+
* The catalog shape mirrors what `node packages/mcp/dist/cli.js --dump-tools`
|
|
9
|
+
* is expected to emit. Until that script lands, the landing pages can fall
|
|
10
|
+
* back to `MOCK_CATALOG` from ./data.ts so we can iterate on visuals.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export type ToolScope = 'read' | 'mutate' | 'export';
|
|
14
|
+
|
|
15
|
+
export type ToolCategory =
|
|
16
|
+
| 'Discovery'
|
|
17
|
+
| 'Query'
|
|
18
|
+
| 'Geometry'
|
|
19
|
+
| 'Validation'
|
|
20
|
+
| 'Mutation'
|
|
21
|
+
| 'BCF'
|
|
22
|
+
| 'bSDD'
|
|
23
|
+
| 'Diff'
|
|
24
|
+
| 'Export'
|
|
25
|
+
| 'Viewer';
|
|
26
|
+
|
|
27
|
+
export interface CatalogTool {
|
|
28
|
+
name: string;
|
|
29
|
+
description: string;
|
|
30
|
+
scope: ToolScope;
|
|
31
|
+
category: ToolCategory;
|
|
32
|
+
inputSchema: unknown;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface McpCatalog {
|
|
36
|
+
generatedAt?: string;
|
|
37
|
+
version?: string;
|
|
38
|
+
tools: CatalogTool[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export type McpClientId = 'claude-desktop' | 'cursor' | 'windsurf' | 'vscode' | 'goose';
|
|
42
|
+
|
|
43
|
+
export interface McpClient {
|
|
44
|
+
id: McpClientId;
|
|
45
|
+
name: string;
|
|
46
|
+
/** Short blurb shown under the button title. */
|
|
47
|
+
blurb: string;
|
|
48
|
+
/** Path to a logo image inside /public, or null if we render a glyph. */
|
|
49
|
+
logo?: string;
|
|
50
|
+
/** Optional deep-link URL scheme prefix (cursor://, windsurf://, vscode:). */
|
|
51
|
+
deepLinkPrefix?: string;
|
|
52
|
+
/** Where the user pastes the JSON snippet. */
|
|
53
|
+
configHint: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface McpRecipe {
|
|
57
|
+
id: string;
|
|
58
|
+
title: string;
|
|
59
|
+
/** The actual prompt the user copies. */
|
|
60
|
+
prompt: string;
|
|
61
|
+
/** Comma-separated tool names this recipe likely fans out to. */
|
|
62
|
+
uses: string[];
|
|
63
|
+
/** Visual category for grouping/coloring. */
|
|
64
|
+
family: 'audit' | 'visualize' | 'validate' | 'author' | 'compare' | 'discover';
|
|
65
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Tiny utilities shared by every landing-page variant:
|
|
7
|
+
*
|
|
8
|
+
* • useFonts(href) — injects a Google Fonts <link> only while a /mcp page
|
|
9
|
+
* is mounted, so the global app stays unbothered.
|
|
10
|
+
* • useCopyToClipboard() — returns [copy, isJustCopied] for the install
|
|
11
|
+
* snippets and recipe cards.
|
|
12
|
+
* • useDocumentMeta(title, themeColor)
|
|
13
|
+
* — keeps <title> / theme-color in sync per variant.
|
|
14
|
+
*
|
|
15
|
+
* None of these touch React Suspense / global state — they’re plain
|
|
16
|
+
* useEffect plumbing so the variants can be lifted out of the chooser
|
|
17
|
+
* without surprises.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { useEffect, useState } from 'react';
|
|
21
|
+
|
|
22
|
+
/** Inject a stylesheet <link> while this hook is mounted. Idempotent. */
|
|
23
|
+
export function useFonts(...hrefs: string[]): void {
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
const tags: HTMLLinkElement[] = [];
|
|
26
|
+
for (const href of hrefs) {
|
|
27
|
+
const existing = document.head.querySelector(`link[data-mcp-font="${href}"]`);
|
|
28
|
+
if (existing) {
|
|
29
|
+
// Already injected by another variant — refcount via a data attribute.
|
|
30
|
+
const refs = Number(existing.getAttribute('data-refs') ?? '1') + 1;
|
|
31
|
+
existing.setAttribute('data-refs', String(refs));
|
|
32
|
+
tags.push(existing as HTMLLinkElement);
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
const link = document.createElement('link');
|
|
36
|
+
link.rel = 'stylesheet';
|
|
37
|
+
link.href = href;
|
|
38
|
+
link.setAttribute('data-mcp-font', href);
|
|
39
|
+
link.setAttribute('data-refs', '1');
|
|
40
|
+
document.head.appendChild(link);
|
|
41
|
+
tags.push(link);
|
|
42
|
+
}
|
|
43
|
+
return () => {
|
|
44
|
+
for (const tag of tags) {
|
|
45
|
+
const refs = Number(tag.getAttribute('data-refs') ?? '1') - 1;
|
|
46
|
+
if (refs <= 0) tag.parentNode?.removeChild(tag);
|
|
47
|
+
else tag.setAttribute('data-refs', String(refs));
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
51
|
+
}, [hrefs.join('|')]);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Browser-safe clipboard with a 1.4s "just copied" indicator. */
|
|
55
|
+
export function useCopyToClipboard(): {
|
|
56
|
+
copy: (text: string, key?: string) => Promise<void>;
|
|
57
|
+
copiedKey: string | null;
|
|
58
|
+
} {
|
|
59
|
+
const [copiedKey, setCopiedKey] = useState<string | null>(null);
|
|
60
|
+
return {
|
|
61
|
+
copiedKey,
|
|
62
|
+
copy: async (text: string, key?: string) => {
|
|
63
|
+
try {
|
|
64
|
+
await navigator.clipboard.writeText(text);
|
|
65
|
+
setCopiedKey(key ?? text);
|
|
66
|
+
setTimeout(() => setCopiedKey((curr) => (curr === (key ?? text) ? null : curr)), 1400);
|
|
67
|
+
} catch {
|
|
68
|
+
// Older browsers — fall through silently. The install dialogs always
|
|
69
|
+
// show the snippet anyway so the user can manually select+copy.
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Set <title> + theme-color while mounted; restore on unmount. */
|
|
76
|
+
export function useDocumentMeta(title: string, themeColor?: string): void {
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
const prevTitle = document.title;
|
|
79
|
+
document.title = title;
|
|
80
|
+
let prevTheme: string | null = null;
|
|
81
|
+
let prevThemeExisted = false;
|
|
82
|
+
let themeMeta: HTMLMetaElement | null = null;
|
|
83
|
+
if (themeColor) {
|
|
84
|
+
themeMeta = document.querySelector('meta[name="theme-color"]');
|
|
85
|
+
if (themeMeta) {
|
|
86
|
+
// Track existence separately so a meta tag without `content`
|
|
87
|
+
// doesn't permanently keep our temporary color after unmount.
|
|
88
|
+
prevThemeExisted = themeMeta.hasAttribute('content');
|
|
89
|
+
prevTheme = themeMeta.getAttribute('content');
|
|
90
|
+
themeMeta.setAttribute('content', themeColor);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return () => {
|
|
94
|
+
document.title = prevTitle;
|
|
95
|
+
if (themeMeta) {
|
|
96
|
+
if (prevThemeExisted && prevTheme !== null) themeMeta.setAttribute('content', prevTheme);
|
|
97
|
+
else themeMeta.removeAttribute('content');
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
}, [title, themeColor]);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Smooth-scroll to an in-page anchor; updates the URL hash. */
|
|
104
|
+
export function scrollToAnchor(id: string): void {
|
|
105
|
+
const el = document.getElementById(id);
|
|
106
|
+
if (!el) return;
|
|
107
|
+
el.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
108
|
+
history.replaceState(null, '', `#${id}`);
|
|
109
|
+
}
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
ArrowRight,
|
|
25
25
|
Box,
|
|
26
26
|
HelpCircle,
|
|
27
|
+
Sparkles,
|
|
27
28
|
Loader2,
|
|
28
29
|
Camera,
|
|
29
30
|
Info,
|
|
@@ -1333,6 +1334,24 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
1333
1334
|
|
|
1334
1335
|
{/* Right Side Actions */}
|
|
1335
1336
|
<div className="flex items-center gap-2 ml-2 pl-2 border-l border-zinc-200 dark:border-zinc-700/60">
|
|
1337
|
+
{/* /mcp cross-link — lives in the meta cluster (Settings / Theme /
|
|
1338
|
+
Help) so it shares space with shell-level navigation rather
|
|
1339
|
+
than competing with the modeling tools to its left. */}
|
|
1340
|
+
<Tooltip>
|
|
1341
|
+
<TooltipTrigger asChild>
|
|
1342
|
+
<Button
|
|
1343
|
+
variant="ghost"
|
|
1344
|
+
size="icon"
|
|
1345
|
+
className="rounded-full"
|
|
1346
|
+
onClick={() => navigateToPath('/mcp')}
|
|
1347
|
+
aria-label="Open ifc-lite MCP"
|
|
1348
|
+
>
|
|
1349
|
+
<Sparkles className="!h-[20px] !w-[20px]" />
|
|
1350
|
+
</Button>
|
|
1351
|
+
</TooltipTrigger>
|
|
1352
|
+
<TooltipContent>Drive ifc-lite from any LLM (MCP)</TooltipContent>
|
|
1353
|
+
</Tooltip>
|
|
1354
|
+
|
|
1336
1355
|
{desktopShell ? (
|
|
1337
1356
|
<Tooltip>
|
|
1338
1357
|
<TooltipTrigger asChild>
|
|
@@ -22,7 +22,7 @@ import { cacheFileBlobs, formatFileSize, getCachedFile, getRecentFiles, recordRe
|
|
|
22
22
|
import { isTauri } from '@/lib/platform';
|
|
23
23
|
import { toast } from '@/components/ui/toast';
|
|
24
24
|
import { describeUnsupportedFormat } from '@/hooks/ingest/pointCloudIngest';
|
|
25
|
-
import { Upload, MousePointer, Layers, Info, Command, AlertTriangle, ChevronDown, ExternalLink, Plus, Clock3 } from 'lucide-react';
|
|
25
|
+
import { Upload, MousePointer, Layers, Info, Command, AlertTriangle, ChevronDown, ExternalLink, Plus, Clock3, Sparkles, ArrowUpRight } from 'lucide-react';
|
|
26
26
|
import type { MeshData, CoordinateInfo, GeometryResult, PointCloudAsset } from '@ifc-lite/geometry';
|
|
27
27
|
import { type IfcDataStore } from '@ifc-lite/parser';
|
|
28
28
|
import { getEffectiveGeoreference } from '@/lib/geo/effective-georef';
|
|
@@ -735,10 +735,18 @@ export function ViewportContainer() {
|
|
|
735
735
|
IFClite
|
|
736
736
|
</h2>
|
|
737
737
|
<p className="text-zinc-500 dark:text-[#565f89] font-mono text-sm text-center mb-8 border-b border-zinc-200 dark:border-[#3b4261] pb-4 w-full">
|
|
738
|
-
|
|
738
|
+
IFC toolkit for the open web
|
|
739
739
|
</p>
|
|
740
740
|
|
|
741
|
-
{/*
|
|
741
|
+
{/*
|
|
742
|
+
Two-track action area: a primary "open file" track and a
|
|
743
|
+
secondary "drive with LLM" track sit in mirrored slots — same
|
|
744
|
+
width, same vertical rhythm, each followed by its own caption
|
|
745
|
+
line. Reads as one balanced composition instead of a primary
|
|
746
|
+
CTA + a tacked-on link, while keeping the file-open path
|
|
747
|
+
visually dominant via the filled-on-hover treatment.
|
|
748
|
+
*/}
|
|
749
|
+
{/* Track 1 — open / drag */}
|
|
742
750
|
<button
|
|
743
751
|
onClick={async () => {
|
|
744
752
|
if (!webgpu.supported) {
|
|
@@ -774,10 +782,33 @@ export function ViewportContainer() {
|
|
|
774
782
|
<span>{webgpu.checking ? 'Checking WebGPU...' : webgpu.supported ? 'Open .ifc file' : 'WebGPU Required'}</span>
|
|
775
783
|
</button>
|
|
776
784
|
|
|
777
|
-
<p className="mt-
|
|
785
|
+
<p className="mt-2.5 text-[11px] font-mono text-center text-zinc-400 dark:text-[#565f89]">
|
|
778
786
|
{webgpu.supported ? 'or drag & drop anywhere' : 'file upload disabled'}
|
|
779
787
|
</p>
|
|
780
788
|
|
|
789
|
+
{/* Subtle "or" rule — anchors the symmetry between the two tracks */}
|
|
790
|
+
<div className="mt-5 mb-5 w-full flex items-center gap-3 text-[10px] font-mono uppercase tracking-[0.22em] text-zinc-400 dark:text-[#565f89]">
|
|
791
|
+
<span className="h-px flex-1 bg-zinc-200 dark:bg-[#3b4261]" />
|
|
792
|
+
<span>or</span>
|
|
793
|
+
<span className="h-px flex-1 bg-zinc-200 dark:bg-[#3b4261]" />
|
|
794
|
+
</div>
|
|
795
|
+
|
|
796
|
+
{/* Track 2 — agent / MCP. Compact inline pill, self-centred so
|
|
797
|
+
it reads as a meta-link sibling to the primary file-open
|
|
798
|
+
CTA, not a competing full-width button. */}
|
|
799
|
+
<a
|
|
800
|
+
href="/mcp"
|
|
801
|
+
className="group inline-flex self-center items-center gap-1.5 px-3 py-1.5 font-mono text-[11px] border border-dashed border-zinc-300 dark:border-[#3b4261] text-zinc-500 dark:text-[#7a82a5] hover:border-primary hover:text-primary transition-all cursor-pointer"
|
|
802
|
+
>
|
|
803
|
+
<Sparkles className="h-3 w-3 transition-transform group-hover:-translate-y-0.5" />
|
|
804
|
+
<span>Drive with any LLM</span>
|
|
805
|
+
<ArrowUpRight className="h-2.5 w-2.5 opacity-60 transition-transform group-hover:translate-x-0.5 group-hover:-translate-y-0.5" />
|
|
806
|
+
</a>
|
|
807
|
+
|
|
808
|
+
<p className="mt-1.5 text-[10px] font-mono text-center text-zinc-400 dark:text-[#565f89]">
|
|
809
|
+
via MCP · install or try the playground
|
|
810
|
+
</p>
|
|
811
|
+
|
|
781
812
|
{recentFiles.length > 0 && (
|
|
782
813
|
<div className="mt-6 w-full border-t border-zinc-200 dark:border-[#3b4261] pt-4">
|
|
783
814
|
<div className="mb-3 flex items-center gap-2 text-xs font-mono uppercase tracking-[0.2em] text-zinc-400 dark:text-[#565f89]">
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
{
|
|
2
|
+
"generatedAt": "2026-05-03T09:30:00Z",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"tools": [
|
|
5
|
+
{ "name": "model_info", "category": "Discovery", "scope": "read", "description": "Schema, entity counts, units, georeferencing — the at-a-glance summary of a loaded IFC.", "inputSchema": { "type": "object", "properties": { "model_id": { "type": "string" } } } },
|
|
6
|
+
{ "name": "model_list", "category": "Discovery", "scope": "read", "description": "List every model loaded in the current MCP session.", "inputSchema": { "type": "object" } },
|
|
7
|
+
{ "name": "model_load", "category": "Discovery", "scope": "mutate", "description": "Load an additional .ifc from disk into the federated session.", "inputSchema": { "type": "object", "required": ["file_path"], "properties": { "file_path": { "type": "string" }, "model_id": { "type": "string" } } } },
|
|
8
|
+
{ "name": "model_unload", "category": "Discovery", "scope": "mutate", "description": "Drop a model from the registry; frees memory.", "inputSchema": { "type": "object", "required": ["model_id"], "properties": { "model_id": { "type": "string" } } } },
|
|
9
|
+
{ "name": "schema_describe", "category": "Discovery", "scope": "read", "description": "Attributes, parent class, and inheritance for any IfcType — useful before mutating.", "inputSchema": { "type": "object", "required": ["type"], "properties": { "type": { "type": "string" }, "include_inherited": { "type": "boolean" } } } },
|
|
10
|
+
|
|
11
|
+
{ "name": "query_entities", "category": "Query", "scope": "read", "description": "Type + property filters with pagination. Returns expressId, GlobalId, name, type for each match.", "inputSchema": { "type": "object", "properties": { "type": { "type": "string" }, "limit": { "type": "integer" } } } },
|
|
12
|
+
{ "name": "count_entities", "category": "Query", "scope": "read", "description": "Group counts by type, storey, or material — histogram form, not the full set.", "inputSchema": { "type": "object", "properties": { "group_by": { "type": "string", "enum": ["type", "storey", "material"] } } } },
|
|
13
|
+
{ "name": "get_entity", "category": "Query", "scope": "read", "description": "Full attributes + property sets for one entity by GlobalId or expressId.", "inputSchema": { "type": "object", "properties": { "global_id": { "type": "string" }, "express_id": { "type": "integer" } } } },
|
|
14
|
+
{ "name": "get_entities_bulk", "category": "Query", "scope": "read", "description": "Batched get_entity for up to 200 ids at once.", "inputSchema": { "type": "object", "required": ["global_ids"], "properties": { "global_ids": { "type": "array", "items": { "type": "string" } } } } },
|
|
15
|
+
{ "name": "spatial_hierarchy", "category": "Query", "scope": "read", "description": "Project → site → building → storey → space tree, with element counts at each node.", "inputSchema": { "type": "object" } },
|
|
16
|
+
{ "name": "containment_chain", "category": "Query", "scope": "read", "description": "Walk up the spatial chain for one entity (storey + parent + grandparent…).", "inputSchema": { "type": "object", "required": ["global_id"] } },
|
|
17
|
+
{ "name": "relationships", "category": "Query", "scope": "read", "description": "Voids, fills, groups, connections — every IfcRel touching this entity.", "inputSchema": { "type": "object", "required": ["global_id"] } },
|
|
18
|
+
{ "name": "properties_unique", "category": "Query", "scope": "read", "description": "Unique values + counts for one property across a type set (perfect for filter UIs).", "inputSchema": { "type": "object", "required": ["type", "pset", "property"] } },
|
|
19
|
+
{ "name": "materials_list", "category": "Query", "scope": "read", "description": "Distinct materials across the model with usage counts.", "inputSchema": { "type": "object" } },
|
|
20
|
+
{ "name": "classifications_list","category": "Query", "scope": "read", "description": "Distinct classification references (system + identification) and how often each is used.", "inputSchema": { "type": "object" } },
|
|
21
|
+
{ "name": "georeferencing", "category": "Query", "scope": "read", "description": "MapConversion, projected CRS, project north, true north — the geo handshake.", "inputSchema": { "type": "object" } },
|
|
22
|
+
{ "name": "units", "category": "Query", "scope": "read", "description": "Length unit scale + the unit assignments declared in the file.", "inputSchema": { "type": "object" } },
|
|
23
|
+
|
|
24
|
+
{ "name": "geometry_bbox", "category": "Geometry", "scope": "read", "description": "Per-entity axis-aligned bounding box (read from quantity sets when available).", "inputSchema": { "type": "object", "required": ["global_id"] } },
|
|
25
|
+
{ "name": "geometry_volume", "category": "Geometry", "scope": "read", "description": "Net/gross volume in m³ for a single entity or a type aggregate.", "inputSchema": { "type": "object", "required": ["global_id"] } },
|
|
26
|
+
{ "name": "geometry_area", "category": "Geometry", "scope": "read", "description": "Surface area for an entity (front/side/footprint depending on what the IFC carries).", "inputSchema": { "type": "object", "required": ["global_id"] } },
|
|
27
|
+
|
|
28
|
+
{ "name": "model_audit", "category": "Validation", "scope": "read", "description": "Out-of-the-box health score + a list of issues (missing GlobalIds, broken refs, orphan entities).", "inputSchema": { "type": "object" } },
|
|
29
|
+
{ "name": "ids_validate", "category": "Validation", "scope": "read", "description": "Run a buildingSMART IDS spec against the loaded model. Per-spec pass/fail with offending entities.", "inputSchema": { "type": "object", "required": ["ids_path"], "properties": { "ids_path": { "type": "string" } } } },
|
|
30
|
+
{ "name": "ids_explain", "category": "Validation", "scope": "read", "description": "Parse + summarize an IDS file in plain language — what each spec asks for, in what order.", "inputSchema": { "type": "object", "required": ["ids_path"] } },
|
|
31
|
+
|
|
32
|
+
{ "name": "entity_set_property", "category": "Mutation", "scope": "mutate", "description": "Queue a Pset.property write on one entity. Persist later via export_ifc / model_save.", "inputSchema": { "type": "object", "required": ["pset", "name"] } },
|
|
33
|
+
{ "name": "entity_delete_property", "category": "Mutation","scope": "mutate", "description": "Queue a property removal from a Pset. Reversible via mutation_undo.", "inputSchema": { "type": "object", "required": ["pset", "name"] } },
|
|
34
|
+
{ "name": "entity_set_attribute","category": "Mutation", "scope": "mutate", "description": "Set Name, Description, ObjectType, or Tag on an entity.", "inputSchema": { "type": "object", "required": ["attribute", "value"] } },
|
|
35
|
+
{ "name": "entity_create", "category": "Mutation", "scope": "mutate", "description": "Create a new IFC entity with raw STEP attributes and get back its expressId.", "inputSchema": { "type": "object", "required": ["type"] } },
|
|
36
|
+
{ "name": "entity_delete", "category": "Mutation", "scope": "mutate", "description": "Delete an entity by expressId/GlobalId. Caller is responsible for cascading rels.", "inputSchema": { "type": "object" } },
|
|
37
|
+
{ "name": "mutation_batch", "category": "Mutation", "scope": "mutate", "description": "Apply N mutation ops in order, returning per-step results.", "inputSchema": { "type": "object", "required": ["operations"] } },
|
|
38
|
+
{ "name": "mutation_diff", "category": "Mutation", "scope": "read", "description": "Inspect every queued mutation vs the original parsed state.", "inputSchema": { "type": "object" } },
|
|
39
|
+
{ "name": "mutation_undo", "category": "Mutation", "scope": "mutate", "description": "Pop the last N pending mutations off the queue.", "inputSchema": { "type": "object", "properties": { "n": { "type": "integer" } } } },
|
|
40
|
+
{ "name": "model_save", "category": "Mutation", "scope": "mutate", "description": "Write the current model (with pending mutations) back to .ifc.", "inputSchema": { "type": "object", "required": ["file_path"] } },
|
|
41
|
+
|
|
42
|
+
{ "name": "bcf_topic_list", "category": "BCF", "scope": "read", "description": "List BCF topics in this session, optionally filtered by status.", "inputSchema": { "type": "object" } },
|
|
43
|
+
{ "name": "bcf_topic_create", "category": "BCF", "scope": "mutate", "description": "Create a topic with title/description/priority and get the GUID for follow-ups.", "inputSchema": { "type": "object", "required": ["title"] } },
|
|
44
|
+
{ "name": "bcf_topic_update", "category": "BCF", "scope": "mutate", "description": "Update topic fields or append a comment.", "inputSchema": { "type": "object", "required": ["guid"] } },
|
|
45
|
+
{ "name": "bcf_topic_close", "category": "BCF", "scope": "mutate", "description": "Mark a topic resolved (status=Closed).", "inputSchema": { "type": "object", "required": ["guid"] } },
|
|
46
|
+
{ "name": "bcf_viewpoint_create","category": "BCF", "scope": "mutate", "description": "Attach a selection-based viewpoint (or full viewer state) to a topic.", "inputSchema": { "type": "object", "required": ["guid"] } },
|
|
47
|
+
{ "name": "bcf_export", "category": "BCF", "scope": "export", "description": "Export the in-memory BCF project as a .bcfzip file.", "inputSchema": { "type": "object", "required": ["file_path"] } },
|
|
48
|
+
|
|
49
|
+
{ "name": "bsdd_search", "category": "bSDD", "scope": "read", "description": "Full-text search the buildingSMART Data Dictionary for classes by keyword.", "inputSchema": { "type": "object", "required": ["query"] } },
|
|
50
|
+
{ "name": "bsdd_class", "category": "bSDD", "scope": "read", "description": "Full bSDD class info for an IFC entity name (definition, parent, properties).", "inputSchema": { "type": "object", "required": ["ifc_type"] } },
|
|
51
|
+
{ "name": "bsdd_property_sets", "category": "bSDD", "scope": "read", "description": "Pset_* groups for an IFC type (Pset_WallCommon for IfcWall, etc.).", "inputSchema": { "type": "object", "required": ["ifc_type"] } },
|
|
52
|
+
{ "name": "bsdd_match", "category": "bSDD", "scope": "read", "description": "Suggest matching bSDD classes for an entity in the loaded model.", "inputSchema": { "type": "object" } },
|
|
53
|
+
|
|
54
|
+
{ "name": "model_diff", "category": "Diff", "scope": "read", "description": "Compare two loaded models. Reports added/removed entities by GlobalId and per-type count deltas.", "inputSchema": { "type": "object", "required": ["a", "b"] } },
|
|
55
|
+
{ "name": "quantity_diff", "category": "Diff", "scope": "read", "description": "Per-entity-type quantity comparison between two models, optionally grouped by storey.", "inputSchema": { "type": "object", "required": ["a", "b"] } },
|
|
56
|
+
|
|
57
|
+
{ "name": "export_ifc", "category": "Export", "scope": "export", "description": "Write the model (with pending mutations) to .ifc/.ifczip on disk.", "inputSchema": { "type": "object", "required": ["file_path"] } },
|
|
58
|
+
{ "name": "export_csv", "category": "Export", "scope": "export", "description": "Tabular property/quantity export. Columns may be Pset_X.Property paths.", "inputSchema": { "type": "object" } },
|
|
59
|
+
{ "name": "export_json", "category": "Export", "scope": "export", "description": "Structured JSON dump of attributes/properties/quantities for a type set.", "inputSchema": { "type": "object" } },
|
|
60
|
+
{ "name": "export_glb", "category": "Export", "scope": "export", "description": "(v0.2) Geometry export to glTF binary — needs the WASM mesh pipeline.", "inputSchema": { "type": "object" } },
|
|
61
|
+
{ "name": "export_ifcx", "category": "Export", "scope": "export", "description": "(v0.2) Export to the new IFCx interchange format.", "inputSchema": { "type": "object" } },
|
|
62
|
+
{ "name": "export_pdf_report", "category": "Export", "scope": "export", "description": "(v0.5) Branded PDF audit report with charts.", "inputSchema": { "type": "object" } },
|
|
63
|
+
|
|
64
|
+
{ "name": "viewer_ask", "category": "Viewer", "scope": "read", "description": "Suggest wording the agent can use to ask the user for permission to open the viewer.", "inputSchema": { "type": "object" } },
|
|
65
|
+
{ "name": "viewer_open", "category": "Viewer", "scope": "read", "description": "Boot the in-process WebGL viewer and return its URL for the user to open.", "inputSchema": { "type": "object" } },
|
|
66
|
+
{ "name": "viewer_close", "category": "Viewer", "scope": "read", "description": "Stop the viewer + clear its selection state.", "inputSchema": { "type": "object" } },
|
|
67
|
+
{ "name": "viewer_status", "category": "Viewer", "scope": "read", "description": "Report whether the viewer is open, on what port, and the current selection.", "inputSchema": { "type": "object" } },
|
|
68
|
+
{ "name": "viewer_colorize", "category": "Viewer", "scope": "read", "description": "Paint a set of entities with a color (hex / rgb / named).", "inputSchema": { "type": "object", "required": ["color"] } },
|
|
69
|
+
{ "name": "viewer_isolate", "category": "Viewer", "scope": "read", "description": "Hide everything except the picked set.", "inputSchema": { "type": "object" } },
|
|
70
|
+
{ "name": "viewer_hide", "category": "Viewer", "scope": "read", "description": "Hide the picked set; everything else stays.", "inputSchema": { "type": "object" } },
|
|
71
|
+
{ "name": "viewer_show", "category": "Viewer", "scope": "read", "description": "Show the picked set (un-hide).", "inputSchema": { "type": "object" } },
|
|
72
|
+
{ "name": "viewer_reset", "category": "Viewer", "scope": "read", "description": "Reset visibility + colors to the model defaults.", "inputSchema": { "type": "object" } },
|
|
73
|
+
{ "name": "viewer_fly_to", "category": "Viewer", "scope": "read", "description": "Frame the camera on a set of entities or a bbox.", "inputSchema": { "type": "object" } },
|
|
74
|
+
{ "name": "viewer_set_section", "category": "Viewer", "scope": "read", "description": "Apply an axis-aligned section plane.", "inputSchema": { "type": "object", "required": ["axis", "position"] } },
|
|
75
|
+
{ "name": "viewer_clear_section","category": "Viewer", "scope": "read", "description": "Remove the active section plane.", "inputSchema": { "type": "object" } },
|
|
76
|
+
{ "name": "viewer_color_by_storey","category": "Viewer", "scope": "read", "description": "Apply a per-storey overlay (built-in viewer preset).", "inputSchema": { "type": "object" } },
|
|
77
|
+
{ "name": "viewer_color_by_property","category": "Viewer", "scope": "read", "description": "Color a type set by property value, returns a legend the agent can describe.", "inputSchema": { "type": "object", "required": ["type", "pset", "property"] } },
|
|
78
|
+
{ "name": "viewer_get_selection","category": "Viewer", "scope": "read", "description": "Return the current selection — type, expressId, GlobalId, name, attributes, materials.", "inputSchema": { "type": "object" } },
|
|
79
|
+
{ "name": "viewer_describe_selection","category": "Viewer","scope": "read", "description": "Kitchen-sink: every section (attributes, properties, quantities, classifications, materials) for the picked entity.", "inputSchema": { "type": "object" } },
|
|
80
|
+
{ "name": "viewer_wait_for_selection","category": "Viewer","scope": "read", "description": "Block until the user picks something in the viewer (or timeout).", "inputSchema": { "type": "object" } }
|
|
81
|
+
]
|
|
82
|
+
}
|
package/vite.config.ts
CHANGED
|
@@ -313,6 +313,12 @@ export default defineConfig({
|
|
|
313
313
|
if (id.includes('/node_modules/apache-arrow/')) return 'arrow';
|
|
314
314
|
if (id.includes('/node_modules/parquet-wasm/')) return 'parquet';
|
|
315
315
|
if (id.includes('/node_modules/cesium/')) return 'cesium';
|
|
316
|
+
// three.js + addons — only the /mcp landing imports them, keep
|
|
317
|
+
// the main viewer / pages off the hook.
|
|
318
|
+
if (
|
|
319
|
+
id.includes('/node_modules/three/') ||
|
|
320
|
+
id.includes('/node_modules/.pnpm/three@')
|
|
321
|
+
) return 'three';
|
|
316
322
|
return undefined;
|
|
317
323
|
},
|
|
318
324
|
},
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{u as o}from"./index-BOi3BuUI.js";import"./cesium-DUOzBlqv.js";import"./arrow-CZ5kQ26f.js";import"./exporters-BraHBeoi.js";import"./bcf-DOG9_WPX.js";import"./zip-DBEtpeu6.js";import"./sandbox-Baez7n-t.js";import"./lens-CSASnhAL.js";import"./drawing-2d-DoxKMqbO.js";import"./server-client-BB6cMAXE.js";import"./ids-DQ5jY0E8.js";const n=700;function v(s){const e=o.getState(),i=e.basketViews.find(t=>t.id===s);if(i){if(e.setDrawing2D(null),e.setDrawing2DPanelVisible(!1),e.updateDrawing2DDisplayOptions({show3DOverlay:!1}),e.clearEntitySelection(),e.restoreBasketEntities(i.entityRefs,s),i.viewpoint){const t=i.transitionMs??n;e.cameraCallbacks.applyViewpoint?.(i.viewpoint,!0,t)}if(i.section){const t=i.section;o.setState({sectionPlane:{...t.plane},drawing2DPanelVisible:!1}),t.plane.enabled?(e.activeTool!=="section"&&e.setSuppressNextSection2DPanelAutoOpen(!0),e.setActiveTool("section")):e.activeTool==="section"&&e.setActiveTool("select")}else{const t=o.getState().sectionPlane;o.setState({sectionPlane:{...t,enabled:!1}}),e.activeTool==="section"&&e.setActiveTool("select")}}}export{v as activateBasketViewFromStore};
|