bc-model-viewer 1.7.23 → 1.7.45

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.
@@ -24,7 +24,12 @@
24
24
  <button onclick="tool.clear()" class="danger">清除所有</button>
25
25
  </div>
26
26
  <div class="control-group">
27
- <button onclick="console.log(tool.getAnnotations())">打印数据</button>
27
+ <button onclick="tool.updateAnnotationsVisibility(20)">显示所有批注</button>
28
+ <button onclick="hideAllAnnotations()">隐藏所有批注</button>
29
+ </div>
30
+ <div class="control-group">
31
+ <button onclick="loadTestAnnotations()">加载测试批注</button>
32
+ <button onclick="console.log(tool.getAnnotations())">打印批注数据</button>
28
33
  </div>
29
34
  <div class="control-group">
30
35
  <p style="font-size: 12px; color: #888; margin-top: 10px;">
@@ -57,8 +62,8 @@
57
62
  })
58
63
 
59
64
  // 加载测试模型
60
- await viewer.loadZipAsync('../../assets/dgz/建筑.dgz')
61
- // await viewer.loadZipAsync('../../assets/new.dgz')
65
+ // await viewer.loadZipAsync('../../assets/dgz/建筑.dgz')
66
+ await viewer.loadZipAsync('../../assets/new.dgz')
62
67
 
63
68
  // AnnotationTool 已在 Viewer 构造函数中自动初始化
64
69
  // 可以通过 viewer.annotationTool 访问
@@ -68,6 +73,63 @@
68
73
 
69
74
  window.tool = tool
70
75
  window.viewer = viewer
76
+
77
+ // 辅助函数:隐藏所有批注
78
+ window.hideAllAnnotations = function() {
79
+ if (!tool || !tool.annotations) return;
80
+
81
+ tool.annotations.forEach((annotation) => {
82
+ annotation.line.visible = false;
83
+ annotation.label.visible = false;
84
+ });
85
+ };
86
+
87
+ // 辅助函数:加载测试批注数据
88
+ window.loadTestAnnotations = function() {
89
+ if (!tool) return;
90
+
91
+ // 测试批注数据(使用简单的坐标)
92
+ const testAnnotations = [
93
+ {
94
+ point: [0, 0, 0],
95
+ label: "测试批注 1",
96
+ viewport: {
97
+ x: 100,
98
+ y: 100,
99
+ width: 800,
100
+ height: 600
101
+ }
102
+ },
103
+ {
104
+ point: [1, 1, 1],
105
+ label: "测试批注 2",
106
+ viewport: {
107
+ x: 100,
108
+ y: 100,
109
+ width: 800,
110
+ height: 600
111
+ }
112
+ },
113
+ {
114
+ point: [-1, -1, 0],
115
+ label: "测试批注 3",
116
+ viewport: {
117
+ x: 100,
118
+ y: 100,
119
+ width: 800,
120
+ height: 600
121
+ }
122
+ }
123
+ ];
124
+
125
+ try {
126
+ tool.loadAnnotationsFromData(testAnnotations);
127
+ console.log('测试批注加载成功');
128
+ } catch (error) {
129
+ console.error('加载测试批注失败:', error);
130
+ }
131
+ };
132
+
71
133
  </script>
72
134
 
73
135
  </body>
@@ -192,6 +192,15 @@
192
192
  font-weight: 600;
193
193
  color: #495057;
194
194
  font-size: 14px;
195
+ flex: 1;
196
+ }
197
+
198
+ .tree-folder-checkbox {
199
+ width: 18px;
200
+ height: 18px;
201
+ margin-right: 8px;
202
+ cursor: pointer;
203
+ accent-color: #667eea;
195
204
  }
196
205
 
197
206
  .tree-layer-item {
@@ -285,6 +294,7 @@
285
294
  <ul style="margin: 0; padding-left: 20px; font-size: 13px; color: #666; line-height: 1.8;">
286
295
  <li>切换"树形视图"、"列表视图"和"DFC视图"查看图层</li>
287
296
  <li>在树形视图中,点击文件夹图标可展开/折叠</li>
297
+ <li>文件夹复选框:可批量控制该文件夹下所有图层的可见性(半选状态表示部分可见)</li>
288
298
  <li>DFC视图:输入 DFC key (如 "dfc-architecture") 查看特定类型的图层树形结构</li>
289
299
  <li>勾选/取消勾选复选框来切换图层可见性</li>
290
300
  <li>使用搜索框快速查找图层(支持文件夹路径搜索)</li>
@@ -315,7 +325,7 @@
315
325
 
316
326
  // 加载测试模型
317
327
  try {
318
- await viewer.loadZipAsync('./dgz/ob.dgz')
328
+ await viewer.loadZipAsync('../../../assets/new.dgz');
319
329
  console.log('模型加载成功')
320
330
 
321
331
  // 模型加载完成后,初始化图层列表
@@ -368,7 +378,7 @@
368
378
  layerList.innerHTML = '<div class="empty-state">请输入 DFC key 并点击"加载 DFC 树形结构"</div>'
369
379
  return
370
380
  }
371
- const dfcTree = viewer.layerManager.getDfcTreeStructure(currentDfcKey)
381
+ const dfcTree = viewer.layerManager.getTreeStructureByPath(currentDfcKey)
372
382
  if (dfcTree.length === 0) {
373
383
  layerList.innerHTML = `<div class="empty-state">未找到匹配的 DFC 图层 (key: ${currentDfcKey})</div>`
374
384
  return
@@ -403,6 +413,36 @@
403
413
  }
404
414
  }
405
415
 
416
+ // 获取文件夹下所有图层节点
417
+ function getAllLayerNodesFromFolder(folderNode) {
418
+ const layerNodes = []
419
+ if (folderNode.children) {
420
+ folderNode.children.forEach(child => {
421
+ if (child.type === 'layer') {
422
+ layerNodes.push(child)
423
+ } else if (child.type === 'folder') {
424
+ layerNodes.push(...getAllLayerNodesFromFolder(child))
425
+ }
426
+ })
427
+ }
428
+ return layerNodes
429
+ }
430
+
431
+ // 计算文件夹的复选框状态
432
+ function getFolderCheckboxState(folderNode) {
433
+ const layerNodes = getAllLayerNodesFromFolder(folderNode)
434
+ if (layerNodes.length === 0) return { checked: false, indeterminate: false }
435
+
436
+ const visibleCount = layerNodes.filter(n => n.layer && n.layer.visible !== false).length
437
+ const allVisible = visibleCount === layerNodes.length
438
+ const noneVisible = visibleCount === 0
439
+
440
+ return {
441
+ checked: allVisible,
442
+ indeterminate: !allVisible && !noneVisible
443
+ }
444
+ }
445
+
406
446
  // 渲染树形视图
407
447
  function renderTreeView(nodes, filterText = '', parentPath = '', parentContainer = null) {
408
448
  const container = parentContainer || document.getElementById('layerList')
@@ -416,16 +456,32 @@
416
456
  const hasMatch = filterText === '' || nodeMatchesFilter(node, filterText, true)
417
457
 
418
458
  if (filterText === '' || hasMatch) {
459
+ // 计算文件夹复选框状态
460
+ const checkboxState = getFolderCheckboxState(node)
419
461
  const folderDiv = document.createElement('div')
420
462
  folderDiv.className = 'tree-folder'
463
+ folderDiv.setAttribute('data-folder-path', currentPath)
464
+
465
+ const checkboxId = `folder-checkbox-${currentPath.replace(/\//g, '-')}`
421
466
  folderDiv.innerHTML = `
422
- <span class="tree-folder-icon ${isExpanded ? '' : 'collapsed'}">▶</span>
423
- <span class="tree-folder-name">📁 ${node.name}</span>
467
+ <input type="checkbox"
468
+ id="${checkboxId}"
469
+ class="tree-folder-checkbox"
470
+ ${checkboxState.checked ? 'checked' : ''}
471
+ onchange="toggleFolderLayers('${currentPath}', this.checked)"
472
+ onclick="event.stopPropagation()">
473
+ <span class="tree-folder-icon ${isExpanded ? '' : 'collapsed'}"
474
+ onclick="event.stopPropagation(); toggleFolder('${currentPath}')">▶</span>
475
+ <span class="tree-folder-name"
476
+ onclick="event.stopPropagation(); toggleFolder('${currentPath}')">📁 ${node.name}</span>
424
477
  `
425
- folderDiv.onclick = (e) => {
426
- e.stopPropagation()
427
- toggleFolder(currentPath)
478
+
479
+ // 设置半选状态
480
+ const checkbox = folderDiv.querySelector(`#${checkboxId}`)
481
+ if (checkbox) {
482
+ checkbox.indeterminate = checkboxState.indeterminate
428
483
  }
484
+
429
485
  container.appendChild(folderDiv)
430
486
 
431
487
  // 渲染子节点
@@ -528,7 +584,7 @@
528
584
  }
529
585
 
530
586
  // 切换文件夹展开/折叠
531
- function toggleFolder(path) {
587
+ window.toggleFolder = function (path) {
532
588
  if (expandedFolders.has(path)) {
533
589
  expandedFolders.delete(path)
534
590
  } else {
@@ -537,6 +593,55 @@
537
593
  renderLayerList()
538
594
  }
539
595
 
596
+ // 切换文件夹下所有图层的可见性
597
+ window.toggleFolderLayers = function (folderPath, visible) {
598
+ // 找到对应的文件夹节点
599
+ const tree = currentView === 'dfc' && currentDfcKey
600
+ ? viewer.layerManager.getTreeStructureByPath(currentDfcKey)
601
+ : viewer.layerManager.getTreeStructure()
602
+
603
+ const folderNode = findFolderNodeByPath(tree, folderPath)
604
+ if (!folderNode) return
605
+
606
+ // 获取文件夹下所有图层节点
607
+ const layerNodes = getAllLayerNodesFromFolder(folderNode)
608
+
609
+ // 更新所有图层的可见性
610
+ layerNodes.forEach(layerNode => {
611
+ if (layerNode.layer && layerNode.layerIndex !== undefined) {
612
+ layerNode.layer.visible = visible
613
+ }
614
+ })
615
+
616
+ // 更新模型显示
617
+ viewer.updateMeshVisibleByLayer()
618
+
619
+ // 更新UI
620
+ renderLayerList()
621
+ updateLayerStats()
622
+
623
+ // 更新所有文件夹复选框状态(包括父文件夹)
624
+ updateFolderCheckboxes()
625
+ }
626
+
627
+ // 根据路径查找文件夹节点
628
+ function findFolderNodeByPath(nodes, targetPath, parentPath = '') {
629
+ for (const node of nodes) {
630
+ if (node.type === 'folder') {
631
+ const currentPath = parentPath ? `${parentPath}/${node.name}` : node.name
632
+ if (currentPath === targetPath) {
633
+ return node
634
+ }
635
+ // 递归查找子节点
636
+ if (node.children) {
637
+ const found = findFolderNodeByPath(node.children, targetPath, currentPath)
638
+ if (found) return found
639
+ }
640
+ }
641
+ }
642
+ return null
643
+ }
644
+
540
645
  // 切换视图模式
541
646
  window.switchView = function (view) {
542
647
  currentView = view
@@ -586,7 +691,7 @@
586
691
 
587
692
  if (currentView === 'dfc' && currentDfcKey) {
588
693
  // DFC 视图:显示匹配的图层统计
589
- const dfcTree = viewer.layerManager.getDfcTreeStructure(currentDfcKey)
694
+ const dfcTree = viewer.layerManager.getTreeStructureByPath(currentDfcKey)
590
695
  const dfcLayers = getAllLayersFromTree(dfcTree)
591
696
  const total = dfcLayers.length
592
697
  const visible = dfcLayers.filter(l => l.visible !== false).length
@@ -645,9 +750,43 @@
645
750
  const checkbox = item.querySelector('input[type="checkbox"]')
646
751
  if (checkbox) checkbox.checked = visible
647
752
  })
753
+
754
+ // 更新父文件夹的复选框状态
755
+ updateFolderCheckboxes()
648
756
  }
649
757
  }
650
758
 
759
+ // 更新所有文件夹复选框的状态
760
+ function updateFolderCheckboxes() {
761
+ const tree = currentView === 'dfc' && currentDfcKey
762
+ ? viewer.layerManager.getTreeStructureByPath(currentDfcKey)
763
+ : viewer.layerManager.getTreeStructure()
764
+
765
+ updateFolderCheckboxRecursive(tree, '')
766
+ }
767
+
768
+ // 递归更新文件夹复选框状态
769
+ function updateFolderCheckboxRecursive(nodes, parentPath) {
770
+ nodes.forEach(node => {
771
+ if (node.type === 'folder') {
772
+ const currentPath = parentPath ? `${parentPath}/${node.name}` : node.name
773
+ const checkboxState = getFolderCheckboxState(node)
774
+ const checkboxId = `folder-checkbox-${currentPath.replace(/\//g, '-')}`
775
+ const checkbox = document.getElementById(checkboxId)
776
+
777
+ if (checkbox) {
778
+ checkbox.checked = checkboxState.checked
779
+ checkbox.indeterminate = checkboxState.indeterminate
780
+ }
781
+
782
+ // 递归更新子文件夹
783
+ if (node.children) {
784
+ updateFolderCheckboxRecursive(node.children, currentPath)
785
+ }
786
+ }
787
+ })
788
+ }
789
+
651
790
  // 兼容旧版本的函数
652
791
  window.toggleLayerVisibility = function (index, visible) {
653
792
  toggleLayerVisibilityByIndex(index, visible)
@@ -705,7 +844,7 @@
705
844
  // 如果有 DFC key,也打印 DFC 树形结构
706
845
  if (currentDfcKey) {
707
846
  console.log(`=== DFC 树形结构 (key: ${currentDfcKey}) ===`)
708
- console.log(viewer.layerManager.getDfcTreeStructure(currentDfcKey))
847
+ console.log(viewer.layerManager.getTreeStructureByPath(currentDfcKey))
709
848
  }
710
849
 
711
850
  // 打印所有有 path 属性的图层(用于查找可用的 DFC key)
@@ -0,0 +1,299 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>属性查看工具演示 - Bc-model-viewer</title>
8
+ <link rel="stylesheet" href="common-styles.css">
9
+ <style>
10
+ .demo {
11
+ position: absolute;
12
+ top: 20px;
13
+ left: 20px;
14
+ z-index: 1000;
15
+ background: rgba(255, 255, 255, 0.95);
16
+ backdrop-filter: blur(10px);
17
+ border-radius: 12px;
18
+ padding: 20px;
19
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
20
+ max-width: 350px;
21
+ }
22
+
23
+ .control-group {
24
+ margin-bottom: 15px;
25
+ }
26
+
27
+ .control-group h3 {
28
+ margin: 0 0 10px 0;
29
+ color: #333;
30
+ font-size: 16px;
31
+ }
32
+
33
+ .btn {
34
+ background: #377ee8;
35
+ color: white;
36
+ border: none;
37
+ padding: 8px 12px;
38
+ border-radius: 4px;
39
+ cursor: pointer;
40
+ font-size: 14px;
41
+ transition: background 0.2s;
42
+ margin-right: 8px;
43
+ margin-bottom: 8px;
44
+ }
45
+
46
+ .btn:hover {
47
+ background: #2a6bd1;
48
+ }
49
+
50
+ .btn.active {
51
+ background: #1e4f9e;
52
+ }
53
+
54
+ .btn:disabled {
55
+ background: #aaa;
56
+ cursor: not-allowed;
57
+ }
58
+
59
+ .instructions {
60
+ background: rgba(55, 126, 232, 0.1);
61
+ border: 1px solid rgba(55, 126, 232, 0.3);
62
+ border-radius: 4px;
63
+ padding: 15px;
64
+ margin-bottom: 15px;
65
+ }
66
+
67
+ .instructions h4 {
68
+ margin: 0 0 10px 0;
69
+ color: #377ee8;
70
+ }
71
+
72
+ .instructions ul {
73
+ margin: 0;
74
+ padding-left: 20px;
75
+ }
76
+
77
+ .instructions li {
78
+ margin-bottom: 5px;
79
+ color: #666;
80
+ font-size: 13px;
81
+ }
82
+
83
+ .model-info {
84
+ background: rgba(0, 0, 0, 0.05);
85
+ border-radius: 4px;
86
+ padding: 15px;
87
+ margin-bottom: 15px;
88
+ }
89
+
90
+ .model-info h4 {
91
+ margin: 0 0 10px 0;
92
+ color: #377ee8;
93
+ }
94
+
95
+ .info-item {
96
+ display: flex;
97
+ justify-content: space-between;
98
+ margin-bottom: 5px;
99
+ font-size: 13px;
100
+ }
101
+
102
+ .info-label {
103
+ color: #666;
104
+ }
105
+
106
+ .info-value {
107
+ color: #333;
108
+ font-weight: 500;
109
+ }
110
+ </style>
111
+ </head>
112
+
113
+ <body>
114
+ <div id="container"></div>
115
+ <a href="index.html" class="back-button">返回示例列表</a>
116
+
117
+ <div class="demo">
118
+ <h2>🔍 属性查看工具演示</h2>
119
+ <p>点击模型上的对象查看其属性信息,包括基本属性和用户自定义数据。</p>
120
+
121
+ <div class="instructions">
122
+ <h4>使用说明</h4>
123
+ <ul>
124
+ <li>点击"激活工具"按钮启用属性查看功能</li>
125
+ <li>点击模型上的任意对象查看其属性</li>
126
+ <li>属性信息将显示在右侧面板中</li>
127
+ <li>点击空白区域可关闭属性面板</li>
128
+ <li>支持查看基本属性和用户自定义属性</li>
129
+ </ul>
130
+ </div>
131
+
132
+ <div class="control-group">
133
+ <h3>工具控制</h3>
134
+ <button id="activateBtn" class="btn">激活工具</button>
135
+ <button id="deactivateBtn" class="btn" disabled>停用工具</button>
136
+ </div>
137
+
138
+ <div class="control-group">
139
+ <h3>加载模型</h3>
140
+ <button id="loadModel1" class="btn">加载测试模型1</button>
141
+ <button id="loadModel2" class="btn">加载测试模型2</button>
142
+ </div>
143
+
144
+ <div class="model-info">
145
+ <h4>当前状态</h4>
146
+ <div class="info-item">
147
+ <span class="info-label">工具状态:</span>
148
+ <span id="toolStatus" class="info-value">未激活</span>
149
+ </div>
150
+ <div class="info-item">
151
+ <span class="info-label">已选择对象:</span>
152
+ <span id="selectedObject" class="info-value">无</span>
153
+ </div>
154
+ <div class="info-item">
155
+ <span class="info-label">模型加载:</span>
156
+ <span id="modelStatus" class="info-value">未加载</span>
157
+ </div>
158
+ </div>
159
+
160
+ <div class="control-group">
161
+ <p style="font-size: 12px; color: #888; margin-top: 10px;">
162
+ 💡 使用提示:<br>
163
+ • 激活工具后,鼠标悬停在对象上会显示对象名称<br>
164
+ • 点击对象后,属性面板会显示在右上角<br>
165
+ • 支持查看位置、旋转、缩放等基本属性<br>
166
+ • 自动提取并显示对象的用户自定义数据<br>
167
+ • 点击空白区域或关闭按钮可隐藏属性面板
168
+ </p>
169
+ </div>
170
+ </div>
171
+
172
+ <script type="module">
173
+ import { Viewer, PropertyViewerTool } from '../../index.ts'
174
+
175
+ const container = document.getElementById('container')
176
+
177
+ // 实例化 Viewer 对象
178
+ const viewer = new Viewer(container, {
179
+ load: {
180
+ cache: {
181
+ enabled: false
182
+ }
183
+ }
184
+ })
185
+
186
+ let propertyViewerTool = null
187
+
188
+ // 更新状态显示
189
+ function updateStatus() {
190
+ document.getElementById('toolStatus').textContent =
191
+ propertyViewerTool && propertyViewerTool.isActive ? '已激活' : '未激活'
192
+
193
+ document.getElementById('selectedObject').textContent =
194
+ propertyViewerTool && propertyViewerTool.selectedObject ?
195
+ (propertyViewerTool.selectedObject.name || propertyViewerTool.selectedObject.type) : '无'
196
+ }
197
+
198
+ // 激活属性查看工具
199
+ document.getElementById('activateBtn').addEventListener('click', () => {
200
+ if (!propertyViewerTool) {
201
+ propertyViewerTool = new PropertyViewerTool(viewer)
202
+ }
203
+
204
+ // 停用其他工具
205
+ // viewer.deactivateAllTools()
206
+
207
+ // 激活属性查看工具
208
+ propertyViewerTool.activate()
209
+
210
+ // 更新按钮状态
211
+ document.getElementById('activateBtn').disabled = true
212
+ document.getElementById('deactivateBtn').disabled = false
213
+
214
+ updateStatus()
215
+ })
216
+
217
+ // 停用属性查看工具
218
+ document.getElementById('deactivateBtn').addEventListener('click', () => {
219
+ if (propertyViewerTool) {
220
+ propertyViewerTool.deactivate()
221
+
222
+ // 更新按钮状态
223
+ document.getElementById('activateBtn').disabled = false
224
+ document.getElementById('deactivateBtn').disabled = true
225
+
226
+ updateStatus()
227
+ }
228
+ })
229
+
230
+ // 加载测试模型1
231
+ document.getElementById('loadModel1').addEventListener('click', async () => {
232
+ try {
233
+ // 加载测试模型
234
+ await viewer.loadZipAsync('../../assets/dgz/建筑.dgz')
235
+ document.getElementById('modelStatus').textContent = '模型1已加载'
236
+
237
+ // 为模型对象添加一些自定义属性用于测试
238
+ setTimeout(() => {
239
+ if (viewer.scene && viewer.scene.children.length > 0) {
240
+ viewer.scene.children.forEach((child, index) => {
241
+ // 添加一些测试用的用户数据
242
+ child.userData = {
243
+ material: 'Steel',
244
+ weight: '15kg',
245
+ manufacturer: 'Test Corp',
246
+ partNumber: `PN-${index + 1000}`,
247
+ description: '这是一个测试对象的描述信息'
248
+ }
249
+ })
250
+ }
251
+ }, 100)
252
+
253
+ } catch (error) {
254
+ console.error('加载模型失败:', error)
255
+ document.getElementById('modelStatus').textContent = '加载失败'
256
+ }
257
+ })
258
+
259
+ // 加载测试模型2
260
+ document.getElementById('loadModel2').addEventListener('click', async () => {
261
+ try {
262
+ // 加载另一个测试模型
263
+ await viewer.loadZipAsync('../../assets/dgz/样板间.dgz')
264
+ document.getElementById('modelStatus').textContent = '模型2已加载'
265
+
266
+ // 为模型对象添加不同的自定义属性
267
+ setTimeout(() => {
268
+ if (viewer.scene && viewer.scene.children.length > 0) {
269
+ viewer.scene.children.forEach((child, index) => {
270
+ child.userData = {
271
+ material: 'Aluminum',
272
+ weight: '8kg',
273
+ manufacturer: 'Demo Inc',
274
+ partNumber: `PN-${index + 2000}`,
275
+ color: 'Blue',
276
+ productionDate: '2024-01-15'
277
+ }
278
+ })
279
+ }
280
+ }, 100)
281
+
282
+ } catch (error) {
283
+ console.error('加载模型失败:', error)
284
+ document.getElementById('modelStatus').textContent = '加载失败'
285
+ }
286
+ })
287
+
288
+ // 初始状态更新
289
+ updateStatus()
290
+
291
+ // 暴露到全局对象方便调试
292
+ window.viewer = viewer
293
+ window.propertyViewerTool = propertyViewerTool
294
+
295
+ console.log('属性查看工具演示已初始化')
296
+ </script>
297
+ </body>
298
+
299
+ </html>