提交代码

This commit is contained in:
yuding
2026-03-16 16:13:36 +08:00
parent 507112fcf9
commit dd4600bb5b
35 changed files with 31811 additions and 9696 deletions

View File

@@ -1,4 +1,4 @@
---
alwaysApply: true
---
你是一个资深的前端工程师,我这个项目需要你每次都看下AI_COLLABORATION.md,文件里面有项目的所有信息
你是一个资深的前端工程师,我这个项目需要你每次都看下文档文档地址是docs/,文件里面有项目的所有信息

View File

@@ -210,6 +210,38 @@
<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>
<!-- 右侧主区域 -->
@@ -218,11 +250,15 @@
</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);">
<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>
<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>
@@ -231,21 +267,28 @@
</div>
<script>
let engine = null;
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 engine3DInitialized = false;
// 初始化引擎
/**
* 销毁所有引擎实例
*/
function destroyAllEngines() {
if (engine) { engine.destroy(); engine = null; }
if (engine2d) { engine2d.destroy(); engine2d = null; }
if (engine720) { engine720.destroy(); engine720 = null; }
}
// 页面加载完成后默认初始化 3D 引擎
window.onload = () => {
if (window.IflowEngine) {
const Engine = window.IflowEngine.BimEngine;
try {
engine = new Engine('app', { locale: 'zh-CN' });
initEngine3D();
console.log('Engine initialized:', engine);
} catch (err) {
console.error('Init failed:', err);
}
@@ -327,14 +370,14 @@
alert('Please add custom group first / 请先添加自定义组');
return;
}
const btnId = 'custom-btn-' + Date.now();
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: (btn) => {
onClick: function (btn) {
alert('Clicked: ' + btn.label);
}
});
@@ -349,60 +392,52 @@
// --- 主题操作 ---
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', // 浅红背景
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 引擎操作 ======
/**
* 初始化 3D 引擎
* 初始化 3D 引擎(独立实例,销毁其他引擎类型)
*/
function initEngine3D() {
if (!engine || !engine.engine) {
alert('引擎未创建,请先等待页面加载完成');
return;
}
if (engine.engine.isInitialized()) {
alert('3D 引擎已经初始化过了');
updateEngineStatus('已初始化');
return;
}
destroyAllEngines();
updateEngineStatus('初始化中...');
update2dStatus('未初始化');
update720Status('未初始化');
document.getElementById('btn-load2d').disabled = true;
document.getElementById('btn-load720').disabled = true;
try {
// 初始化引擎,使用默认配置
const success = engine.engine.initialize({
backgroundColor: 0x333333, // 深色背景
version: 'v2', // WebGL 版本
showStats: false, // 显示性能统计
showViewCube: true // 显示视图立方体
engine = new IflowEngine.BimEngine('app', { locale: 'zh-CN' });
var success = engine.engine.initialize({
backgroundColor: 0x333333,
version: 'v2',
showStats: false,
showViewCube: true
});
if (success) {
engine3DInitialized = true;
updateEngineStatus('已初始化');
console.log('✅ 3D 引擎初始化成功');
loadModel();
@@ -416,72 +451,46 @@
}
}
/**
* 加载 3D 模型
*/
function loadModel() {
if (!engine || !engine.engine) {
alert('引擎未创建,请先等待页面加载完成');
return;
}
if (!engine.engine.isInitialized()) {
if (!engine || !engine.engine || !engine.engine.isInitialized()) {
alert('请先初始化 3D 引擎!');
return;
}
try {
// 加载模型文件(从 model 目录)
const modelUrl = 'https://lyz-1259524260.cos.ap-guangzhou.myqcloud.com/iflow/models/417664a3-76c8-4d94-9344-1337246a5d4e/';
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] // 初始缩放
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) {
alert('引擎未创建,请先等待页面加载完成');
return;
}
if (!engine.engine.isInitialized()) {
if (!engine || !engine.engine || !engine.engine.isInitialized()) {
alert('请先初始化 3D 引擎!');
return;
}
try {
const modelUrls = [
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 引擎!');
@@ -491,9 +500,6 @@
console.log('✅ 渲染已暂停');
}
/**
* 恢复渲染
*/
function resumeRendering() {
if (!engine || !engine.engine || !engine.engine.isInitialized()) {
alert('请先初始化 3D 引擎!');
@@ -503,26 +509,13 @@
console.log('✅ 渲染已恢复');
}
/**
* 切换模型 - 输入新的模型 URL
*/
function switchModel() {
if (!engine || !engine.engine) {
alert('引擎未创建,请先等待页面加载完成');
return;
}
if (!engine.engine.isInitialized()) {
if (!engine || !engine.engine || !engine.engine.isInitialized()) {
alert('请先初始化 3D 引擎!');
return;
}
const newUrl = prompt('请输入新的模型 URL:', 'https://lyz-1259524260.cos.ap-guangzhou.myqcloud.com/iflow/models/8634e556-a94e-4ba7-be3e-2ea1507cced5/');
if (!newUrl || newUrl.trim() === '') {
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],
@@ -535,9 +528,6 @@
}
}
/**
* 打开属性面板
*/
function openPropertyPanel() {
if (!engine || !engine.propertyPanel) {
console.error('Property panel not available');
@@ -546,13 +536,12 @@
engine.propertyPanel.show();
}
// --- 构件树数据 ---
function getLevelTree() {
if (!engine || !engine.engine || !engine.engine.isInitialized()) {
alert('请先初始化 3D 引擎并加载模型!');
return;
}
const data = engine.engine.getLevelTreeData();
var data = engine.engine.getLevelTreeData();
console.log('🌳 楼层树数据:', data);
}
@@ -561,7 +550,7 @@
alert('请先初始化 3D 引擎并加载模型!');
return;
}
const data = engine.engine.getTypeTreeData();
var data = engine.engine.getTypeTreeData();
console.log('🌳 类型树数据:', data);
}
@@ -570,25 +559,20 @@
alert('请先初始化 3D 引擎并加载模型!');
return;
}
const data = engine.engine.getMajorTreeData();
var data = engine.engine.getMajorTreeData();
console.log('🌳 专业树数据:', data);
}
/**
* 更新引擎状态显示
*/
function updateEngineStatus(status) {
const statusEl = document.getElementById('engine-status');
if (statusEl) {
statusEl.textContent = status;
// 根据状态设置颜色
if (status === '已初始化') {
statusEl.style.color = '#28a745';
} else if (status === '初始化失败' || status === '初始化错误') {
statusEl.style.color = '#dc3545';
} else {
statusEl.style.color = '#666';
}
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';
}
}
@@ -598,69 +582,48 @@
alert('请先初始化 3D 引擎!');
return;
}
const overlay = document.getElementById('combine-overlay');
overlay.style.display = 'flex';
document.getElementById('combine-overlay').style.display = 'flex';
}
function closeCombineDialog() {
const overlay = document.getElementById('combine-overlay');
overlay.style.display = 'none';
document.getElementById('combine-overlay').style.display = 'none';
}
function confirmCombineLoad() {
let raw = document.getElementById('combine-urls').value.trim();
if (!raw) {
alert('请输入至少一个模型 URL');
return;
}
var raw = document.getElementById('combine-urls').value.trim();
if (!raw) { alert('请输入至少一个模型 URL'); return; }
// 1) 统一清洗去除智能引号、零宽字符、BOM 等不可见字符
raw = raw
.replace(/[\u2018\u2019]/g, "'") // “” → '
.replace(/[\u201C\u201D]/g, '"') // “” → "
.replace(/[\u200B\uFEFF\u00A0]/g, '') // 零宽空格、BOM、不换行空格
.replace(/[\u2018\u2019]/g, "'")
.replace(/[\u201C\u201D]/g, '"')
.replace(/[\u200B\uFEFF\u00A0]/g, '')
.trim();
// 2) 尝试当 JSON 数组解析
let urls = [];
var urls = [];
try {
const jsonStr = raw.replace(/'/g, '"');
const parsed = JSON.parse(jsonStr);
var jsonStr = raw.replace(/'/g, '"');
var parsed = JSON.parse(jsonStr);
if (Array.isArray(parsed)) {
urls = parsed.map(s => String(s).trim()).filter(Boolean);
urls = parsed.map(function (s) { return String(s).trim(); }).filter(Boolean);
}
} catch(e) {
// 3) JSON 失败,走分割逻辑
} catch (e) {
urls = raw.split(/[,;\n\r]+/)
.map(s => s.trim().replace(/^['"|\[\]\s]+|['"|\[\]\s]+$/g, ''))
.map(function (s) { return s.trim().replace(/^['"|\[\]\s]+|['"|\[\]\s]+$/g, ''); })
.filter(Boolean);
}
// 4) 只保留 http/https 开头的合法 URL
urls = urls.filter(s => /^https?:\/\//i.test(s));
urls = urls.filter(function (s) { return /^https?:\/\//i.test(s); });
if (urls.length === 0) { alert('未解析到有效的 URL'); return; }
if (urls.length === 0) {
alert('未解析到有效的 URL请确保以 http:// 或 https:// 开头');
return;
}
// 5) 逐个校验 URL 是否合法
for (let i = 0; i < urls.length; i++) {
try {
new URL(urls[i]);
} catch(e) {
alert('第 ' + (i+1) + ' 个 URL 无效:\n' + urls[i] + '\n\n请检查是否包含特殊字符');
console.error('❌ 无效 URL原始字符:', JSON.stringify(urls[i]));
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]
});
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 = '';
@@ -669,6 +632,162 @@
}
}
// ====== 2D 图纸操作 ======
/**
* 初始化 2D 引擎(独立实例,销毁其他引擎类型)
*/
function initEngine2d() {
destroyAllEngines();
updateEngineStatus('未初始化');
update2dStatus('初始化中...');
update720Status('未初始化');
document.getElementById('btn-load720').disabled = true;
try {
engine2d = new IflowEngine.BimEngine2d('app', {
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', {
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>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

581
demo/viewer-2d.html Normal file
View File

@@ -0,0 +1,581 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>iFlow Engine - 2D 图纸查看器</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: 16px;
box-shadow: 2px 0 5px rgba(0, 0, 0, 0.05);
z-index: 10;
}
.sidebar h1 {
font-size: 1.3rem;
color: #333;
margin-bottom: 4px;
}
.sidebar .subtitle {
font-size: 0.85rem;
color: #888;
margin-bottom: 8px;
}
.control-group {
background: #f9f9f9;
padding: 15px;
border-radius: 8px;
border: 1px solid #eee;
}
.control-group h2 {
font-size: 0.95rem;
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.85rem;
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;
}
button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* URL 输入框 */
.url-input {
width: 100%;
padding: 6px 10px;
font-size: 0.85rem;
border: 1px solid #ddd;
border-radius: 4px;
outline: none;
transition: border-color 0.2s;
font-family: 'Consolas', 'Monaco', monospace;
}
.url-input:focus {
border-color: #0078d4;
box-shadow: 0 0 0 2px rgba(0, 120, 212, 0.15);
}
.url-input::placeholder {
color: #bbb;
}
/* 图层列表 */
.layer-list {
max-height: 300px;
overflow-y: auto;
margin-top: 8px;
}
.layer-item {
display: flex;
align-items: center;
gap: 8px;
padding: 6px 8px;
border-radius: 4px;
cursor: pointer;
font-size: 0.85rem;
color: #444;
transition: background 0.15s;
}
.layer-item:hover {
background: #f0f0f0;
}
.layer-item input[type="checkbox"] {
cursor: pointer;
}
.layer-item label {
cursor: pointer;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* 状态显示 */
.status {
font-size: 0.8rem;
color: #888;
margin-top: 8px;
padding: 8px;
background: #f0f0f0;
border-radius: 4px;
}
.status .label {
font-weight: 600;
color: #555;
}
.status.success {
background: #e8f5e9;
color: #2e7d32;
}
.status.error {
background: #ffebee;
color: #c62828;
}
.status.loading {
background: #e3f2fd;
color: #1565c0;
}
/* 右侧主内容区 */
.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;
}
/* 日志区域 */
.log-area {
margin-top: 8px;
max-height: 150px;
overflow-y: auto;
background: #1e1e1e;
color: #d4d4d4;
font-family: 'Consolas', 'Monaco', monospace;
font-size: 0.75rem;
padding: 8px;
border-radius: 4px;
line-height: 1.4;
}
.log-area .log-entry {
margin-bottom: 2px;
}
.log-area .log-entry.info { color: #569cd6; }
.log-area .log-entry.success { color: #6a9955; }
.log-area .log-entry.error { color: #f44747; }
.log-area .log-entry.warn { color: #d7ba7d; }
</style>
</head>
<body>
<!-- 左侧控制面板 -->
<aside class="sidebar">
<div>
<h1>📐 2D 图纸查看器</h1>
<div class="subtitle">iFlow Engine SDK - 2D Drawing Viewer</div>
</div>
<!-- 引擎控制 -->
<div class="control-group">
<h2>🚀 引擎控制</h2>
<div class="btn-container">
<button class="primary" id="btn-init" onclick="initEngine2d()">初始化 2D 引擎</button>
</div>
<div class="btn-container" style="margin-top: 8px;">
<button id="btn-load" onclick="loadDrawing()" disabled>加载默认图纸</button>
</div>
<div style="margin-top: 10px;">
<input type="text" id="url-input" class="url-input" placeholder="输入图纸 URL 后点击切换图纸" />
</div>
<div class="btn-container" style="margin-top: 8px;">
<button class="primary" id="btn-switch" onclick="switchDrawing()" disabled>切换图纸</button>
</div>
<div id="engine-status" class="status">
<span class="label">状态:</span> 未初始化
</div>
</div>
<!-- 视图控制 -->
<div class="control-group">
<h2>🔍 视图控制</h2>
<div class="btn-container">
<button onclick="resetView()" id="btn-reset">重置视图</button>
<button onclick="fitToView()" id="btn-fit">适应视图</button>
</div>
<div class="btn-container" style="margin-top: 8px;">
<button onclick="zoomIn()">放大</button>
<button onclick="zoomOut()">缩小</button>
</div>
</div>
<!-- 图层控制 -->
<div class="control-group">
<h2>📑 图层控制</h2>
<div class="btn-container">
<button onclick="refreshLayers()">刷新图层</button>
<button onclick="showAllLayers()">全部显示</button>
<button onclick="hideAllLayers()">全部隐藏</button>
</div>
<div id="layer-list" class="layer-list">
<div style="color: #aaa; font-size: 0.8rem; padding: 8px;">
加载图纸后显示图层列表
</div>
</div>
</div>
<!-- 主题切换 -->
<div class="control-group">
<h2>🎨 主题</h2>
<div class="btn-container">
<button onclick="setTheme('dark')">深色</button>
<button onclick="setTheme('light')">浅色</button>
</div>
</div>
<!-- 日志 -->
<div class="control-group">
<h2>📋 日志</h2>
<div id="log-area" class="log-area">
<div class="log-entry info">等待初始化...</div>
</div>
</div>
</aside>
<!-- 右侧主区域 -->
<main class="main-content">
<div id="app"></div>
</main>
<script>
// ==================== 全局变量 ====================
let bimEngine = null;
let engine2dInitialized = false;
// 默认图纸 URL
const DEFAULT_DRAWING_URL = 'https://lyz-1259524260.cos.ap-guangzhou.myqcloud.com/iflow/models/97353007-32da-4e07-b85e-8f6b0c736732';
// ==================== 日志工具 ====================
function addLog(message, type) {
type = type || 'info';
var logArea = document.getElementById('log-area');
var entry = document.createElement('div');
entry.className = 'log-entry ' + type;
var time = new Date().toLocaleTimeString();
entry.textContent = '[' + time + '] ' + message;
logArea.appendChild(entry);
logArea.scrollTop = logArea.scrollHeight;
}
function updateStatus(message, type) {
type = type || '';
var statusEl = document.getElementById('engine-status');
statusEl.className = 'status ' + type;
statusEl.innerHTML = '<span class="label">状态:</span> ' + message;
}
// ==================== 引擎初始化 ====================
window.onload = function() {
if (window.IflowEngine) {
addLog('SDK 加载成功,版本可用', 'success');
try {
var Engine = window.IflowEngine.BimEngine;
bimEngine = new Engine('app', { locale: 'zh-CN', theme: 'light' });
addLog('BimEngine 实例创建成功', 'success');
} catch (err) {
addLog('BimEngine 创建失败: ' + err.message, 'error');
updateStatus('创建失败', 'error');
}
} else {
addLog('SDK 未找到,请确保 iflow-engine.umd.js 已加载', 'error');
updateStatus('SDK 未加载', 'error');
}
};
// ==================== 初始化 2D 引擎 ====================
function initEngine2d() {
if (!bimEngine || !bimEngine.engine2d) {
addLog('BimEngine 或 engine2d 管理器不可用', 'error');
updateStatus('不可用', 'error');
return;
}
if (bimEngine.engine2d.isInitialized()) {
addLog('2D 引擎已经初始化过了', 'warn');
updateStatus('已初始化', 'success');
return;
}
try {
updateStatus('正在初始化...', 'loading');
addLog('开始初始化 2D 引擎...');
var success = bimEngine.engine2d.initialize({
backgroundColor: 0xffffff,
gridEnabled: true,
axesEnabled: false
});
if (success) {
engine2dInitialized = true;
updateStatus('已初始化 ✓', 'success');
addLog('2D 引擎初始化成功', 'success');
document.getElementById('btn-load').disabled = false;
document.getElementById('btn-switch').disabled = false;
document.getElementById('btn-init').disabled = true;
// 监听 2D 引擎事件
bimEngine.on('engine2d:drawing-loaded', function(payload) {
addLog('图纸加载完成: ' + payload.url, 'success');
updateStatus('图纸已加载 ✓', 'success');
// 自动刷新图层列表
refreshLayers();
});
bimEngine.on('engine2d:entity-clicked', function(payload) {
addLog('实体点击: ' + JSON.stringify(payload.data));
});
bimEngine.on('engine2d:layer-changed', function(payload) {
addLog('图层变更: ' + JSON.stringify(payload.data));
});
// 自动加载图纸
loadDrawing();
} else {
updateStatus('初始化失败', 'error');
addLog('2D 引擎初始化返回 false', 'error');
}
} catch (error) {
updateStatus('初始化错误', 'error');
addLog('2D 引擎初始化错误: ' + error.message, 'error');
}
}
// ==================== 加载图纸 ====================
function loadDrawing() {
if (!bimEngine || !bimEngine.engine2d || !bimEngine.engine2d.isInitialized()) {
addLog('请先初始化 2D 引擎', 'warn');
return;
}
updateStatus('正在加载图纸...', 'loading');
addLog('开始加载图纸: ' + DEFAULT_DRAWING_URL);
try {
bimEngine.engine2d.loadDrawing(DEFAULT_DRAWING_URL).then(function() {
addLog('loadDrawing Promise 已完成', 'success');
}).catch(function(err) {
addLog('图纸加载失败: ' + err.message, 'error');
updateStatus('加载失败', 'error');
});
} catch (error) {
addLog('加载图纸错误: ' + error.message, 'error');
updateStatus('加载错误', 'error');
}
}
// ==================== 切换图纸 ====================
function switchDrawing() {
var urlInput = document.getElementById('url-input');
var newUrl = urlInput.value.trim();
if (!newUrl) {
addLog('请输入图纸 URL', 'warn');
return;
}
if (!bimEngine || !bimEngine.engine2d || !bimEngine.engine2d.isInitialized()) {
addLog('请先初始化 2D 引擎', 'warn');
return;
}
updateStatus('正在切换图纸...', 'loading');
addLog('切换图纸: ' + newUrl);
try {
bimEngine.engine2d.loadDrawing(newUrl).then(function() {
addLog('图纸切换完成', 'success');
}).catch(function(err) {
addLog('图纸切换失败: ' + err.message, 'error');
updateStatus('切换失败', 'error');
});
} catch (error) {
addLog('切换图纸错误: ' + error.message, 'error');
updateStatus('切换错误', 'error');
}
}
// ==================== 视图控制 ====================
function resetView() {
if (!bimEngine || !bimEngine.engine2d) return;
bimEngine.engine2d.resetView();
addLog('视图已重置');
}
function fitToView() {
if (!bimEngine || !bimEngine.engine2d) return;
bimEngine.engine2d.fitToView();
addLog('视图已适应');
}
function zoomIn() {
if (!bimEngine || !bimEngine.engine2d) return;
var comp = bimEngine.engine2d.getEngine2dComponent();
if (comp) {
var currentZoom = comp.getZoom();
comp.setZoom(currentZoom * 1.2);
addLog('放大至: ' + (currentZoom * 1.2).toFixed(2));
}
}
function zoomOut() {
if (!bimEngine || !bimEngine.engine2d) return;
var comp = bimEngine.engine2d.getEngine2dComponent();
if (comp) {
var currentZoom = comp.getZoom();
comp.setZoom(currentZoom / 1.2);
addLog('缩小至: ' + (currentZoom / 1.2).toFixed(2));
}
}
// ==================== 图层控制 ====================
function refreshLayers() {
if (!bimEngine || !bimEngine.engine2d) return;
var layers = bimEngine.engine2d.getLayers();
var listEl = document.getElementById('layer-list');
if (!layers || layers.length === 0) {
listEl.innerHTML = '<div style="color: #aaa; font-size: 0.8rem; padding: 8px;">暂无图层数据</div>';
addLog('未获取到图层数据', 'warn');
return;
}
listEl.innerHTML = '';
addLog('获取到 ' + layers.length + ' 个图层', 'success');
layers.forEach(function(layer, index) {
var item = document.createElement('div');
item.className = 'layer-item';
var checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.checked = layer.visible !== false;
checkbox.id = 'layer-' + index;
checkbox.addEventListener('change', function() {
toggleLayer(layer.name, this.checked);
});
var label = document.createElement('label');
label.htmlFor = 'layer-' + index;
label.textContent = layer.name;
label.title = layer.name;
item.appendChild(checkbox);
item.appendChild(label);
listEl.appendChild(item);
});
}
function toggleLayer(name, visible) {
if (!bimEngine || !bimEngine.engine2d) return;
bimEngine.engine2d.setLayerVisible(name, visible);
addLog('图层 "' + name + '" ' + (visible ? '显示' : '隐藏'));
}
function showAllLayers() {
if (!bimEngine || !bimEngine.engine2d) return;
var layers = bimEngine.engine2d.getLayers();
if (layers) {
layers.forEach(function(layer) {
bimEngine.engine2d.setLayerVisible(layer.name, true);
});
refreshLayers();
addLog('已显示所有图层', 'success');
}
}
function hideAllLayers() {
if (!bimEngine || !bimEngine.engine2d) return;
var layers = bimEngine.engine2d.getLayers();
if (layers) {
layers.forEach(function(layer) {
bimEngine.engine2d.setLayerVisible(layer.name, false);
});
refreshLayers();
addLog('已隐藏所有图层', 'warn');
}
}
// ==================== 主题切换 ====================
function setTheme(theme) {
if (bimEngine) {
bimEngine.setTheme(theme);
addLog('主题已切换为: ' + theme);
}
}
</script>
</body>
</html>

297
docs/ENGINE_2D/API文档.md Normal file
View File

@@ -0,0 +1,297 @@
# 2D 图纸引擎 API 文档
## 概述
`BimEngine2d` 是独立的 2D CAD/DWG 图纸查看引擎。轻量级设计,不依赖 3D 引擎的 UI 管理器toolbar、measure 等),构造时自动初始化。
**文件路径**: `src/bim-engine-2d.ts`
**底层依赖**: `iflow-engine-base``createEngine2d()`
---
## 导入
```typescript
// ESM
import { BimEngine2d } from 'iflow-engine';
// UMD
const { BimEngine2d } = IflowEngine;
```
---
## BimEngine2d
### 构造函数
```typescript
constructor(container: HTMLElement | string, options?: BimEngine2dOptions)
```
**参数**:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `container` | `HTMLElement \| string` | ✅ | DOM 元素或元素 ID |
| `options` | `BimEngine2dOptions` | | 构造选项 |
构造时自动初始化引擎,无需额外调用 `initialize()` 方法。
### BimEngine2dOptions
```typescript
interface BimEngine2dOptions {
/** 语言 */
locale?: 'zh-CN' | 'en-US';
/** 主题 */
theme?: 'dark' | 'light';
/** 背景颜色(十六进制数值,如 0xffffff */
backgroundColor?: number;
/** 是否启用网格 */
gridEnabled?: boolean;
/** 是否启用坐标轴 */
axesEnabled?: boolean;
/** 选中构件颜色 */
selectionColor?: number;
/** 高亮构件颜色 */
highlightColor?: number;
/** 是否启用性能监控 */
enablePerformanceMonitoring?: boolean;
}
```
---
## 图纸操作
### loadDrawing()
```typescript
public async loadDrawing(url: string, options?: DrawingLoadOptions): Promise<void>
```
**功能**: 加载 2D 图纸
**参数**:
- `url`: string — 图纸资源 URL
- `options`: DrawingLoadOptions — 加载选项(可选)
**返回值**: Promise\<void\>
```typescript
interface DrawingLoadOptions {
/** 分块大小 */
chunkSize?: number;
/** 是否启用分块加载 */
enableChunkedLoading?: boolean;
/** 是否启用数据校验 */
enableValidation?: boolean;
}
```
**示例**:
```typescript
await engine2d.loadDrawing('https://example.com/drawing-id');
```
### getLayers()
```typescript
public getLayers(): Drawing2dLayer[]
```
**功能**: 获取当前图纸的所有图层
**返回值**: Drawing2dLayer[]
```typescript
interface Drawing2dLayer {
/** 图层名称 */
name: string;
/** 图层是否可见 */
visible: boolean;
}
```
**示例**:
```typescript
const layers = engine2d.getLayers();
layers.forEach(layer => {
console.log(`${layer.name}: ${layer.visible ? '可见' : '隐藏'}`);
});
```
### setLayerVisible()
```typescript
public setLayerVisible(name: string, visible: boolean): void
```
**功能**: 设置指定图层的可见性
**参数**:
- `name`: string — 图层名称
- `visible`: boolean — 是否可见
**示例**:
```typescript
engine2d.setLayerVisible('标注层', false); // 隐藏标注层
engine2d.setLayerVisible('墙体', true); // 显示墙体层
```
---
## 视图控制
### resetView()
```typescript
public resetView(): void
```
**功能**: 重置视图到初始状态
### fitToView()
```typescript
public fitToView(): void
```
**功能**: 缩放到适应全部图纸内容
### setZoom()
```typescript
public setZoom(zoom: number): void
```
**功能**: 设置缩放级别
**参数**:
- `zoom`: number — 缩放值
### getZoom()
```typescript
public getZoom(): number
```
**功能**: 获取当前缩放级别
**返回值**: number — 当前缩放值,默认 1
---
## 主题
### setTheme()
```typescript
public setTheme(theme: 'dark' | 'light'): void
```
**功能**: 切换主题
**参数**:
- `theme`: 'dark' | 'light'
---
## 事件
### on()
```typescript
public on<K extends keyof EngineEvents>(
event: K,
listener: (payload: EngineEvents[K]) => void
): () => void
```
**功能**: 订阅事件
**返回值**: 取消订阅函数
**示例**:
```typescript
const unsub = engine2d.on('engine:model-loaded', (payload) => {
console.log('图纸加载完成');
});
// 取消订阅
unsub();
```
### off()
```typescript
public off<K extends keyof EngineEvents>(
event: K,
listener: (payload: EngineEvents[K]) => void
): void
```
**功能**: 取消事件订阅
### onRawEvent()
```typescript
public onRawEvent(event: string, handler: (...args: any[]) => void): void
```
**功能**: 订阅底层 2D 引擎的原始事件
### offRawEvent()
```typescript
public offRawEvent(event: string, handler: (...args: any[]) => void): void
```
**功能**: 取消订阅底层 2D 引擎的原始事件
---
## 生命周期
### destroy()
```typescript
public destroy(): void
```
**功能**: 销毁引擎,释放所有资源
- 销毁底层 Engine2d 组件
- 重置 ManagerRegistry
- 调用后实例不可再使用
**重要**: 切换引擎类型或页面卸载时务必调用。
---
## 内部架构
```text
BimEngine2d (用户 API)
├─ ManagerRegistry (仅事件总线,不注册 Manager)
└─ Engine2d 组件 (IBimComponent)
└─ createEngine2d() (iflow-engine-base)
```
`BimEngine`3D的关键区别
| | BimEngine (3D) | BimEngine2d (2D) |
|---|---|---|
| Manager 层 | 15 个 Manager | 无(跳过 Manager 层) |
| UI 组件 | toolbar、dialog、menu 等 | 无 |
| 初始化 | 需要调用 `engine.initialize()` | 构造时自动初始化 |
| ManagerRegistry | 完整注册所有 Manager | 仅用于事件发射 |
---
**文档版本**: 1.0.0 | **更新日期**: 2026-03-10

View File

@@ -0,0 +1,307 @@
# 720° 全景引擎 API 文档
## 概述
`BimEngine720` 是独立的 720° 全景图查看引擎。轻量级设计,支持鼠标/触摸旋转和缩放,不依赖 3D 引擎的 UI 管理器,构造时自动初始化。
**文件路径**: `src/bim-engine-720.ts`
**底层依赖**: `iflow-engine-base``createEngine720()`
---
## 导入
```typescript
// ESM
import { BimEngine720 } from 'iflow-engine';
// UMD
const { BimEngine720 } = IflowEngine;
```
---
## BimEngine720
### 构造函数
```typescript
constructor(container: HTMLElement | string, options?: BimEngine720Options)
```
**参数**:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `container` | `HTMLElement \| string` | ✅ | DOM 元素或元素 ID |
| `options` | `BimEngine720Options` | | 构造选项 |
构造时自动初始化引擎,无需额外调用 `initialize()` 方法。
### BimEngine720Options
```typescript
interface BimEngine720Options {
/** 语言 */
locale?: 'zh-CN' | 'en-US';
/** 主题 */
theme?: 'dark' | 'light';
/** 视场角(默认 75 */
fov?: number;
/** 是否启用缩放(默认 true */
enableZoom?: boolean;
/** 是否启用旋转(默认 true */
enableRotate?: boolean;
/** 球体半径(默认 500 */
sphereRadius?: number;
/** 旋转速度 */
rotateSpeed?: number;
/** 缩放速度 */
zoomSpeed?: number;
/** 是否启用阻尼(惯性效果) */
enableDamping?: boolean;
/** 阻尼因子 */
dampingFactor?: number;
/** 最小视场角 */
minFov?: number;
/** 最大视场角 */
maxFov?: number;
}
```
---
## 全景操作
### loadPanorama()
```typescript
public async loadPanorama(url: string, options?: PanoramaLoadOptions): Promise<void>
```
**功能**: 加载全景图
**参数**:
- `url`: string — 全景图片 URL等距柱状投影格式
- `options`: PanoramaLoadOptions — 加载选项(可选)
**返回值**: Promise\<void\>
```typescript
interface PanoramaLoadOptions {
/** 加载超时时间(毫秒) */
timeout?: number;
}
```
**示例**:
```typescript
await engine720.loadPanorama('https://example.com/panorama.png');
```
### preloadPanoramas()
```typescript
public async preloadPanoramas(urls: string[]): Promise<void>
```
**功能**: 预加载多个全景图(用于场景切换时减少加载等待)
**参数**:
- `urls`: string[] — 全景图片 URL 数组
**示例**:
```typescript
await engine720.preloadPanoramas([
'https://example.com/room1.png',
'https://example.com/room2.png',
'https://example.com/room3.png'
]);
```
---
## 视角控制
### setFov()
```typescript
public setFov(fov: number): void
```
**功能**: 设置视场角Field of View
**参数**:
- `fov`: number — 视场角度数
### getFov()
```typescript
public getFov(): number
```
**功能**: 获取当前视场角
**返回值**: number — 当前视场角,默认 75
### lookAt()
```typescript
public lookAt(phi: number, theta: number, animated?: boolean): void
```
**功能**: 设置相机朝向(球面坐标)
**参数**:
- `phi`: number — 水平角度
- `theta`: number — 垂直角度
- `animated`: boolean — 是否动画过渡(可选)
**示例**:
```typescript
// 看向指定方向
engine720.lookAt(Math.PI / 4, Math.PI / 6, true);
```
### resetView()
```typescript
public resetView(): void
```
**功能**: 重置视角到初始状态
---
## 主题
### setTheme()
```typescript
public setTheme(theme: 'dark' | 'light'): void
```
**功能**: 切换主题
**参数**:
- `theme`: 'dark' | 'light'
---
## 事件
### on()
```typescript
public on<K extends keyof EngineEvents>(
event: K,
listener: (payload: EngineEvents[K]) => void
): () => void
```
**功能**: 订阅事件
**返回值**: 取消订阅函数
**示例**:
```typescript
const unsub = engine720.on('engine:model-loaded', (payload) => {
console.log('全景图加载完成');
});
// 取消订阅
unsub();
```
### off()
```typescript
public off<K extends keyof EngineEvents>(
event: K,
listener: (payload: EngineEvents[K]) => void
): void
```
**功能**: 取消事件订阅
### onRawEvent()
```typescript
public onRawEvent(event: string, handler: (...args: any[]) => void): void
```
**功能**: 订阅底层 720 引擎的原始事件
### offRawEvent()
```typescript
public offRawEvent(event: string, handler: (...args: any[]) => void): void
```
**功能**: 取消订阅底层 720 引擎的原始事件
---
## 生命周期
### destroy()
```typescript
public destroy(): void
```
**功能**: 销毁引擎,释放所有资源
- 销毁底层 Engine720 组件
- 重置 ManagerRegistry
- 调用后实例不可再使用
**重要**: 切换引擎类型或页面卸载时务必调用。
---
## 内部架构
```text
BimEngine720 (用户 API)
├─ ManagerRegistry (仅事件总线,不注册 Manager)
└─ Engine720 组件 (IBimComponent)
└─ createEngine720() (iflow-engine-base)
```
`BimEngine`3D的关键区别
| | BimEngine (3D) | BimEngine720 (720°) |
|---|---|---|
| Manager 层 | 15 个 Manager | 无(跳过 Manager 层) |
| UI 组件 | toolbar、dialog、menu 等 | 无 |
| 初始化 | 需要调用 `engine.initialize()` | 构造时自动初始化 |
| ManagerRegistry | 完整注册所有 Manager | 仅用于事件发射 |
---
## 相关类型
```typescript
/** 全景标注信息(预留扩展) */
interface PanoramaAnnotation {
/** 标注 ID */
id: string;
/** 标注位置(球面坐标 phi */
phi?: number;
/** 标注位置(球面坐标 theta */
theta?: number;
/** 标注文本 */
text?: string;
/** 自定义数据 */
data?: any;
}
```
---
**文档版本**: 1.0.0 | **更新日期**: 2026-03-10

View File

@@ -14,7 +14,7 @@
```
src/core/
├── event-emitter.ts # 事件发射器
├── manager-registry.ts # Manager 注册表(单例
├── manager-registry.ts # Manager 注册表(实例化
├── base-manager.ts # Manager 抽象基类
└── base-dialog-manager.ts # 对话框 Manager 基类
```
@@ -22,7 +22,7 @@ src/core/
| 文件 | 行数 | 职责 | 导出 |
|------|------|------|------|
| `event-emitter.ts` | 70 | 发布/订阅事件系统 | `EventEmitter` |
| `manager-registry.ts` | 127 | 全局单例注册表 | `ManagerRegistry` |
| `manager-registry.ts` | 127 | 实例级 Manager 注册表 | `ManagerRegistry` |
| `base-manager.ts` | 57 | Manager 基类 | `BaseManager` |
| `base-dialog-manager.ts` | 145 | 对话框 Manager 基类 | `BaseDialogManager` |
@@ -33,7 +33,7 @@ src/core/
```
EventEmitter
↑ (组合)
ManagerRegistry (例)
ManagerRegistry (例)
↑ (访问)
BaseManager (抽象类)
↑ (继承)
@@ -46,7 +46,7 @@ BaseDialogManager (抽象类)
| 模式 | 应用 | 说明 |
|------|------|------|
| 例模式 | ManagerRegistry | 全局唯一实例 |
| 例模式 | ManagerRegistry | 每个引擎实例独立注册表 |
| 发布/订阅 | EventEmitter | 解耦事件通信 |
| 模板方法 | BaseDialogManager | 定义对话框生命周期 |
@@ -99,17 +99,14 @@ unsubscribe();
### 概述
全局单例注册表,集中管理所有 Manager 实例,提供跨 Manager 通信
实例级注册表,集中管理单个引擎实例下的 Manager 与事件总线,支持多实例隔离
### API
```typescript
class ManagerRegistry {
// 获取单
static getInstance(): ManagerRegistry;
// 重置单例(用于测试)
static reset(): void;
// 创建实
constructor();
// 容器元素
container: HTMLElement | null;
@@ -125,25 +122,30 @@ class ManagerRegistry {
componentDetail: ComponentDetailManager | null;
measure: MeasureDialogManager | null;
walkControl: WalkControlManager | null;
map: MapDialogManager | null;
sectionPlane: SectionPlaneDialogManager | null;
sectionAxis: SectionAxisDialogManager | null;
sectionBox: SectionBoxDialogManager | null;
walkPath: WalkPathDialogManager | null;
walkPlanView: WalkPlanViewDialogManager | null;
engineInfo: EngineInfoDialogManager | null;
aiChat: AiChatManager | null;
setting: SettingDialogManager | null;
// 事件方法
emit<K extends keyof EngineEvents>(event: K, payload: EngineEvents[K]): void;
on<K extends keyof EngineEvents>(event: K, listener: (payload: EngineEvents[K]) => void): () => void;
off<K extends keyof EngineEvents>(event: K, listener: (payload: EngineEvents[K]) => void): void;
clearEvents(): void;
reset(): void;
}
```
### 使用示例
```typescript
const registry = ManagerRegistry.getInstance();
const registry = new ManagerRegistry();
// 访问 Manager
// 由引擎入口在初始化时写入各个 Manager 实例
registry.toolbar?.show();
registry.measure?.switchMode('distance');

View File

@@ -5,8 +5,8 @@
| 模块 | 路径 | 职责 | 文档 |
|------|------|------|------|
| **core** | `src/core/` | 核心基础设施:事件系统、管理器基类、注册表 | [核心模块.md](核心模块.md) |
| **managers** | `src/managers/` | 15 个管理器,处理业务逻辑和组件协调 | [管理器模块.md](管理器模块.md) |
| **components** | `src/components/` | 20+ UI 组件 | [组件模块.md](组件模块.md) |
| **managers** | `src/managers/` | 15 个管理器,处理业务逻辑和组件协调(仅 3D | [管理器模块.md](管理器模块.md) |
| **components** | `src/components/` | 3 个引擎组件 + 20+ UI 组件 | [组件模块.md](组件模块.md) |
| **services** | `src/services/` | 全局服务:国际化、主题管理 | [服务模块.md](服务模块.md) |
## Core 模块
@@ -16,7 +16,7 @@
| 类 | 文件 | 职责 |
|-----|------|------|
| EventEmitter | `event-emitter.ts` | 事件发布/订阅系统 |
| ManagerRegistry | `manager-registry.ts` | 全局单例注册表 |
| ManagerRegistry | `manager-registry.ts` | 实例级注册表,隔离每个引擎实例的 Manager 与事件 |
| BaseManager | `base-manager.ts` | Manager 抽象基类 |
| BaseDialogManager | `base-dialog-manager.ts` | 对话框 Manager 基类 |
@@ -57,11 +57,18 @@
纯 UI 组件,不包含业务逻辑。
### 核心组件
### 引擎组件
| 组件 | 职责 |
|------|------|
| Engine | 3D 视口容器(包装 iflow-engine-base |
| Engine2d | 2D 图纸视口(包装 createEngine2d |
| Engine720 | 720° 全景视口(包装 createEngine720 |
### 核心 UI 组件
| 组件 | 职责 |
|------|------|
| Engine | 3D 视口容器 |
| BimDialog | 通用对话框 |
| BimTree | 树形控件 |
| BimMenu | 菜单组件 |
@@ -106,7 +113,7 @@
| 文件 | 内容 |
|------|------|
| `types/component.ts` | 组件接口 IBimComponent |
| `types/events.ts` | 事件类型 EngineEvents |
| `types/events.ts` | 事件类型 EngineEvents(含 3D/2D/720 事件) |
| `types/measure.ts` | 测量类型定义 |
| `locales/types.ts` | 国际化类型 |
| `themes/types.ts` | 主题配置类型 |
@@ -119,4 +126,4 @@
---
**文档生成时间**: 2026-01-23
**文档更新时间**: 2026-03-10

View File

@@ -5,15 +5,17 @@
| 项目 | 内容 |
|------|------|
| **模块名** | components |
| **职责** | 提供 BIM 3D 引擎 SDK 的所有 UI 组件 |
| **公开 API** | 20+ 组件 |
| **职责** | 提供 BIM SDK 的所有引擎组件3D / 2D / 720 UI 组件 |
| **公开 API** | 3 个引擎组件 + 20+ UI 组件 |
| **状态** | ✅ 稳定 |
## 代码地图
```
src/components/
├── engine/ # 3D 引擎组件
├── engine/ # 3D 引擎组件 (包装 iflow-engine-base)
├── engine-2d/ # 2D 图纸引擎组件 (包装 createEngine2d)
├── engine-720/ # 720° 全景引擎组件 (包装 createEngine720)
├── dialog/ # 通用弹窗组件
├── tree/ # 树形控件组件
├── menu/ # 菜单组件
@@ -30,7 +32,6 @@ src/components/
├── walk-plan-view-panel/ # 漫游平面图面板
├── right-key/ # 右键菜单组件
└── map-panel/ # 地图面板组件
```
## 组件接口
@@ -52,7 +53,7 @@ interface IBimComponent {
### 概述
包装第三方 3D 引擎 SDK提供模型加载、视角控制、测量功能。
包装第三方 3D 引擎 SDK提供模型加载、视角控制、测量功能。仅由 `BimEngine` 通过 `EngineManager` 使用。
### 配置
@@ -69,33 +70,135 @@ interface EngineOptions {
### API
```typescript
class Engine {
class Engine implements IBimComponent {
init(): void;
loadModel(url: string, options?: ModelLoadOptions): void;
CameraGoHome(): void;
getEngine(): any;
activateMeasure(mode: MeasureMode): void;
deactivateMeasure(): void;
onRawEvent(event: string, handler: Function): void;
offRawEvent(event: string, handler: Function): void;
setTheme(theme: ThemeConfig): void;
destroy(): void;
}
```
### 测量模式
---
| 模式 | 说明 |
|------|------|
| `distance` | 距离测量 |
| `minDistance` | 最小距离 |
| `angle` | 角度测量 |
| `elevation` | 标高测量 |
| `volume` | 体积测量 |
| `laserDistance` | 激光测距 |
| `slope` | 坡度测量 |
| `spaceVolume` | 空间体积 |
## Engine2d2D 图纸引擎组件)
### 概述
包装 `createEngine2d()`,提供 2D CAD/DWG 图纸加载与查看。由 `BimEngine2d` 直接使用,跳过 Manager 层。
### 配置
```typescript
interface Engine2dOptions {
container: HTMLElement;
backgroundColor?: number;
gridEnabled?: boolean;
axesEnabled?: boolean;
selectionColor?: number;
highlightColor?: number;
enablePerformanceMonitoring?: boolean;
}
```
### API
```typescript
class Engine2d implements IBimComponent {
init(): void;
loadDrawing(url: string, options?: DrawingLoadOptions): Promise<void>;
getLayers(): Drawing2dLayer[];
setLayerVisible(name: string, visible: boolean): void;
resetView(): void;
fitToView(): void;
setZoom(zoom: number): void;
getZoom(): number;
onRawEvent(event: string, handler: Function): void;
offRawEvent(event: string, handler: Function): void;
setTheme(theme: ThemeConfig): void;
destroy(): void;
}
```
### 相关类型
```typescript
interface DrawingLoadOptions {
chunkSize?: number;
enableChunkedLoading?: boolean;
enableValidation?: boolean;
}
interface Drawing2dLayer {
name: string;
visible: boolean;
}
```
---
## Engine720720° 全景引擎组件)
### 概述
包装 `createEngine720()`,提供 720° 全景图加载与查看。由 `BimEngine720` 直接使用,跳过 Manager 层。
### 配置
```typescript
interface Engine720Options {
container: HTMLElement;
fov?: number; // 默认 75
enableZoom?: boolean; // 默认 true
enableRotate?: boolean; // 默认 true
sphereRadius?: number; // 默认 500
rotateSpeed?: number;
zoomSpeed?: number;
enableDamping?: boolean;
dampingFactor?: number;
minFov?: number;
maxFov?: number;
}
```
### API
```typescript
class Engine720 implements IBimComponent {
init(): void;
loadPanorama(url: string, options?: PanoramaLoadOptions): Promise<void>;
preloadPanoramas(urls: string[]): Promise<void>;
setFov(fov: number): void;
getFov(): number;
lookAt(phi: number, theta: number, animated?: boolean): void;
resetView(): void;
onRawEvent(event: string, handler: Function): void;
offRawEvent(event: string, handler: Function): void;
setTheme(theme: ThemeConfig): void;
destroy(): void;
}
```
### 相关类型
```typescript
interface PanoramaLoadOptions {
timeout?: number;
}
interface PanoramaAnnotation {
id: string;
phi?: number;
theta?: number;
text?: string;
data?: any;
}
```
## BimDialog通用弹窗组件
### 概述
@@ -602,4 +705,4 @@ interface WalkControlState {
---
**文档生成时间**: 2026-01-23
**文档更新时间**: 2026-03-10

View File

@@ -20,83 +20,129 @@ yarn add iflow-engine
pnpm add iflow-engine
```
## 基础使用
## 引擎概览
### 1. 创建 HTML 容器
SDK 提供三个独立引擎类,按需引入,互不依赖:
```html
<div id="bim-container" style="width: 100%; height: 100vh;"></div>
| 类名 | 用途 | 定位 |
|---|---|---|
| `BimEngine` | 3D BIM 模型可视化 | 完整功能(工具栏、测量、剖切、漫游等 UI 组件) |
| `BimEngine2d` | 2D CAD/DWG 图纸查看 | 轻量级,无 UI 管理器 |
| `BimEngine720` | 720° 全景图查看 | 轻量级,无 UI 管理器 |
```typescript
// ESM 按需导入
import { BimEngine } from 'iflow-engine'; // 3D
import { BimEngine2d } from 'iflow-engine'; // 2D
import { BimEngine720 } from 'iflow-engine'; // 720°
// 也可以一次导入全部
import { BimEngine, BimEngine2d, BimEngine720 } from 'iflow-engine';
```
### 2. 初始化引擎
```html
<!-- UMD 方式(原生 HTML -->
<script src="./lib/iflow-engine.umd.js"></script>
<script>
const { BimEngine, BimEngine2d, BimEngine720 } = IflowEngine;
</script>
```
> **注意**:三个引擎类共用同一个容器时,需先销毁当前实例再创建新实例。详见 [多引擎切换](#多引擎切换)。
---
## 一、3D 引擎BimEngine
完整的 BIM 3D 可视化引擎,包含工具栏、测量、剖切、漫游等全部 UI 功能。
### 基本用法
```typescript
import { BimEngine } from 'iflow-engine';
// 创建引擎实例
const engine = new BimEngine('bim-container', {
locale: 'zh-CN', // 语言:'zh-CN' | 'en-US'
theme: 'light' // 主题:'light' | 'dark'
const engine = new BimEngine('container', {
locale: 'zh-CN', // 'zh-CN' | 'en-US'
theme: 'dark' // 'dark' | 'light'
});
```
### 3. 加载模型
```typescript
// 初始化 3D 引擎
engine.engine?.initialize();
// 初始化 3D 渲染引擎
engine.engine?.initialize({
backgroundColor: 0x333333,
showViewCube: true
});
// 加载模型
engine.engine?.loadModel('https://example.com/model.gltf', {
autoFit: true // 自动适配视角
engine.engine?.loadModel(['https://example.com/model/'], {
position: [0, 0, 0],
rotation: [0, 0, 0],
scale: [1, 1, 1]
});
```
## 完整示例
### 构造参数
### Vue 3
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `container` | `HTMLElement \| string` | ✅ | DOM 元素或元素 ID |
| `options.locale` | `'zh-CN' \| 'en-US'` | | 界面语言,默认 `'zh-CN'` |
| `options.theme` | `'dark' \| 'light'` | | 主题,默认 `'dark'` |
### 管理器
BimEngine 构造时自动初始化以下管理器:
```typescript
engine.engine // EngineManager - 3D 引擎(加载模型、控制渲染)
engine.toolbar // ToolbarManager - 底部工具栏
engine.buttonGroup // ButtonGroupManager - 按钮组
engine.dialog // DialogManager - 弹窗管理
engine.rightKey // RightKeyManager - 右键菜单
engine.constructTreeBtn // ConstructTreeManagerBtn - 构件树
engine.measure // MeasureDialogManager - 测量工具
engine.sectionPlane // SectionPlaneDialogManager - 拾取面剖切
engine.sectionAxis // SectionAxisDialogManager - 轴向剖切
engine.sectionBox // SectionBoxDialogManager - 剖切盒
engine.walkControl // WalkControlManager - 漫游控制
engine.componentDetail // ComponentDetailManager - 构件详情
engine.aiChat // AiChatManager - AI 对话
engine.setting // SettingDialogManager - 设置
```
### Vue 3 示例
```vue
<template>
<div ref="containerRef" class="bim-container"></div>
<div ref="containerRef" style="width: 100vw; height: 100vh;" />
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue';
import { BimEngine } from 'iflow-engine';
const containerRef = ref<HTMLElement | null>(null);
const containerRef = ref<HTMLElement>();
let engine: BimEngine | null = null;
onMounted(() => {
if (containerRef.value) {
engine = new BimEngine(containerRef.value, {
locale: 'zh-CN',
theme: 'light'
});
if (!containerRef.value) return;
// 初始化 3D 引擎
engine.engine?.initialize();
engine = new BimEngine(containerRef.value, {
locale: 'zh-CN',
theme: 'dark'
});
// 加载模型
engine.engine?.loadModel('/models/building.gltf');
}
engine.engine?.initialize({ backgroundColor: 0x333333 });
engine.engine?.loadModel(['https://example.com/model/']);
});
onUnmounted(() => {
engine?.destroy();
engine = null;
});
</script>
<style scoped>
.bim-container {
width: 100%;
height: 100vh;
}
</style>
```
### React
### React 示例
```tsx
import { useRef, useEffect } from 'react';
@@ -104,285 +150,514 @@ import { BimEngine } from 'iflow-engine';
export function BimViewer() {
const containerRef = useRef<HTMLDivElement>(null);
const engineRef = useRef<BimEngine | null>(null);
useEffect(() => {
if (containerRef.current && !engineRef.current) {
engineRef.current = new BimEngine(containerRef.current, {
locale: 'zh-CN',
theme: 'light'
});
if (!containerRef.current) return;
// 初始化 3D 引擎
engineRef.current.engine?.initialize();
const engine = new BimEngine(containerRef.current, {
locale: 'zh-CN',
theme: 'dark'
});
// 加载模型
engineRef.current.engine?.loadModel('/models/building.gltf');
}
engine.engine?.initialize({ backgroundColor: 0x333333 });
engine.engine?.loadModel(['https://example.com/model/']);
return () => {
engineRef.current?.destroy();
engineRef.current = null;
};
return () => engine.destroy();
}, []);
return <div ref={containerRef} style={{ width: '100vw', height: '100vh' }} />;
}
```
---
## 二、2D 图纸引擎BimEngine2d
轻量级 2D CAD/DWG 图纸查看引擎,无 UI 管理器,构造时自动初始化。
### 基本用法
```typescript
import { BimEngine2d } from 'iflow-engine';
const engine2d = new BimEngine2d('container', {
locale: 'zh-CN',
theme: 'dark',
backgroundColor: 0xf0f0f0,
gridEnabled: true,
axesEnabled: true
});
// 加载图纸
await engine2d.loadDrawing('https://example.com/drawing-id');
// 销毁
engine2d.destroy();
```
### 构造参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `container` | `HTMLElement \| string` | ✅ | DOM 元素或元素 ID |
| `options.locale` | `'zh-CN' \| 'en-US'` | | 界面语言 |
| `options.theme` | `'dark' \| 'light'` | | 主题 |
| `options.backgroundColor` | `number` | | 背景色,如 `0xffffff` |
| `options.gridEnabled` | `boolean` | | 是否显示网格 |
| `options.axesEnabled` | `boolean` | | 是否显示坐标轴 |
| `options.selectionColor` | `number` | | 选中构件颜色 |
| `options.highlightColor` | `number` | | 高亮构件颜色 |
| `options.enablePerformanceMonitoring` | `boolean` | | 是否启用性能监控 |
### API
```typescript
// 图纸操作
await engine2d.loadDrawing(url, options?) // 加载图纸
engine2d.getLayers() // 获取所有图层 → Drawing2dLayer[]
engine2d.setLayerVisible(name, visible) // 设置图层可见性
// 视图控制
engine2d.resetView() // 重置视图
engine2d.fitToView() // 适应全部内容
engine2d.setZoom(zoom) // 设置缩放级别
engine2d.getZoom() // 获取当前缩放级别
// 主题
engine2d.setTheme('dark' | 'light')
// 事件
const unsub = engine2d.on('event-name', (payload) => { ... })
unsub() // 取消订阅
engine2d.onRawEvent(event, handler) // 底层引擎事件
engine2d.offRawEvent(event, handler)
// 生命周期
engine2d.destroy() // 销毁引擎
```
### Vue 3 示例
```vue
<template>
<div ref="containerRef" style="width: 100%; height: 100vh;" />
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue';
import { BimEngine2d } from 'iflow-engine';
const containerRef = ref<HTMLElement>();
let engine2d: BimEngine2d | null = null;
onMounted(async () => {
if (!containerRef.value) return;
engine2d = new BimEngine2d(containerRef.value, {
theme: 'light',
gridEnabled: true
});
await engine2d.loadDrawing('https://example.com/drawing-id');
});
onUnmounted(() => {
engine2d?.destroy();
engine2d = null;
});
</script>
```
### React 示例
```tsx
import { useRef, useEffect } from 'react';
import { BimEngine2d } from 'iflow-engine';
export function DrawingViewer() {
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!containerRef.current) return;
const engine2d = new BimEngine2d(containerRef.current, {
theme: 'light',
gridEnabled: true
});
engine2d.loadDrawing('https://example.com/drawing-id');
return () => engine2d.destroy();
}, []);
return <div ref={containerRef} style={{ width: '100%', height: '100vh' }} />;
}
```
### 原生 HTML
---
## 三、720° 全景引擎BimEngine720
轻量级 720° 全景图查看引擎,支持鼠标/触摸旋转和缩放,构造时自动初始化。
### 基本用法
```typescript
import { BimEngine720 } from 'iflow-engine';
const engine720 = new BimEngine720('container', {
fov: 75,
enableZoom: true,
enableRotate: true,
sphereRadius: 500
});
// 加载全景图
await engine720.loadPanorama('https://example.com/panorama.png');
// 销毁
engine720.destroy();
```
### 构造参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `container` | `HTMLElement \| string` | ✅ | DOM 元素或元素 ID |
| `options.locale` | `'zh-CN' \| 'en-US'` | | 界面语言 |
| `options.theme` | `'dark' \| 'light'` | | 主题 |
| `options.fov` | `number` | | 视场角,默认 `75` |
| `options.enableZoom` | `boolean` | | 是否启用缩放,默认 `true` |
| `options.enableRotate` | `boolean` | | 是否启用旋转,默认 `true` |
| `options.sphereRadius` | `number` | | 球体半径,默认 `500` |
| `options.rotateSpeed` | `number` | | 旋转速度 |
| `options.zoomSpeed` | `number` | | 缩放速度 |
| `options.enableDamping` | `boolean` | | 是否启用阻尼(惯性效果) |
| `options.dampingFactor` | `number` | | 阻尼因子 |
| `options.minFov` | `number` | | 最小视场角 |
| `options.maxFov` | `number` | | 最大视场角 |
### API
```typescript
// 全景操作
await engine720.loadPanorama(url, options?) // 加载全景图
await engine720.preloadPanoramas(urls) // 预加载多个全景图
// 视角控制
engine720.setFov(fov) // 设置视场角
engine720.getFov() // 获取当前视场角
engine720.lookAt(phi, theta, animated?) // 设置相机朝向
engine720.resetView() // 重置视图
// 主题
engine720.setTheme('dark' | 'light')
// 事件
const unsub = engine720.on('event-name', (payload) => { ... })
unsub() // 取消订阅
engine720.onRawEvent(event, handler) // 底层引擎事件
engine720.offRawEvent(event, handler)
// 生命周期
engine720.destroy() // 销毁引擎
```
### Vue 3 示例
```vue
<template>
<div ref="containerRef" style="width: 100%; height: 100vh;" />
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue';
import { BimEngine720 } from 'iflow-engine';
const containerRef = ref<HTMLElement>();
let engine720: BimEngine720 | null = null;
onMounted(async () => {
if (!containerRef.value) return;
engine720 = new BimEngine720(containerRef.value, {
fov: 75,
enableZoom: true,
enableRotate: true,
sphereRadius: 500
});
await engine720.loadPanorama('https://example.com/panorama.png');
});
onUnmounted(() => {
engine720?.destroy();
engine720 = null;
});
</script>
```
### React 示例
```tsx
import { useRef, useEffect } from 'react';
import { BimEngine720 } from 'iflow-engine';
export function PanoramaViewer() {
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!containerRef.current) return;
const engine720 = new BimEngine720(containerRef.current, {
fov: 75,
enableZoom: true,
enableRotate: true,
sphereRadius: 500
});
engine720.loadPanorama('https://example.com/panorama.png');
return () => engine720.destroy();
}, []);
return <div ref={containerRef} style={{ width: '100%', height: '100vh' }} />;
}
```
---
## 多引擎切换
三个引擎类可以共用同一个容器,但同一时刻只能有一个实例。切换时需先销毁当前实例:
```typescript
import { BimEngine, BimEngine2d, BimEngine720 } from 'iflow-engine';
let current: BimEngine | BimEngine2d | BimEngine720 | null = null;
function destroyCurrent() {
current?.destroy();
current = null;
}
// 切换到 3D
function switchTo3D() {
destroyCurrent();
const engine = new BimEngine('container', { theme: 'dark' });
engine.engine?.initialize({ backgroundColor: 0x333333 });
engine.engine?.loadModel(['https://example.com/model/']);
current = engine;
}
// 切换到 2D
function switchTo2D() {
destroyCurrent();
const engine2d = new BimEngine2d('container', { theme: 'dark' });
engine2d.loadDrawing('https://example.com/drawing-id');
current = engine2d;
}
// 切换到 720°
function switchTo720() {
destroyCurrent();
const engine720 = new BimEngine720('container', {
fov: 75,
enableZoom: true,
enableRotate: true,
sphereRadius: 500
});
engine720.loadPanorama('https://example.com/panorama.png');
current = engine720;
}
```
---
## 原生 HTML 完整示例
```html
<!DOCTYPE html>
<html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>iflow-engine Demo</title>
<style>
#bim-container {
width: 100%;
height: 100vh;
}
body { margin: 0; font-family: sans-serif; }
#container { width: 100vw; height: 100vh; }
.btn-group { position: fixed; top: 10px; left: 10px; z-index: 100; }
.btn-group button { padding: 8px 16px; margin-right: 8px; cursor: pointer; }
</style>
</head>
<body>
<div id="bim-container"></div>
<div class="btn-group">
<button onclick="switchTo3D()">3D 引擎</button>
<button onclick="switchTo2D()">2D 图纸</button>
<button onclick="switchTo720()">720° 全景</button>
</div>
<div id="container"></div>
<script type="module">
import { BimEngine } from './lib/iflow-engine.es.js';
<script src="./lib/iflow-engine.umd.js"></script>
<script>
var BimEngine = IflowEngine.BimEngine;
var BimEngine2d = IflowEngine.BimEngine2d;
var BimEngine720 = IflowEngine.BimEngine720;
const engine = new BimEngine('bim-container', {
locale: 'zh-CN',
theme: 'light'
});
var current = null;
// 初始化 3D 引擎
engine.engine?.initialize();
function destroyCurrent() {
if (current) { current.destroy(); current = null; }
}
// 加载模型
engine.engine?.loadModel('./models/building.gltf');
function switchTo3D() {
destroyCurrent();
var engine = new BimEngine('container', { locale: 'zh-CN', theme: 'dark' });
engine.engine.initialize({ backgroundColor: 0x333333, showViewCube: true });
engine.engine.loadModel(['https://example.com/model/']);
current = engine;
}
function switchTo2D() {
destroyCurrent();
var engine2d = new BimEngine2d('container', { theme: 'dark', gridEnabled: true });
engine2d.loadDrawing('https://example.com/drawing-id');
current = engine2d;
}
function switchTo720() {
destroyCurrent();
var engine720 = new BimEngine720('container', {
fov: 75, enableZoom: true, enableRotate: true, sphereRadius: 500
});
engine720.loadPanorama('https://example.com/panorama.png');
current = engine720;
}
// 默认加载 3D
switchTo3D();
</script>
</body>
</html>
```
## 主题切换
```typescript
// 切换到暗色主题
engine.setTheme('dark');
// 切换到亮色主题
engine.setTheme('light');
// 自定义主题
engine.setCustomTheme({
name: 'custom',
primary: '#ff6b6b',
primaryHover: '#ff5252',
primaryActive: '#ff4757',
// ... 其他属性
});
```
## 语言切换
```typescript
// 切换到英文
engine.setLocale('en-US');
// 切换到中文
engine.setLocale('zh-CN');
```
## 工具栏操作
```typescript
// 显示/隐藏工具栏
engine.toolbar?.show();
engine.toolbar?.hide();
// 添加自定义按钮
engine.toolbar?.addButton({
id: 'custom-btn',
type: 'button',
label: '自定义',
icon: '<svg>...</svg>',
onClick: () => {
console.log('Custom button clicked');
}
});
// 设置按钮激活状态
engine.toolbar?.setBtnActive('measure', true);
```
## 测量功能
```typescript
// 显示测量面板
engine.measure?.show();
// 切换测量模式
engine.measure?.switchMode('distance'); // 距离
engine.measure?.switchMode('angle'); // 角度
engine.measure?.switchMode('volume'); // 体积
engine.measure?.switchMode('elevation'); // 标高
engine.measure?.switchMode('slope'); // 坡度
// 获取测量配置
const config = engine.measure?.getConfig();
console.log(config); // { unit: 'mm', precision: 2 }
// 设置测量配置
engine.measure?.setConfig({ unit: 'cm', precision: 1 });
// 清除所有测量结果
engine.measure?.clearAll();
```
## 剖切功能
### 平面剖切
```typescript
// 显示剖切面板
engine.sectionPlane?.show();
// 隐藏剖切面板
engine.sectionPlane?.hide();
```
### 轴向剖切
```typescript
// 显示轴向剖切
engine.sectionAxis?.show();
// 设置剖切轴向
engine.sectionAxis?.setActiveAxis('x'); // X 轴
engine.sectionAxis?.setActiveAxis('y'); // Y 轴
engine.sectionAxis?.setActiveAxis('z'); // Z 轴
```
### 剖切盒
```typescript
// 显示剖切盒
engine.sectionBox?.show();
// 获取剖切范围
const range = engine.sectionBox?.getRange();
console.log(range); // { minX, maxX, minY, maxY, minZ, maxZ }
// 设置剖切范围
engine.sectionBox?.setRange({ minX: 0, maxX: 100 });
// 反向剖切
engine.sectionBox?.setReversedState(true);
```
## 漫游功能
```typescript
// 显示漫游控制面板
engine.walkControl?.show();
// 隐藏漫游控制面板
engine.walkControl?.hide();
```
## 事件监听
```typescript
// 监听模型加载完成
const unsubscribe = engine.on('engine:model-loaded', (payload) => {
console.log('Model loaded:', payload.url);
});
// 监听对象点击
engine.on('engine:object-clicked', (payload) => {
console.log('Object clicked:', payload.objectId, payload.position);
});
// 监听主题变更
engine.on('sys:theme-changed', (payload) => {
console.log('Theme changed:', payload.theme);
});
// 取消监听
unsubscribe();
```
## 销毁引擎
```typescript
// 在组件卸载或页面关闭时销毁
engine.destroy();
```
---
## TypeScript 类型
SDK 提供完整的 TypeScript 类型支持
SDK 导出所有相关类型,开箱即用
```typescript
import type {
// 主入口
BimEngine,
// 配置类型
// 3D 引擎
EngineOptions,
ModelLoadOptions,
// 2D 引擎
Engine2dOptions,
DrawingLoadOptions,
Drawing2dLayer,
// 720 引擎
Engine720Options,
PanoramaLoadOptions,
PanoramaAnnotation,
// 通用
ThemeConfig,
ThemeType,
EngineEvents,
// UI 组件(仅 BimEngine 3D 使用)
DialogOptions,
DialogPosition,
ButtonConfig,
ButtonGroupOptions,
TreeOptions,
TreeNodeConfig,
TreeNodeCheckState,
CollapseOptions,
CollapseItemConfig,
DescriptionOptions,
DescriptionItem,
// 主题类型
ThemeConfig,
ThemeType,
// 事件类型
EngineEvents,
} from 'iflow-engine';
```
## 常见问题
---
### 1. 模型加载失败
## 主题与语言
- 检查模型 URL 是否正确
- 确保模型格式受支持GLTF/GLB/IFC
- 检查 CORS 配置
三个引擎类均支持主题切换和语言设置:
### 2. 工具栏不显示
```typescript
// 构造时设置
const engine = new BimEngine('container', { theme: 'dark', locale: 'zh-CN' });
const engine2d = new BimEngine2d('container', { theme: 'light' });
const engine720 = new BimEngine720('container', { theme: 'dark' });
- 确保容器有足够的尺寸
- 检查是否调用了 `engine.initialize()`
// 运行时切换主题
engine.setTheme('light');
engine2d.setTheme('dark');
engine720.setTheme('light');
### 3. 主题不生效
// 运行时切换语言(仅 3D 引擎支持)
engine.setLocale('en-US');
- 确保在引擎初始化后再设置主题
- 检查自定义主题配置是否完整
### 4. 内存泄漏
- 确保在组件卸载时调用 `engine.destroy()`
- 取消不再需要的事件监听
## 下一步
- 查看 [架构文档](架构设计.md) 了解设计原理
- 查看 [模块文档](MODULES/模块索引.md) 了解详细 API
- 查看 [demo](../demo/) 获取更多示例
// 自定义主题(仅 3D 引擎支持)
engine.setCustomTheme({
name: 'custom',
primary: '#1890ff',
primaryHover: '#40a9ff',
// ...
});
```
---
**文档生成时间**: 2026-01-23
**文档版本**: 1.0.0
## 常见问题
### 1. 如何只引入需要的引擎?
直接按需 import构建工具会自动 tree-shaking 未使用的代码:
```typescript
// 只用 2D不会打包 3D 和 720 的代码
import { BimEngine2d } from 'iflow-engine';
```
### 2. 多个引擎能同时存在吗?
可以,但需要**不同的容器**。同一个容器同时只能挂载一个引擎实例:
```typescript
const engine3d = new BimEngine('container-3d', { theme: 'dark' });
const engine2d = new BimEngine2d('container-2d', { theme: 'dark' });
const engine720 = new BimEngine720('container-720', { theme: 'dark' });
```
### 3. 切换引擎时页面闪烁
确保先调用 `destroy()` 再创建新实例。`destroy()` 会清空容器内容。
### 4. 模型/图纸加载失败
- 检查 URL 是否可访问
- 检查浏览器控制台是否有 CORS 错误
- 确保使用 HTTP 服务器运行(不能直接打开 HTML 文件)
### 5. 页面关闭时如何释放资源?
务必调用 `destroy()` 释放 WebGL 上下文和事件监听:
```typescript
window.addEventListener('beforeunload', () => {
engine?.destroy();
});
```
---
## 下一步
- [架构设计](架构设计.md) — 了解 Manager 模式和分层架构
- [引擎 API 对接](引擎API对接.md) — 底层引擎 API 详细说明
- [API 调用链](API调用链.md) — 调用关系与数据流
- [demo/](../demo/) — 完整可运行示例
---
**文档版本**: 2.0.0 | **更新日期**: 2026-03-10

View File

@@ -7,74 +7,83 @@
| 项目 | 内容 |
|------|------|
| **名称** | iflow-engine |
| **版本** | 1.0.1 |
| **描述** | BIM 3D 引擎 SDK支持 Vue2、Vue3、React 和原生 HTML |
| **版本** | 2.2.1 |
| **描述** | BIM 引擎 SDK支持 3D 模型、2D 图纸、720° 全景,适用于 Vue2、Vue3、React 和原生 HTML |
| **类型** | 技术项目 / SDK |
| **语言** | TypeScript |
| **构建工具** | Vite |
| **核心依赖** | Three.js, iflow-engine-base |
## 快速导航
| 文档 | 说明 | 适合人群 |
|------|------|---------|
| [架构设计.md](架构设计.md) | 架构设计、设计模式、模块依赖 | 架构师、核心开发 |
| [快速开始.md](快速开始.md) | 快速开始、安装、基础用法 | 新用户、集成开发 |
| [快速开始.md](快速开始.md) | 快速开始、安装、基础用法(含 3D/2D/720 | 新用户、集成开发 |
| [MODULES/模块索引.md](MODULES/模块索引.md) | 所有模块的详细文档索引 | 所有开发者 |
| [ENGINNE_3D/API文档.md](ENGINNE_3D/API文档.md) | 3D 引擎底层 API 参考 | 3D 引擎开发者 |
| [ENGINE_2D/API文档.md](ENGINE_2D/API文档.md) | 2D 图纸引擎 API 参考 | 2D 引擎开发者 |
| [ENGINE_720/API文档.md](ENGINE_720/API文档.md) | 720° 全景引擎 API 参考 | 720 引擎开发者 |
| [贡献指南.md](贡献指南.md) | 开发协作、脚本说明、最小验证集 | 项目贡献者 |
| [运维手册.md](运维手册.md) | 构建/发布/回滚流程与常见问题 | 维护者 |
| [API调用链.md](API调用链.md) | API 调用链(用户交互 → Manager → Engine → 底层引擎) | SDK 维护者 |
## 模块概览
```
src/
├── bim-engine.ts # 主入口类 BimEngine
├── bim-engine.ts # 3D 引擎主入口类 BimEngine
├── bim-engine-2d.ts # 2D 图纸引擎 BimEngine2d独立轻量
├── bim-engine-720.ts # 720° 全景引擎 BimEngine720独立轻量
├── index.ts # 导出文件
├── core/ # 核心基础设施
├── managers/ # 管理器层(业务逻辑
├── components/ # UI 组件
├── managers/ # 管理器层(仅 3D 使用
├── components/ # 组件层engine / engine-2d / engine-720 + UI 组件
├── services/ # 全局服务(主题、国际化)
├── themes/ # 主题预设和类型
├── locales/ # 国际化文案
├── types/ # 公共类型定义
└── utils/ # 工具函数
```
## 引擎类概览
| 引擎类 | 路径 | 定位 | 文档 |
|--------|------|------|------|
| **BimEngine** | `src/bim-engine.ts` | 3D 完整引擎(含工具栏、测量、剖切等 UI | [快速开始](快速开始.md#一3d-引擎bimengine) |
| **BimEngine2d** | `src/bim-engine-2d.ts` | 2D 图纸引擎(轻量独立) | [快速开始](快速开始.md#二2d-图纸引擎bimengine2d) / [API](ENGINE_2D/API文档.md) |
| **BimEngine720** | `src/bim-engine-720.ts` | 720° 全景引擎(轻量独立) | [快速开始](快速开始.md#三720-全景引擎bimengine720) / [API](ENGINE_720/API文档.md) |
## 核心模块
| 模块 | 路径 | 职责 | 文档 |
|------|------|------|------|
| **BimEngine** | `src/bim-engine.ts` | 主入口类,整合所有功能 | 暂无独立文档 |
| **core** | `src/core/` | 事件系统、管理器基类、注册表 | [详情](MODULES/核心模块.md) |
| **managers** | `src/managers/` | 15 个管理器,处理业务逻辑 | [详情](MODULES/管理器模块.md) |
| **components** | `src/components/` | 20+ UI 组件 | [详情](MODULES/组件模块.md) |
| **managers** | `src/managers/` | 15 个管理器,处理业务逻辑(仅 3D | [详情](MODULES/管理器模块.md) |
| **components** | `src/components/` | 引擎组件 + 20+ UI 组件 | [详情](MODULES/组件模块.md) |
| **services** | `src/services/` | 主题管理、国际化服务 | [详情](MODULES/服务模块.md) |
## 架构分层
```
┌─────────────────────────────────────┐
BimEngine (主引擎) │ ← 用户 API 入口
─────────────────────────────────────
│ ManagerRegistry (单例注册表) │ ← 全局状态管理
├─────────────────────────────────────┤
管理器层 (15个Manager类) │ ← 业务逻辑
─────────────────────────────────────
│ 组件层 (20+ UI组件) │ ← 视图渲染
├─────────────────────────────────────┤
│ 服务层 (LocaleManager, ThemeManager) │ ← 横切关注点
├─────────────────────────────────────┤
│ 核心基础设施 │ ← 事件、基类
(EventEmitter, BaseManager)
└─────────────────────────────────────┘
─────────────────────────────────────────────────────────────┐
│ BimEngine (3D) BimEngine2d (2D) BimEngine720 (720)│ ← 用户 API 入口
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
ManagerRegistry│ │ManagerRegistry│ │ManagerRegistry│ │
│ │ + 15 Managers │ │ (仅事件总线) │ │ (仅事件总线) │ │
│ + 20+ UI 组件 │ │ + Engine2d │ │ + Engine720 │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ 服务层: LocaleManager (全局), ThemeManager (全局) │
├─────────────────────────────────────────────────────────────┤
│ 核心: EventEmitter, BaseManager │
├─────────────────────────────────────────────────────────────┤
底层: iflow-engine-base (createEngine/createEngine2d/720)
└─────────────────────────────────────────────────────────────
```
## 关键设计模式
| 模式 | 应用位置 | 说明 |
|------|---------|------|
| **例模式** | ManagerRegistry, LocaleManager, ThemeManager | 全局唯一实例 |
| **例模式** | ManagerRegistry | 每个引擎实例独立注册表 |
| **单例模式** | LocaleManager, ThemeManager | 全局共享服务 |
| **观察者模式** | EventEmitter, 服务订阅 | 事件驱动通信 |
| **模板方法** | BaseManager, BaseDialogManager | 统一生命周期 |
| **工厂模式** | BimEngine 初始化 | 创建管理器实例 |
@@ -83,18 +92,18 @@ src/
| 类别 | 数量 |
|------|------|
| 管理器类 | 15 个 |
| UI 组件 | 20+ 个 |
| 引擎类 | 3 个BimEngine / BimEngine2d / BimEngine720 |
| 管理器类 | 15 个(仅 3D |
| UI 组件 | 20+ 个(仅 3D |
| 事件类型 | 30+ 个 |
| 主题属性 | 70+ 个 |
| 国际化键 | 200+ 个 |
## 技术栈
- **语言**: TypeScript 5.x
- **构建**: Vite 7.x
- **3D 引擎**: Three.js 0.182.x
- **核心 SDK**: iflow-engine-base 1.0.x
- **核心 SDK**: iflow-engine-base 3.1.x
## 相关链接
@@ -103,5 +112,5 @@ src/
---
**文档生成时间**: 2026-01-23
**文档版本**: 1.0.0
**文档生成时间**: 2026-03-10
**文档版本**: 2.0.0

View File

@@ -2,32 +2,45 @@
## 概述
iflow-engine 采用分层架构:`BimEngine` 作为入口,`ManagerRegistry` 作为实例级注册表Manager 层负责编排Component 层负责 UI/引擎封装Services/Core 层提供基础能力。
iflow-engine 提供三个独立引擎类,均可单独导入:
| 类 | 定位 | 内部结构 |
|---|---|---|
| `BimEngine` | 3D 完整引擎 | ManagerRegistry + 15 Managers + 20+ UI 组件 |
| `BimEngine2d` | 2D 图纸引擎 | ManagerRegistry(仅事件) + Engine2d 组件 |
| `BimEngine720` | 720° 全景引擎 | ManagerRegistry(仅事件) + Engine720 组件 |
三个类互不依赖,各自创建独立的 `ManagerRegistry` 实例。`BimEngine2d``BimEngine720` 跳过 Manager 层,直接使用底层组件。
2026-03 的核心调整:
- 新增 `BimEngine2d``BimEngine720` 独立类,按需导入
- `BimEngine` 变为纯 3D 引擎,不再包含 2D/720 代码
- `EngineManager` 从“大量透传”收敛为“少量公共 API + 生命周期编排”
- 内部 Manager 不再通过 `registry.engine3d?.xxx()` 间接透传,而是通过 `BaseManager.engineComponent` 直接访问 `Engine` 组件
- 非 Manager 组件(如 toolbar 按钮、walk-path-panel通过 `registry.engine3d?.getEngineComponent()?.xxx()` 访问 `Engine`
- 内部 Manager 统一通过 `BaseManager.engineComponent` 直接访问 `Engine` 组件
- `Engine.getEngine()` 已移除,改为 `onRawEvent()/offRawEvent()` 订阅底层事件
---
## 分层结构
```text
BimEngine (入口 / Facade)
└─ ManagerRegistry (实例级,不是全局单例)
├─ Managers (业务编排)
│ ├─ EngineManager (公共 API + 初始化 + 右键菜单组装)
├─ *DialogManager / *ControlManager
└─ ...
├─ Components (UI + Engine 封装)
│ ├─ Engine (封装 iflow-engine-base)
├─ Dialog/Toolbar/Panel...
│ └─ ...
├─ Services (Theme / Locale)
└─ Core (EventEmitter / BaseManager / BaseDialogManager)
┌───────────────────────────────────────────────────────────────────┐
│ 用户 API 入口 │
│ BimEngine (3D) BimEngine2d (2D) BimEngine720 (720°) │
└────┬──────────────────┬────────────────────┬────────────────────┘
▼ ▼
ManagerRegistry ManagerRegistry ManagerRegistry
+ 15 Managers (仅事件总线) (仅事件总线)
+ 20+ UI 组件 + Engine2d 组件 + Engine720 组件
│ │ │
└──────┬───────────┴────────┬───────────┘
│ │
▼ ▼
Services (Theme/Locale) Core (EventEmitter/BaseManager)
iflow-engine-base (createEngine / createEngine2d / createEngine720)
```
---
@@ -151,3 +164,54 @@ SectionBoxDialogManager (BaseDialogManager)
## 后续建议
当前已完成“Manager 瘦身”阶段。若后续继续演进,可考虑将 `Engine` 组件按领域继续拆分measure/section/walk/model/scene进一步降低单类复杂度。
---
## 2D / 720 独立引擎设计
### 设计决策
不采用“一个 BimEngine 懒加载三种引擎”的方案,而是创建三个独立类。理由:
1. **按需导入**:只用 2D 的用户不需要打包 3D 的 UI 组件
2. **职责清晰**3D 需要完整的 Manager 层2D/720 只需轻量封装
3. **生命周期独立**:各自 destroy() 不影响其他实例
4. **共享服务**`themeManager``localeManager` 依然是全局单例,主题切换对所有实例生效
### BimEngine2d 结构
```text
BimEngine2d
├─ ManagerRegistry (仅事件总线)
└─ Engine2d 组件
└─ createEngine2d() (iflow-engine-base)
```
- 构造时自动初始化,无需 `initialize()` 调用
- 直接暴露 `loadDrawing()``getLayers()``setLayerVisible()` 等 API
- 不创建任何 UI 组件toolbar、dialog 等)
### BimEngine720 结构
```text
BimEngine720
├─ ManagerRegistry (仅事件总线)
└─ Engine720 组件
└─ createEngine720() (iflow-engine-base)
```
- 构造时自动初始化,配置 fov、zoom、rotate 等参数
- 直接暴露 `loadPanorama()``setFov()``lookAt()` 等 API
- 不创建任何 UI 组件
### 多引擎切换模式
```text
同一容器同时只能有一个引擎实例。切换时:
1. current.destroy() // 销毁当前
2. new BimEngineXxx() // 创建新实例
```
---
**文档更新时间**: 2026-03-10

12
package-lock.json generated
View File

@@ -1,15 +1,15 @@
{
"name": "iflow-engine",
"version": "1.3.2",
"version": "2.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "iflow-engine",
"version": "1.3.2",
"version": "2.1.0",
"license": "MIT",
"dependencies": {
"iflow-engine-base": "^2.0.6",
"iflow-engine-base": "^3.1.3",
"three": "^0.182.0"
},
"devDependencies": {
@@ -1787,9 +1787,9 @@
}
},
"node_modules/iflow-engine-base": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/iflow-engine-base/-/iflow-engine-base-2.0.6.tgz",
"integrity": "sha512-ogC1miovUIAfC1XwlATl/y1UmZfs5RneUUxKRwWwLQ1Juz6VgWMHts8dyhmu3FRkAuUAhnknn1Gr/2MdLwxRvA==",
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/iflow-engine-base/-/iflow-engine-base-3.1.3.tgz",
"integrity": "sha512-EqAoFvWoHpmoaL+xEzkaZBcBOTg0KlOu2IoIGlY6hWfPo3wKFZ9Jv/2fMDP6aVZGgmdQn8wrSjxyb1DX0tfXRQ==",
"license": "ISC",
"dependencies": {
"@types/three": "^0.181.0",

View File

@@ -1,6 +1,6 @@
{
"name": "iflow-engine",
"version": "1.3.3",
"version": "2.2.1",
"description": "iFlow Engine SDK for Vue2, Vue3, React and HTML",
"main": "./dist/iflow-engine.umd.js",
"module": "./dist/iflow-engine.es.js",
@@ -59,7 +59,7 @@
"vite-plugin-dts": "^4.5.4"
},
"dependencies": {
"iflow-engine-base": "^2.0.6",
"iflow-engine-base": "^3.1.3",
"three": "^0.182.0"
}
}

13934
session-ses_3dec.md Normal file

File diff suppressed because it is too large Load Diff

151
src/bim-engine-2d.ts Normal file
View File

@@ -0,0 +1,151 @@
/**
* BimEngine2d - 独立的 2D 图纸引擎
* 轻量级独立类,不依赖 3D 引擎的 UI 管理器toolbar、measure 等)
* 用法import { BimEngine2d } from 'iflow-engine'
*/
import { Engine2d } from './components/engine-2d';
import type { DrawingLoadOptions, Drawing2dLayer } from './components/engine-2d/types';
import { ManagerRegistry } from './core/manager-registry';
import { localeManager } from './services/locale';
import { themeManager } from './services/theme';
import type { LocaleType } from './locales/types';
import type { ThemeType } from './themes/types';
import type { EngineEvents } from './types/events';
/**
* BimEngine2d 构造选项
* 合并引擎配置与主题/语言设置
*/
export interface BimEngine2dOptions {
/** 语言 */
locale?: LocaleType;
/** 主题 */
theme?: ThemeType;
/** 背景颜色(十六进制数值,如 0xffffff */
backgroundColor?: number;
/** 是否启用网格 */
gridEnabled?: boolean;
/** 是否启用坐标轴 */
axesEnabled?: boolean;
/** 选中构件颜色 */
selectionColor?: number;
/** 高亮构件颜色 */
highlightColor?: number;
/** 是否启用性能监控 */
enablePerformanceMonitoring?: boolean;
}
/**
* 独立 2D 图纸引擎
*
* 内部复用 Engine2d 组件,跳过 Manager 层
* 构造时自动初始化,可直接调用 loadDrawing() 加载图纸
*/
export class BimEngine2d {
public container: HTMLElement;
private registry: ManagerRegistry;
private engineComponent: Engine2d | null = null;
constructor(container: HTMLElement | string, options?: BimEngine2dOptions) {
const el = typeof container === 'string' ? document.getElementById(container) : container;
if (!el) throw new Error('Container not found');
this.container = el;
this.registry = new ManagerRegistry();
if (options?.locale) localeManager.setLocale(options.locale);
if (options?.theme && options.theme !== 'custom') {
themeManager.setTheme(options.theme as 'dark' | 'light');
}
// 创建并初始化 2D 引擎
this.engineComponent = new Engine2d({
container: this.container,
backgroundColor: options?.backgroundColor,
gridEnabled: options?.gridEnabled,
axesEnabled: options?.axesEnabled,
selectionColor: options?.selectionColor,
highlightColor: options?.highlightColor,
enablePerformanceMonitoring: options?.enablePerformanceMonitoring,
}, this.registry);
this.engineComponent.init();
}
// ─── 图纸操作 ───
/** 加载 2D 图纸 */
public async loadDrawing(url: string, options?: DrawingLoadOptions): Promise<void> {
return this.engineComponent?.loadDrawing(url, options);
}
/** 获取所有图层 */
public getLayers(): Drawing2dLayer[] {
return this.engineComponent?.getLayers() ?? [];
}
/** 设置图层可见性 */
public setLayerVisible(name: string, visible: boolean): void {
this.engineComponent?.setLayerVisible(name, visible);
}
// ─── 视图控制 ───
/** 重置视图 */
public resetView(): void {
this.engineComponent?.resetView();
}
/** 适应视图(缩放到全部内容) */
public fitToView(): void {
this.engineComponent?.fitToView();
}
/** 设置缩放级别 */
public setZoom(zoom: number): void {
this.engineComponent?.setZoom(zoom);
}
/** 获取当前缩放级别 */
public getZoom(): number {
return this.engineComponent?.getZoom() ?? 1;
}
// ─── 主题 ───
/** 设置主题 */
public setTheme(theme: 'dark' | 'light'): void {
themeManager.setTheme(theme);
}
// ─── 事件 ───
/** 订阅事件 */
public on<K extends keyof EngineEvents>(event: K, listener: (payload: EngineEvents[K]) => void): () => void {
return this.registry.on(event, listener);
}
/** 取消订阅事件 */
public off<K extends keyof EngineEvents>(event: K, listener: (payload: EngineEvents[K]) => void): void {
this.registry.off(event, listener);
}
/** 订阅原始 2D 引擎事件 */
public onRawEvent(event: string, handler: (...args: any[]) => void): void {
this.engineComponent?.onRawEvent(event, handler);
}
/** 取消订阅原始 2D 引擎事件 */
public offRawEvent(event: string, handler: (...args: any[]) => void): void {
this.engineComponent?.offRawEvent(event, handler);
}
// ─── 生命周期 ───
/** 销毁引擎,释放所有资源 */
public destroy(): void {
this.engineComponent?.destroy();
this.engineComponent = null;
this.registry.reset();
}
}

158
src/bim-engine-720.ts Normal file
View File

@@ -0,0 +1,158 @@
/**
* BimEngine720 - 独立的 720° 全景引擎
* 轻量级独立类,不依赖 3D 引擎的 UI 管理器toolbar、measure 等)
* 用法import { BimEngine720 } from 'iflow-engine'
*/
import { Engine720 } from './components/engine-720';
import type { PanoramaLoadOptions } from './components/engine-720/types';
import { ManagerRegistry } from './core/manager-registry';
import { localeManager } from './services/locale';
import { themeManager } from './services/theme';
import type { LocaleType } from './locales/types';
import type { ThemeType } from './themes/types';
import type { EngineEvents } from './types/events';
/**
* BimEngine720 构造选项
* 合并引擎配置与主题/语言设置
*/
export interface BimEngine720Options {
/** 语言 */
locale?: LocaleType;
/** 主题 */
theme?: ThemeType;
/** 视场角(默认 75 */
fov?: number;
/** 是否启用缩放(默认 true */
enableZoom?: boolean;
/** 是否启用旋转(默认 true */
enableRotate?: boolean;
/** 球体半径(默认 500 */
sphereRadius?: number;
/** 旋转速度 */
rotateSpeed?: number;
/** 缩放速度 */
zoomSpeed?: number;
/** 是否启用阻尼 */
enableDamping?: boolean;
/** 阻尼因子 */
dampingFactor?: number;
/** 最小视场角 */
minFov?: number;
/** 最大视场角 */
maxFov?: number;
}
/**
* 独立 720° 全景引擎
*
* 内部复用 Engine720 组件,跳过 Manager 层
* 构造时自动初始化,可直接调用 loadPanorama() 加载全景图
*/
export class BimEngine720 {
public container: HTMLElement;
private registry: ManagerRegistry;
private engineComponent: Engine720 | null = null;
constructor(container: HTMLElement | string, options?: BimEngine720Options) {
const el = typeof container === 'string' ? document.getElementById(container) : container;
if (!el) throw new Error('Container not found');
this.container = el;
this.registry = new ManagerRegistry();
if (options?.locale) localeManager.setLocale(options.locale);
if (options?.theme && options.theme !== 'custom') {
themeManager.setTheme(options.theme as 'dark' | 'light');
}
// 创建并初始化 720 引擎
this.engineComponent = new Engine720({
container: this.container,
fov: options?.fov,
enableZoom: options?.enableZoom,
enableRotate: options?.enableRotate,
sphereRadius: options?.sphereRadius,
rotateSpeed: options?.rotateSpeed,
zoomSpeed: options?.zoomSpeed,
enableDamping: options?.enableDamping,
dampingFactor: options?.dampingFactor,
minFov: options?.minFov,
maxFov: options?.maxFov,
}, this.registry);
this.engineComponent.init();
}
// ─── 全景操作 ───
/** 加载全景图 */
public async loadPanorama(url: string, options?: PanoramaLoadOptions): Promise<void> {
return this.engineComponent?.loadPanorama(url, options);
}
/** 预加载多个全景图 */
public async preloadPanoramas(urls: string[]): Promise<void> {
return this.engineComponent?.preloadPanoramas(urls);
}
// ─── 视角控制 ───
/** 设置视场角 */
public setFov(fov: number): void {
this.engineComponent?.setFov(fov);
}
/** 获取当前视场角 */
public getFov(): number {
return this.engineComponent?.getFov() ?? 75;
}
/** 设置相机朝向 */
public lookAt(phi: number, theta: number, animated?: boolean): void {
this.engineComponent?.lookAt(phi, theta, animated);
}
/** 重置视图 */
public resetView(): void {
this.engineComponent?.resetView();
}
// ─── 主题 ───
/** 设置主题 */
public setTheme(theme: 'dark' | 'light'): void {
themeManager.setTheme(theme);
}
// ─── 事件 ───
/** 订阅事件 */
public on<K extends keyof EngineEvents>(event: K, listener: (payload: EngineEvents[K]) => void): () => void {
return this.registry.on(event, listener);
}
/** 取消订阅事件 */
public off<K extends keyof EngineEvents>(event: K, listener: (payload: EngineEvents[K]) => void): void {
this.registry.off(event, listener);
}
/** 订阅原始 720 引擎事件 */
public onRawEvent(event: string, handler: (...args: any[]) => void): void {
this.engineComponent?.onRawEvent(event, handler);
}
/** 取消订阅原始 720 引擎事件 */
public offRawEvent(event: string, handler: (...args: any[]) => void): void {
this.engineComponent?.offRawEvent(event, handler);
}
// ─── 生命周期 ───
/** 销毁引擎,释放所有资源 */
public destroy(): void {
this.engineComponent?.destroy();
this.engineComponent = null;
this.registry.reset();
}
}

View File

@@ -36,3 +36,16 @@
font-family: system-ui, -apple-system, sans-serif;
letter-spacing: 0.3px;
}
.bim-engine-size {
position: absolute;
bottom: 6px;
left: 56px;
font-size: 11px;
color: rgba(0, 0, 0, 0.35);
pointer-events: none;
user-select: none;
z-index: 1;
font-family: system-ui, -apple-system, sans-serif;
letter-spacing: 0.3px;
}

View File

@@ -29,6 +29,10 @@ export type { EngineOptions, ModelLoadOptions };
export class BimEngine {
public container: HTMLElement;
private wrapper: HTMLElement | null = null;
private sizeEl: HTMLElement | null = null;
private resizeObserver: ResizeObserver | null = null;
private lastSyncedWidth = -1;
private lastSyncedHeight = -1;
private registry: ManagerRegistry;
public toolbar: ToolbarManager | null = null;
@@ -48,6 +52,10 @@ export class BimEngine {
public aiChat: AiChatManager | null = null;
public setting: SettingDialogManager | null = null;
private readonly handleWindowResize = () => {
this.updateClientSizeDisplay();
};
constructor(
container: HTMLElement | string,
options?: {
@@ -108,6 +116,12 @@ export class BimEngine {
versionEl.textContent = `v${__APP_VERSION__}`;
this.wrapper.appendChild(versionEl);
this.sizeEl = document.createElement('div');
this.sizeEl.className = 'bim-engine-size';
this.wrapper.appendChild(this.sizeEl);
this.updateClientSizeDisplay();
this.bindSizeObserver();
this.registry.container = this.container;
this.registry.wrapper = this.wrapper;
@@ -141,6 +155,7 @@ export class BimEngine {
this.registry.walkControl = this.walkControl;
this.registry.engineInfo = this.engineInfo;
this.componentDetail = new ComponentDetailManager(this.registry);
this.registry.componentDetail = this.componentDetail;
this.componentDetail.init();
@@ -165,7 +180,56 @@ export class BimEngine {
}
}
private updateClientSizeDisplay(): void {
const width = this.container.clientWidth;
const height = this.container.clientHeight;
if (this.sizeEl) {
this.sizeEl.textContent = `${width}px x ${height}px`;
}
this.syncEngineSize(width, height);
}
private syncEngineSize(width: number, height: number): void {
if (width <= 0 || height <= 0) {
return;
}
if (width === this.lastSyncedWidth && height === this.lastSyncedHeight) {
return;
}
this.lastSyncedWidth = width;
this.lastSyncedHeight = height;
this.engine?.getEngineComponent()?.resize(width, height);
}
private bindSizeObserver(): void {
if (typeof ResizeObserver !== 'undefined') {
this.resizeObserver = new ResizeObserver(() => {
this.updateClientSizeDisplay();
});
this.resizeObserver.observe(this.container);
return;
}
window.addEventListener('resize', this.handleWindowResize);
}
private unbindSizeObserver(): void {
if (this.resizeObserver) {
this.resizeObserver.disconnect();
this.resizeObserver = null;
return;
}
window.removeEventListener('resize', this.handleWindowResize);
}
public destroy() {
this.unbindSizeObserver();
this.toolbar?.destroy();
this.buttonGroup?.destroy();
this.engine?.destroy();
@@ -179,6 +243,10 @@ export class BimEngine {
this.walkControl?.destroy();
this.aiChat?.destroy();
this.setting?.destroy();
this.sizeEl = null;
this.lastSyncedWidth = -1;
this.lastSyncedHeight = -1;
this.container.innerHTML = '';
this.registry.reset();
}

View File

@@ -0,0 +1,379 @@
/**
* 2D 引擎组件
* 负责创建和管理第三方 2D 图纸引擎实例
* 镜像 3D Engine 组件的设计模式
*/
import type { ThemeConfig } from '../../themes/types';
import { IBimComponent } from '../../types/component';
import { themeManager } from '../../services/theme';
import type { Engine2dOptions, DrawingLoadOptions, Drawing2dLayer } from './types';
import type { ManagerRegistry } from '../../core/manager-registry';
// 导入第三方 SDK 的 createEngine2d 函数(从 npm 包引入)
import { createEngine2d as createEngine2dSDK } from 'iflow-engine-base';
import "../../../../bim_engine_base/dist/iflow-engine-base.css";
export type { Engine2dOptions, DrawingLoadOptions, Drawing2dLayer };
/**
* 创建 Engine2d 实例的工厂函数
* 兼容旧代码直接 import 的方式
*/
export const createEngine2d = (options: Engine2dOptions, registry: ManagerRegistry) => {
return new Engine2d(options, registry);
};
/**
* 2D 图纸引擎组件
* 负责创建和管理第三方 2D 引擎实例
*/
export class Engine2d implements IBimComponent {
/** 第三方 2D 引擎实例 */
private engine: any = null;
/** 管理器注册表实例 */
private registry: ManagerRegistry;
/** 引擎挂载的容器元素 */
private container: HTMLElement;
/** 引擎容器 ID用于传递给 createEngine2d */
private containerId: string;
/** 引擎配置选项(不包含 container */
private options: Omit<Engine2dOptions, 'container'>;
/** 是否已初始化 */
private _isInitialized = false;
/** 是否已销毁 */
private _isDestroyed = false;
/** 主题订阅取消函数 */
private unsubscribeTheme: (() => void) | null = null;
/**
* 构造函数
* @param options 2D 引擎配置选项
* @param registry 管理器注册表
*/
constructor(options: Engine2dOptions, registry: ManagerRegistry) {
// 保存注册表
this.registry = registry;
// 解析容器元素
this.container = options.container;
// 如果容器没有 id生成一个唯一的 id
if (!this.container.id) {
this.containerId = `engine2d-container-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
this.container.id = this.containerId;
} else {
this.containerId = this.container.id;
}
// 保存配置选项(设置默认值)
this.options = {
backgroundColor: options.backgroundColor ?? 0x1a1a1a,
gridEnabled: options.gridEnabled ?? true,
axesEnabled: options.axesEnabled ?? true,
selectionColor: options.selectionColor,
highlightColor: options.highlightColor,
enablePerformanceMonitoring: options.enablePerformanceMonitoring ?? true,
};
}
/**
* 初始化组件 (接口实现)
* 创建 div 容器并初始化 2D 引擎
*/
public init(): void {
if (this._isInitialized) {
console.warn('[Engine2d] Engine already initialized.');
return;
}
if (this._isDestroyed) {
console.error('[Engine2d] Cannot initialize destroyed engine.');
return;
}
try {
// 创建引擎配置对象
const engineConfig: any = {
containerId: this.containerId,
backgroundColor: this.options.backgroundColor,
gridEnabled: this.options.gridEnabled,
axesEnabled: this.options.axesEnabled,
};
// 仅在用户提供时传递可选参数
if (this.options.selectionColor !== undefined) {
engineConfig.selectionColor = this.options.selectionColor;
}
if (this.options.highlightColor !== undefined) {
engineConfig.highlightColor = this.options.highlightColor;
}
if (this.options.enablePerformanceMonitoring !== undefined) {
engineConfig.enablePerformanceMonitoring = this.options.enablePerformanceMonitoring;
}
// 输出配置信息
console.log('[Engine2d] 引擎配置信息:', engineConfig);
// 调用引擎创建函数创建 2D 引擎实例
this.engine = createEngine2dSDK(engineConfig);
if (!this.engine) {
throw new Error('Failed to create 2D engine instance');
}
// 标记为已初始化
this._isInitialized = true;
// 订阅主题变化
this.unsubscribeTheme = themeManager.subscribe((theme) => {
this.setTheme(theme);
});
// 应用当前主题
this.setTheme(themeManager.getTheme());
// 监听 2D 图纸实体点击事件
if (this.engine.events) {
this.engine.events.on('entity-click', (data: any) => {
console.log('[Engine2d] 实体点击:', data);
this.registry.emit('engine2d:entity-clicked', { data });
});
this.engine.events.on('layer-visibility-changed', (data: any) => {
console.log('[Engine2d] 图层可见性变更:', data);
this.registry.emit('engine2d:layer-changed', { data });
});
}
} catch (error) {
console.error('[Engine2d] Failed to initialize 2D engine:', error);
this._isInitialized = false;
throw error;
}
}
/**
* 设置主题 (接口实现)
* @param _theme 全局主题配置
*/
public setTheme(_theme: ThemeConfig): void {
// 2D 引擎可根据主题切换深色/浅色模式
// if (!this._isInitialized || !this.engine) return;
// try {
// if (typeof this.engine.setTheme === 'function') {
// 根据主题背景色判断深色/浅色
//const isDark = _theme.bgBase ? this.isColorDark(_theme.bgBase) : false;
//this.engine.setTheme(isDark ? 'dark' : 'light');
// }
// } catch (e) {
// console.warn('[Engine2d] Failed to set theme:', e);
// }
}
/**
* 设置语言 (接口实现)
*/
public setLocales(): void {
// 2D 引擎组件暂时不需要本地化
}
/**
* 检查是否已初始化
*/
public isInitialized(): boolean {
return this._isInitialized;
}
/**
* 加载 2D 图纸
* @param url 图纸文件 URL
* @param options 加载选项
*/
public async loadDrawing(url: string, options?: DrawingLoadOptions): Promise<void> {
if (!this._isInitialized || !this.engine) {
console.error('[Engine2d] Engine not initialized. Please call init() first.');
return;
}
if (!url) {
console.error('[Engine2d] Drawing URL is required.');
return;
}
try {
console.log('[Engine2d] 开始加载图纸:', url);
await this.engine.loadModel(url, options);
console.log('[Engine2d] 图纸加载完成:', url);
this.registry.emit('engine2d:drawing-loaded', { url });
} catch (error) {
console.error('[Engine2d] 图纸加载失败:', error);
throw error;
}
}
/**
* 获取所有图层
* @returns 图层列表
*/
public getLayers(): Drawing2dLayer[] {
if (!this._isInitialized || !this.engine) {
return [];
}
try {
return this.engine.getLayers?.() ?? [];
} catch (e) {
console.warn('[Engine2d] Failed to get layers:', e);
return [];
}
}
/**
* 设置图层可见性
* @param name 图层名称
* @param visible 是否可见
*/
public setLayerVisible(name: string, visible: boolean): void {
if (!this._isInitialized || !this.engine) {
console.warn('[Engine2d] Engine not initialized.');
return;
}
try {
this.engine.setLayerVisible?.(name, visible);
} catch (e) {
console.warn('[Engine2d] Failed to set layer visibility:', e);
}
}
/**
* 重置视图(适应画布)
*/
public resetView(): void {
if (!this._isInitialized || !this.engine) return;
try {
this.engine.resetView?.();
} catch (e) {
console.warn('[Engine2d] Failed to reset view:', e);
}
}
/**
* 适应视图(缩放到全部内容)
*/
public fitToView(): void {
if (!this._isInitialized || !this.engine) return;
try {
this.engine.fitToView?.();
} catch (e) {
console.warn('[Engine2d] Failed to fit to view:', e);
}
}
/**
* 设置缩放级别
* @param zoom 缩放值
*/
public setZoom(zoom: number): void {
if (!this._isInitialized || !this.engine) return;
this.engine.setZoom?.(zoom);
}
/**
* 获取当前缩放级别
* @returns 当前缩放值
*/
public getZoom(): number {
if (!this._isInitialized || !this.engine) return 1;
return this.engine.getZoom?.() ?? 1;
}
/**
* 设置背景色
* @param color 颜色值(十六进制数值)
*/
public setBackgroundColor(color: number): void {
if (!this._isInitialized || !this.engine) return;
this.engine.setBackgroundColor?.(color);
}
/**
* 清除场景
*/
public clearScene(): void {
if (!this._isInitialized || !this.engine) return;
this.engine.clearScene?.();
}
/**
* 调整引擎尺寸
*/
public resize(): void {
if (!this._isInitialized || !this.engine) return;
this.engine.resize?.();
}
/**
* 订阅原始 2D 引擎事件
* @param event 事件名称
* @param handler 事件处理函数
*/
public onRawEvent(event: string, handler: (...args: any[]) => void): void {
this.engine?.events?.on(event, handler);
}
/**
* 取消订阅原始 2D 引擎事件
* @param event 事件名称
* @param handler 事件处理函数
*/
public offRawEvent(event: string, handler: (...args: any[]) => void): void {
this.engine?.events?.off(event, handler);
}
/**
* 销毁组件 (接口实现)
* 清理资源、取消订阅、销毁引擎实例
*/
public destroy(): void {
if (this._isDestroyed) {
return;
}
// 取消主题订阅
if (this.unsubscribeTheme) {
this.unsubscribeTheme();
this.unsubscribeTheme = null;
}
// 销毁 2D 引擎,释放资源
if (this.engine) {
try {
this.engine.dispose?.();
} catch (e) {
console.warn('[Engine2d] Error during dispose:', e);
}
this.engine = null;
}
// 清理容器
this.container.innerHTML = '';
// 更新状态
this._isDestroyed = true;
this._isInitialized = false;
}
/**
* 判断颜色字符串是否为深色
* @param color CSS 颜色字符串
* @returns 是否为深色
*/
// private isColorDark(color: string): boolean {
// // 简单判断:如果包含常见深色关键字或数值较低则为深色
// if (color.includes('#')) {
// const hex = color.replace('#', '');
// const r = parseInt(hex.substring(0, 2), 16) || 0;
// const g = parseInt(hex.substring(2, 4), 16) || 0;
// const b = parseInt(hex.substring(4, 6), 16) || 0;
// const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
// return luminance < 0.5;
// }
// return false;
// }
}

View File

@@ -0,0 +1,46 @@
/**
* 2D 引擎类型定义
* 定义 2D CAD/DWG 图纸引擎的配置选项和相关类型
*/
/**
* 2D 引擎配置选项
*/
export interface Engine2dOptions {
/** 引擎挂载的容器元素 */
container: HTMLElement;
/** 背景颜色(十六进制数值,如 0xffffff */
backgroundColor?: number;
/** 是否启用网格 */
gridEnabled?: boolean;
/** 是否启用坐标轴 */
axesEnabled?: boolean;
/** 选中构件颜色 */
selectionColor?: number;
/** 高亮构件颜色 */
highlightColor?: number;
/** 是否启用性能监控 */
enablePerformanceMonitoring?: boolean;
}
/**
* 2D 图纸加载选项
*/
export interface DrawingLoadOptions {
/** 分块大小 */
chunkSize?: number;
/** 是否启用分块加载 */
enableChunkedLoading?: boolean;
/** 是否启用数据校验 */
enableValidation?: boolean;
}
/**
* 2D 图纸图层信息
*/
export interface Drawing2dLayer {
/** 图层名称 */
name: string;
/** 图层是否可见 */
visible: boolean;
}

View File

@@ -0,0 +1,356 @@
/**
* 720 全景引擎组件
* 负责创建和管理第三方 720° 全景引擎实例
* 镜像 Engine2d 组件的设计模式
*/
import type { ThemeConfig } from '../../themes/types';
import { IBimComponent } from '../../types/component';
import { themeManager } from '../../services/theme';
import type { Engine720Options, PanoramaLoadOptions, PanoramaAnnotation } from './types';
import type { ManagerRegistry } from '../../core/manager-registry';
// 导入第三方 SDK 的 createEngine720 函数(从 npm 包引入)
import { createEngine720 as createEngine720SDK } from 'iflow-engine-base';
import "../../../../bim_engine_base/dist/iflow-engine-base.css";
export type { Engine720Options, PanoramaLoadOptions, PanoramaAnnotation };
/**
* 创建 Engine720 实例的工厂函数
* 兼容旧代码直接 import 的方式
*/
export const createEngine720 = (options: Engine720Options, registry: ManagerRegistry) => {
return new Engine720(options, registry);
};
/**
* 720° 全景引擎组件
* 负责创建和管理第三方 720 全景引擎实例
*/
export class Engine720 implements IBimComponent {
/** 第三方 720 引擎实例 */
private engine: any = null;
/** 管理器注册表实例 */
private registry: ManagerRegistry;
/** 引擎挂载的容器元素 */
private container: HTMLElement;
/** 引擎容器 ID用于传递给 createEngine720 */
private containerId: string;
/** 引擎配置选项(不包含 container */
private options: Omit<Engine720Options, 'container'>;
/** 是否已初始化 */
private _isInitialized = false;
/** 是否已销毁 */
private _isDestroyed = false;
/** 主题订阅取消函数 */
private unsubscribeTheme: (() => void) | null = null;
/**
* 构造函数
* @param options 720 引擎配置选项
* @param registry 管理器注册表
*/
constructor(options: Engine720Options, registry: ManagerRegistry) {
// 保存注册表
this.registry = registry;
// 解析容器元素
this.container = options.container;
// 如果容器没有 id生成一个唯一的 id
if (!this.container.id) {
this.containerId = `engine720-container-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
this.container.id = this.containerId;
} else {
this.containerId = this.container.id;
}
// 保存配置选项(设置默认值)
this.options = {
fov: options.fov ?? 75,
enableZoom: options.enableZoom ?? true,
enableRotate: options.enableRotate ?? true,
sphereRadius: options.sphereRadius ?? 500,
rotateSpeed: options.rotateSpeed,
zoomSpeed: options.zoomSpeed,
enableDamping: options.enableDamping,
dampingFactor: options.dampingFactor,
minFov: options.minFov,
maxFov: options.maxFov,
};
}
/**
* 初始化组件 (接口实现)
* 创建 div 容器并初始化 720 引擎
*/
public init(): void {
if (this._isInitialized) {
console.warn('[Engine720] Engine already initialized.');
return;
}
if (this._isDestroyed) {
console.error('[Engine720] Cannot initialize destroyed engine.');
return;
}
try {
// 创建引擎配置对象
const engineConfig: any = {
containerId: this.containerId,
fov: this.options.fov,
enableZoom: this.options.enableZoom,
enableRotate: this.options.enableRotate,
sphereRadius: this.options.sphereRadius,
};
// 仅在用户提供时传递可选参数
if (this.options.rotateSpeed !== undefined) {
engineConfig.rotateSpeed = this.options.rotateSpeed;
}
if (this.options.zoomSpeed !== undefined) {
engineConfig.zoomSpeed = this.options.zoomSpeed;
}
if (this.options.enableDamping !== undefined) {
engineConfig.enableDamping = this.options.enableDamping;
}
if (this.options.dampingFactor !== undefined) {
engineConfig.dampingFactor = this.options.dampingFactor;
}
if (this.options.minFov !== undefined) {
engineConfig.minFov = this.options.minFov;
}
if (this.options.maxFov !== undefined) {
engineConfig.maxFov = this.options.maxFov;
}
// 输出配置信息
console.log('[Engine720] 引擎配置信息:', engineConfig);
// 调用引擎创建函数创建 720 引擎实例
this.engine = createEngine720SDK(engineConfig);
if (!this.engine) {
throw new Error('Failed to create 720 engine instance');
}
// 标记为已初始化
this._isInitialized = true;
// 订阅主题变化
this.unsubscribeTheme = themeManager.subscribe((theme) => {
this.setTheme(theme);
});
// 应用当前主题
this.setTheme(themeManager.getTheme());
// 监听 720 全景事件
if (this.engine.events) {
this.engine.events.on('panorama-loaded', (data: any) => {
console.log('[Engine720] 全景加载完成:', data);
this.registry.emit('engine720:panorama-loaded', { data });
});
this.engine.events.on('panorama-load-error', (data: any) => {
console.error('[Engine720] 全景加载失败:', data);
this.registry.emit('engine720:load-error', { data });
});
this.engine.events.on('annotation-click', (data: any) => {
console.log('[Engine720] 标注点击:', data);
this.registry.emit('engine720:annotation-click', { data });
});
this.engine.events.on('view-angle-changed', (data: any) => {
this.registry.emit('engine720:view-changed', { data });
});
}
} catch (error) {
console.error('[Engine720] Failed to initialize 720 engine:', error);
this._isInitialized = false;
throw error;
}
}
/**
* 设置主题 (接口实现)
* @param _theme 全局主题配置
*/
public setTheme(_theme: ThemeConfig): void {
// 720 引擎暂时不需要主题切换(全景图本身覆盖整个视口)
if (!this._isInitialized || !this.engine) return;
// 预留:如果底层引擎支持 setTheme可在此调用
}
/**
* 设置语言 (接口实现)
*/
public setLocales(): void {
// 720 引擎组件暂时不需要本地化
}
/**
* 检查是否已初始化
*/
public isInitialized(): boolean {
return this._isInitialized;
}
/**
* 加载全景图
* @param url 全景图片 URL
* @param _options 加载选项(预留)
*/
public async loadPanorama(url: string, _options?: PanoramaLoadOptions): Promise<void> {
if (!this._isInitialized || !this.engine) {
console.error('[Engine720] Engine not initialized. Please call init() first.');
return;
}
if (!url) {
console.error('[Engine720] Panorama URL is required.');
return;
}
try {
console.log('[Engine720] 开始加载全景图:', url);
await this.engine.loadPanorama(url);
console.log('[Engine720] 全景图加载完成:', url);
} catch (error) {
console.error('[Engine720] 全景图加载失败:', error);
throw error;
}
}
/**
* 预加载多个全景图
* @param urls 全景图 URL 列表
*/
public async preloadPanoramas(urls: string[]): Promise<void> {
if (!this._isInitialized || !this.engine) return;
try {
await this.engine.preloadPanoramas?.(urls);
} catch (e) {
console.warn('[Engine720] Failed to preload panoramas:', e);
}
}
/**
* 设置视场角
* @param fov 视场角值
*/
public setFov(fov: number): void {
if (!this._isInitialized || !this.engine) return;
this.engine.setFov?.(fov);
}
/**
* 获取当前视场角
* @returns 当前视场角值
*/
public getFov(): number {
if (!this._isInitialized || !this.engine) return 75;
return this.engine.getFov?.() ?? 75;
}
/**
* 设置相机朝向
* @param phi 水平角度
* @param theta 垂直角度
* @param animated 是否动画过渡
*/
public lookAt(phi: number, theta: number, animated?: boolean): void {
if (!this._isInitialized || !this.engine) return;
this.engine.lookAt?.(phi, theta, animated);
}
/**
* 重置视图
*/
public resetView(): void {
if (!this._isInitialized || !this.engine) return;
try {
// 重置到默认视角
this.engine.lookAt?.(0, 0, true);
this.engine.setFov?.(75);
} catch (e) {
console.warn('[Engine720] Failed to reset view:', e);
}
}
/**
* 暂停渲染
*/
public pauseRendering(): void {
if (!this._isInitialized || !this.engine) return;
this.engine.pauseRendering?.();
}
/**
* 恢复渲染
*/
public resumeRendering(): void {
if (!this._isInitialized || !this.engine) return;
this.engine.resumeRendering?.();
}
/**
* 调整引擎尺寸
* @param width 宽度
* @param height 高度
*/
public resize(width?: number, height?: number): void {
if (!this._isInitialized || !this.engine) return;
this.engine.resize?.(width, height);
}
/**
* 订阅原始 720 引擎事件
* @param event 事件名称
* @param handler 事件处理函数
*/
public onRawEvent(event: string, handler: (...args: any[]) => void): void {
this.engine?.events?.on(event, handler);
}
/**
* 取消订阅原始 720 引擎事件
* @param event 事件名称
* @param handler 事件处理函数
*/
public offRawEvent(event: string, handler: (...args: any[]) => void): void {
this.engine?.events?.off(event, handler);
}
/**
* 销毁组件 (接口实现)
* 清理资源、取消订阅、销毁引擎实例
*/
public destroy(): void {
if (this._isDestroyed) {
return;
}
// 取消主题订阅
if (this.unsubscribeTheme) {
this.unsubscribeTheme();
this.unsubscribeTheme = null;
}
// 销毁 720 引擎,释放资源
if (this.engine) {
try {
this.engine.dispose?.();
} catch (e) {
console.warn('[Engine720] Error during dispose:', e);
}
this.engine = null;
}
// 清理容器
this.container.innerHTML = '';
// 更新状态
this._isDestroyed = true;
this._isInitialized = false;
}
}

View File

@@ -0,0 +1,56 @@
/**
* 720 全景引擎类型定义
* 定义 720° 全景查看引擎的配置选项和相关类型
*/
/**
* 720 全景引擎配置选项
*/
export interface Engine720Options {
/** 引擎挂载的容器元素 */
container: HTMLElement;
/** 视场角(默认 75 */
fov?: number;
/** 是否启用缩放(默认 true */
enableZoom?: boolean;
/** 是否启用旋转(默认 true */
enableRotate?: boolean;
/** 球体半径(默认 500 */
sphereRadius?: number;
/** 旋转速度 */
rotateSpeed?: number;
/** 缩放速度 */
zoomSpeed?: number;
/** 是否启用阻尼(惯性) */
enableDamping?: boolean;
/** 阻尼因子 */
dampingFactor?: number;
/** 最小视场角 */
minFov?: number;
/** 最大视场角 */
maxFov?: number;
}
/**
* 全景加载选项
*/
export interface PanoramaLoadOptions {
/** 加载超时时间(毫秒) */
timeout?: number;
}
/**
* 全景标注信息
*/
export interface PanoramaAnnotation {
/** 标注 ID */
id: string;
/** 标注位置(球面坐标 phi */
phi?: number;
/** 标注位置(球面坐标 theta */
theta?: number;
/** 标注文本 */
text?: string;
/** 自定义数据 */
data?: any;
}

View File

@@ -122,6 +122,8 @@ export class Engine implements IBimComponent {
// 标记为已初始化
this._isInitialized = true;
this.resize(this.container.clientWidth, this.container.clientHeight);
// 订阅主题变化
this.unsubscribeTheme = themeManager.subscribe((theme) => {
this.setTheme(theme);
@@ -260,11 +262,21 @@ export class Engine implements IBimComponent {
* 调整渲染器尺寸
* 容器大小变化时调用,自动更新渲染器、相机投影矩阵和后处理合成器
*/
public resize(): void {
public resize(width?: number, height?: number): void {
if (!this._isInitialized || !this.engine) {
return;
}
// this.engine.handleWindowResize();
const nextWidth = typeof width === 'number' ? Math.floor(width) : this.container.clientWidth;
const nextHeight = typeof height === 'number' ? Math.floor(height) : this.container.clientHeight;
if (nextWidth <= 0 || nextHeight <= 0) {
return;
}
if (typeof this.engine.handleWindowResize === 'function') {
this.engine.handleWindowResize(nextWidth, nextHeight);
}
}
// ==================== 测量功能方法 ====================

View File

@@ -21,12 +21,10 @@ export class SectionBoxPanel implements IBimComponent {
private range: SectionBoxRange;
private hideBtn!: HTMLButtonElement;
private reverseBtn!: HTMLButtonElement;
private fitBtn!: HTMLButtonElement;
private resetBtn!: HTMLButtonElement;
private hideLabelEl!: HTMLElement;
private reverseLabelEl!: HTMLElement;
private fitLabelEl!: HTMLElement;
private resetLabelEl!: HTMLElement;
private xLabelEl!: HTMLElement;
@@ -134,19 +132,13 @@ export class SectionBoxPanel implements IBimComponent {
this.options.onHideToggle?.(this.isHidden);
}, 'hide');
this.reverseBtn = this.createButton('reverse', t('sectionBox.actions.reverse'), () => {
this.isReversed = !this.isReversed;
this.updateButtonStates();
this.options.onReverseToggle?.(this.isReversed);
}, 'reverse');
this.fitBtn = this.createButton('fit', t('sectionBox.actions.fitToModel'), () => {
this.options.onFitToModel?.();
}, 'fit');
this.resetBtn = this.createButton('reset', t('sectionBox.actions.reset'), () => this.reset(), 'reset');
[this.hideBtn, this.reverseBtn, this.fitBtn, this.resetBtn].forEach(btn => buttonsContainer.appendChild(btn));
[this.hideBtn, this.fitBtn, this.resetBtn].forEach(btn => buttonsContainer.appendChild(btn));
const slidersContainer = document.createElement('div');
slidersContainer.className = 'section-box-sliders';
@@ -184,7 +176,6 @@ export class SectionBoxPanel implements IBimComponent {
labelEl.textContent = label;
if (ref === 'hide') this.hideLabelEl = labelEl;
else if (ref === 'reverse') this.reverseLabelEl = labelEl;
else if (ref === 'fit') this.fitLabelEl = labelEl;
else if (ref === 'reset') this.resetLabelEl = labelEl;
@@ -325,20 +316,17 @@ export class SectionBoxPanel implements IBimComponent {
private updateButtonStates(): void {
if (this.hideBtn) this.hideBtn.classList.toggle('active', this.isHidden);
if (this.reverseBtn) this.reverseBtn.classList.toggle('active', this.isReversed);
}
public setLocales(): void {
if (!this.hideLabelEl) return;
this.hideLabelEl.textContent = t('sectionBox.actions.hide');
this.reverseLabelEl.textContent = t('sectionBox.actions.reverse');
this.fitLabelEl.textContent = t('sectionBox.actions.fitToModel');
this.resetLabelEl.textContent = t('sectionBox.actions.reset');
this.xLabelEl.textContent = t('sectionBox.axes.x');
this.yLabelEl.textContent = t('sectionBox.axes.y');
this.zLabelEl.textContent = t('sectionBox.axes.z');
this.hideBtn.title = t('sectionBox.actions.hide');
this.reverseBtn.title = t('sectionBox.actions.reverse');
this.fitBtn.title = t('sectionBox.actions.fitToModel');
this.resetBtn.title = t('sectionBox.actions.reset');
}

View File

@@ -1,5 +1,7 @@
// Main Entry
export * from './bim-engine';
export * from './bim-engine-2d';
export * from './bim-engine-720';
// Types - Core
export * from './types/component';
@@ -8,6 +10,8 @@ export type { ThemeConfig, ThemeType } from './themes/types';
// Types - Components
export type { EngineOptions, ModelLoadOptions } from './components/engine/types';
export type { Engine2dOptions, DrawingLoadOptions, Drawing2dLayer } from './components/engine-2d/types';
export type { Engine720Options, PanoramaLoadOptions, PanoramaAnnotation } from './components/engine-720/types';
export type { DialogOptions, DialogPosition } from './components/dialog/index.type';
export type { ButtonConfig, ButtonGroupOptions } from './components/button-group/index.type';
export type { TreeOptions, TreeNodeConfig, TreeNodeCheckState, NodeClickAction } from './components/tree/types';

View File

@@ -1,7 +1,6 @@
import { BaseManager } from '../core/base-manager';
import { ManagerRegistry } from '../core/manager-registry';
import { BimCollapse } from '../components/collapse/index';
import { BimTab } from '../components/tab';
import { t } from '../services/locale';
export class ComponentDetailManager extends BaseManager {
@@ -10,7 +9,6 @@ export class ComponentDetailManager extends BaseManager {
private currentSelection: { url: string; id: string } | null = null;
private unsubscribeSelected: (() => void) | null = null;
private unsubscribeDeselected: (() => void) | null = null;
private tabInstance: BimTab | null = null;
private propertiesData: any = null;
constructor(registry: ManagerRegistry) {
@@ -98,34 +96,11 @@ export class ComponentDetailManager extends BaseManager {
private renderTabbedContent(): void {
if (!this.dialog) return;
if (this.tabInstance) {
this.tabInstance.destroy();
this.tabInstance = null;
}
const container = document.createElement('div');
container.style.cssText = 'height:100%;display:flex;flex-direction:column;';
const propertiesPanel = this.createPropertiesPanel();
const materialsPanel = this.createMaterialsPanel();
this.tabInstance = new BimTab({
container,
activeId: 'properties',
tabs: [
{
id: 'properties',
title: 'panel.property.tab.props',
content: propertiesPanel
},
{
id: 'materials',
title: 'panel.property.tab.material',
content: materialsPanel
}
]
});
this.tabInstance.init();
container.appendChild(propertiesPanel);
this.dialog.setContent(container);
}
@@ -135,13 +110,14 @@ export class ComponentDetailManager extends BaseManager {
container.style.cssText = 'height:100%;overflow-y:auto;';
const properties = this.propertiesData?.properties || [];
const reversedProperties = [...properties].reverse();
if (properties.length === 0) {
if (reversedProperties.length === 0) {
container.innerHTML = '<div style="padding:20px;text-align:center;color:var(--bim-text-secondary,#999);">无属性数据</div>';
return container;
}
const collapseItems = properties.map((category: any, index: number) => ({
const collapseItems = reversedProperties.map((category: any, index: number) => ({
id: `category-${index}`,
title: category.name || `分类 ${index + 1}`,
content: this.createCategoryContent(category.children || [])
@@ -169,45 +145,6 @@ export class ComponentDetailManager extends BaseManager {
return container;
}
private createMaterialsPanel(): HTMLElement {
const container = document.createElement('div');
container.style.cssText = 'height:100%;overflow-y:auto;';
const materials = this.propertiesData?.materials || [];
if (materials.length === 0) {
container.innerHTML = '<div style="padding:20px;text-align:center;color:var(--bim-text-secondary,#999);">无材质数据</div>';
return container;
}
const collapseItems = materials.map((material: any, index: number) => ({
id: `material-${index}`,
title: material.name || `材质 ${index + 1}`,
content: this.createCategoryContent(material.children || material.properties || [])
}));
new BimCollapse({
container,
accordion: false,
ghost: true,
activeIds: collapseItems.length > 0 ? [collapseItems[0].id] : [],
items: collapseItems
});
const style = document.createElement('style');
style.textContent = `
#${this.dialogId} .bim-collapse-header {
background-color: var(--bim-component-bg-hover) !important;
}
#${this.dialogId} .bim-collapse-header:hover {
background-color: var(--bim-component-bg-active) !important;
}
`;
container.appendChild(style);
return container;
}
private createCategoryContent(items: any[]): HTMLElement {
const container = document.createElement('div');
@@ -256,10 +193,6 @@ export class ComponentDetailManager extends BaseManager {
}
public hide(): void {
if (this.tabInstance) {
this.tabInstance.destroy();
this.tabInstance = null;
}
if (this.dialog) {
this.dialog.destroy();
this.dialog = null;

View File

@@ -0,0 +1,133 @@
/**
* 2D 图纸引擎管理器
* 负责管理 2D 图纸渲染引擎的初始化、图纸加载和生命周期
*
* 设计原则:
* - 镜像 EngineManager 的设计模式
* - Engine2dManager 只暴露面向外部用户的公共 API
* - 内部管理器通过 getEngine2dComponent() 直接访问 Engine2d 组件
*/
import { Engine2d, type Engine2dOptions, type DrawingLoadOptions, type Drawing2dLayer } from '../components/engine-2d';
import { BaseManager } from '../core/base-manager';
import { ManagerRegistry } from '../core/manager-registry';
/**
* 2D 图纸引擎管理器
* 封装底层 2D 引擎,提供图纸加载、图层控制等公共 API
*/
export class Engine2dManager extends BaseManager {
/** 容器元素 */
private container: HTMLElement;
/** 2D 引擎组件实例 */
private engineInstance: Engine2d | null = null;
constructor(container: HTMLElement, registry: ManagerRegistry) {
super(registry);
this.container = container;
}
/**
* 获取 Engine2d 组件实例
* 内部管理器通过此方法直接访问 Engine2d 组件的全部能力
* @returns Engine2d 组件实例,未初始化时返回 null
*/
public getEngine2dComponent(): Engine2d | null {
return this.engineInstance;
}
/**
* 初始化 2D 引擎
* @param options 引擎配置选项
* @returns 是否初始化成功
*/
public initialize(options?: Omit<Engine2dOptions, 'container'>): boolean {
if (this.engineInstance && this.engineInstance.isInitialized()) {
console.warn('[Engine2dManager] 2D Engine already initialized. Destroying old instance...');
this.engineInstance.destroy();
this.engineInstance = null;
}
try {
this.engineInstance = new Engine2d({
container: this.container,
...options,
}, this.registry);
this.engineInstance.init();
return this.engineInstance.isInitialized();
} catch (error) {
console.error('[Engine2dManager] Failed to initialize 2D engine:', error);
this.engineInstance = null;
return false;
}
}
/**
* 检查引擎是否已初始化
* @returns 是否已初始化
*/
public isInitialized(): boolean {
return this.engineInstance !== null && this.engineInstance.isInitialized();
}
/**
* 加载 2D 图纸
* @param url 图纸文件 URL
* @param options 加载选项
*/
public async loadDrawing(url: string, options?: DrawingLoadOptions): Promise<void> {
if (!this.engineInstance) {
console.warn('[Engine2dManager] 2D Engine not initialized.');
return;
}
await this.engineInstance.loadDrawing(url, options);
}
/**
* 获取所有图层
* @returns 图层列表
*/
public getLayers(): Drawing2dLayer[] {
if (!this.engineInstance) {
return [];
}
return this.engineInstance.getLayers();
}
/**
* 设置图层可见性
* @param name 图层名称
* @param visible 是否可见
*/
public setLayerVisible(name: string, visible: boolean): void {
if (!this.engineInstance) {
console.warn('[Engine2dManager] 2D Engine not initialized.');
return;
}
this.engineInstance.setLayerVisible(name, visible);
}
/**
* 重置视图
*/
public resetView(): void {
this.engineInstance?.resetView();
}
/**
* 适应视图
*/
public fitToView(): void {
this.engineInstance?.fitToView();
}
/** 销毁 2D 引擎管理器 */
public destroy(): void {
if (this.engineInstance) {
this.engineInstance.destroy();
this.engineInstance = null;
}
super.destroy();
}
}

View File

@@ -0,0 +1,130 @@
/**
* 720 全景引擎管理器
* 负责管理 720° 全景渲染引擎的初始化、全景加载和生命周期
*
* 设计原则:
* - 镜像 Engine2dManager 的设计模式
* - Engine720Manager 只暴露面向外部用户的公共 API
* - 内部管理器通过 getEngine720Component() 直接访问 Engine720 组件
*/
import { Engine720, type Engine720Options, type PanoramaLoadOptions } from '../components/engine-720';
import { BaseManager } from '../core/base-manager';
import { ManagerRegistry } from '../core/manager-registry';
/**
* 720 全景引擎管理器
* 封装底层 720 引擎,提供全景加载、视角控制等公共 API
*/
export class Engine720Manager extends BaseManager {
/** 容器元素 */
private container: HTMLElement;
/** 720 引擎组件实例 */
private engineInstance: Engine720 | null = null;
constructor(container: HTMLElement, registry: ManagerRegistry) {
super(registry);
this.container = container;
}
/**
* 获取 Engine720 组件实例
* 内部管理器通过此方法直接访问 Engine720 组件的全部能力
* @returns Engine720 组件实例,未初始化时返回 null
*/
public getEngine720Component(): Engine720 | null {
return this.engineInstance;
}
/**
* 初始化 720 引擎
* @param options 引擎配置选项
* @returns 是否初始化成功
*/
public initialize(options?: Omit<Engine720Options, 'container'>): boolean {
if (this.engineInstance && this.engineInstance.isInitialized()) {
console.warn('[Engine720Manager] 720 Engine already initialized. Destroying old instance...');
this.engineInstance.destroy();
this.engineInstance = null;
}
try {
this.engineInstance = new Engine720({
container: this.container,
...options,
}, this.registry);
this.engineInstance.init();
return this.engineInstance.isInitialized();
} catch (error) {
console.error('[Engine720Manager] Failed to initialize 720 engine:', error);
this.engineInstance = null;
return false;
}
}
/**
* 检查引擎是否已初始化
* @returns 是否已初始化
*/
public isInitialized(): boolean {
return this.engineInstance !== null && this.engineInstance.isInitialized();
}
/**
* 加载全景图
* @param url 全景图片 URL
* @param options 加载选项
*/
public async loadPanorama(url: string, options?: PanoramaLoadOptions): Promise<void> {
if (!this.engineInstance) {
console.warn('[Engine720Manager] 720 Engine not initialized.');
return;
}
await this.engineInstance.loadPanorama(url, options);
}
/**
* 预加载多个全景图
* @param urls 全景图 URL 列表
*/
public async preloadPanoramas(urls: string[]): Promise<void> {
if (!this.engineInstance) {
console.warn('[Engine720Manager] 720 Engine not initialized.');
return;
}
await this.engineInstance.preloadPanoramas(urls);
}
/**
* 设置视场角
* @param fov 视场角值
*/
public setFov(fov: number): void {
this.engineInstance?.setFov(fov);
}
/**
* 获取当前视场角
* @returns 当前视场角值
*/
public getFov(): number {
return this.engineInstance?.getFov() ?? 75;
}
/**
* 重置视图
*/
public resetView(): void {
this.engineInstance?.resetView();
}
/** 销毁 720 引擎管理器 */
public destroy(): void {
if (this.engineInstance) {
this.engineInstance.destroy();
this.engineInstance = null;
}
super.destroy();
}
}

View File

@@ -70,8 +70,7 @@ export class SectionAxisDialogManager extends BaseDialogManager {
}
},
onReverse: () => {
// 反向功能:第三方引擎无 API仅输出日志
console.log('[SectionAxisDialogManager] 反向剖切(暂不支持)');
this.engineComponent?.reverseSection();
},
onAxisChange: (axis) => {
console.log('[SectionAxisDialogManager] 切换轴向:', axis);

View File

@@ -61,11 +61,6 @@ export class SectionBoxDialogManager extends BaseDialogManager {
this.engineComponent?.recoverSection();
}
},
onReverseToggle: (isReversed) => {
console.log('[SectionBoxDialogManager] 反向切换:', isReversed);
// 底层 reverse() 为“切换一次”,这里不使用 isReversed 作为入参,只要用户点击就触发。
this.engineComponent?.reverseSection();
},
onFitToModel: () => {
// 对接底层 scaleBox():缩放剖切盒到场景整体包围盒
this.engineComponent?.scaleSectionBox();

View File

@@ -28,7 +28,7 @@ export class SectionPlaneDialogManager extends BaseDialogManager {
protected get dialogHeight(): number { return 120; }
/** 初始化 */
public init(): void {}
public init(): void { }
/**
* 获取对话框位置
@@ -62,10 +62,12 @@ export class SectionPlaneDialogManager extends BaseDialogManager {
}
},
onReverse: () => {
console.log('[SectionPlaneDialogManager] 反向 (not supported in new API)');
this.engineComponent?.reverseSection();
},
onReset: () => {
console.log('[SectionPlaneDialogManager] 重置 (not supported in new API)');
// 先关闭剖切再开启
this.engineComponent?.deactivateSection();
this.engineComponent?.activeSection('face');
}
});
this.panel.init();

View File

@@ -47,5 +47,16 @@ export interface EngineEvents {
'aiChat:question-answered': { questionId: string; optionId: string; customAnswer?: string };
'aiChat:new-chat': {};
'aiChat:history-opened': {};
// 2D 引擎事件
'engine2d:drawing-loaded': { url: string };
'engine2d:entity-clicked': { data: any };
'engine2d:layer-changed': { data: any };
// 720 全景引擎事件
'engine720:panorama-loaded': { data: any };
'engine720:load-error': { data: any };
'engine720:annotation-click': { data: any };
'engine720:view-changed': { data: any };
'aiChat:settings-opened': {};
}