2025-12-03 18:35:05 +08:00
|
|
|
|
<!DOCTYPE html>
|
|
|
|
|
|
<html lang="en">
|
|
|
|
|
|
|
|
|
|
|
|
<head>
|
|
|
|
|
|
<meta charset="UTF-8">
|
|
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
2026-01-22 11:29:51 +08:00
|
|
|
|
<title>iFlow Engine Demo</title>
|
2025-12-04 18:39:07 +08:00
|
|
|
|
<!-- 从本地 lib 目录加载 SDK 文件 -->
|
2026-02-02 16:36:17 +08:00
|
|
|
|
<script src="./lib/iflow-engine.umd.js"></script>
|
2025-12-04 15:24:44 +08:00
|
|
|
|
<style>
|
|
|
|
|
|
* {
|
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
padding: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
body {
|
|
|
|
|
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
|
|
|
|
background-color: #f5f5f5;
|
|
|
|
|
|
height: 100vh;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
/* 防止整个页面滚动 */
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 左侧侧边栏:控制面板 */
|
|
|
|
|
|
.sidebar {
|
|
|
|
|
|
width: 320px;
|
|
|
|
|
|
background: white;
|
|
|
|
|
|
border-right: 1px solid #e0e0e0;
|
|
|
|
|
|
padding: 20px;
|
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
|
/* 内容过多时可滚动 */
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 20px;
|
|
|
|
|
|
box-shadow: 2px 0 5px rgba(0, 0, 0, 0.05);
|
|
|
|
|
|
z-index: 10;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.sidebar h1 {
|
|
|
|
|
|
font-size: 1.4rem;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
margin-bottom: 10px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.control-group {
|
|
|
|
|
|
background: #f9f9f9;
|
|
|
|
|
|
padding: 15px;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
border: 1px solid #eee;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.control-group h2 {
|
|
|
|
|
|
font-size: 1rem;
|
|
|
|
|
|
color: #555;
|
|
|
|
|
|
margin-bottom: 10px;
|
|
|
|
|
|
padding-bottom: 5px;
|
|
|
|
|
|
border-bottom: 2px solid #e0e0e0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.btn-container {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
button {
|
|
|
|
|
|
padding: 6px 12px;
|
|
|
|
|
|
font-size: 0.9rem;
|
|
|
|
|
|
border: 1px solid #ddd;
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
transition: all 0.2s;
|
|
|
|
|
|
flex: 1 1 auto;
|
|
|
|
|
|
/* 按钮自动填充 */
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
button:hover {
|
|
|
|
|
|
background: #f0f0f0;
|
|
|
|
|
|
border-color: #ccc;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
button.primary {
|
|
|
|
|
|
background: #0078d4;
|
|
|
|
|
|
color: white;
|
|
|
|
|
|
border-color: #0063b1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
button.primary:hover {
|
|
|
|
|
|
background: #0063b1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 右侧主内容区:引擎展示 */
|
|
|
|
|
|
.main-content {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
background: #eef2f5;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#app {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|
2025-12-03 18:35:05 +08:00
|
|
|
|
</head>
|
|
|
|
|
|
|
|
|
|
|
|
<body>
|
2025-12-04 15:24:44 +08:00
|
|
|
|
<!-- 左侧控制面板 -->
|
|
|
|
|
|
<aside class="sidebar">
|
|
|
|
|
|
<h1>BIM SDK Demo</h1>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 1. 语言设置 -->
|
|
|
|
|
|
<div class="control-group">
|
|
|
|
|
|
<h2>🌍 语言 (Language)</h2>
|
|
|
|
|
|
<div class="btn-container">
|
|
|
|
|
|
<button class="primary" onclick="setLang('zh-CN')">中文</button>
|
2026-03-30 10:53:39 +08:00
|
|
|
|
<button class="primary" onclick="setLang('zh-TW')">繁體中文</button>
|
2025-12-04 15:24:44 +08:00
|
|
|
|
<button class="primary" onclick="setLang('en-US')">English</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 2. 弹窗测试 -->
|
|
|
|
|
|
<div class="control-group">
|
|
|
|
|
|
<h2>🪟 弹窗 (Dialog)</h2>
|
|
|
|
|
|
<div class="btn-container">
|
|
|
|
|
|
<button onclick="openTestDialog()">测试弹窗</button>
|
|
|
|
|
|
<button onclick="openInfoDialog()">信息弹窗</button>
|
|
|
|
|
|
<button onclick="openRedDialog()">警告弹窗</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-03-30 10:53:39 +08:00
|
|
|
|
<!-- 3. 工具栏操作 (CusBimEngine 中已移除 ToolbarManager) -->
|
|
|
|
|
|
<div class="control-group" style="opacity: 0.6;">
|
|
|
|
|
|
<h2>🛠️ 工具栏 (Toolbar) <span style="color: #999; font-size: 0.75rem;">[CusBimEngine 已移除]</span></h2>
|
|
|
|
|
|
<div style="font-size: 0.8rem; color: #888; margin-bottom: 8px;">
|
|
|
|
|
|
当前使用 CusBimEngine,不包含工具栏功能
|
|
|
|
|
|
</div>
|
2025-12-04 15:24:44 +08:00
|
|
|
|
<div class="btn-container">
|
2026-03-30 10:53:39 +08:00
|
|
|
|
<button disabled>显隐工具栏</button>
|
|
|
|
|
|
<button disabled>显隐标签</button>
|
|
|
|
|
|
<button disabled>显隐定位按钮</button>
|
2025-12-04 15:24:44 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<div class="btn-container" style="margin-top: 8px;">
|
2026-03-30 10:53:39 +08:00
|
|
|
|
<button disabled>加组</button>
|
|
|
|
|
|
<button disabled>加按钮</button>
|
2025-12-04 15:24:44 +08:00
|
|
|
|
</div>
|
2026-01-28 12:00:55 +08:00
|
|
|
|
<div class="btn-container" style="margin-top: 8px;">
|
2026-03-30 10:53:39 +08:00
|
|
|
|
<button disabled>默认样式</button>
|
|
|
|
|
|
<button disabled>胶囊样式</button>
|
2026-01-28 12:00:55 +08:00
|
|
|
|
</div>
|
2025-12-04 15:24:44 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 4. 样式主题 -->
|
|
|
|
|
|
<div class="control-group">
|
|
|
|
|
|
<h2>🎨 样式 (Theme)</h2>
|
|
|
|
|
|
<div class="btn-container">
|
|
|
|
|
|
<button onclick="setTheme('dark')">深色 (Dark)</button>
|
|
|
|
|
|
<button onclick="setTheme('light')">浅色 (Light)</button>
|
|
|
|
|
|
<button onclick="setCustomTheme()">自定义 (Red)</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-12-04 18:39:07 +08:00
|
|
|
|
|
2025-12-22 15:39:58 +08:00
|
|
|
|
<!-- 6. 功能面板 -->
|
|
|
|
|
|
<div class="control-group">
|
|
|
|
|
|
<h2>📑 功能面板 (Panels)</h2>
|
|
|
|
|
|
<div class="btn-container">
|
|
|
|
|
|
<button onclick="openPropertyPanel()">属性面板</button>
|
2026-03-30 10:53:39 +08:00
|
|
|
|
<button onclick="viewPresetCacheData()">查看预设缓存</button>
|
2025-12-22 15:39:58 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-02-28 10:09:17 +08:00
|
|
|
|
<!-- 7. 多Tab加载 -->
|
|
|
|
|
|
<div class="control-group">
|
|
|
|
|
|
<h2>📺 多Tab加载 (Multi-Tab)</h2>
|
|
|
|
|
|
<div class="btn-container">
|
|
|
|
|
|
<button class="primary" onclick="window.open('./multi-tab.html', '_blank')">打开多Tab视图</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div style="margin-top: 8px; font-size: 0.8rem; color: #888;">
|
|
|
|
|
|
两个独立面板,各自加载模型,可拖拽分割线调整宽度
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-03-04 16:40:35 +08:00
|
|
|
|
<!-- 8. 构件树数据 -->
|
|
|
|
|
|
<div class="control-group">
|
|
|
|
|
|
<h2>🌳 构件树 (Tree Data)</h2>
|
|
|
|
|
|
<div class="btn-container">
|
|
|
|
|
|
<button onclick="getLevelTree()">楼层树</button>
|
|
|
|
|
|
<button onclick="getTypeTree()">类型树</button>
|
|
|
|
|
|
<button onclick="getMajorTree()">专业树</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-03-26 09:34:21 +08:00
|
|
|
|
<!-- 9. 拾取构件 (Issue Report) -->
|
|
|
|
|
|
<div class="control-group">
|
|
|
|
|
|
<h2>🔍 拾取构件 (Issue Report)</h2>
|
|
|
|
|
|
<div class="btn-container">
|
|
|
|
|
|
<button id="btn-pick-component" class="primary" onclick="startPickComponent()">拾取构件</button>
|
|
|
|
|
|
<button onclick="getSelectedComponentInfo()">获取选中信息</button>
|
|
|
|
|
|
<button class="primary" onclick="openJumpToCameraDialog()">跳转构件</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div style="margin-top: 10px; font-size: 0.85rem; color: #666;">
|
|
|
|
|
|
<div>拾取状态: <span id="pick-status">未开始</span></div>
|
|
|
|
|
|
<div id="picked-component-info" style="margin-top: 4px; word-break: break-all;"></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2025-12-04 18:39:07 +08:00
|
|
|
|
<!-- 5. 3D 引擎 -->
|
|
|
|
|
|
<div class="control-group">
|
|
|
|
|
|
<h2>🎮 3D 引擎 (Engine3D)</h2>
|
|
|
|
|
|
<div class="btn-container">
|
|
|
|
|
|
<button class="primary" onclick="initEngine3D()">初始化引擎</button>
|
|
|
|
|
|
<button class="primary" onclick="loadModel()">加载模型</button>
|
2026-01-23 16:27:04 +08:00
|
|
|
|
<button onclick="switchModel()">切换模型</button>
|
2026-02-28 10:09:17 +08:00
|
|
|
|
<button onclick="loadCombinedModel()">组合模型</button>
|
2026-03-05 17:43:50 +08:00
|
|
|
|
<button onclick="openCombineDialog()">自动组合</button>
|
2026-01-23 16:27:04 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<div class="btn-container" style="margin-top: 8px;">
|
|
|
|
|
|
<button onclick="pauseRendering()">暂停渲染</button>
|
|
|
|
|
|
<button onclick="resumeRendering()">恢复渲染</button>
|
2025-12-04 18:39:07 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<div style="margin-top: 10px; font-size: 0.85rem; color: #666;">
|
|
|
|
|
|
<div>状态: <span id="engine-status">未初始化</span></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-03-16 16:13:36 +08:00
|
|
|
|
|
|
|
|
|
|
<!-- 2D 图纸 -->
|
|
|
|
|
|
<div class="control-group">
|
|
|
|
|
|
<h2>📐 2D 图纸 (Engine2D)</h2>
|
|
|
|
|
|
<div class="btn-container">
|
|
|
|
|
|
<button class="primary" id="btn-init2d" onclick="initEngine2d()">初始化 2D 引擎</button>
|
|
|
|
|
|
<button id="btn-load2d" onclick="loadDrawing()" disabled>加载图纸</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div style="margin-top: 8px;">
|
|
|
|
|
|
<input type="text" id="url-input-2d"
|
|
|
|
|
|
style="width:100%;padding:6px 8px;font-size:0.85rem;border:1px solid #ddd;border-radius:4px;font-family:Consolas,Monaco,monospace;outline:none;"
|
|
|
|
|
|
placeholder="输入新的图纸 URL" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="btn-container" style="margin-top: 6px;">
|
|
|
|
|
|
<button class="primary" id="btn-switch2d" onclick="switchDrawing()" disabled>切换图纸</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div style="margin-top: 6px; font-size: 0.85rem; color: #666;">
|
|
|
|
|
|
<div>状态: <span id="engine2d-status">未初始化</span></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 720 全景 -->
|
|
|
|
|
|
<div class="control-group">
|
|
|
|
|
|
<h2>🎥 720 全景 (Engine720)</h2>
|
|
|
|
|
|
<div class="btn-container">
|
|
|
|
|
|
<button class="primary" id="btn-init720" onclick="initEngine720()">初始化 720 引擎</button>
|
|
|
|
|
|
<button id="btn-load720" onclick="loadPanorama()" disabled>加载全景图</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div style="margin-top: 6px; font-size: 0.85rem; color: #666;">
|
|
|
|
|
|
<div>状态: <span id="engine720-status">未初始化</span></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-12-04 15:24:44 +08:00
|
|
|
|
</aside>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 右侧主区域 -->
|
|
|
|
|
|
<main class="main-content">
|
|
|
|
|
|
<div id="app"></div>
|
|
|
|
|
|
</main>
|
2025-12-03 18:35:05 +08:00
|
|
|
|
|
2026-03-05 17:43:50 +08:00
|
|
|
|
<!-- 自动组合弹窗 -->
|
2026-03-16 16:13:36 +08:00
|
|
|
|
<div id="combine-overlay"
|
|
|
|
|
|
style="display:none; position:fixed; inset:0; background:rgba(0,0,0,.45); z-index:9999; display:none; justify-content:center; align-items:center;">
|
|
|
|
|
|
<div
|
|
|
|
|
|
style="background:#fff; border-radius:10px; padding:24px; width:520px; max-width:90vw; box-shadow:0 8px 32px rgba(0,0,0,.18);">
|
2026-03-05 17:43:50 +08:00
|
|
|
|
<h3 style="margin:0 0 12px; font-size:1.1rem; color:#333;">自动组合加载</h3>
|
|
|
|
|
|
<p style="margin:0 0 10px; font-size:.85rem; color:#888;">请输入模型 URL,每行一个或用英文逗号分隔:</p>
|
2026-03-16 16:13:36 +08:00
|
|
|
|
<textarea id="combine-urls" rows="6"
|
|
|
|
|
|
style="width:100%; padding:10px; font-size:.85rem; border:1px solid #ddd; border-radius:6px; resize:vertical; font-family:monospace;"
|
|
|
|
|
|
placeholder="https://example.com/model1/ https://example.com/model2/"></textarea>
|
2026-03-05 17:43:50 +08:00
|
|
|
|
<div style="display:flex; justify-content:flex-end; gap:8px; margin-top:14px;">
|
|
|
|
|
|
<button onclick="closeCombineDialog()" style="min-width:72px;">取消</button>
|
|
|
|
|
|
<button class="primary" onclick="confirmCombineLoad()" style="min-width:72px;">确定加载</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-03-26 09:34:21 +08:00
|
|
|
|
<!-- 跳转构件弹窗 -->
|
|
|
|
|
|
<div id="jump-camera-overlay"
|
|
|
|
|
|
style="display:none; position:fixed; inset:0; background:rgba(0,0,0,.45); z-index:9999; justify-content:center; align-items:center;">
|
|
|
|
|
|
<div
|
|
|
|
|
|
style="background:#fff; border-radius:10px; padding:24px; width:560px; max-width:90vw; box-shadow:0 8px 32px rgba(0,0,0,.18);">
|
|
|
|
|
|
<h3 style="margin:0 0 12px; font-size:1.1rem; color:#333;">跳转到构件视角</h3>
|
|
|
|
|
|
<p style="margin:0 0 10px; font-size:.85rem; color:#888;">请输入相机配置 JSON(参考格式:modelJson.cameraRestorePose):</p>
|
|
|
|
|
|
<textarea id="jump-camera-json" rows="12"
|
|
|
|
|
|
style="width:100%; padding:10px; font-size:.85rem; border:1px solid #ddd; border-radius:6px; resize:vertical; font-family:monospace;"
|
|
|
|
|
|
placeholder='{
|
|
|
|
|
|
"type": "orthographic",
|
|
|
|
|
|
"position": {"x": 229.68, "y": 247.98, "z": 233.79},
|
|
|
|
|
|
"target": {"x": -4.58, "y": 13.72, "z": -0.47},
|
|
|
|
|
|
"orthographicHeight": 140.56
|
|
|
|
|
|
}'></textarea>
|
|
|
|
|
|
<div style="margin-top:10px; font-size:.8rem; color:#666;">
|
|
|
|
|
|
<details>
|
|
|
|
|
|
<summary style="cursor:pointer; color:#0078d4;">查看完整示例格式</summary>
|
|
|
|
|
|
<pre style="margin-top:8px; padding:10px; background:#f5f5f5; border-radius:4px; overflow-x:auto; font-size:.75rem;">{
|
|
|
|
|
|
"type": "orthographic",
|
|
|
|
|
|
"position": {"x": 229.68, "y": 247.98, "z": 233.79},
|
|
|
|
|
|
"rotation": {"x": -0.79, "y": 0.62, "z": 0.52},
|
|
|
|
|
|
"quaternion": {"x": -0.28, "y": 0.36, "z": 0.12, "w": 0.88},
|
|
|
|
|
|
"target": {"x": -4.58, "y": 13.72, "z": -0.47},
|
|
|
|
|
|
"zoom": 1,
|
|
|
|
|
|
"orthographicHeight": 140.56
|
|
|
|
|
|
}</pre>
|
|
|
|
|
|
</details>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div style="display:flex; justify-content:flex-end; gap:8px; margin-top:14px;">
|
|
|
|
|
|
<button onclick="closeJumpToCameraDialog()" style="min-width:72px;">取消</button>
|
|
|
|
|
|
<button class="primary" onclick="confirmJumpToCamera()" style="min-width:72px;">确定跳转</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2025-12-03 18:35:05 +08:00
|
|
|
|
<script>
|
2026-03-16 16:13:36 +08:00
|
|
|
|
let engine = null; // BimEngine (3D) 实例
|
|
|
|
|
|
let engine2d = null; // BimEngine2d 实例
|
|
|
|
|
|
let engine720 = null; // BimEngine720 实例
|
2025-12-03 18:35:05 +08:00
|
|
|
|
let isToolbarVisible = true;
|
|
|
|
|
|
let isLabelVisible = true;
|
|
|
|
|
|
let isLocationVisible = true;
|
|
|
|
|
|
let customGroupAdded = false;
|
2026-03-30 10:53:39 +08:00
|
|
|
|
let currentLocale = 'zh-CN';
|
|
|
|
|
|
let unsubscribePresetSaved = null;
|
|
|
|
|
|
let unsubscribePresetChanged = null;
|
|
|
|
|
|
let unsubscribePresetDeleted = null;
|
|
|
|
|
|
|
|
|
|
|
|
const PRESET_CACHE_KEY = 'iflow-demo-setting-presets-v1';
|
|
|
|
|
|
|
|
|
|
|
|
function injectSettingPresetsFromCache() {
|
|
|
|
|
|
if (!engine || !engine.setting || typeof engine.setting.setPresetList !== 'function') {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
engine.setting.setPresetList(buildPresetListFromCache());
|
|
|
|
|
|
console.log('✅ 已注入缓存预设列表');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function safeClone(data) {
|
|
|
|
|
|
return JSON.parse(JSON.stringify(data));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function loadPresetCache() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const raw = localStorage.getItem(PRESET_CACHE_KEY);
|
|
|
|
|
|
if (!raw) return [];
|
|
|
|
|
|
const parsed = JSON.parse(raw);
|
|
|
|
|
|
return Array.isArray(parsed) ? parsed : [];
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.warn('读取预设缓存失败,已忽略:', error);
|
|
|
|
|
|
return [];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function savePresetCache(list) {
|
|
|
|
|
|
localStorage.setItem(PRESET_CACHE_KEY, JSON.stringify(list));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function buildPresetListFromCache() {
|
|
|
|
|
|
return loadPresetCache().filter((item) => item && typeof item.id === 'string');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function upsertPresetCache(preset) {
|
|
|
|
|
|
const list = loadPresetCache();
|
|
|
|
|
|
const index = list.findIndex((item) => item.id === preset.id);
|
|
|
|
|
|
const normalizedPreset = { ...preset, isDefault: Boolean(preset.isDefault) };
|
|
|
|
|
|
|
|
|
|
|
|
if (index >= 0) {
|
|
|
|
|
|
list[index] = normalizedPreset;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
list.push(normalizedPreset);
|
|
|
|
|
|
}
|
|
|
|
|
|
savePresetCache(list);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function deletePresetCache(preset) {
|
|
|
|
|
|
const list = loadPresetCache();
|
|
|
|
|
|
const next = list.filter((item) => item && item.id !== preset.id);
|
|
|
|
|
|
savePresetCache(next);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function bindPresetEvents() {
|
|
|
|
|
|
if (!engine || typeof engine.on !== 'function') return;
|
|
|
|
|
|
|
|
|
|
|
|
if (unsubscribePresetSaved) {
|
|
|
|
|
|
unsubscribePresetSaved();
|
|
|
|
|
|
unsubscribePresetSaved = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (unsubscribePresetChanged) {
|
|
|
|
|
|
unsubscribePresetChanged();
|
|
|
|
|
|
unsubscribePresetChanged = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (unsubscribePresetDeleted) {
|
|
|
|
|
|
unsubscribePresetDeleted();
|
|
|
|
|
|
unsubscribePresetDeleted = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
unsubscribePresetSaved = engine.on('setting:preset-saved', ({ preset }) => {
|
|
|
|
|
|
upsertPresetCache(safeClone(preset));
|
|
|
|
|
|
injectSettingPresetsFromCache();
|
|
|
|
|
|
console.log('💾 已写入预设缓存:', preset);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
unsubscribePresetChanged = engine.on('setting:preset-changed', ({ preset }) => {
|
|
|
|
|
|
console.log('🔁 已切换预设:', preset);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
unsubscribePresetDeleted = engine.on('setting:preset-deleted', (preset) => {
|
|
|
|
|
|
deletePresetCache(safeClone(preset));
|
|
|
|
|
|
injectSettingPresetsFromCache();
|
|
|
|
|
|
console.log('🗑️ 已删除预设缓存:', preset);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function viewPresetCacheData() {
|
|
|
|
|
|
const data = loadPresetCache();
|
|
|
|
|
|
console.log('📦 当前预设缓存数据:', data);
|
|
|
|
|
|
window.alert(data.length === 0 ? '当前没有预设缓存数据' : JSON.stringify(data, null, 2));
|
|
|
|
|
|
}
|
2025-12-03 18:35:05 +08:00
|
|
|
|
|
2026-03-16 16:13:36 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 销毁所有引擎实例
|
|
|
|
|
|
*/
|
|
|
|
|
|
function destroyAllEngines() {
|
2026-03-30 10:53:39 +08:00
|
|
|
|
if (unsubscribePresetSaved) {
|
|
|
|
|
|
unsubscribePresetSaved();
|
|
|
|
|
|
unsubscribePresetSaved = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (unsubscribePresetChanged) {
|
|
|
|
|
|
unsubscribePresetChanged();
|
|
|
|
|
|
unsubscribePresetChanged = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (unsubscribePresetDeleted) {
|
|
|
|
|
|
unsubscribePresetDeleted();
|
|
|
|
|
|
unsubscribePresetDeleted = null;
|
|
|
|
|
|
}
|
2026-03-16 16:13:36 +08:00
|
|
|
|
if (engine) { engine.destroy(); engine = null; }
|
|
|
|
|
|
if (engine2d) { engine2d.destroy(); engine2d = null; }
|
|
|
|
|
|
if (engine720) { engine720.destroy(); engine720 = null; }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 页面加载完成后默认初始化 3D 引擎
|
2025-12-03 18:35:05 +08:00
|
|
|
|
window.onload = () => {
|
2026-01-22 11:29:51 +08:00
|
|
|
|
if (window.IflowEngine) {
|
2025-12-03 18:35:05 +08:00
|
|
|
|
try {
|
2026-01-15 14:13:13 +08:00
|
|
|
|
initEngine3D();
|
2025-12-03 18:35:05 +08:00
|
|
|
|
} catch (err) {
|
|
|
|
|
|
console.error('Init failed:', err);
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
console.error('SDK not found');
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-12-04 15:24:44 +08:00
|
|
|
|
// --- 语言设置 ---
|
|
|
|
|
|
function setLang(lang) {
|
2026-03-30 10:53:39 +08:00
|
|
|
|
currentLocale = lang;
|
2025-12-04 15:24:44 +08:00
|
|
|
|
if (engine) engine.setLocale(lang);
|
2026-03-30 10:53:39 +08:00
|
|
|
|
if (engine2d) engine2d.setLocale(lang);
|
|
|
|
|
|
if (engine720) engine720.setLocale(lang);
|
2025-12-04 15:24:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// --- 弹窗测试 ---
|
|
|
|
|
|
function openTestDialog() {
|
|
|
|
|
|
if (!engine || !engine.dialog) return;
|
|
|
|
|
|
engine.dialog.create({
|
|
|
|
|
|
title: 'dialog.testTitle',
|
|
|
|
|
|
width: 350,
|
|
|
|
|
|
height: 200,
|
|
|
|
|
|
position: 'center',
|
|
|
|
|
|
draggable: true,
|
|
|
|
|
|
resizable: true
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function openInfoDialog() {
|
|
|
|
|
|
if (!engine || !engine.dialog) return;
|
|
|
|
|
|
engine.dialog.showInfoDialog();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function openRedDialog() {
|
|
|
|
|
|
if (!engine || !engine.dialog) return;
|
|
|
|
|
|
engine.dialog.create({
|
|
|
|
|
|
title: 'Alert',
|
|
|
|
|
|
content: '<div style="color: #ffcccc;">Critical Warning!</div>',
|
|
|
|
|
|
width: 300,
|
|
|
|
|
|
height: 150,
|
|
|
|
|
|
backgroundColor: 'rgba(100, 0, 0, 0.95)',
|
|
|
|
|
|
headerBackgroundColor: '#cc0000',
|
|
|
|
|
|
titleColor: '#ffffff',
|
|
|
|
|
|
borderColor: '#ff6666',
|
|
|
|
|
|
position: { x: 50, y: 50 }
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// --- 工具栏操作 ---
|
2025-12-03 18:35:05 +08:00
|
|
|
|
function toggleToolbar() {
|
|
|
|
|
|
if (!engine || !engine.toolbar) return;
|
|
|
|
|
|
isToolbarVisible = !isToolbarVisible;
|
|
|
|
|
|
engine.toolbar.setVisible(isToolbarVisible);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function toggleLabel() {
|
|
|
|
|
|
if (!engine || !engine.toolbar) return;
|
|
|
|
|
|
isLabelVisible = !isLabelVisible;
|
|
|
|
|
|
engine.toolbar.setShowLabel(isLabelVisible);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-04 15:24:44 +08:00
|
|
|
|
function toggleLocationBtn() {
|
|
|
|
|
|
if (!engine || !engine.toolbar) return;
|
|
|
|
|
|
isLocationVisible = !isLocationVisible;
|
|
|
|
|
|
engine.toolbar.setButtonVisibility('location', isLocationVisible);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-03 18:35:05 +08:00
|
|
|
|
function addCustomGroup() {
|
|
|
|
|
|
if (!engine || !engine.toolbar) return;
|
|
|
|
|
|
if (customGroupAdded) {
|
|
|
|
|
|
alert('已添加过');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
engine.toolbar.addGroup('custom-group', 'group-1');
|
|
|
|
|
|
customGroupAdded = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function addCustomButton() {
|
|
|
|
|
|
if (!engine || !engine.toolbar) return;
|
|
|
|
|
|
if (!customGroupAdded) {
|
2025-12-04 15:24:44 +08:00
|
|
|
|
alert('Please add custom group first / 请先添加自定义组');
|
2025-12-03 18:35:05 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-03-16 16:13:36 +08:00
|
|
|
|
var btnId = 'custom-btn-' + Date.now();
|
2025-12-03 18:35:05 +08:00
|
|
|
|
engine.toolbar.addButton({
|
|
|
|
|
|
id: btnId,
|
|
|
|
|
|
groupId: 'custom-group',
|
|
|
|
|
|
type: 'button',
|
2025-12-04 15:24:44 +08:00
|
|
|
|
label: 'New',
|
2025-12-03 18:35:05 +08:00
|
|
|
|
icon: '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"></circle><path d="M8 14s1.5 2 4 2 4-2 4-2"></path><line x1="9" y1="9" x2="9.01" y2="9"></line><line x1="15" y1="9" x2="15.01" y2="9"></line></svg>',
|
2026-03-16 16:13:36 +08:00
|
|
|
|
onClick: function (btn) {
|
2025-12-04 15:24:44 +08:00
|
|
|
|
alert('Clicked: ' + btn.label);
|
2025-12-03 18:35:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-28 12:00:55 +08:00
|
|
|
|
function setToolbarType(type) {
|
|
|
|
|
|
if (!engine || !engine.toolbar) return;
|
|
|
|
|
|
engine.toolbar.setType(type);
|
|
|
|
|
|
console.log('Toolbar type changed to:', type);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-04 15:24:44 +08:00
|
|
|
|
// --- 主题操作 ---
|
|
|
|
|
|
function setTheme(themeName) {
|
|
|
|
|
|
if (engine) engine.setTheme(themeName);
|
2026-03-16 16:13:36 +08:00
|
|
|
|
if (engine2d) engine2d.setTheme(themeName);
|
|
|
|
|
|
if (engine720) engine720.setTheme(themeName);
|
2025-12-03 18:35:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-04 15:24:44 +08:00
|
|
|
|
function setCustomTheme() {
|
|
|
|
|
|
if (!engine) return;
|
|
|
|
|
|
engine.setCustomTheme({
|
|
|
|
|
|
name: 'red-alert',
|
|
|
|
|
|
primary: '#d32f2f',
|
|
|
|
|
|
primaryHover: '#b71c1c',
|
2026-03-16 16:13:36 +08:00
|
|
|
|
background: '#ffebee',
|
2025-12-04 15:24:44 +08:00
|
|
|
|
panelBackground: 'rgba(255, 255, 255, 0.9)',
|
|
|
|
|
|
textPrimary: '#b71c1c',
|
|
|
|
|
|
textSecondary: '#e57373',
|
|
|
|
|
|
border: '#ffcdd2',
|
|
|
|
|
|
icon: '#d32f2f',
|
|
|
|
|
|
iconActive: '#b71c1c',
|
|
|
|
|
|
componentBackground: 'rgba(255, 205, 210, 0.3)',
|
|
|
|
|
|
componentHover: 'rgba(255, 205, 210, 0.8)',
|
|
|
|
|
|
componentActive: '#e57373'
|
2025-12-03 18:35:05 +08:00
|
|
|
|
});
|
|
|
|
|
|
}
|
2025-12-04 18:39:07 +08:00
|
|
|
|
|
2026-03-16 16:13:36 +08:00
|
|
|
|
// ====== 3D 引擎操作 ======
|
|
|
|
|
|
|
2025-12-04 18:39:07 +08:00
|
|
|
|
/**
|
2026-03-16 16:13:36 +08:00
|
|
|
|
* 初始化 3D 引擎(独立实例,销毁其他引擎类型)
|
2026-03-30 10:53:39 +08:00
|
|
|
|
* 使用 CusBimEngine(移除了按钮组和构件树)
|
2025-12-04 18:39:07 +08:00
|
|
|
|
*/
|
|
|
|
|
|
function initEngine3D() {
|
2026-03-16 16:13:36 +08:00
|
|
|
|
destroyAllEngines();
|
|
|
|
|
|
updateEngineStatus('初始化中...');
|
|
|
|
|
|
update2dStatus('未初始化');
|
|
|
|
|
|
update720Status('未初始化');
|
|
|
|
|
|
document.getElementById('btn-load2d').disabled = true;
|
|
|
|
|
|
document.getElementById('btn-load720').disabled = true;
|
2025-12-04 18:39:07 +08:00
|
|
|
|
|
|
|
|
|
|
try {
|
2026-03-30 10:53:39 +08:00
|
|
|
|
engine = new IflowEngine.CusBimEngine('app', { locale: currentLocale });
|
2026-03-16 16:13:36 +08:00
|
|
|
|
var success = engine.engine.initialize({
|
|
|
|
|
|
backgroundColor: 0x333333,
|
|
|
|
|
|
version: 'v2',
|
|
|
|
|
|
showStats: false,
|
|
|
|
|
|
showViewCube: true
|
2025-12-04 18:39:07 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (success) {
|
2026-03-30 10:53:39 +08:00
|
|
|
|
injectSettingPresetsFromCache();
|
|
|
|
|
|
bindPresetEvents();
|
2025-12-04 18:39:07 +08:00
|
|
|
|
updateEngineStatus('已初始化');
|
|
|
|
|
|
console.log('✅ 3D 引擎初始化成功');
|
2026-01-15 14:13:13 +08:00
|
|
|
|
loadModel();
|
2025-12-04 18:39:07 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
updateEngineStatus('初始化失败');
|
|
|
|
|
|
console.error('❌ 3D 引擎初始化失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
updateEngineStatus('初始化错误');
|
|
|
|
|
|
console.error('❌ 3D 引擎初始化错误:', error);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function loadModel() {
|
2026-03-16 16:13:36 +08:00
|
|
|
|
if (!engine || !engine.engine || !engine.engine.isInitialized()) {
|
2025-12-04 18:39:07 +08:00
|
|
|
|
alert('请先初始化 3D 引擎!');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
try {
|
2026-03-16 16:13:36 +08:00
|
|
|
|
var modelUrl = 'https://lyz-1259524260.cos.ap-guangzhou.myqcloud.com/iflow/models/417664a3-76c8-4d94-9344-1337246a5d4e/';
|
2026-02-26 16:03:38 +08:00
|
|
|
|
engine.engine.loadModel([modelUrl], {
|
2026-03-16 16:13:36 +08:00
|
|
|
|
position: [0, 0, 0],
|
|
|
|
|
|
rotation: [0, 0, 0],
|
|
|
|
|
|
scale: [1, 1, 1]
|
2025-12-04 18:39:07 +08:00
|
|
|
|
});
|
|
|
|
|
|
console.log('✅ 模型加载请求已发送:', modelUrl);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('❌ 模型加载错误:', error);
|
2026-01-23 16:27:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-28 10:09:17 +08:00
|
|
|
|
function loadCombinedModel() {
|
2026-03-16 16:13:36 +08:00
|
|
|
|
if (!engine || !engine.engine || !engine.engine.isInitialized()) {
|
2026-02-28 10:09:17 +08:00
|
|
|
|
alert('请先初始化 3D 引擎!');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
try {
|
2026-03-16 16:13:36 +08:00
|
|
|
|
var modelUrls = [
|
2026-03-04 16:40:35 +08:00
|
|
|
|
'https://lyz-1259524260.cos.ap-guangzhou.myqcloud.com/iflow/models/974feae2-4be3-4e85-9d5e-ea655b0c890d/',
|
|
|
|
|
|
'https://lyz-1259524260.cos.ap-guangzhou.myqcloud.com/iflow/models/6129815d-9ae4-4414-a9ab-e7c3ee1b2584/',
|
|
|
|
|
|
'https://lyz-1259524260.cos.ap-guangzhou.myqcloud.com/iflow/models/8ddf95f4-28cc-4218-94c3-165e69fa51b1/'
|
2026-02-28 10:09:17 +08:00
|
|
|
|
];
|
|
|
|
|
|
engine.engine.loadModel(modelUrls, {
|
|
|
|
|
|
position: [0, 0, 0],
|
|
|
|
|
|
rotation: [0, 0, 0],
|
|
|
|
|
|
scale: [1, 1, 1]
|
|
|
|
|
|
});
|
|
|
|
|
|
console.log('✅ 组合模型加载请求已发送:', modelUrls);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('❌ 组合模型加载错误:', error);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-23 16:27:04 +08:00
|
|
|
|
function pauseRendering() {
|
|
|
|
|
|
if (!engine || !engine.engine || !engine.engine.isInitialized()) {
|
|
|
|
|
|
alert('请先初始化 3D 引擎!');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
engine.engine.pauseRendering();
|
|
|
|
|
|
console.log('✅ 渲染已暂停');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function resumeRendering() {
|
|
|
|
|
|
if (!engine || !engine.engine || !engine.engine.isInitialized()) {
|
|
|
|
|
|
alert('请先初始化 3D 引擎!');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
engine.engine.resumeRendering();
|
|
|
|
|
|
console.log('✅ 渲染已恢复');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function switchModel() {
|
2026-03-16 16:13:36 +08:00
|
|
|
|
if (!engine || !engine.engine || !engine.engine.isInitialized()) {
|
2026-01-23 16:27:04 +08:00
|
|
|
|
alert('请先初始化 3D 引擎!');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-03-16 16:13:36 +08:00
|
|
|
|
var newUrl = prompt('请输入新的模型 URL:', 'https://lyz-1259524260.cos.ap-guangzhou.myqcloud.com/iflow/models/8634e556-a94e-4ba7-be3e-2ea1507cced5/');
|
|
|
|
|
|
if (!newUrl || newUrl.trim() === '') return;
|
2026-01-23 16:27:04 +08:00
|
|
|
|
try {
|
2026-02-26 16:03:38 +08:00
|
|
|
|
engine.engine.loadModel([newUrl.trim()], {
|
2026-01-23 16:27:04 +08:00
|
|
|
|
position: [0, 0, 0],
|
|
|
|
|
|
rotation: [0, 0, 0],
|
|
|
|
|
|
scale: [1, 1, 1]
|
|
|
|
|
|
});
|
|
|
|
|
|
console.log('✅ 切换模型请求已发送:', newUrl);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('❌ 切换模型错误:', error);
|
2025-12-04 18:39:07 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-22 15:39:58 +08:00
|
|
|
|
function openPropertyPanel() {
|
|
|
|
|
|
if (!engine || !engine.propertyPanel) {
|
|
|
|
|
|
console.error('Property panel not available');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
engine.propertyPanel.show();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-04 16:40:35 +08:00
|
|
|
|
function getLevelTree() {
|
|
|
|
|
|
if (!engine || !engine.engine || !engine.engine.isInitialized()) {
|
|
|
|
|
|
alert('请先初始化 3D 引擎并加载模型!');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-03-16 16:13:36 +08:00
|
|
|
|
var data = engine.engine.getLevelTreeData();
|
2026-03-04 16:40:35 +08:00
|
|
|
|
console.log('🌳 楼层树数据:', data);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function getTypeTree() {
|
|
|
|
|
|
if (!engine || !engine.engine || !engine.engine.isInitialized()) {
|
|
|
|
|
|
alert('请先初始化 3D 引擎并加载模型!');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-03-16 16:13:36 +08:00
|
|
|
|
var data = engine.engine.getTypeTreeData();
|
2026-03-04 16:40:35 +08:00
|
|
|
|
console.log('🌳 类型树数据:', data);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function getMajorTree() {
|
|
|
|
|
|
if (!engine || !engine.engine || !engine.engine.isInitialized()) {
|
|
|
|
|
|
alert('请先初始化 3D 引擎并加载模型!');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-03-16 16:13:36 +08:00
|
|
|
|
var data = engine.engine.getMajorTreeData();
|
2026-03-04 16:40:35 +08:00
|
|
|
|
console.log('🌳 专业树数据:', data);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-04 18:39:07 +08:00
|
|
|
|
function updateEngineStatus(status) {
|
2026-03-16 16:13:36 +08:00
|
|
|
|
var statusEl = document.getElementById('engine-status');
|
|
|
|
|
|
if (!statusEl) return;
|
|
|
|
|
|
statusEl.textContent = status;
|
|
|
|
|
|
if (status === '已初始化') {
|
|
|
|
|
|
statusEl.style.color = '#28a745';
|
|
|
|
|
|
} else if (status.indexOf('失败') !== -1 || status.indexOf('错误') !== -1) {
|
|
|
|
|
|
statusEl.style.color = '#dc3545';
|
|
|
|
|
|
} else {
|
|
|
|
|
|
statusEl.style.color = '#666';
|
2025-12-04 18:39:07 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-03-05 17:43:50 +08:00
|
|
|
|
|
2026-03-26 09:34:21 +08:00
|
|
|
|
// --- 拾取构件功能 (Issue Report) ---
|
|
|
|
|
|
let isPickModeActive = false;
|
|
|
|
|
|
let unsubscribePickListener = null;
|
|
|
|
|
|
|
|
|
|
|
|
function startPickComponent() {
|
|
|
|
|
|
if (!engine) {
|
|
|
|
|
|
alert('请先初始化 3D 引擎!');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
isPickModeActive = true;
|
|
|
|
|
|
updatePickStatus('等待点击构件...');
|
|
|
|
|
|
document.getElementById('btn-pick-component').textContent = '取消拾取';
|
|
|
|
|
|
document.getElementById('btn-pick-component').onclick = stopPickComponent;
|
|
|
|
|
|
|
|
|
|
|
|
// 订阅构件点击事件
|
|
|
|
|
|
unsubscribePickListener = engine.on('component:selected', handleComponentPicked);
|
|
|
|
|
|
|
|
|
|
|
|
console.log('🔍 拾取模式已启动,请点击模型中的构件');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function stopPickComponent() {
|
|
|
|
|
|
isPickModeActive = false;
|
|
|
|
|
|
updatePickStatus('已取消');
|
|
|
|
|
|
document.getElementById('btn-pick-component').textContent = '拾取构件';
|
|
|
|
|
|
document.getElementById('btn-pick-component').onclick = startPickComponent;
|
|
|
|
|
|
|
|
|
|
|
|
// 取消订阅事件
|
|
|
|
|
|
if (unsubscribePickListener) {
|
|
|
|
|
|
unsubscribePickListener();
|
|
|
|
|
|
unsubscribePickListener = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
console.log('🔍 拾取模式已取消');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function handleComponentPicked(payload) {
|
|
|
|
|
|
if (!isPickModeActive) return;
|
|
|
|
|
|
|
|
|
|
|
|
console.log('✅ 拾取到构件:', payload);
|
|
|
|
|
|
|
|
|
|
|
|
// 更新 UI 显示
|
|
|
|
|
|
updatePickStatus('已选中构件');
|
|
|
|
|
|
document.getElementById('picked-component-info').innerHTML = `
|
|
|
|
|
|
<strong>URL:</strong> ${payload.url}<br>
|
|
|
|
|
|
<strong>ID:</strong> ${payload.id}
|
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
|
|
// 调用 getModelPosition 方法获取详细信息
|
|
|
|
|
|
try {
|
|
|
|
|
|
const result = engine.engine?.getModelPosition({
|
|
|
|
|
|
url: payload.url,
|
|
|
|
|
|
ids: [Number(payload.id)]
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
console.log('📦 getModelPosition 返回结果:', result);
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
console.error('❌ 调用 getModelPosition 失败:', err);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 自动停止拾取模式(可选)
|
|
|
|
|
|
// stopPickComponent();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function getSelectedComponentInfo() {
|
|
|
|
|
|
if (!engine || !engine.engine) {
|
|
|
|
|
|
alert('请先初始化 3D 引擎!');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const selected = engine.engine.getSelectedComponent();
|
|
|
|
|
|
if (!selected) {
|
|
|
|
|
|
alert('请先选中一个构件!');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
console.log('📋 当前选中的构件:', selected);
|
|
|
|
|
|
|
|
|
|
|
|
// 获取详细属性
|
|
|
|
|
|
engine.engine.getComponentProperties(selected.url, selected.id, (data) => {
|
|
|
|
|
|
console.log('📋 构件属性:', data);
|
|
|
|
|
|
alert(`构件属性已输出到控制台,请查看`);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function updatePickStatus(status) {
|
|
|
|
|
|
var el = document.getElementById('pick-status');
|
|
|
|
|
|
if (!el) return;
|
|
|
|
|
|
el.textContent = status;
|
|
|
|
|
|
if (status === '已选中构件') {
|
|
|
|
|
|
el.style.color = '#28a745';
|
|
|
|
|
|
} else if (status === '等待点击构件...') {
|
|
|
|
|
|
el.style.color = '#1565c0';
|
|
|
|
|
|
} else {
|
|
|
|
|
|
el.style.color = '#666';
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// --- 跳转构件功能 (Jump to Camera) ---
|
|
|
|
|
|
function openJumpToCameraDialog() {
|
|
|
|
|
|
if (!engine || !engine.engine || !engine.engine.isInitialized()) {
|
|
|
|
|
|
alert('请先初始化 3D 引擎!');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
document.getElementById('jump-camera-overlay').style.display = 'flex';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function closeJumpToCameraDialog() {
|
|
|
|
|
|
document.getElementById('jump-camera-overlay').style.display = 'none';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function confirmJumpToCamera() {
|
|
|
|
|
|
var raw = document.getElementById('jump-camera-json').value.trim();
|
|
|
|
|
|
if (!raw) {
|
|
|
|
|
|
alert('请输入相机配置 JSON');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var cameraData;
|
|
|
|
|
|
try {
|
|
|
|
|
|
cameraData = JSON.parse(raw);
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
alert('JSON 格式错误:\n' + e.message);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
console.log('[Demo] Jumping to camera:', cameraData);
|
|
|
|
|
|
|
|
|
|
|
|
var success = engine.engine.jumpToCamera(cameraData);
|
|
|
|
|
|
|
|
|
|
|
|
if (success) {
|
|
|
|
|
|
console.log('[Demo] Successfully jumped to camera position');
|
|
|
|
|
|
closeJumpToCameraDialog();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
console.error('[Demo] Failed to jump to camera');
|
|
|
|
|
|
alert('跳转失败,请检查控制台日志');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-05 17:43:50 +08:00
|
|
|
|
// --- 自动组合加载 ---
|
|
|
|
|
|
function openCombineDialog() {
|
|
|
|
|
|
if (!engine || !engine.engine || !engine.engine.isInitialized()) {
|
|
|
|
|
|
alert('请先初始化 3D 引擎!');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-03-16 16:13:36 +08:00
|
|
|
|
document.getElementById('combine-overlay').style.display = 'flex';
|
2026-03-05 17:43:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function closeCombineDialog() {
|
2026-03-16 16:13:36 +08:00
|
|
|
|
document.getElementById('combine-overlay').style.display = 'none';
|
2026-03-05 17:43:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function confirmCombineLoad() {
|
2026-03-16 16:13:36 +08:00
|
|
|
|
var raw = document.getElementById('combine-urls').value.trim();
|
|
|
|
|
|
if (!raw) { alert('请输入至少一个模型 URL'); return; }
|
2026-03-05 17:43:50 +08:00
|
|
|
|
|
|
|
|
|
|
raw = raw
|
2026-03-16 16:13:36 +08:00
|
|
|
|
.replace(/[\u2018\u2019]/g, "'")
|
|
|
|
|
|
.replace(/[\u201C\u201D]/g, '"')
|
|
|
|
|
|
.replace(/[\u200B\uFEFF\u00A0]/g, '')
|
2026-03-05 17:43:50 +08:00
|
|
|
|
.trim();
|
|
|
|
|
|
|
2026-03-16 16:13:36 +08:00
|
|
|
|
var urls = [];
|
2026-03-05 17:43:50 +08:00
|
|
|
|
try {
|
2026-03-16 16:13:36 +08:00
|
|
|
|
var jsonStr = raw.replace(/'/g, '"');
|
|
|
|
|
|
var parsed = JSON.parse(jsonStr);
|
2026-03-05 17:43:50 +08:00
|
|
|
|
if (Array.isArray(parsed)) {
|
2026-03-16 16:13:36 +08:00
|
|
|
|
urls = parsed.map(function (s) { return String(s).trim(); }).filter(Boolean);
|
2026-03-05 17:43:50 +08:00
|
|
|
|
}
|
2026-03-16 16:13:36 +08:00
|
|
|
|
} catch (e) {
|
2026-03-05 17:43:50 +08:00
|
|
|
|
urls = raw.split(/[,;\n\r]+/)
|
2026-03-16 16:13:36 +08:00
|
|
|
|
.map(function (s) { return s.trim().replace(/^['"|\[\]\s]+|['"|\[\]\s]+$/g, ''); })
|
2026-03-05 17:43:50 +08:00
|
|
|
|
.filter(Boolean);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-16 16:13:36 +08:00
|
|
|
|
urls = urls.filter(function (s) { return /^https?:\/\//i.test(s); });
|
|
|
|
|
|
if (urls.length === 0) { alert('未解析到有效的 URL'); return; }
|
2026-03-05 17:43:50 +08:00
|
|
|
|
|
2026-03-16 16:13:36 +08:00
|
|
|
|
for (var i = 0; i < urls.length; i++) {
|
|
|
|
|
|
try { new URL(urls[i]); } catch (e) {
|
|
|
|
|
|
alert('第 ' + (i + 1) + ' 个 URL 无效:\n' + urls[i]);
|
2026-03-05 17:43:50 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
2026-03-16 16:13:36 +08:00
|
|
|
|
engine.engine.loadModel(urls, { position: [0, 0, 0], rotation: [0, 0, 0], scale: [1, 1, 1] });
|
2026-03-05 17:43:50 +08:00
|
|
|
|
console.log('✅ 自动组合加载已发送:', urls);
|
|
|
|
|
|
closeCombineDialog();
|
|
|
|
|
|
document.getElementById('combine-urls').value = '';
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('❌ 自动组合加载错误:', error);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-16 16:13:36 +08:00
|
|
|
|
// ====== 2D 图纸操作 ======
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 初始化 2D 引擎(独立实例,销毁其他引擎类型)
|
|
|
|
|
|
*/
|
|
|
|
|
|
function initEngine2d() {
|
|
|
|
|
|
destroyAllEngines();
|
|
|
|
|
|
updateEngineStatus('未初始化');
|
|
|
|
|
|
update2dStatus('初始化中...');
|
|
|
|
|
|
update720Status('未初始化');
|
|
|
|
|
|
document.getElementById('btn-load720').disabled = true;
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
engine2d = new IflowEngine.BimEngine2d('app', {
|
2026-03-30 10:53:39 +08:00
|
|
|
|
locale: currentLocale,
|
2026-03-16 16:13:36 +08:00
|
|
|
|
backgroundColor: 0x1a1a1a,
|
|
|
|
|
|
gridEnabled: true,
|
|
|
|
|
|
axesEnabled: true,
|
|
|
|
|
|
enablePerformanceMonitoring: true
|
|
|
|
|
|
});
|
|
|
|
|
|
update2dStatus('已初始化');
|
|
|
|
|
|
document.getElementById('btn-load2d').disabled = false;
|
|
|
|
|
|
document.getElementById('btn-switch2d').disabled = false;
|
|
|
|
|
|
console.log('✅ 2D 引擎初始化成功');
|
|
|
|
|
|
loadDrawing();
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
update2dStatus('初始化错误');
|
|
|
|
|
|
console.error('❌ 2D 引擎初始化错误:', error);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function loadDrawing() {
|
|
|
|
|
|
if (!engine2d) {
|
|
|
|
|
|
alert('请先初始化 2D 引擎!');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
var url = 'https://lyz-1259524260.cos.ap-guangzhou.myqcloud.com/iflow/models/cc389dc8-c6d5-43db-81f1-6998d4eee8f6/312';
|
|
|
|
|
|
update2dStatus('正在加载...');
|
|
|
|
|
|
try {
|
|
|
|
|
|
engine2d.loadDrawing(url).then(function () {
|
|
|
|
|
|
update2dStatus('图纸已加载');
|
|
|
|
|
|
console.log('✅ 2D 图纸加载完成');
|
|
|
|
|
|
}).catch(function (err) {
|
|
|
|
|
|
update2dStatus('加载失败');
|
|
|
|
|
|
console.error('❌ 2D 图纸加载失败:', err);
|
|
|
|
|
|
});
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
update2dStatus('加载错误');
|
|
|
|
|
|
console.error('❌ 2D 图纸加载错误:', error);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function switchDrawing() {
|
|
|
|
|
|
var urlInput = document.getElementById('url-input-2d');
|
|
|
|
|
|
var newUrl = urlInput.value.trim();
|
|
|
|
|
|
if (!newUrl) {
|
|
|
|
|
|
alert('请输入图纸 URL');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!engine2d) {
|
|
|
|
|
|
alert('请先初始化 2D 引擎!');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
update2dStatus('正在切换...');
|
|
|
|
|
|
try {
|
|
|
|
|
|
engine2d.loadDrawing(newUrl).then(function () {
|
|
|
|
|
|
update2dStatus('图纸已加载');
|
|
|
|
|
|
console.log('✅ 2D 图纸切换完成: ' + newUrl);
|
|
|
|
|
|
}).catch(function (err) {
|
|
|
|
|
|
update2dStatus('切换失败');
|
|
|
|
|
|
console.error('❌ 2D 图纸切换失败:', err);
|
|
|
|
|
|
});
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
update2dStatus('切换错误');
|
|
|
|
|
|
console.error('❌ 2D 图纸切换错误:', error);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function update2dStatus(status) {
|
|
|
|
|
|
var el = document.getElementById('engine2d-status');
|
|
|
|
|
|
if (!el) return;
|
|
|
|
|
|
el.textContent = status;
|
|
|
|
|
|
if (status === '已初始化' || status === '图纸已加载') {
|
|
|
|
|
|
el.style.color = '#28a745';
|
|
|
|
|
|
} else if (status === '正在加载...' || status === '初始化中...') {
|
|
|
|
|
|
el.style.color = '#1565c0';
|
|
|
|
|
|
} else if (status.indexOf('失败') !== -1 || status.indexOf('错误') !== -1) {
|
|
|
|
|
|
el.style.color = '#dc3545';
|
|
|
|
|
|
} else {
|
|
|
|
|
|
el.style.color = '#666';
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ====== 720 全景操作 ======
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 初始化 720 引擎(独立实例,销毁其他引擎类型)
|
|
|
|
|
|
*/
|
|
|
|
|
|
function initEngine720() {
|
|
|
|
|
|
destroyAllEngines();
|
|
|
|
|
|
updateEngineStatus('未初始化');
|
|
|
|
|
|
update2dStatus('未初始化');
|
|
|
|
|
|
update720Status('初始化中...');
|
|
|
|
|
|
document.getElementById('btn-load2d').disabled = true;
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
engine720 = new IflowEngine.BimEngine720('app', {
|
2026-03-30 10:53:39 +08:00
|
|
|
|
locale: currentLocale,
|
2026-03-16 16:13:36 +08:00
|
|
|
|
fov: 75,
|
|
|
|
|
|
enableZoom: true,
|
|
|
|
|
|
enableRotate: true,
|
|
|
|
|
|
sphereRadius: 500
|
|
|
|
|
|
});
|
|
|
|
|
|
update720Status('已初始化');
|
|
|
|
|
|
document.getElementById('btn-load720').disabled = false;
|
|
|
|
|
|
console.log('✅ 720 引擎初始化成功');
|
|
|
|
|
|
loadPanorama();
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
update720Status('初始化错误');
|
|
|
|
|
|
console.error('❌ 720 引擎初始化错误:', error);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function loadPanorama() {
|
|
|
|
|
|
if (!engine720) {
|
|
|
|
|
|
alert('请先初始化 720 引擎!');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
var url = 'http://lyz-1259524260.cos.ap-guangzhou.myqcloud.com/qcloud/2026/03/10/8ed54edd7e2740c7b6a3f534dd672d12.png';
|
|
|
|
|
|
update720Status('正在加载...');
|
|
|
|
|
|
try {
|
|
|
|
|
|
engine720.loadPanorama(url).then(function () {
|
|
|
|
|
|
update720Status('全景图已加载');
|
|
|
|
|
|
console.log('✅ 720 全景图加载完成');
|
|
|
|
|
|
}).catch(function (err) {
|
|
|
|
|
|
update720Status('加载失败');
|
|
|
|
|
|
console.error('❌ 720 全景图加载失败:', err);
|
|
|
|
|
|
});
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
update720Status('加载错误');
|
|
|
|
|
|
console.error('❌ 720 全景图加载错误:', error);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function update720Status(status) {
|
|
|
|
|
|
var el = document.getElementById('engine720-status');
|
|
|
|
|
|
if (!el) return;
|
|
|
|
|
|
el.textContent = status;
|
|
|
|
|
|
if (status === '已初始化' || status === '全景图已加载') {
|
|
|
|
|
|
el.style.color = '#28a745';
|
|
|
|
|
|
} else if (status === '正在加载...' || status === '初始化中...') {
|
|
|
|
|
|
el.style.color = '#1565c0';
|
|
|
|
|
|
} else if (status.indexOf('失败') !== -1 || status.indexOf('错误') !== -1) {
|
|
|
|
|
|
el.style.color = '#dc3545';
|
|
|
|
|
|
} else {
|
|
|
|
|
|
el.style.color = '#666';
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-03 18:35:05 +08:00
|
|
|
|
</script>
|
|
|
|
|
|
</body>
|
|
|
|
|
|
|
2026-03-30 10:53:39 +08:00
|
|
|
|
</html>
|