Files
bim_engine/demo/index.html

1104 lines
42 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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>
<!-- 从本地 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>
<!-- 6. 功能面板 -->
<div class="control-group">
<h2>📑 功能面板 (Panels)</h2>
<div class="btn-container">
<button onclick="openPropertyPanel()">属性面板</button>
<button onclick="viewPresetCacheData()">查看预设缓存</button>
</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>
<!-- 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>
<!-- 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>
</div>
<div style="margin-top: 10px; font-size: 0.85rem; color: #666;">
<div>状态: <span id="engine-status">未初始化</span></div>
</div>
</div>
<!-- 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>
<!-- 自动组合弹窗 -->
<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>
<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>
<!-- 跳转构件弹窗 -->
<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>
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));
}
/**
* 销毁所有引擎实例
*/
function destroyAllEngines() {
if (unsubscribePresetSaved) {
unsubscribePresetSaved();
unsubscribePresetSaved = null;
}
if (unsubscribePresetChanged) {
unsubscribePresetChanged();
unsubscribePresetChanged = null;
}
if (unsubscribePresetDeleted) {
unsubscribePresetDeleted();
unsubscribePresetDeleted = null;
}
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;
}
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>',
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);
if (engine2d) engine2d.setTheme(themeName);
if (engine720) engine720.setTheme(themeName);
}
function setCustomTheme() {
if (!engine) return;
engine.setCustomTheme({
name: 'red-alert',
primary: '#d32f2f',
primaryHover: '#b71c1c',
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'
});
}
// ====== 3D 引擎操作 ======
/**
* 初始化 3D 引擎(独立实例,销毁其他引擎类型)
* 使用 BimEngine
*/
function initEngine3D() {
destroyAllEngines();
updateEngineStatus('初始化中...');
update2dStatus('未初始化');
update720Status('未初始化');
document.getElementById('btn-load2d').disabled = true;
document.getElementById('btn-load720').disabled = true;
try {
engine = new IflowEngine.BimEngine('app', { locale: currentLocale });
var success = engine.engine.initialize({
backgroundColor: 0x333333,
version: 'v2',
showStats: false,
showViewCube: true
});
if (success) {
injectSettingPresetsFromCache();
bindPresetEvents();
updateEngineStatus('已初始化');
console.log('✅ 3D 引擎初始化成功');
loadModel();
} else {
updateEngineStatus('初始化失败');
console.error('❌ 3D 引擎初始化失败');
}
} catch (error) {
updateEngineStatus('初始化错误');
console.error('❌ 3D 引擎初始化错误:', error);
}
}
function loadModel() {
if (!engine || !engine.engine || !engine.engine.isInitialized()) {
alert('请先初始化 3D 引擎!');
return;
}
try {
var modelUrl = 'https://lyz-1259524260.cos.ap-guangzhou.myqcloud.com/iflow/models/417664a3-76c8-4d94-9344-1337246a5d4e/';
engine.engine.loadModel([modelUrl], {
position: [0, 0, 0],
rotation: [0, 0, 0],
scale: [1, 1, 1]
});
console.log('✅ 模型加载请求已发送:', modelUrl);
} catch (error) {
console.error('❌ 模型加载错误:', error);
}
}
function loadCombinedModel() {
if (!engine || !engine.engine || !engine.engine.isInitialized()) {
alert('请先初始化 3D 引擎!');
return;
}
try {
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() {
if (!engine || !engine.engine || !engine.engine.isInitialized()) {
alert('请先初始化 3D 引擎!');
return;
}
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);
}
}
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;
}
var data = engine.engine.getLevelTreeData();
console.log('🌳 楼层树数据:', data);
}
function getTypeTree() {
if (!engine || !engine.engine || !engine.engine.isInitialized()) {
alert('请先初始化 3D 引擎并加载模型!');
return;
}
var data = engine.engine.getTypeTreeData();
console.log('🌳 类型树数据:', data);
}
function getMajorTree() {
if (!engine || !engine.engine || !engine.engine.isInitialized()) {
alert('请先初始化 3D 引擎并加载模型!');
return;
}
var data = engine.engine.getMajorTreeData();
console.log('🌳 专业树数据:', data);
}
function updateEngineStatus(status) {
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';
}
}
// --- 拾取构件功能 (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;
}
document.getElementById('combine-overlay').style.display = 'flex';
}
function closeCombineDialog() {
document.getElementById('combine-overlay').style.display = 'none';
}
function confirmCombineLoad() {
var raw = document.getElementById('combine-urls').value.trim();
if (!raw) { alert('请输入至少一个模型 URL'); return; }
raw = raw
.replace(/[\u2018\u2019]/g, "'")
.replace(/[\u201C\u201D]/g, '"')
.replace(/[\u200B\uFEFF\u00A0]/g, '')
.trim();
var urls = [];
try {
var jsonStr = raw.replace(/'/g, '"');
var parsed = JSON.parse(jsonStr);
if (Array.isArray(parsed)) {
urls = parsed.map(function (s) { return String(s).trim(); }).filter(Boolean);
}
} catch (e) {
urls = raw.split(/[,;\n\r]+/)
.map(function (s) { return s.trim().replace(/^['"|\[\]\s]+|['"|\[\]\s]+$/g, ''); })
.filter(Boolean);
}
urls = urls.filter(function (s) { return /^https?:\/\//i.test(s); });
if (urls.length === 0) { alert('未解析到有效的 URL'); return; }
for (var i = 0; i < urls.length; i++) {
try { new URL(urls[i]); } catch (e) {
alert('第 ' + (i + 1) + ' 个 URL 无效:\n' + urls[i]);
return;
}
}
try {
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);
}
}
// ====== 2D 图纸操作 ======
/**
* 初始化 2D 引擎(独立实例,销毁其他引擎类型)
*/
function initEngine2d() {
destroyAllEngines();
updateEngineStatus('未初始化');
update2dStatus('初始化中...');
update720Status('未初始化');
document.getElementById('btn-load720').disabled = true;
try {
engine2d = new IflowEngine.BimEngine2d('app', {
locale: currentLocale,
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,
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>