1174 lines
45 KiB
HTML
1174 lines
45 KiB
HTML
<!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 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>
|
||
<div class="btn-container" style="margin-top: 8px;">
|
||
<button onclick="pauseRendering()">暂停渲染</button>
|
||
<button onclick="resumeRendering()">恢复渲染</button>
|
||
</div>
|
||
<div class="btn-container" style="margin-top: 8px;">
|
||
<button onclick="readModelCodeFormStoge()">读取缓存编码</button>
|
||
<button onclick="startOneClickEncoding()">启动一键编码</button>
|
||
<button onclick="checkHasModelCode()">检查模型编码</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/ 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 DEFAULT_3D_MODEL_URL = 'https://pub-8092fd0db2e14822a9f742ad0050750c.r2.dev/e9603d6b-c885-4f1b-84b0-2589bc9dc44f';
|
||
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://pub-8092fd0db2e14822a9f742ad0050750c.r2.dev/66ad9a66-5ca8-47ac-9139-6aa8756069c1/';
|
||
let current3dModelUrl = DEFAULT_3D_MODEL_URL;
|
||
|
||
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 {
|
||
const modelInput = document.getElementById('url-input-3d');
|
||
if (modelInput) {
|
||
modelInput.value = current3dModelUrl;
|
||
}
|
||
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;
|
||
}
|
||
|
||
const urlInput = document.getElementById('url-input-3d');
|
||
const inputValue = urlInput && urlInput.value ? urlInput.value.trim() : '';
|
||
if (inputValue) {
|
||
current3dModelUrl = inputValue;
|
||
}
|
||
|
||
const modelUrl = current3dModelUrl;
|
||
try {
|
||
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 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 ? '所有模型已编码' : '未全部编码'));
|
||
}
|
||
|
||
function switchModel() {
|
||
if (!engine || !engine.engine || !engine.engine.isInitialized()) {
|
||
alert('请先初始化 3D 引擎!');
|
||
return;
|
||
}
|
||
|
||
var urlInput = document.getElementById('url-input-3d');
|
||
var newUrl = urlInput && urlInput.value ? urlInput.value.trim() : '';
|
||
if (!newUrl) {
|
||
alert('请输入模型 URL');
|
||
return;
|
||
}
|
||
|
||
current3dModelUrl = newUrl;
|
||
try {
|
||
engine.engine.loadModel([current3dModelUrl], {
|
||
position: [0, 0, 0],
|
||
rotation: [0, 0, 0],
|
||
scale: [1, 1, 1]
|
||
});
|
||
console.log('✅ 切换模型请求已发送:', current3dModelUrl);
|
||
} 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> |