@ifc-lite/viewer 1.1.6 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +373 -0
- package/dist/apple-touch-icon.png +0 -0
- package/dist/assets/Arrow.dom-B0e15b_b.js +20 -0
- package/dist/assets/arrow2-bb-jcVEo.js +2 -0
- package/dist/assets/arrow2_bg-4Y7xYo54.wasm +0 -0
- package/dist/assets/arrow2_bg-BlXl-cSQ.js +1 -0
- package/dist/assets/arrow2_bg-BoXCojjR.wasm +0 -0
- package/dist/assets/desktop-cache-oPzaWXYE.js +1 -0
- package/dist/assets/event-DIOks52T.js +1 -0
- package/dist/assets/ifc-cache-BAN4vcd4.js +1 -0
- package/dist/assets/ifc-lite_bg-C6kblxf9.wasm +0 -0
- package/dist/assets/index-Dgd6vzw_.js +65252 -0
- package/dist/assets/index-v3mcCUPN.css +1 -0
- package/dist/assets/native-bridge-Ci7NLjlZ.js +111 -0
- package/dist/assets/wasm-bridge-Dc82YpdZ.js +1 -0
- package/dist/favicon-16x16-cropped.png +0 -0
- package/dist/favicon-16x16.png +0 -0
- package/dist/favicon-192x192-cropped.png +0 -0
- package/dist/favicon-192x192.png +0 -0
- package/dist/favicon-32x32-cropped.png +0 -0
- package/dist/favicon-32x32.png +0 -0
- package/dist/favicon-48x48-cropped.png +0 -0
- package/dist/favicon-48x48.png +0 -0
- package/dist/favicon-512x512-cropped.png +0 -0
- package/dist/favicon-512x512.png +0 -0
- package/dist/favicon-64x64-cropped.png +0 -0
- package/dist/favicon-64x64.png +0 -0
- package/dist/favicon-96x96-cropped.png +0 -0
- package/dist/favicon-96x96.png +0 -0
- package/dist/favicon-square-512.png +0 -0
- package/dist/favicon.ico +0 -0
- package/dist/favicon.png +0 -0
- package/dist/favicon.svg +3 -0
- package/dist/index.html +44 -0
- package/dist/logo.png +0 -0
- package/dist/manifest.json +48 -0
- package/index.html +33 -2
- package/package.json +34 -17
- package/public/apple-touch-icon.png +0 -0
- package/public/favicon-16x16-cropped.png +0 -0
- package/public/favicon-16x16.png +0 -0
- package/public/favicon-192x192-cropped.png +0 -0
- package/public/favicon-192x192.png +0 -0
- package/public/favicon-32x32-cropped.png +0 -0
- package/public/favicon-32x32.png +0 -0
- package/public/favicon-48x48-cropped.png +0 -0
- package/public/favicon-48x48.png +0 -0
- package/public/favicon-512x512-cropped.png +0 -0
- package/public/favicon-512x512.png +0 -0
- package/public/favicon-64x64-cropped.png +0 -0
- package/public/favicon-64x64.png +0 -0
- package/public/favicon-96x96-cropped.png +0 -0
- package/public/favicon-96x96.png +0 -0
- package/public/favicon-square-512.png +0 -0
- package/public/favicon.ico +0 -0
- package/public/favicon.png +0 -0
- package/public/favicon.svg +3 -0
- package/public/logo.png +0 -0
- package/public/manifest.json +48 -0
- package/src/App.tsx +2 -0
- package/src/components/ui/alert.tsx +62 -0
- package/src/components/ui/badge.tsx +39 -0
- package/src/components/ui/dialog.tsx +120 -0
- package/src/components/ui/label.tsx +27 -0
- package/src/components/ui/select.tsx +151 -0
- package/src/components/ui/switch.tsx +30 -0
- package/src/components/ui/table.tsx +120 -0
- package/src/components/ui/tabs.tsx +1 -1
- package/src/components/viewer/BCFPanel.tsx +1164 -0
- package/src/components/viewer/BulkPropertyEditor.tsx +875 -0
- package/src/components/viewer/DataConnector.tsx +840 -0
- package/src/components/viewer/DrawingSettingsPanel.tsx +536 -0
- package/src/components/viewer/EntityContextMenu.tsx +45 -17
- package/src/components/viewer/ExportChangesButton.tsx +195 -0
- package/src/components/viewer/ExportDialog.tsx +402 -0
- package/src/components/viewer/HierarchyPanel.tsx +1132 -218
- package/src/components/viewer/IDSPanel.tsx +661 -0
- package/src/components/viewer/KeyboardShortcutsDialog.tsx +245 -39
- package/src/components/viewer/MainToolbar.tsx +418 -94
- package/src/components/viewer/PropertiesPanel.tsx +1355 -91
- package/src/components/viewer/PropertyEditor.tsx +611 -0
- package/src/components/viewer/Section2DPanel.tsx +3313 -0
- package/src/components/viewer/SheetSetupPanel.tsx +502 -0
- package/src/components/viewer/StatusBar.tsx +27 -16
- package/src/components/viewer/TitleBlockEditor.tsx +437 -0
- package/src/components/viewer/ToolOverlays.tsx +935 -127
- package/src/components/viewer/ViewerLayout.tsx +40 -11
- package/src/components/viewer/Viewport.tsx +1276 -336
- package/src/components/viewer/ViewportContainer.tsx +554 -18
- package/src/components/viewer/ViewportOverlays.tsx +24 -7
- package/src/hooks/useBCF.ts +504 -0
- package/src/hooks/useIDS.ts +1065 -0
- package/src/hooks/useIfc.ts +1534 -205
- package/src/hooks/useIfcCache.ts +279 -0
- package/src/hooks/useKeyboardShortcuts.ts +50 -8
- package/src/hooks/useModelSelection.ts +61 -0
- package/src/hooks/useViewerSelectors.ts +218 -0
- package/src/hooks/useWebGPU.ts +80 -0
- package/src/index.css +265 -27
- package/src/lib/platform.ts +23 -0
- package/src/services/cacheService.ts +142 -0
- package/src/services/desktop-cache.ts +143 -0
- package/src/services/fs-cache.ts +212 -0
- package/src/services/ifc-cache.ts +14 -6
- package/src/store/constants.ts +85 -0
- package/src/store/index.ts +214 -0
- package/src/store/slices/bcfSlice.ts +372 -0
- package/src/store/slices/cameraSlice.ts +63 -0
- package/src/store/slices/dataSlice.test.ts +226 -0
- package/src/store/slices/dataSlice.ts +112 -0
- package/src/store/slices/drawing2DSlice.ts +340 -0
- package/src/store/slices/hoverSlice.ts +40 -0
- package/src/store/slices/idsSlice.ts +310 -0
- package/src/store/slices/loadingSlice.ts +33 -0
- package/src/store/slices/measurementSlice.test.ts +217 -0
- package/src/store/slices/measurementSlice.ts +293 -0
- package/src/store/slices/modelSlice.test.ts +271 -0
- package/src/store/slices/modelSlice.ts +211 -0
- package/src/store/slices/mutationSlice.ts +502 -0
- package/src/store/slices/sectionSlice.test.ts +125 -0
- package/src/store/slices/sectionSlice.ts +58 -0
- package/src/store/slices/selectionSlice.test.ts +286 -0
- package/src/store/slices/selectionSlice.ts +263 -0
- package/src/store/slices/sheetSlice.ts +565 -0
- package/src/store/slices/uiSlice.ts +58 -0
- package/src/store/slices/visibilitySlice.test.ts +304 -0
- package/src/store/slices/visibilitySlice.ts +277 -0
- package/src/store/types.test.ts +135 -0
- package/src/store/types.ts +248 -0
- package/src/store.ts +40 -515
- package/src/utils/ifcConfig.ts +82 -0
- package/src/utils/localParsingUtils.ts +287 -0
- package/src/utils/serverDataModel.ts +783 -0
- package/src/utils/spatialHierarchy.ts +283 -0
- package/src/utils/viewportUtils.ts +334 -0
- package/src/vite-env.d.ts +23 -0
- package/src/webgpu-types.d.ts +128 -0
- package/src-tauri/Cargo.toml +29 -0
- package/src-tauri/build.rs +7 -0
- package/src-tauri/capabilities/default.json +18 -0
- package/src-tauri/icons/128x128.png +0 -0
- package/src-tauri/icons/128x128@2x.png +0 -0
- package/src-tauri/icons/32x32.png +0 -0
- package/src-tauri/icons/Square107x107Logo.png +0 -0
- package/src-tauri/icons/Square142x142Logo.png +0 -0
- package/src-tauri/icons/Square150x150Logo.png +0 -0
- package/src-tauri/icons/Square284x284Logo.png +0 -0
- package/src-tauri/icons/Square30x30Logo.png +0 -0
- package/src-tauri/icons/Square310x310Logo.png +0 -0
- package/src-tauri/icons/Square44x44Logo.png +0 -0
- package/src-tauri/icons/Square71x71Logo.png +0 -0
- package/src-tauri/icons/Square89x89Logo.png +0 -0
- package/src-tauri/icons/StoreLogo.png +0 -0
- package/src-tauri/icons/icon.icns +0 -0
- package/src-tauri/icons/icon.ico +0 -0
- package/src-tauri/icons/icon.png +0 -0
- package/src-tauri/src/lib.rs +21 -0
- package/src-tauri/src/main.rs +10 -0
- package/src-tauri/tauri.conf.json +39 -0
- package/vite.config.ts +174 -26
- package/public/ifc-lite_bg.wasm +0 -0
- package/public/web-ifc.wasm +0 -0
- package/src/components/Viewport.tsx +0 -723
- package/src/components/viewer/BoxSelectionOverlay.tsx +0 -53
|
@@ -0,0 +1,502 @@
|
|
|
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
|
+
* SheetSetupPanel - Configure drawing sheet for architectural output
|
|
7
|
+
*
|
|
8
|
+
* Provides controls for:
|
|
9
|
+
* - Paper size selection (ISO, ANSI, ARCH)
|
|
10
|
+
* - Drawing frame style
|
|
11
|
+
* - Scale selection
|
|
12
|
+
* - Title block configuration
|
|
13
|
+
* - Scale bar and north arrow
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import React, { useCallback, useState, useMemo } from 'react';
|
|
17
|
+
import {
|
|
18
|
+
X,
|
|
19
|
+
FileText,
|
|
20
|
+
ChevronDown,
|
|
21
|
+
ChevronRight,
|
|
22
|
+
Ruler,
|
|
23
|
+
Compass,
|
|
24
|
+
Edit3,
|
|
25
|
+
Save,
|
|
26
|
+
Trash2,
|
|
27
|
+
Plus,
|
|
28
|
+
} from 'lucide-react';
|
|
29
|
+
import { Button } from '@/components/ui/button';
|
|
30
|
+
import { Label } from '@/components/ui/label';
|
|
31
|
+
import { Switch } from '@/components/ui/switch';
|
|
32
|
+
import { Input } from '@/components/ui/input';
|
|
33
|
+
import {
|
|
34
|
+
Select,
|
|
35
|
+
SelectContent,
|
|
36
|
+
SelectItem,
|
|
37
|
+
SelectTrigger,
|
|
38
|
+
SelectValue,
|
|
39
|
+
} from '@/components/ui/select';
|
|
40
|
+
import {
|
|
41
|
+
Collapsible,
|
|
42
|
+
CollapsibleContent,
|
|
43
|
+
CollapsibleTrigger,
|
|
44
|
+
} from '@/components/ui/collapsible';
|
|
45
|
+
import { useViewerStore } from '@/store';
|
|
46
|
+
import {
|
|
47
|
+
PAPER_SIZE_REGISTRY,
|
|
48
|
+
FRAME_PRESETS,
|
|
49
|
+
TITLE_BLOCK_PRESETS,
|
|
50
|
+
COMMON_SCALES,
|
|
51
|
+
type FrameStyle,
|
|
52
|
+
type TitleBlockLayout,
|
|
53
|
+
type DrawingScale,
|
|
54
|
+
} from '@ifc-lite/drawing-2d';
|
|
55
|
+
|
|
56
|
+
interface SheetSetupPanelProps {
|
|
57
|
+
onClose: () => void;
|
|
58
|
+
onOpenTitleBlockEditor?: () => void;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Group paper sizes by category
|
|
62
|
+
const PAPER_SIZE_GROUPS = {
|
|
63
|
+
ISO: ['A0_PORTRAIT', 'A0_LANDSCAPE', 'A1_PORTRAIT', 'A1_LANDSCAPE', 'A2_PORTRAIT', 'A2_LANDSCAPE', 'A3_PORTRAIT', 'A3_LANDSCAPE', 'A4_PORTRAIT', 'A4_LANDSCAPE'],
|
|
64
|
+
ANSI: ['LETTER_PORTRAIT', 'LETTER_LANDSCAPE', 'LEGAL_PORTRAIT', 'LEGAL_LANDSCAPE', 'TABLOID_PORTRAIT', 'TABLOID_LANDSCAPE', 'ANSI_C', 'ANSI_D', 'ANSI_E'],
|
|
65
|
+
ARCH: ['ARCH_A', 'ARCH_B', 'ARCH_C', 'ARCH_D', 'ARCH_E', 'ARCH_E1'],
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const FRAME_STYLE_OPTIONS: { value: FrameStyle; label: string }[] = [
|
|
69
|
+
{ value: 'simple', label: 'Simple' },
|
|
70
|
+
{ value: 'professional', label: 'Professional' },
|
|
71
|
+
{ value: 'minimal', label: 'Minimal' },
|
|
72
|
+
{ value: 'iso', label: 'ISO Standard' },
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
const TITLE_BLOCK_LAYOUT_OPTIONS: { value: TitleBlockLayout; label: string }[] = [
|
|
76
|
+
{ value: 'standard', label: 'Standard (Bottom Right)' },
|
|
77
|
+
{ value: 'extended', label: 'Extended (Full Width)' },
|
|
78
|
+
{ value: 'compact', label: 'Compact (Smaller)' },
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
export function SheetSetupPanel({ onClose, onOpenTitleBlockEditor }: SheetSetupPanelProps): React.ReactElement {
|
|
82
|
+
const activeSheet = useViewerStore((s) => s.activeSheet);
|
|
83
|
+
const sheetEnabled = useViewerStore((s) => s.sheetEnabled);
|
|
84
|
+
const setSheetEnabled = useViewerStore((s) => s.setSheetEnabled);
|
|
85
|
+
const createSheet = useViewerStore((s) => s.createSheet);
|
|
86
|
+
const setPaperSize = useViewerStore((s) => s.setPaperSize);
|
|
87
|
+
const setFrameStyle = useViewerStore((s) => s.setFrameStyle);
|
|
88
|
+
const setDrawingScale = useViewerStore((s) => s.setDrawingScale);
|
|
89
|
+
const setTitleBlockLayout = useViewerStore((s) => s.setTitleBlockLayout);
|
|
90
|
+
const toggleScaleBar = useViewerStore((s) => s.toggleScaleBar);
|
|
91
|
+
const toggleNorthArrow = useViewerStore((s) => s.toggleNorthArrow);
|
|
92
|
+
const savedSheetTemplates = useViewerStore((s) => s.savedSheetTemplates);
|
|
93
|
+
const saveAsTemplate = useViewerStore((s) => s.saveAsTemplate);
|
|
94
|
+
const loadTemplate = useViewerStore((s) => s.loadTemplate);
|
|
95
|
+
const deleteTemplate = useViewerStore((s) => s.deleteTemplate);
|
|
96
|
+
|
|
97
|
+
// Section state
|
|
98
|
+
const [paperSizeOpen, setPaperSizeOpen] = useState(true);
|
|
99
|
+
const [frameOpen, setFrameOpen] = useState(true);
|
|
100
|
+
const [scaleOpen, setScaleOpen] = useState(true);
|
|
101
|
+
const [titleBlockOpen, setTitleBlockOpen] = useState(true);
|
|
102
|
+
const [scaleBarOpen, setScaleBarOpen] = useState(true);
|
|
103
|
+
const [templatesOpen, setTemplatesOpen] = useState(false);
|
|
104
|
+
const [newTemplateName, setNewTemplateName] = useState('');
|
|
105
|
+
|
|
106
|
+
// Get current paper size ID
|
|
107
|
+
const currentPaperId = useMemo(() => {
|
|
108
|
+
if (!activeSheet) return 'A3_LANDSCAPE';
|
|
109
|
+
const paper = activeSheet.paper;
|
|
110
|
+
// Find matching paper in registry
|
|
111
|
+
for (const [id, def] of Object.entries(PAPER_SIZE_REGISTRY)) {
|
|
112
|
+
if (def.widthMm === paper.widthMm && def.heightMm === paper.heightMm) {
|
|
113
|
+
return id;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return 'A3_LANDSCAPE';
|
|
117
|
+
}, [activeSheet]);
|
|
118
|
+
|
|
119
|
+
// Initialize sheet if needed
|
|
120
|
+
const handleEnableSheet = useCallback((enabled: boolean) => {
|
|
121
|
+
if (enabled && !activeSheet) {
|
|
122
|
+
createSheet();
|
|
123
|
+
}
|
|
124
|
+
setSheetEnabled(enabled);
|
|
125
|
+
}, [activeSheet, createSheet, setSheetEnabled]);
|
|
126
|
+
|
|
127
|
+
// Paper size change
|
|
128
|
+
const handlePaperSizeChange = useCallback((paperId: string) => {
|
|
129
|
+
setPaperSize(paperId);
|
|
130
|
+
}, [setPaperSize]);
|
|
131
|
+
|
|
132
|
+
// Frame style change
|
|
133
|
+
const handleFrameStyleChange = useCallback((style: string) => {
|
|
134
|
+
setFrameStyle(style as FrameStyle);
|
|
135
|
+
}, [setFrameStyle]);
|
|
136
|
+
|
|
137
|
+
// Scale change
|
|
138
|
+
const handleScaleChange = useCallback((scaleName: string) => {
|
|
139
|
+
const scale = COMMON_SCALES.find((s) => s.name === scaleName);
|
|
140
|
+
if (scale) {
|
|
141
|
+
setDrawingScale(scale);
|
|
142
|
+
}
|
|
143
|
+
}, [setDrawingScale]);
|
|
144
|
+
|
|
145
|
+
// Title block layout change
|
|
146
|
+
const handleTitleBlockLayoutChange = useCallback((layout: string) => {
|
|
147
|
+
setTitleBlockLayout(layout as TitleBlockLayout);
|
|
148
|
+
}, [setTitleBlockLayout]);
|
|
149
|
+
|
|
150
|
+
// Save template
|
|
151
|
+
const handleSaveTemplate = useCallback(() => {
|
|
152
|
+
if (newTemplateName.trim()) {
|
|
153
|
+
saveAsTemplate(newTemplateName.trim());
|
|
154
|
+
setNewTemplateName('');
|
|
155
|
+
}
|
|
156
|
+
}, [newTemplateName, saveAsTemplate]);
|
|
157
|
+
|
|
158
|
+
return (
|
|
159
|
+
<div className="flex flex-col h-full bg-background border-l">
|
|
160
|
+
{/* Header */}
|
|
161
|
+
<div className="flex items-center justify-between px-4 py-3 border-b bg-muted/50">
|
|
162
|
+
<div className="flex items-center gap-2">
|
|
163
|
+
<FileText className="h-5 w-5 text-primary" />
|
|
164
|
+
<h2 className="font-semibold text-sm">Drawing Sheet</h2>
|
|
165
|
+
</div>
|
|
166
|
+
<div className="flex items-center gap-2">
|
|
167
|
+
<Switch
|
|
168
|
+
checked={sheetEnabled}
|
|
169
|
+
onCheckedChange={handleEnableSheet}
|
|
170
|
+
/>
|
|
171
|
+
<Button variant="ghost" size="icon-sm" onClick={onClose}>
|
|
172
|
+
<X className="h-4 w-4" />
|
|
173
|
+
</Button>
|
|
174
|
+
</div>
|
|
175
|
+
</div>
|
|
176
|
+
|
|
177
|
+
{/* Content */}
|
|
178
|
+
<div className="flex-1 overflow-y-auto">
|
|
179
|
+
{!activeSheet && !sheetEnabled ? (
|
|
180
|
+
<div className="p-4 text-center text-muted-foreground">
|
|
181
|
+
<p className="text-sm">Enable drawing sheet to configure paper size, frame, and title block.</p>
|
|
182
|
+
<Button
|
|
183
|
+
variant="outline"
|
|
184
|
+
size="sm"
|
|
185
|
+
className="mt-4"
|
|
186
|
+
onClick={() => handleEnableSheet(true)}
|
|
187
|
+
>
|
|
188
|
+
Enable Sheet
|
|
189
|
+
</Button>
|
|
190
|
+
</div>
|
|
191
|
+
) : (
|
|
192
|
+
<>
|
|
193
|
+
{/* Paper Size Section */}
|
|
194
|
+
<Collapsible open={paperSizeOpen} onOpenChange={setPaperSizeOpen}>
|
|
195
|
+
<CollapsibleTrigger asChild>
|
|
196
|
+
<button className="w-full flex items-center justify-between px-4 py-2 hover:bg-muted/50 transition-colors border-b">
|
|
197
|
+
<span className="text-sm font-medium">Paper Size</span>
|
|
198
|
+
{paperSizeOpen ? (
|
|
199
|
+
<ChevronDown className="h-4 w-4 text-muted-foreground" />
|
|
200
|
+
) : (
|
|
201
|
+
<ChevronRight className="h-4 w-4 text-muted-foreground" />
|
|
202
|
+
)}
|
|
203
|
+
</button>
|
|
204
|
+
</CollapsibleTrigger>
|
|
205
|
+
<CollapsibleContent>
|
|
206
|
+
<div className="px-4 py-3 space-y-3">
|
|
207
|
+
<Select value={currentPaperId} onValueChange={handlePaperSizeChange}>
|
|
208
|
+
<SelectTrigger className="h-8 text-sm">
|
|
209
|
+
<SelectValue />
|
|
210
|
+
</SelectTrigger>
|
|
211
|
+
<SelectContent>
|
|
212
|
+
{Object.entries(PAPER_SIZE_GROUPS).map(([group, ids]) => (
|
|
213
|
+
<React.Fragment key={group}>
|
|
214
|
+
<div className="px-2 py-1 text-xs font-semibold text-muted-foreground">
|
|
215
|
+
{group}
|
|
216
|
+
</div>
|
|
217
|
+
{ids.filter((id) => id in PAPER_SIZE_REGISTRY).map((id) => {
|
|
218
|
+
const paper = PAPER_SIZE_REGISTRY[id];
|
|
219
|
+
return (
|
|
220
|
+
<SelectItem key={id} value={id}>
|
|
221
|
+
{paper.name} ({paper.widthMm}×{paper.heightMm}mm)
|
|
222
|
+
</SelectItem>
|
|
223
|
+
);
|
|
224
|
+
})}
|
|
225
|
+
</React.Fragment>
|
|
226
|
+
))}
|
|
227
|
+
</SelectContent>
|
|
228
|
+
</Select>
|
|
229
|
+
|
|
230
|
+
{activeSheet && (
|
|
231
|
+
<div className="text-xs text-muted-foreground">
|
|
232
|
+
{activeSheet.paper.widthMm} × {activeSheet.paper.heightMm} mm
|
|
233
|
+
</div>
|
|
234
|
+
)}
|
|
235
|
+
</div>
|
|
236
|
+
</CollapsibleContent>
|
|
237
|
+
</Collapsible>
|
|
238
|
+
|
|
239
|
+
{/* Frame Section */}
|
|
240
|
+
<Collapsible open={frameOpen} onOpenChange={setFrameOpen}>
|
|
241
|
+
<CollapsibleTrigger asChild>
|
|
242
|
+
<button className="w-full flex items-center justify-between px-4 py-2 hover:bg-muted/50 transition-colors border-b">
|
|
243
|
+
<span className="text-sm font-medium">Frame Style</span>
|
|
244
|
+
{frameOpen ? (
|
|
245
|
+
<ChevronDown className="h-4 w-4 text-muted-foreground" />
|
|
246
|
+
) : (
|
|
247
|
+
<ChevronRight className="h-4 w-4 text-muted-foreground" />
|
|
248
|
+
)}
|
|
249
|
+
</button>
|
|
250
|
+
</CollapsibleTrigger>
|
|
251
|
+
<CollapsibleContent>
|
|
252
|
+
<div className="px-4 py-3 space-y-3">
|
|
253
|
+
<Select
|
|
254
|
+
value={activeSheet?.frame.style || 'professional'}
|
|
255
|
+
onValueChange={handleFrameStyleChange}
|
|
256
|
+
>
|
|
257
|
+
<SelectTrigger className="h-8 text-sm">
|
|
258
|
+
<SelectValue />
|
|
259
|
+
</SelectTrigger>
|
|
260
|
+
<SelectContent>
|
|
261
|
+
{FRAME_STYLE_OPTIONS.map((opt) => (
|
|
262
|
+
<SelectItem key={opt.value} value={opt.value}>
|
|
263
|
+
{opt.label}
|
|
264
|
+
</SelectItem>
|
|
265
|
+
))}
|
|
266
|
+
</SelectContent>
|
|
267
|
+
</Select>
|
|
268
|
+
|
|
269
|
+
{activeSheet && (
|
|
270
|
+
<div className="text-xs text-muted-foreground">
|
|
271
|
+
Margins: {activeSheet.frame.margins.top}/{activeSheet.frame.margins.right}/{activeSheet.frame.margins.bottom}/{activeSheet.frame.margins.left}mm
|
|
272
|
+
</div>
|
|
273
|
+
)}
|
|
274
|
+
</div>
|
|
275
|
+
</CollapsibleContent>
|
|
276
|
+
</Collapsible>
|
|
277
|
+
|
|
278
|
+
{/* Scale Section */}
|
|
279
|
+
<Collapsible open={scaleOpen} onOpenChange={setScaleOpen}>
|
|
280
|
+
<CollapsibleTrigger asChild>
|
|
281
|
+
<button className="w-full flex items-center justify-between px-4 py-2 hover:bg-muted/50 transition-colors border-b">
|
|
282
|
+
<div className="flex items-center gap-2">
|
|
283
|
+
<Ruler className="h-4 w-4 text-muted-foreground" />
|
|
284
|
+
<span className="text-sm font-medium">Drawing Scale</span>
|
|
285
|
+
</div>
|
|
286
|
+
{scaleOpen ? (
|
|
287
|
+
<ChevronDown className="h-4 w-4 text-muted-foreground" />
|
|
288
|
+
) : (
|
|
289
|
+
<ChevronRight className="h-4 w-4 text-muted-foreground" />
|
|
290
|
+
)}
|
|
291
|
+
</button>
|
|
292
|
+
</CollapsibleTrigger>
|
|
293
|
+
<CollapsibleContent>
|
|
294
|
+
<div className="px-4 py-3 space-y-3">
|
|
295
|
+
<Select
|
|
296
|
+
value={activeSheet?.scale.name || '1:100'}
|
|
297
|
+
onValueChange={handleScaleChange}
|
|
298
|
+
>
|
|
299
|
+
<SelectTrigger className="h-8 text-sm">
|
|
300
|
+
<SelectValue />
|
|
301
|
+
</SelectTrigger>
|
|
302
|
+
<SelectContent>
|
|
303
|
+
{COMMON_SCALES.map((scale) => (
|
|
304
|
+
<SelectItem key={scale.name} value={scale.name}>
|
|
305
|
+
{scale.name} - {scale.useCase}
|
|
306
|
+
</SelectItem>
|
|
307
|
+
))}
|
|
308
|
+
</SelectContent>
|
|
309
|
+
</Select>
|
|
310
|
+
</div>
|
|
311
|
+
</CollapsibleContent>
|
|
312
|
+
</Collapsible>
|
|
313
|
+
|
|
314
|
+
{/* Title Block Section */}
|
|
315
|
+
<Collapsible open={titleBlockOpen} onOpenChange={setTitleBlockOpen}>
|
|
316
|
+
<CollapsibleTrigger asChild>
|
|
317
|
+
<button className="w-full flex items-center justify-between px-4 py-2 hover:bg-muted/50 transition-colors border-b">
|
|
318
|
+
<span className="text-sm font-medium">Title Block</span>
|
|
319
|
+
{titleBlockOpen ? (
|
|
320
|
+
<ChevronDown className="h-4 w-4 text-muted-foreground" />
|
|
321
|
+
) : (
|
|
322
|
+
<ChevronRight className="h-4 w-4 text-muted-foreground" />
|
|
323
|
+
)}
|
|
324
|
+
</button>
|
|
325
|
+
</CollapsibleTrigger>
|
|
326
|
+
<CollapsibleContent>
|
|
327
|
+
<div className="px-4 py-3 space-y-3">
|
|
328
|
+
<div>
|
|
329
|
+
<Label className="text-xs">Layout</Label>
|
|
330
|
+
<Select
|
|
331
|
+
value={activeSheet?.titleBlock.layout || 'standard'}
|
|
332
|
+
onValueChange={handleTitleBlockLayoutChange}
|
|
333
|
+
>
|
|
334
|
+
<SelectTrigger className="h-8 text-sm mt-1">
|
|
335
|
+
<SelectValue />
|
|
336
|
+
</SelectTrigger>
|
|
337
|
+
<SelectContent>
|
|
338
|
+
{TITLE_BLOCK_LAYOUT_OPTIONS.map((opt) => (
|
|
339
|
+
<SelectItem key={opt.value} value={opt.value}>
|
|
340
|
+
{opt.label}
|
|
341
|
+
</SelectItem>
|
|
342
|
+
))}
|
|
343
|
+
</SelectContent>
|
|
344
|
+
</Select>
|
|
345
|
+
</div>
|
|
346
|
+
|
|
347
|
+
{activeSheet && (
|
|
348
|
+
<div className="text-xs text-muted-foreground">
|
|
349
|
+
{activeSheet.titleBlock.widthMm} × {activeSheet.titleBlock.heightMm}mm
|
|
350
|
+
<br />
|
|
351
|
+
{activeSheet.titleBlock.fields.length} fields configured
|
|
352
|
+
</div>
|
|
353
|
+
)}
|
|
354
|
+
|
|
355
|
+
<Button
|
|
356
|
+
variant="outline"
|
|
357
|
+
size="sm"
|
|
358
|
+
className="w-full"
|
|
359
|
+
onClick={onOpenTitleBlockEditor}
|
|
360
|
+
>
|
|
361
|
+
<Edit3 className="h-4 w-4 mr-2" />
|
|
362
|
+
Edit Title Block Fields
|
|
363
|
+
</Button>
|
|
364
|
+
</div>
|
|
365
|
+
</CollapsibleContent>
|
|
366
|
+
</Collapsible>
|
|
367
|
+
|
|
368
|
+
{/* Scale Bar & North Arrow Section */}
|
|
369
|
+
<Collapsible open={scaleBarOpen} onOpenChange={setScaleBarOpen}>
|
|
370
|
+
<CollapsibleTrigger asChild>
|
|
371
|
+
<button className="w-full flex items-center justify-between px-4 py-2 hover:bg-muted/50 transition-colors border-b">
|
|
372
|
+
<div className="flex items-center gap-2">
|
|
373
|
+
<Compass className="h-4 w-4 text-muted-foreground" />
|
|
374
|
+
<span className="text-sm font-medium">Scale Bar & North Arrow</span>
|
|
375
|
+
</div>
|
|
376
|
+
{scaleBarOpen ? (
|
|
377
|
+
<ChevronDown className="h-4 w-4 text-muted-foreground" />
|
|
378
|
+
) : (
|
|
379
|
+
<ChevronRight className="h-4 w-4 text-muted-foreground" />
|
|
380
|
+
)}
|
|
381
|
+
</button>
|
|
382
|
+
</CollapsibleTrigger>
|
|
383
|
+
<CollapsibleContent>
|
|
384
|
+
<div className="px-4 py-3 space-y-3">
|
|
385
|
+
{/* Scale Bar Toggle */}
|
|
386
|
+
<div className="flex items-center justify-between">
|
|
387
|
+
<Label className="text-xs">Scale Bar</Label>
|
|
388
|
+
<Switch
|
|
389
|
+
checked={activeSheet?.scaleBar.visible ?? true}
|
|
390
|
+
onCheckedChange={toggleScaleBar}
|
|
391
|
+
/>
|
|
392
|
+
</div>
|
|
393
|
+
|
|
394
|
+
{/* North Arrow Toggle */}
|
|
395
|
+
<div className="flex items-center justify-between">
|
|
396
|
+
<Label className="text-xs">North Arrow</Label>
|
|
397
|
+
<Switch
|
|
398
|
+
checked={(activeSheet?.northArrow.style ?? 'simple') !== 'none'}
|
|
399
|
+
onCheckedChange={toggleNorthArrow}
|
|
400
|
+
/>
|
|
401
|
+
</div>
|
|
402
|
+
</div>
|
|
403
|
+
</CollapsibleContent>
|
|
404
|
+
</Collapsible>
|
|
405
|
+
|
|
406
|
+
{/* Templates Section */}
|
|
407
|
+
<Collapsible open={templatesOpen} onOpenChange={setTemplatesOpen}>
|
|
408
|
+
<CollapsibleTrigger asChild>
|
|
409
|
+
<button className="w-full flex items-center justify-between px-4 py-2 hover:bg-muted/50 transition-colors border-b">
|
|
410
|
+
<span className="text-sm font-medium">Saved Templates</span>
|
|
411
|
+
<div className="flex items-center gap-2">
|
|
412
|
+
<span className="text-xs text-muted-foreground">
|
|
413
|
+
{savedSheetTemplates.length}
|
|
414
|
+
</span>
|
|
415
|
+
{templatesOpen ? (
|
|
416
|
+
<ChevronDown className="h-4 w-4 text-muted-foreground" />
|
|
417
|
+
) : (
|
|
418
|
+
<ChevronRight className="h-4 w-4 text-muted-foreground" />
|
|
419
|
+
)}
|
|
420
|
+
</div>
|
|
421
|
+
</button>
|
|
422
|
+
</CollapsibleTrigger>
|
|
423
|
+
<CollapsibleContent>
|
|
424
|
+
<div className="px-4 py-3 space-y-3">
|
|
425
|
+
{/* Save current as template */}
|
|
426
|
+
<div className="flex gap-2">
|
|
427
|
+
<Input
|
|
428
|
+
placeholder="Template name..."
|
|
429
|
+
value={newTemplateName}
|
|
430
|
+
onChange={(e) => setNewTemplateName(e.target.value)}
|
|
431
|
+
className="h-8 text-sm flex-1"
|
|
432
|
+
/>
|
|
433
|
+
<Button
|
|
434
|
+
variant="outline"
|
|
435
|
+
size="sm"
|
|
436
|
+
onClick={handleSaveTemplate}
|
|
437
|
+
disabled={!newTemplateName.trim() || !activeSheet}
|
|
438
|
+
>
|
|
439
|
+
<Save className="h-4 w-4" />
|
|
440
|
+
</Button>
|
|
441
|
+
</div>
|
|
442
|
+
|
|
443
|
+
{/* Template list */}
|
|
444
|
+
{savedSheetTemplates.length === 0 ? (
|
|
445
|
+
<div className="text-xs text-muted-foreground text-center py-2">
|
|
446
|
+
No saved templates
|
|
447
|
+
</div>
|
|
448
|
+
) : (
|
|
449
|
+
<div className="space-y-1">
|
|
450
|
+
{savedSheetTemplates.map((template) => (
|
|
451
|
+
<div
|
|
452
|
+
key={template.id}
|
|
453
|
+
className="flex items-center justify-between px-2 py-1.5 bg-muted/30 rounded text-xs"
|
|
454
|
+
>
|
|
455
|
+
<span className="truncate flex-1">{template.name}</span>
|
|
456
|
+
<div className="flex gap-1">
|
|
457
|
+
<Button
|
|
458
|
+
variant="ghost"
|
|
459
|
+
size="icon-sm"
|
|
460
|
+
className="h-6 w-6"
|
|
461
|
+
onClick={() => loadTemplate(template.id)}
|
|
462
|
+
>
|
|
463
|
+
<Plus className="h-3 w-3" />
|
|
464
|
+
</Button>
|
|
465
|
+
<Button
|
|
466
|
+
variant="ghost"
|
|
467
|
+
size="icon-sm"
|
|
468
|
+
className="h-6 w-6 text-destructive hover:text-destructive"
|
|
469
|
+
onClick={() => deleteTemplate(template.id)}
|
|
470
|
+
>
|
|
471
|
+
<Trash2 className="h-3 w-3" />
|
|
472
|
+
</Button>
|
|
473
|
+
</div>
|
|
474
|
+
</div>
|
|
475
|
+
))}
|
|
476
|
+
</div>
|
|
477
|
+
)}
|
|
478
|
+
</div>
|
|
479
|
+
</CollapsibleContent>
|
|
480
|
+
</Collapsible>
|
|
481
|
+
|
|
482
|
+
{/* Viewport Info */}
|
|
483
|
+
{activeSheet && (
|
|
484
|
+
<div className="px-4 py-3 border-t">
|
|
485
|
+
<div className="text-xs text-muted-foreground space-y-1">
|
|
486
|
+
<div>
|
|
487
|
+
<strong>Drawing Area:</strong>{' '}
|
|
488
|
+
{activeSheet.viewportBounds.width.toFixed(1)} ×{' '}
|
|
489
|
+
{activeSheet.viewportBounds.height.toFixed(1)} mm
|
|
490
|
+
</div>
|
|
491
|
+
<div>
|
|
492
|
+
<strong>Scale:</strong> {activeSheet.scale.name}
|
|
493
|
+
</div>
|
|
494
|
+
</div>
|
|
495
|
+
</div>
|
|
496
|
+
)}
|
|
497
|
+
</>
|
|
498
|
+
)}
|
|
499
|
+
</div>
|
|
500
|
+
</div>
|
|
501
|
+
);
|
|
502
|
+
}
|
|
@@ -3,26 +3,22 @@
|
|
|
3
3
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
4
|
|
|
5
5
|
import { useMemo, useState, useEffect } from 'react';
|
|
6
|
-
import { Boxes, Triangle, CheckCircle2, AlertCircle } from 'lucide-react';
|
|
6
|
+
import { Boxes, Triangle, CheckCircle2, AlertCircle, Loader2 } from 'lucide-react';
|
|
7
7
|
import { Separator } from '@/components/ui/separator';
|
|
8
8
|
import { formatNumber, formatBytes } from '@/lib/utils';
|
|
9
9
|
import { useViewerStore } from '@/store';
|
|
10
10
|
import { useIfc } from '@/hooks/useIfc';
|
|
11
|
+
import { useWebGPU } from '@/hooks/useWebGPU';
|
|
11
12
|
|
|
12
13
|
export function StatusBar() {
|
|
13
14
|
const { loading, geometryResult, ifcDataStore } = useIfc();
|
|
14
15
|
const progress = useViewerStore((s) => s.progress);
|
|
15
16
|
const error = useViewerStore((s) => s.error);
|
|
16
|
-
const
|
|
17
|
+
const selectedStoreys = useViewerStore((s) => s.selectedStoreys);
|
|
18
|
+
const webgpu = useWebGPU();
|
|
17
19
|
|
|
18
20
|
const [fps, setFps] = useState(60);
|
|
19
21
|
const [memory, setMemory] = useState(0);
|
|
20
|
-
const [webgpuSupported, setWebgpuSupported] = useState<boolean | null>(null);
|
|
21
|
-
|
|
22
|
-
// Check WebGPU support
|
|
23
|
-
useEffect(() => {
|
|
24
|
-
setWebgpuSupported('gpu' in navigator);
|
|
25
|
-
}, []);
|
|
26
22
|
|
|
27
23
|
// FPS counter (simplified)
|
|
28
24
|
useEffect(() => {
|
|
@@ -71,12 +67,19 @@ export function StatusBar() {
|
|
|
71
67
|
}, [geometryResult]);
|
|
72
68
|
|
|
73
69
|
const visibleElements = useMemo(() => {
|
|
74
|
-
if (
|
|
70
|
+
if (selectedStoreys.size === 0 || !ifcDataStore?.spatialHierarchy) {
|
|
75
71
|
return stats.elements;
|
|
76
72
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
73
|
+
// Count elements from all selected storeys
|
|
74
|
+
let count = 0;
|
|
75
|
+
for (const storeyId of selectedStoreys) {
|
|
76
|
+
const storeyElements = ifcDataStore.spatialHierarchy.byStorey.get(storeyId);
|
|
77
|
+
if (storeyElements) {
|
|
78
|
+
count += storeyElements.length;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return count || stats.elements;
|
|
82
|
+
}, [selectedStoreys, ifcDataStore, stats.elements]);
|
|
80
83
|
|
|
81
84
|
return (
|
|
82
85
|
<div className="h-7 px-3 border-t bg-muted/30 flex items-center justify-between text-xs text-muted-foreground">
|
|
@@ -97,7 +100,7 @@ export function StatusBar() {
|
|
|
97
100
|
<Boxes className="h-3.5 w-3.5" />
|
|
98
101
|
<span>
|
|
99
102
|
{formatNumber(visibleElements)}
|
|
100
|
-
{
|
|
103
|
+
{selectedStoreys.size > 0 && stats.elements !== visibleElements && (
|
|
101
104
|
<span className="opacity-60"> / {formatNumber(stats.elements)}</span>
|
|
102
105
|
)}
|
|
103
106
|
{' '}elements
|
|
@@ -128,13 +131,21 @@ export function StatusBar() {
|
|
|
128
131
|
<Separator orientation="vertical" className="h-3.5" />
|
|
129
132
|
|
|
130
133
|
<div className="flex items-center gap-1">
|
|
131
|
-
{
|
|
134
|
+
{webgpu.checking ? (
|
|
135
|
+
<Loader2 className="h-3.5 w-3.5 text-zinc-400 animate-spin" />
|
|
136
|
+
) : webgpu.supported ? (
|
|
132
137
|
<CheckCircle2 className="h-3.5 w-3.5 text-green-500" />
|
|
133
138
|
) : (
|
|
134
|
-
<AlertCircle className="h-3.5 w-3.5 text-
|
|
139
|
+
<AlertCircle className="h-3.5 w-3.5 text-[#f7768e]" />
|
|
135
140
|
)}
|
|
136
|
-
<span
|
|
141
|
+
<span className={!webgpu.supported && !webgpu.checking ? 'text-[#f7768e]' : ''}>
|
|
142
|
+
{webgpu.checking ? 'Checking...' : webgpu.supported ? 'WebGPU' : 'No WebGPU'}
|
|
143
|
+
</span>
|
|
137
144
|
</div>
|
|
145
|
+
|
|
146
|
+
<Separator orientation="vertical" className="h-3.5" />
|
|
147
|
+
|
|
148
|
+
<span className="opacity-60">v{__APP_VERSION__}</span>
|
|
138
149
|
</div>
|
|
139
150
|
</div>
|
|
140
151
|
);
|