Files
bim_engine/demo/index.html

1104 lines
42 KiB
HTML
Raw Normal View History

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>iFlow Engine Demo</title>
2025-12-04 18:39:07 +08:00
<!-- 从本地 lib 目录加载 SDK 文件 -->
<script src="./lib/iflow-engine.umd.js"></script>
<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>
</head>
<body>
<!-- 左侧控制面板 -->
<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>
<button class="primary" onclick="setLang('zh-TW')">繁體中文</button>
<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>
<div class="control-group" style="opacity: 0.6;">
<h2>🛠️ 工具栏 (Toolbar) <span style="color: #999; font-size: 0.75rem;">[当前使用 Radial Toolbar]</span></h2>
<div style="font-size: 0.8rem; color: #888; margin-bottom: 8px;">
当前演示默认使用 Radial Toolbar以下旧 Toolbar API 按钮已停用
</div>
<div class="btn-container">
<button disabled>显隐工具栏</button>
<button disabled>显隐标签</button>
<button disabled>显隐定位按钮</button>
</div>
<div class="btn-container" style="margin-top: 8px;">
<button disabled>加组</button>
<button disabled>加按钮</button>
</div>
<div class="btn-container" style="margin-top: 8px;">
<button disabled>默认样式</button>
<button disabled>胶囊样式</button>
</div>
</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>
<button onclick="viewPresetCacheData()">查看预设缓存</button>
2025-12-22 15:39:58 +08:00
</div>
</div>
<!-- 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>
<!-- 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>
<button onclick="switchModel()">切换模型</button>
<button onclick="loadCombinedModel()">组合模型</button>
<button onclick="openCombineDialog()">自动组合</button>
</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>
</aside>
<!-- 右侧主区域 -->
<main class="main-content">
<div id="app"></div>
</main>
<!-- 自动组合弹窗 -->
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);">
<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/&#10;https://example.com/model2/"></textarea>
<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>
<script>
2026-03-16 16:13:36 +08:00
let engine = null; // BimEngine (3D) 实例
let engine2d = null; // BimEngine2d 实例
let engine720 = null; // BimEngine720 实例
let isToolbarVisible = true;
let isLabelVisible = true;
let isLocationVisible = true;
let customGroupAdded = false;
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));
}
2026-03-16 16:13:36 +08:00
/**
* 销毁所有引擎实例
*/
function destroyAllEngines() {
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 引擎
window.onload = () => {
if (window.IflowEngine) {
try {
initEngine3D();
} catch (err) {
console.error('Init failed:', err);
}
} else {
console.error('SDK not found');
}
};
// --- 语言设置 ---
function setLang(lang) {
currentLocale = lang;
if (engine) engine.setLocale(lang);
if (engine2d) engine2d.setLocale(lang);
if (engine720) engine720.setLocale(lang);
}
// --- 弹窗测试 ---
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 }
});
}
// --- 工具栏操作 ---
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);
}
function toggleLocationBtn() {
if (!engine || !engine.toolbar) return;
isLocationVisible = !isLocationVisible;
engine.toolbar.setButtonVisibility('location', isLocationVisible);
}
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) {
alert('Please add custom group first / 请先添加自定义组');
return;
}
2026-03-16 16:13:36 +08:00
var btnId = 'custom-btn-' + Date.now();
engine.toolbar.addButton({
id: btnId,
groupId: 'custom-group',
type: 'button',
label: 'New',
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) {
alert('Clicked: ' + btn.label);
}
});
}
function setToolbarType(type) {
if (!engine || !engine.toolbar) return;
engine.toolbar.setType(type);
console.log('Toolbar type changed to:', type);
}
// --- 主题操作 ---
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);
}
function setCustomTheme() {
if (!engine) return;
engine.setCustomTheme({
name: 'red-alert',
primary: '#d32f2f',
primaryHover: '#b71c1c',
2026-03-16 16:13:36 +08:00
background: '#ffebee',
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-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 引擎(独立实例,销毁其他引擎类型)
* 使用 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 {
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) {
injectSettingPresetsFromCache();
bindPresetEvents();
2025-12-04 18:39:07 +08:00
updateEngineStatus('已初始化');
console.log('✅ 3D 引擎初始化成功');
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/';
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);
}
}
function loadCombinedModel() {
2026-03-16 16:13:36 +08:00
if (!engine || !engine.engine || !engine.engine.isInitialized()) {
alert('请先初始化 3D 引擎!');
return;
}
try {
2026-03-16 16:13:36 +08:00
var modelUrls = [
'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/'
];
engine.engine.loadModel(modelUrls, {
position: [0, 0, 0],
rotation: [0, 0, 0],
scale: [1, 1, 1]
});
console.log('✅ 组合模型加载请求已发送:', modelUrls);
} catch (error) {
console.error('❌ 组合模型加载错误:', error);
}
}
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()) {
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;
try {
engine.engine.loadModel([newUrl.trim()], {
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();
}
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();
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();
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();
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-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('跳转失败,请检查控制台日志');
}
}
// --- 自动组合加载 ---
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';
}
function closeCombineDialog() {
2026-03-16 16:13:36 +08:00
document.getElementById('combine-overlay').style.display = 'none';
}
function confirmCombineLoad() {
2026-03-16 16:13:36 +08:00
var raw = document.getElementById('combine-urls').value.trim();
if (!raw) { alert('请输入至少一个模型 URL'); return; }
raw = raw
2026-03-16 16:13:36 +08:00
.replace(/[\u2018\u2019]/g, "'")
.replace(/[\u201C\u201D]/g, '"')
.replace(/[\u200B\uFEFF\u00A0]/g, '')
.trim();
2026-03-16 16:13:36 +08:00
var urls = [];
try {
2026-03-16 16:13:36 +08:00
var jsonStr = raw.replace(/'/g, '"');
var parsed = JSON.parse(jsonStr);
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-16 16:13:36 +08:00
} catch (e) {
urls = raw.split(/[,;\n\r]+/)
2026-03-16 16:13:36 +08:00
.map(function (s) { return s.trim().replace(/^['"|\[\]\s]+|['"|\[\]\s]+$/g, ''); })
.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-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]);
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] });
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', {
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', {
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';
}
}
</script>
</body>
</html>