2026-01-22 15:23:57 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 3D 引擎管理器
|
2026-03-05 11:15:57 +08:00
|
|
|
|
* 负责管理 3D 渲染引擎的初始化、模型加载和生命周期
|
|
|
|
|
|
*
|
|
|
|
|
|
* 设计原则:
|
|
|
|
|
|
* - EngineManager 只暴露面向外部用户的公共 API
|
|
|
|
|
|
* - 内部管理器通过 getEngineComponent() 直接访问 Engine 组件
|
|
|
|
|
|
* - 不再透传 Engine 组件的方法
|
2026-01-22 15:23:57 +08:00
|
|
|
|
*/
|
2026-03-05 11:15:57 +08:00
|
|
|
|
import { Engine, type EngineOptions, type ModelLoadOptions } from '../components/engine';
|
2026-01-22 15:23:57 +08:00
|
|
|
|
import { BaseManager } from '../core/base-manager';
|
2025-12-10 09:42:05 +08:00
|
|
|
|
import { RightKeyManager } from './right-key-manager';
|
2026-01-28 11:57:20 +08:00
|
|
|
|
import type { MenuItemConfig } from '../components/menu/item';
|
|
|
|
|
|
import { ManagerRegistry } from '../core/manager-registry';
|
2025-12-04 18:41:11 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 3D 引擎管理器
|
2026-03-05 11:15:57 +08:00
|
|
|
|
* 封装底层 3D 引擎,提供模型加载、渲染控制等公共 API
|
2025-12-04 18:41:11 +08:00
|
|
|
|
*/
|
2026-01-22 15:23:57 +08:00
|
|
|
|
export class EngineManager extends BaseManager {
|
|
|
|
|
|
/** 容器元素 */
|
2025-12-04 18:41:11 +08:00
|
|
|
|
private container: HTMLElement;
|
2026-03-05 11:15:57 +08:00
|
|
|
|
/** 引擎组件实例 */
|
2025-12-08 10:02:24 +08:00
|
|
|
|
private engineInstance: Engine | null = null;
|
2026-01-22 15:23:57 +08:00
|
|
|
|
/** 右键菜单管理器 */
|
|
|
|
|
|
public rightKey: RightKeyManager | null = null;
|
2025-12-04 18:41:11 +08:00
|
|
|
|
|
2026-02-28 10:08:36 +08:00
|
|
|
|
constructor(container: HTMLElement, registry: ManagerRegistry) {
|
|
|
|
|
|
super(registry);
|
2025-12-04 18:41:11 +08:00
|
|
|
|
this.container = container;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-05 11:15:57 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 获取 Engine 组件实例
|
|
|
|
|
|
* 内部管理器通过此方法直接访问 Engine 组件的全部能力
|
|
|
|
|
|
* @returns Engine 组件实例,未初始化时返回 null
|
|
|
|
|
|
*/
|
|
|
|
|
|
public getEngineComponent(): Engine | null {
|
|
|
|
|
|
return this.engineInstance;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-04 18:41:11 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 初始化 3D 引擎
|
2026-01-22 15:23:57 +08:00
|
|
|
|
* @param options 引擎配置选项
|
2025-12-04 18:41:11 +08:00
|
|
|
|
* @returns 是否初始化成功
|
|
|
|
|
|
*/
|
|
|
|
|
|
public initialize(options?: Omit<EngineOptions, 'container'>): boolean {
|
2025-12-08 10:02:24 +08:00
|
|
|
|
if (this.engineInstance && this.engineInstance.isInitialized()) {
|
2025-12-04 18:41:11 +08:00
|
|
|
|
console.warn('[EngineManager] 3D Engine already initialized. Destroying old instance...');
|
2025-12-08 10:02:24 +08:00
|
|
|
|
this.engineInstance.destroy();
|
|
|
|
|
|
this.engineInstance = null;
|
2025-12-04 18:41:11 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
2025-12-08 10:02:24 +08:00
|
|
|
|
this.engineInstance = new Engine({
|
2025-12-04 18:41:11 +08:00
|
|
|
|
container: this.container,
|
2026-01-22 15:23:57 +08:00
|
|
|
|
...options,
|
2026-02-28 10:08:36 +08:00
|
|
|
|
}, this.registry);
|
2025-12-04 18:41:11 +08:00
|
|
|
|
|
2025-12-08 10:02:24 +08:00
|
|
|
|
this.engineInstance.init();
|
2025-12-04 18:41:11 +08:00
|
|
|
|
|
2026-02-28 10:08:36 +08:00
|
|
|
|
this.rightKey = new RightKeyManager(this.container, this.registry);
|
2025-12-10 09:42:05 +08:00
|
|
|
|
|
|
|
|
|
|
this.rightKey.registerHandler((_e) => {
|
2026-03-05 11:15:57 +08:00
|
|
|
|
const selected = this.engineInstance?.getSelectedComponent();
|
2026-01-28 11:57:20 +08:00
|
|
|
|
const items: MenuItemConfig[] = [];
|
|
|
|
|
|
|
|
|
|
|
|
if (selected) {
|
2026-01-28 17:19:36 +08:00
|
|
|
|
// 1. 构件详情
|
2026-01-28 11:57:20 +08:00
|
|
|
|
items.push({
|
|
|
|
|
|
id: 'componentDetail',
|
|
|
|
|
|
label: 'menu.componentDetail',
|
|
|
|
|
|
group: 'component',
|
2026-01-28 17:19:36 +08:00
|
|
|
|
order: 1,
|
|
|
|
|
|
divider: true,
|
2026-01-28 11:57:20 +08:00
|
|
|
|
onClick: () => {
|
2026-02-28 10:08:36 +08:00
|
|
|
|
const registry = this.registry;
|
2026-01-28 17:19:36 +08:00
|
|
|
|
registry.componentDetail?.show();
|
|
|
|
|
|
this.rightKey?.hide();
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 隐藏选中构件
|
|
|
|
|
|
items.push({
|
|
|
|
|
|
id: 'hideSelected',
|
|
|
|
|
|
label: 'menu.hideSelected',
|
|
|
|
|
|
group: 'component',
|
|
|
|
|
|
order: 2,
|
|
|
|
|
|
onClick: () => {
|
2026-03-05 11:15:57 +08:00
|
|
|
|
const models = this.engineInstance?.getHighlightModels();
|
2026-02-04 15:28:50 +08:00
|
|
|
|
if (models) {
|
2026-03-05 11:15:57 +08:00
|
|
|
|
this.engineInstance?.hideModels(models);
|
2026-02-04 15:28:50 +08:00
|
|
|
|
}
|
2026-01-28 17:19:36 +08:00
|
|
|
|
this.rightKey?.hide();
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 半透明选中构件
|
|
|
|
|
|
items.push({
|
|
|
|
|
|
id: 'transparentSelected',
|
|
|
|
|
|
label: 'menu.transparentSelected',
|
|
|
|
|
|
group: 'component',
|
|
|
|
|
|
order: 3,
|
|
|
|
|
|
onClick: () => {
|
2026-03-05 11:15:57 +08:00
|
|
|
|
const models = this.engineInstance?.getHighlightModels();
|
2026-02-04 15:28:50 +08:00
|
|
|
|
if (models) {
|
2026-03-05 11:15:57 +08:00
|
|
|
|
this.engineInstance?.translucentModels(models);
|
2026-02-04 15:28:50 +08:00
|
|
|
|
}
|
2026-01-28 17:19:36 +08:00
|
|
|
|
this.rightKey?.hide();
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-02-04 15:10:51 +08:00
|
|
|
|
// 4. 取消半透明
|
|
|
|
|
|
items.push({
|
|
|
|
|
|
id: 'cancelTranslucent',
|
|
|
|
|
|
label: 'menu.cancelTranslucent',
|
|
|
|
|
|
group: 'component',
|
|
|
|
|
|
order: 4,
|
|
|
|
|
|
onClick: () => {
|
2026-03-05 11:15:57 +08:00
|
|
|
|
this.engineInstance?.unTranslucentModel();
|
2026-02-04 15:10:51 +08:00
|
|
|
|
this.rightKey?.hide();
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 5. 隔离选中构件(带子菜单)
|
2026-01-28 17:19:36 +08:00
|
|
|
|
items.push({
|
|
|
|
|
|
id: 'isolateSelected',
|
|
|
|
|
|
label: 'menu.isolateSelected',
|
|
|
|
|
|
group: 'component',
|
2026-02-04 15:10:51 +08:00
|
|
|
|
order: 5,
|
2026-01-28 17:19:36 +08:00
|
|
|
|
divider: true,
|
|
|
|
|
|
children: [
|
|
|
|
|
|
{
|
|
|
|
|
|
id: 'hideOthers',
|
|
|
|
|
|
label: 'menu.hideOthers',
|
|
|
|
|
|
onClick: () => {
|
2026-03-05 11:15:57 +08:00
|
|
|
|
const models = this.engineInstance?.getHighlightModels();
|
2026-02-04 15:28:50 +08:00
|
|
|
|
if (models) {
|
2026-03-05 11:15:57 +08:00
|
|
|
|
this.engineInstance?.isolateModels(models);
|
2026-02-04 15:28:50 +08:00
|
|
|
|
}
|
2026-01-28 17:19:36 +08:00
|
|
|
|
this.rightKey?.hide();
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
id: 'transparentOthers',
|
|
|
|
|
|
label: 'menu.transparentOthers',
|
|
|
|
|
|
onClick: () => {
|
2026-03-05 11:15:57 +08:00
|
|
|
|
const models = this.engineInstance?.getHighlightModels();
|
2026-02-04 15:28:50 +08:00
|
|
|
|
if (models) {
|
2026-03-05 11:15:57 +08:00
|
|
|
|
this.engineInstance?.translucentOtherModels(models);
|
2026-02-04 15:28:50 +08:00
|
|
|
|
}
|
2026-01-28 17:19:36 +08:00
|
|
|
|
this.rightKey?.hide();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-02-04 15:10:51 +08:00
|
|
|
|
// 6. 快速选择(带子菜单)
|
2026-02-03 18:01:31 +08:00
|
|
|
|
items.push({
|
|
|
|
|
|
id: 'quickSelect',
|
|
|
|
|
|
label: 'menu.quickSelect',
|
|
|
|
|
|
group: 'component',
|
2026-02-04 15:10:51 +08:00
|
|
|
|
order: 6,
|
2026-02-03 18:01:31 +08:00
|
|
|
|
children: [
|
|
|
|
|
|
{
|
|
|
|
|
|
id: 'selectSameType',
|
|
|
|
|
|
label: 'menu.selectSameType',
|
|
|
|
|
|
onClick: () => {
|
2026-03-05 11:15:57 +08:00
|
|
|
|
const models = this.engineInstance?.getHighlightModels();
|
2026-02-04 15:28:50 +08:00
|
|
|
|
if (models) {
|
2026-03-05 11:15:57 +08:00
|
|
|
|
this.engineInstance?.batchSelectSameTypeModel(models);
|
2026-02-04 15:28:50 +08:00
|
|
|
|
}
|
2026-02-03 18:01:31 +08:00
|
|
|
|
this.rightKey?.hide();
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
id: 'selectSameLevel',
|
|
|
|
|
|
label: 'menu.selectSameLevel',
|
|
|
|
|
|
onClick: () => {
|
2026-03-05 11:15:57 +08:00
|
|
|
|
const models = this.engineInstance?.getHighlightModels();
|
2026-02-04 15:28:50 +08:00
|
|
|
|
if (models) {
|
2026-03-05 11:15:57 +08:00
|
|
|
|
this.engineInstance?.batchSelectSameLevelModel(models);
|
2026-02-04 15:28:50 +08:00
|
|
|
|
}
|
2026-02-03 18:01:31 +08:00
|
|
|
|
this.rightKey?.hide();
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
id: 'selectSameLevelType',
|
|
|
|
|
|
label: 'menu.selectSameLevelType',
|
|
|
|
|
|
onClick: () => {
|
2026-03-05 11:15:57 +08:00
|
|
|
|
const models = this.engineInstance?.getHighlightModels();
|
2026-02-04 15:28:50 +08:00
|
|
|
|
if (models) {
|
2026-03-05 11:15:57 +08:00
|
|
|
|
this.engineInstance?.batchSelectSameLevelTypeModel(models);
|
2026-02-04 15:28:50 +08:00
|
|
|
|
}
|
2026-02-03 18:01:31 +08:00
|
|
|
|
this.rightKey?.hide();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-02-04 15:10:51 +08:00
|
|
|
|
// 7. 剖切盒适应
|
2026-01-28 17:19:36 +08:00
|
|
|
|
items.push({
|
|
|
|
|
|
id: 'fitSectionBox',
|
|
|
|
|
|
label: 'menu.fitSectionBox',
|
|
|
|
|
|
group: 'component',
|
2026-02-04 15:10:51 +08:00
|
|
|
|
order: 7,
|
2026-01-28 17:19:36 +08:00
|
|
|
|
onClick: () => {
|
2026-03-05 11:15:57 +08:00
|
|
|
|
this.engineInstance?.fitSectionBoxToModel();
|
2026-01-28 11:57:20 +08:00
|
|
|
|
this.rightKey?.hide();
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-04 15:10:51 +08:00
|
|
|
|
// 8. 显示全部(始终显示)
|
2026-01-28 11:57:20 +08:00
|
|
|
|
items.push({
|
|
|
|
|
|
id: 'showAll',
|
|
|
|
|
|
label: 'menu.showAll',
|
|
|
|
|
|
group: 'component',
|
2026-02-04 15:10:51 +08:00
|
|
|
|
order: 8,
|
2026-01-28 11:57:20 +08:00
|
|
|
|
onClick: () => {
|
2026-03-05 11:15:57 +08:00
|
|
|
|
this.engineInstance?.showAllModels();
|
2026-02-04 18:20:30 +08:00
|
|
|
|
this.emit('menu:show-all', {});
|
2026-01-28 11:57:20 +08:00
|
|
|
|
this.rightKey?.hide();
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return items;
|
2025-12-10 09:42:05 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
2025-12-08 10:02:24 +08:00
|
|
|
|
return this.engineInstance.isInitialized();
|
2025-12-04 18:41:11 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('[EngineManager] Failed to initialize 3D engine:', error);
|
2025-12-08 10:02:24 +08:00
|
|
|
|
this.engineInstance = null;
|
2025-12-04 18:41:11 +08:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-01-22 15:23:57 +08:00
|
|
|
|
|
2025-12-04 18:41:11 +08:00
|
|
|
|
/**
|
2026-01-22 15:23:57 +08:00
|
|
|
|
* 检查引擎是否已初始化
|
|
|
|
|
|
* @returns 是否已初始化
|
2025-12-04 18:41:11 +08:00
|
|
|
|
*/
|
|
|
|
|
|
public isInitialized(): boolean {
|
2025-12-08 10:02:24 +08:00
|
|
|
|
return this.engineInstance !== null && this.engineInstance.isInitialized();
|
2025-12-04 18:41:11 +08:00
|
|
|
|
}
|
2026-01-22 15:23:57 +08:00
|
|
|
|
|
2025-12-04 18:41:11 +08:00
|
|
|
|
/**
|
2026-01-22 15:23:57 +08:00
|
|
|
|
* 加载模型
|
2026-02-26 16:03:38 +08:00
|
|
|
|
* @param urls 模型 URL 数组
|
2026-01-22 15:23:57 +08:00
|
|
|
|
* @param options 加载选项
|
2025-12-04 18:41:11 +08:00
|
|
|
|
*/
|
2026-02-26 16:03:38 +08:00
|
|
|
|
public loadModel(urls: string[], options?: ModelLoadOptions): void {
|
2026-01-15 14:13:13 +08:00
|
|
|
|
if (!this.engineInstance) {
|
|
|
|
|
|
console.warn('[EngineManager] 3D Engine not initialized.');
|
2025-12-04 18:41:11 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-02-26 16:03:38 +08:00
|
|
|
|
this.engineInstance.loadModel(urls, options);
|
2025-12-04 18:41:11 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-23 16:27:04 +08:00
|
|
|
|
/** 暂停渲染 */
|
|
|
|
|
|
public pauseRendering(): void {
|
|
|
|
|
|
if (!this.engineInstance) {
|
|
|
|
|
|
console.warn('[EngineManager] 3D Engine not initialized.');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
this.engineInstance.pauseRendering();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** 恢复渲染 */
|
|
|
|
|
|
public resumeRendering(): void {
|
|
|
|
|
|
if (!this.engineInstance) {
|
|
|
|
|
|
console.warn('[EngineManager] 3D Engine not initialized.');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
this.engineInstance.resumeRendering();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-22 15:23:57 +08:00
|
|
|
|
/** 销毁引擎管理器 */
|
2025-12-04 18:41:11 +08:00
|
|
|
|
public destroy(): void {
|
2025-12-08 10:02:24 +08:00
|
|
|
|
if (this.engineInstance) {
|
|
|
|
|
|
this.engineInstance.destroy();
|
|
|
|
|
|
this.engineInstance = null;
|
2025-12-04 18:41:11 +08:00
|
|
|
|
}
|
2025-12-10 09:42:05 +08:00
|
|
|
|
if (this.rightKey) {
|
|
|
|
|
|
this.rightKey.destroy();
|
|
|
|
|
|
this.rightKey = null;
|
|
|
|
|
|
}
|
2026-01-22 15:23:57 +08:00
|
|
|
|
super.destroy();
|
2025-12-04 18:41:11 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|