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
|
|
|
|
<div class="control-group" style="opacity: 0.6;">
|
2026-03-30 15:56:18 +08:00
|
|
|
|
<h2>🛠️ 工具栏 (Toolbar) <span style="color: #999; font-size: 0.75rem;">[当前使用 Radial Toolbar]</span></h2>
|
2026-03-30 10:53:39 +08:00
|
|
|
|
<div style="font-size: 0.8rem; color: #888; margin-bottom: 8px;">
|
2026-03-30 15:56:18 +08:00
|
|
|
|
当前演示默认使用 Radial Toolbar,以下旧 Toolbar API 按钮已停用
|
2026-03-30 10:53:39 +08:00
|
|
|
|
</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>
|
2026-04-14 10:16:37 +08:00
|
|
|
|
<div style="margin-top: 8px;">
|
|
|
|
|
|
<input type="text" id="url-input-3d"
|
|
|
|
|
|
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>
|
2026-01-23 16:27:04 +08:00
|
|
|
|
<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>
|
2026-04-20 10:38:42 +08:00
|
|
|
|
<div class="btn-container" style="margin-top: 8px;">
|
|
|
|
|
|
<button onclick="readModelCodeFormStoge()">读取缓存编码</button>
|
|
|
|
|
|
<button onclick="startOneClickEncoding()">启动一键编码</button>
|
|
|
|
|
|
<button onclick="checkHasModelCode()">检查模型编码</button>
|
|
|
|
|
|
</div>
|
2025-12-04 18:39:07 +08:00
|
|
|
|
<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>
|
2026-04-14 10:16:37 +08:00
|
|
|
|
<p style="margin:0 0 10px; font-size:.85rem; color:#888;">请输入相机配置 JSON(参考格式:modelJson.cameraRestorePose):
|
|
|
|
|
|
</p>
|
2026-03-26 09:34:21 +08:00
|
|
|
|
<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>
|
2026-04-14 10:16:37 +08:00
|
|
|
|
<pre
|
|
|
|
|
|
style="margin-top:8px; padding:10px; background:#f5f5f5; border-radius:4px; overflow-x:auto; font-size:.75rem;">{
|
2026-03-26 09:34:21 +08:00
|
|
|
|
"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;
|
2026-04-24 11:16:37 +08:00
|
|
|
|
let unsubscribePinCreated = null;
|
|
|
|
|
|
let unsubscribePinDeleted = null;
|
|
|
|
|
|
let unsubscribePinChanged = null;
|
|
|
|
|
|
const PIN_CACHE_KEY = 'iflow-demo-pins-v1';
|
|
|
|
|
|
let pinCache = [];
|
|
|
|
|
|
|
|
|
|
|
|
// 主视图缓存相关
|
|
|
|
|
|
let unsubscribeMainViewSaved = null;
|
|
|
|
|
|
let unsubscribeMainViewRestored = null;
|
|
|
|
|
|
let unsubscribeModelLoaded = null;
|
|
|
|
|
|
const MAIN_VIEW_CACHE_KEY = 'iflow-demo-main-view-v1';
|
|
|
|
|
|
|
|
|
|
|
|
function loadMainViewCache() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const raw = localStorage.getItem(MAIN_VIEW_CACHE_KEY);
|
|
|
|
|
|
if (!raw) return {};
|
|
|
|
|
|
const parsed = JSON.parse(raw);
|
|
|
|
|
|
return typeof parsed === 'object' && parsed !== null ? parsed : {};
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.warn('读取主视图缓存失败:', error);
|
|
|
|
|
|
return {};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function saveMainViewCache(cache) {
|
|
|
|
|
|
localStorage.setItem(MAIN_VIEW_CACHE_KEY, JSON.stringify(cache));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function saveMainViewToCache(url, viewData) {
|
|
|
|
|
|
const cache = loadMainViewCache();
|
|
|
|
|
|
cache[url] = {
|
|
|
|
|
|
viewData: viewData,
|
|
|
|
|
|
timestamp: Date.now()
|
|
|
|
|
|
};
|
|
|
|
|
|
saveMainViewCache(cache);
|
|
|
|
|
|
console.log('💾 主视图已缓存:', url);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function applyMainViewIfExists(url) {
|
|
|
|
|
|
const cache = loadMainViewCache();
|
|
|
|
|
|
const cached = cache[url];
|
|
|
|
|
|
if (cached && cached.viewData) {
|
|
|
|
|
|
if (engine && engine.engine) {
|
|
|
|
|
|
engine.engine.setMainViewPort(cached.viewData);
|
|
|
|
|
|
console.log('✅ 已应用缓存的主视图:', url);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function clearMainViewCache(url) {
|
|
|
|
|
|
const cache = loadMainViewCache();
|
|
|
|
|
|
if (cache[url]) {
|
|
|
|
|
|
delete cache[url];
|
|
|
|
|
|
saveMainViewCache(cache);
|
|
|
|
|
|
console.log('🗑️ 主视图缓存已清除:', url);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function bindMainViewEvents() {
|
|
|
|
|
|
if (!engine || typeof engine.on !== 'function') return;
|
|
|
|
|
|
|
|
|
|
|
|
if (unsubscribeMainViewSaved) {
|
|
|
|
|
|
unsubscribeMainViewSaved();
|
|
|
|
|
|
unsubscribeMainViewSaved = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (unsubscribeModelLoaded) {
|
|
|
|
|
|
unsubscribeModelLoaded();
|
|
|
|
|
|
unsubscribeModelLoaded = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 监听主视图保存事件
|
|
|
|
|
|
unsubscribeMainViewSaved = engine.on('view:main-view-saved', (data) => {
|
|
|
|
|
|
console.log('[Demo] view:main-view-saved:', data);
|
|
|
|
|
|
saveMainViewToCache(current3dModelUrl, data.viewData);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 监听主视图重置事件
|
|
|
|
|
|
unsubscribeMainViewRestored = engine.on('view:main-view-restored', () => {
|
|
|
|
|
|
console.log('[Demo] view:main-view-restored');
|
|
|
|
|
|
clearMainViewCache(current3dModelUrl);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 监听模型加载完成事件
|
|
|
|
|
|
unsubscribeModelLoaded = engine.on('engine:model-loading-completed', () => {
|
|
|
|
|
|
console.log('[Demo] engine:model-loading-completed, applying main view if exists');
|
|
|
|
|
|
applyMainViewIfExists(current3dModelUrl);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
console.log('✅ 主视图事件绑定完成');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function loadPinCache() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const raw = localStorage.getItem(PIN_CACHE_KEY);
|
|
|
|
|
|
if (!raw) return [];
|
|
|
|
|
|
const parsed = JSON.parse(raw);
|
|
|
|
|
|
return Array.isArray(parsed) ? parsed : [];
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.warn('读取图钉缓存失败,已忽略:', error);
|
|
|
|
|
|
return [];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function savePinCache(list) {
|
|
|
|
|
|
localStorage.setItem(PIN_CACHE_KEY, JSON.stringify(list));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function normalizeParentId(parentId) {
|
|
|
|
|
|
return parentId == null || parentId === '' ? null : parentId;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function sortPinRecords(list) {
|
|
|
|
|
|
return [...list].sort((a, b) => {
|
|
|
|
|
|
const parentA = normalizeParentId(a.parentId) || '';
|
|
|
|
|
|
const parentB = normalizeParentId(b.parentId) || '';
|
|
|
|
|
|
if (parentA !== parentB) return parentA.localeCompare(parentB);
|
|
|
|
|
|
return (a.seq || 0) - (b.seq || 0);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function resequencePinRecords(list, parentId) {
|
|
|
|
|
|
const normalizedParentId = normalizeParentId(parentId);
|
|
|
|
|
|
const siblings = sortPinRecords(list.filter((item) => normalizeParentId(item.parentId) === normalizedParentId));
|
|
|
|
|
|
siblings.forEach((item, index) => {
|
|
|
|
|
|
item.seq = index;
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function collectDescendantIds(list, parentId) {
|
|
|
|
|
|
const ids = [];
|
|
|
|
|
|
const stack = [parentId];
|
|
|
|
|
|
while (stack.length > 0) {
|
|
|
|
|
|
const currentId = stack.pop();
|
|
|
|
|
|
list.forEach((item) => {
|
|
|
|
|
|
if (normalizeParentId(item.parentId) === currentId) {
|
|
|
|
|
|
ids.push(item.id);
|
|
|
|
|
|
stack.push(item.id);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
return ids;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function pushPinRecordsToEngine() {
|
|
|
|
|
|
if (!engine || !engine.engine) return;
|
|
|
|
|
|
if (typeof engine.engine.setPinRecords === 'function') {
|
|
|
|
|
|
engine.engine.setPinRecords(pinCache);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (typeof engine.engine.setPinList === 'function') {
|
|
|
|
|
|
engine.engine.setPinList(pinCache);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function injectPinsFromCache() {
|
|
|
|
|
|
pinCache = loadPinCache();
|
|
|
|
|
|
if (pinCache.length > 0) {
|
|
|
|
|
|
pushPinRecordsToEngine();
|
|
|
|
|
|
console.log('✅ 已注入缓存图钉列表,数量:', pinCache.length);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function bindPinEvents() {
|
|
|
|
|
|
if (!engine || typeof engine.on !== 'function') return;
|
|
|
|
|
|
|
|
|
|
|
|
if (unsubscribePinCreated) {
|
|
|
|
|
|
unsubscribePinCreated();
|
|
|
|
|
|
unsubscribePinCreated = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (unsubscribePinDeleted) {
|
|
|
|
|
|
unsubscribePinDeleted();
|
|
|
|
|
|
unsubscribePinDeleted = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (unsubscribePinChanged) {
|
|
|
|
|
|
unsubscribePinChanged();
|
|
|
|
|
|
unsubscribePinChanged = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
unsubscribePinCreated = engine.on('drawingPin:create', ({ record }) => {
|
|
|
|
|
|
console.log('[Demo] drawingPin:create:', record);
|
|
|
|
|
|
const normalizedRecord = {
|
|
|
|
|
|
...record,
|
|
|
|
|
|
parentId: normalizeParentId(record.parentId)
|
|
|
|
|
|
};
|
|
|
|
|
|
pinCache.push(normalizedRecord);
|
|
|
|
|
|
resequencePinRecords(pinCache, normalizedRecord.parentId);
|
|
|
|
|
|
savePinCache(pinCache);
|
|
|
|
|
|
pushPinRecordsToEngine();
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
unsubscribePinDeleted = engine.on('drawingPin:delete', ({ id }) => {
|
|
|
|
|
|
console.log('[Demo] drawingPin:delete:', id);
|
|
|
|
|
|
const record = pinCache.find((item) => item.id === id);
|
|
|
|
|
|
if (!record) return;
|
|
|
|
|
|
const targetParentId = normalizeParentId(record.parentId);
|
|
|
|
|
|
const descendantIds = collectDescendantIds(pinCache, id);
|
|
|
|
|
|
const deleteIds = new Set([id, ...descendantIds]);
|
|
|
|
|
|
pinCache = pinCache.filter((item) => !deleteIds.has(item.id));
|
|
|
|
|
|
resequencePinRecords(pinCache, targetParentId);
|
|
|
|
|
|
savePinCache(pinCache);
|
|
|
|
|
|
pushPinRecordsToEngine();
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
unsubscribePinChanged = engine.on('drawingPin:update', ({ id, patch }) => {
|
|
|
|
|
|
console.log('[Demo] drawingPin:update:', id, patch);
|
|
|
|
|
|
const index = pinCache.findIndex((item) => item.id === id);
|
|
|
|
|
|
if (index !== -1) {
|
|
|
|
|
|
const previousParentId = normalizeParentId(pinCache[index].parentId);
|
|
|
|
|
|
const nextParentId = Object.prototype.hasOwnProperty.call(patch, 'parentId')
|
|
|
|
|
|
? normalizeParentId(patch.parentId)
|
|
|
|
|
|
: previousParentId;
|
|
|
|
|
|
pinCache[index] = {
|
|
|
|
|
|
...pinCache[index],
|
|
|
|
|
|
...patch,
|
|
|
|
|
|
parentId: nextParentId,
|
|
|
|
|
|
};
|
|
|
|
|
|
resequencePinRecords(pinCache, previousParentId);
|
|
|
|
|
|
resequencePinRecords(pinCache, nextParentId);
|
|
|
|
|
|
}
|
|
|
|
|
|
savePinCache(pinCache);
|
|
|
|
|
|
pushPinRecordsToEngine();
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
console.log('✅ 图钉事件绑定完成');
|
|
|
|
|
|
}
|
2026-04-20 10:38:42 +08:00
|
|
|
|
// const DEFAULT_3D_MODEL_URL = 'https://pub-8092fd0db2e14822a9f742ad0050750c.r2.dev/e9603d6b-c885-4f1b-84b0-2589bc9dc44f';
|
2026-04-24 11:16:37 +08:00
|
|
|
|
// const DEFAULT_3D_MODEL_URL = 'https://lyz-1259524260.cos.ap-guangzhou.myqcloud.com/iflow/models/417664a3-76c8-4d94-9344-1337246a5d4e';
|
|
|
|
|
|
//const DEFAULT_3D_MODEL_URL = 'https://lyz-1259524260.cos.ap-guangzhou.myqcloud.com/iflow/models/cc389dc8-c6d5-43db-81f1-6998d4eee8f6';
|
|
|
|
|
|
const DEFAULT_3D_MODEL_URL = 'https://pub-8092fd0db2e14822a9f742ad0050750c.r2.dev/bc7de1ae-a47e-458f-b3da-2a6d6b8f39af';
|
2026-04-14 10:16:37 +08:00
|
|
|
|
//const DEFAULT_3D_MODEL_URL = 'https://pub-8092fd0db2e14822a9f742ad0050750c.r2.dev/66ad9a66-5ca8-47ac-9139-6aa8756069c1/';
|
|
|
|
|
|
let current3dModelUrl = DEFAULT_3D_MODEL_URL;
|
2026-03-30 10:53:39 +08:00
|
|
|
|
|
|
|
|
|
|
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-04-24 11:16:37 +08:00
|
|
|
|
if (unsubscribePinCreated) {
|
|
|
|
|
|
unsubscribePinCreated();
|
|
|
|
|
|
unsubscribePinCreated = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (unsubscribePinDeleted) {
|
|
|
|
|
|
unsubscribePinDeleted();
|
|
|
|
|
|
unsubscribePinDeleted = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (unsubscribePinChanged) {
|
|
|
|
|
|
unsubscribePinChanged();
|
|
|
|
|
|
unsubscribePinChanged = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (unsubscribeMainViewSaved) {
|
|
|
|
|
|
unsubscribeMainViewSaved();
|
|
|
|
|
|
unsubscribeMainViewSaved = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (unsubscribeMainViewRestored) {
|
|
|
|
|
|
unsubscribeMainViewRestored();
|
|
|
|
|
|
unsubscribeMainViewRestored = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (unsubscribeModelLoaded) {
|
|
|
|
|
|
unsubscribeModelLoaded();
|
|
|
|
|
|
unsubscribeModelLoaded = 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-04-14 10:16:37 +08:00
|
|
|
|
const modelInput = document.getElementById('url-input-3d');
|
|
|
|
|
|
if (modelInput) {
|
|
|
|
|
|
modelInput.value = current3dModelUrl;
|
|
|
|
|
|
}
|
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 15:56:18 +08:00
|
|
|
|
* 使用 BimEngine
|
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 15:56:18 +08:00
|
|
|
|
engine = new IflowEngine.BimEngine('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();
|
2026-04-24 11:16:37 +08:00
|
|
|
|
bindPinEvents();
|
|
|
|
|
|
bindMainViewEvents();
|
|
|
|
|
|
injectPinsFromCache();
|
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;
|
|
|
|
|
|
}
|
2026-04-14 10:16:37 +08:00
|
|
|
|
|
|
|
|
|
|
const urlInput = document.getElementById('url-input-3d');
|
|
|
|
|
|
const inputValue = urlInput && urlInput.value ? urlInput.value.trim() : '';
|
|
|
|
|
|
if (inputValue) {
|
|
|
|
|
|
current3dModelUrl = inputValue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const modelUrl = current3dModelUrl;
|
2025-12-04 18:39:07 +08:00
|
|
|
|
try {
|
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('✅ 渲染已恢复');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-20 10:38:42 +08:00
|
|
|
|
function readModelCodeFormStoge() {
|
|
|
|
|
|
if (!engine || !engine.engine || !engine.engine.isInitialized()) {
|
|
|
|
|
|
alert('请先初始化 3D 引擎!');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
engine.engine.getEngineComponent()?.readModelCodeFormStoge();
|
|
|
|
|
|
console.log('✅ 读取缓存编码数据已触发');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function startOneClickEncoding() {
|
|
|
|
|
|
if (!engine || !engine.engine || !engine.engine.isInitialized()) {
|
|
|
|
|
|
alert('请先初始化 3D 引擎!');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
engine.on('encoding:start', function (data) {
|
|
|
|
|
|
console.log('编码开始', data);
|
|
|
|
|
|
});
|
|
|
|
|
|
engine.on('encoding:complete', function (data) {
|
|
|
|
|
|
console.log('编码完成', data);
|
|
|
|
|
|
});
|
|
|
|
|
|
engine.on('encoding:error', function (data) {
|
|
|
|
|
|
console.log('编码失败', data);
|
|
|
|
|
|
});
|
|
|
|
|
|
engine.engine.getEngineComponent()?.startOneClickEncoding();
|
|
|
|
|
|
console.log('✅ 一键编码已启动');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function checkHasModelCode() {
|
|
|
|
|
|
if (!engine || !engine.engine || !engine.engine.isInitialized()) {
|
|
|
|
|
|
alert('请先初始化 3D 引擎!');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
var result = engine.engine.getEngineComponent()?.hasModelCode();
|
|
|
|
|
|
console.log('✅ 检查模型编码结果:', result);
|
|
|
|
|
|
alert('模型编码检查结果: ' + (result ? '所有模型已编码' : '未全部编码'));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-23 16:27:04 +08:00
|
|
|
|
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-04-14 10:16:37 +08:00
|
|
|
|
|
|
|
|
|
|
var urlInput = document.getElementById('url-input-3d');
|
|
|
|
|
|
var newUrl = urlInput && urlInput.value ? urlInput.value.trim() : '';
|
|
|
|
|
|
if (!newUrl) {
|
|
|
|
|
|
alert('请输入模型 URL');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
current3dModelUrl = newUrl;
|
2026-01-23 16:27:04 +08:00
|
|
|
|
try {
|
2026-04-14 10:16:37 +08:00
|
|
|
|
engine.engine.loadModel([current3dModelUrl], {
|
2026-01-23 16:27:04 +08:00
|
|
|
|
position: [0, 0, 0],
|
|
|
|
|
|
rotation: [0, 0, 0],
|
|
|
|
|
|
scale: [1, 1, 1]
|
|
|
|
|
|
});
|
2026-04-14 10:16:37 +08:00
|
|
|
|
console.log('✅ 切换模型请求已发送:', current3dModelUrl);
|
2026-01-23 16:27:04 +08:00
|
|
|
|
} 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-04-24 11:16:37 +08:00
|
|
|
|
</html>
|