refactor: sync managers and section box actions

Wire section box scale/reverse/reset to clipping APIs and sync demo artifacts.
This commit is contained in:
yuding
2026-02-04 18:20:30 +08:00
parent b12940f49c
commit 191c571f40
64 changed files with 10569 additions and 7065 deletions

View File

@@ -1,64 +1,257 @@
/**
* @file construct-tree-manager-btn.ts
* @description 构件树管理器 - 负责管理构件树按钮和对话框
*
* 功能概述:
* 1. 在界面左上角显示构件树按钮
* 2. 点击按钮打开构件树对话框,包含三个选项卡:楼层树、类型树、专业树
* 3. 点击树节点时,如果节点有 ids则高亮对应的 3D 模型构件
*
* 数据流:
* 1. 从 3D 引擎获取原始树数据 (getLevelTreeData/getTypeTreeData/getMajorTreeData)
* 2. 通过 transformTreeData 转换为 SDK 标准的 TreeNodeConfig 格式
* 3. 创建 BimTree 组件渲染树结构
* 4. 用户点击节点时,通过 onNodeSelect 回调触发模型高亮
*/
import type { ButtonGroupColors, ButtonConfig } from '../components/button-group/index.type';
import { BaseManager } from '../core/base-manager';
import { BimButtonGroup } from '../components/button-group';
import { BimTree } from '../components/tree';
import { TreeNodeConfig } from '../components/tree/types';
import { TreeNodeConfig, TreeNodeCheckState } from '../components/tree/types';
import type { BimTreeNode } from '../components/tree/tree-node';
import { BimDialog } from '../components/dialog';
import { BimTab } from '../components/tab';
import { getIcon } from '../utils/icon-manager';
function transformTreeData(apiData: any[]): TreeNodeConfig[] {
if (!apiData || apiData.length === 0) return [];
// ============================================================================
// 类型定义
// ============================================================================
return apiData.map((model, modelIndex) => {
const transformNode = (node: any, index: number): TreeNodeConfig => {
const hasChildren = node.children && node.children.length > 0;
return {
id: node.id || `node-${modelIndex}-${index}`,
label: node.name || node.label || '未命名',
expanded: false,
clickAction: hasChildren ? 'expand' : 'select',
children: hasChildren
? node.children.map((child: any, childIndex: number) => transformNode(child, childIndex))
: undefined,
data: node
};
};
const hasChildren = model.children && model.children.length > 0;
return {
id: `model-${modelIndex}`,
label: model.name || '模型',
expanded: true,
clickAction: 'expand',
children: hasChildren
? model.children.map((child: any, childIndex: number) => transformNode(child, childIndex))
: undefined
};
});
/**
* 3D 引擎返回的原始树节点数据结构
*
* @example
* {
* name: "标高 1",
* id: null,
* ids: ["350518", "350520", ...], // 构件 ID 数组,用于高亮模型
* children: [...],
* isLeaf: false
* }
*/
interface EngineTreeNode {
/** 节点显示名称 */
name?: string;
/** 节点 ID可能为 null */
id?: string | null;
/** 构件 ID 数组 - 用于调用 highlightModel 高亮模型 */
ids?: string[] | null;
/** 子节点列表 */
children?: EngineTreeNode[] | null;
/** 是否为叶子节点 */
isLeaf?: boolean;
}
/**
* 3D 引擎返回的原始模型数据结构(最外层)
*
* @example
* {
* name: "栈桥模型",
* url: "https://xxx.com/models/xxx/", // 模型 URL用于 highlightModel
* children: [...]
* }
*/
interface EngineModelData {
/** 模型显示名称 */
name?: string;
/** 模型资源 URL - 调用 highlightModel 时需要此参数 */
url: string;
/** 子节点列表 */
children?: EngineTreeNode[] | null;
}
/**
* 扩展的节点数据,包含模型 URL
* 转换后存储在 TreeNodeConfig.data 中
*/
interface TransformedNodeData extends EngineTreeNode {
/** 模型 URL - 从最外层 model.url 传递下来 */
_modelUrl: string;
}
// ============================================================================
// 工具函数
// ============================================================================
/**
* 使用 SHA-256 算法对 ids 数组生成唯一哈希值
*
* 为什么需要 hash
* - 原始数据中 node.id 可能为 null
* - ids 数组可能很长(几百个元素),不适合直接作为 ID
* - 需要一个稳定且唯一的标识符用于树组件的 nodeMap
*
* @param ids - 构件 ID 数组
* @returns 64 位十六进制哈希字符串
*
* @example
* hashIds(["350518", "350520"]) // => "a1b2c3d4e5f6..."
*/
async function hashIds(ids: string[]): Promise<string> {
const str = JSON.stringify(ids);
const data = new TextEncoder().encode(str);
const buf = await crypto.subtle.digest('SHA-256', data);
return Array.from(new Uint8Array(buf))
.map(b => b.toString(16).padStart(2, '0'))
.join('');
}
/**
* 递归收集节点及其所有子孙节点的 ids
* 用于父级节点操作时,获取其下所有叶子节点对应的构件 ids
*/
function collectAllIds(node: BimTreeNode): string[] {
const result: string[] = [];
const data = node.config.data as TransformedNodeData | undefined;
if (data?.ids?.length) {
result.push(...data.ids);
}
for (const child of node.children || []) {
result.push(...collectAllIds(child));
}
return result;
}
/**
* 将 3D 引擎返回的原始树数据转换为 SDK 标准的 TreeNodeConfig 格式
*
* 转换过程:
* 1. 遍历最外层的模型数组,提取每个模型的 url
* 2. 递归转换每个节点,将 url 注入到所有子节点的 data 中
* 3. 如果节点有 ids 数组,使用 SHA-256 生成唯一 ID
*
* 点击行为:
* - 点击节点内容:触发 onNodeSelect高亮+跳转)
* - 点击箭头:展开/折叠子节点
*
* 数据结构转换示意:
*
* 输入:[{ name, url, children: [{ name, ids, children }] }]
* 输出:[{ id, label, data: { ids, _modelUrl }, children }]
*
* @param apiData - 3D 引擎返回的原始树数据
* @returns 转换后的 TreeNodeConfig 数组
*/
let nodeIdCounter = 0;
async function transformTreeData(apiData: EngineModelData[]): Promise<TreeNodeConfig[]> {
if (!apiData || apiData.length === 0) return [];
/**
* 递归转换单个节点
* @param node - 原始节点数据
* @param modelUrl - 从最外层传递的模型 URL
*/
const transformNode = async (node: EngineTreeNode, modelUrl: string): Promise<TreeNodeConfig> => {
const hasChildren = node.children && node.children.length > 0;
// 生成节点 ID优先使用 ids 哈希,其次使用原始 id最后生成唯一 ID
let id: string;
if (node.ids?.length) {
id = await hashIds(node.ids);
} else if (node.id) {
id = node.id;
} else {
id = `node_${++nodeIdCounter}`;
}
return {
id,
label: node.name || '未命名',
expanded: false,
checked: true,
children: hasChildren
? await Promise.all(node.children!.map(child => transformNode(child, modelUrl)))
: undefined,
data: {
...node,
_modelUrl: modelUrl
} as TransformedNodeData
};
};
// 遍历最外层模型数组
return Promise.all(apiData.map(async (model) => {
const hasChildren = model.children && model.children.length > 0;
// ⭐ 提取模型 URL将传递给所有子节点
const modelUrl = model.url;
return {
id: modelUrl,
label: model.name || '模型',
expanded: true,
checked: true,
children: hasChildren
? await Promise.all(model.children!.map(child => transformNode(child, modelUrl)))
: undefined,
data: {
_modelUrl: modelUrl
} as TransformedNodeData
};
}));
}
// ============================================================================
// 管理器类
// ============================================================================
/**
* 构件树管理器
*
* 职责:
* 1. 管理构件树按钮的生命周期(创建、显示/隐藏、销毁)
* 2. 管理构件树对话框(打开、关闭、内容渲染)
* 3. 处理树节点点击事件,调用 3D 引擎高亮模型
*
* 使用示例:
* ```typescript
* const manager = new ConstructTreeManagerBtn(container);
* manager.openConstructTreeDialog(); // 打开构件树对话框
* ```
*/
export class ConstructTreeManagerBtn extends BaseManager {
/** 按钮组实例 */
/** 按钮组实例 - 用于渲染构件树按钮 */
private toolbar: BimButtonGroup | null = null;
/** 按钮容器元素 */
private toolbarContainer: HTMLElement | null = null;
/** 主容器元素 */
/** 主容器元素 - 按钮挂载的父容器 */
private container: HTMLElement;
/** 构件树对话框实例 */
private dialog: BimDialog | null = null;
/**
* 创建构件树管理器
* @param container - 按钮挂载的容器元素
*/
constructor(container: HTMLElement) {
super();
this.container = container;
this.init();
}
/** 初始化按钮 */
/**
* 初始化按钮
* 创建按钮容器和按钮组,添加构件树按钮
*/
private init() {
// 创建按钮容器
this.toolbarContainer = document.createElement('div');
this.toolbarContainer.id = 'bim-construct-tree';
this.container.appendChild(this.toolbarContainer);
// 创建按钮组
this.toolbar = new BimButtonGroup({
container: this.toolbarContainer,
showLabel: false,
@@ -69,6 +262,8 @@ export class ConstructTreeManagerBtn extends BaseManager {
});
this.toolbar.init();
this.toolbar.addGroup('construct-tree');
// 添加构件树按钮
this.toolbar.addButton({
id: 'construct-tree-btn',
groupId: 'construct-tree',
@@ -82,31 +277,103 @@ export class ConstructTreeManagerBtn extends BaseManager {
this.toolbar.render();
}
public openConstructTreeDialog() {
/**
* 打开构件树对话框
*
* 流程:
* 1. 隐藏按钮组
* 2. 从 3D 引擎获取三种树数据(楼层/类型/专业)
* 3. 转换数据格式
* 4. 创建三个 BimTree 实例
* 5. 创建选项卡组件
* 6. 创建对话框并显示
*/
public async openConstructTreeDialog() {
// 隐藏按钮组,避免遮挡对话框
this.setVisible(false);
// 从 3D 引擎获取原始树数据
const levelTreeData = this.registry.engine3d?.getLevelTreeData() ?? [];
const typeTreeData = this.registry.engine3d?.getTypeTreeData() ?? [];
const majorTreeData = this.registry.engine3d?.getMajorTreeData() ?? [];
console.log('[ConstructTree] 构件树数据 (Level):', levelTreeData);
console.log('[ConstructTree] 类型树数据 (Type):', typeTreeData);
console.log('[ConstructTree] 专业树数据 (Major):', majorTreeData);
// 调试日志:输出原始数据
console.log('[ConstructTree] 原始数据 (Level):', levelTreeData);
console.log('[ConstructTree] 原始数据 (Type):', typeTreeData);
console.log('[ConstructTree] 原始数据 (Major):', majorTreeData);
const createTree = (data: any[]) => {
/**
* 创建树组件
* @param data - 原始树数据
* @param label - 调试用标签
*/
const createTree = async (data: any[], label: string) => {
// 转换数据格式
const transformedData = await transformTreeData(data);
console.log(`[ConstructTree] 转换后数据 (${label}):`, transformedData);
const tree = new BimTree({
data: transformTreeData(data),
data: transformedData,
checkable: true,
indent: 0,
enableSearch: true,
checkStrictly: true,
defaultExpandAll: true,
/**
* 节点勾选回调 - 显示/隐藏模型构件
* 勾选 → showModel取消勾选 → hideModels
*/
onNodeCheck: (node) => {
console.log('onNodeCheck', node);
const nodeData = node.config.data as TransformedNodeData | undefined;
if (!nodeData?._modelUrl) return;
const ids = nodeData.ids?.length
? nodeData.ids
: collectAllIds(node);
if (!ids.length) return;
const modelParam = [{
url: nodeData._modelUrl,
ids: ids.map(Number)
}];
if (node.checkState === TreeNodeCheckState.Checked) {
this.registry.engine3d?.showModel(modelParam);
} else {
this.registry.engine3d?.hideModels(modelParam);
}
},
/**
* 节点选中回调 - 高亮并跳转到模型构件
*/
onNodeSelect: (node) => {
console.log('onNodeSelect', node);
const nodeData = node.config.data as TransformedNodeData | undefined;
if (!nodeData?._modelUrl) return;
const ids = nodeData.ids?.length
? nodeData.ids
: collectAllIds(node);
if (!ids.length) return;
const modelParam = [{
url: nodeData._modelUrl,
ids: ids.map(Number)
}];
this.registry.engine3d?.unhighlightAllModels();
this.registry.engine3d?.highlightModel(modelParam);
this.registry.engine3d?.viewScaleToModel(modelParam);
},
// 再次点击已选中节点时取消高亮
onNodeDeselect: () => {
this.registry.engine3d?.unhighlightAllModels();
},
onNodeExpand: () => {
this.dialog?.fitWidth();
},
@@ -115,10 +382,12 @@ export class ConstructTreeManagerBtn extends BaseManager {
return tree;
};
const componentTree = createTree(levelTreeData);
const typeTree = createTree(typeTreeData);
const majorTree = createTree(majorTreeData);
// 创建三个树实例
const componentTree = await createTree(levelTreeData, 'Level');
const typeTree = await createTree(typeTreeData, 'Type');
const majorTree = await createTree(majorTreeData, 'Major');
// 创建选项卡面板容器
const componentPanel = document.createElement('div');
componentPanel.className = 'construct-tab__panel-content';
componentPanel.appendChild(componentTree.element);
@@ -131,10 +400,23 @@ export class ConstructTreeManagerBtn extends BaseManager {
majorPanel.className = 'construct-tab__panel-content';
majorPanel.appendChild(majorTree.element);
// 创建选项卡组件
const tabMount = document.createElement('div');
tabMount.className = 'construct-tab__container';
tabMount.style.height = '100%';
tabMount.style.overflow = 'hidden';
/**
* 重置所有树的勾选状态并显示所有模型
* 在 Tab 切换和对话框初始化时调用
*/
const resetAllTrees = () => {
this.registry.engine3d?.showAllModels();
componentTree.checkAllNodes(true);
typeTree.checkAllNodes(true);
majorTree.checkAllNodes(true);
};
const tab = new BimTab({
container: tabMount,
tabs: [
@@ -144,11 +426,21 @@ export class ConstructTreeManagerBtn extends BaseManager {
],
activeId: 'component',
onChange: () => {
resetAllTrees();
this.dialog?.fitWidth();
}
});
tab.init();
// BimTab 初始化时不触发 onChange需要手动调用
resetAllTrees();
// 监听右键菜单"显示全部"事件
const unsubscribeShowAll = this.registry.on('menu:show-all', () => {
resetAllTrees();
});
// 创建对话框
this.dialog = this.registry.dialog!.create({
title: 'constructTree.title',
minWidth: 320,
@@ -157,6 +449,7 @@ export class ConstructTreeManagerBtn extends BaseManager {
position: { x: 20, y: 20 },
resizable: false,
onClose: () => {
unsubscribeShowAll();
tab.destroy();
componentTree.destroy();
typeTree.destroy();
@@ -181,8 +474,8 @@ export class ConstructTreeManagerBtn extends BaseManager {
/**
* 添加按钮组
* @param groupId 组 ID
* @param beforeGroupId 插入位置
* @param groupId - 组 ID
* @param beforeGroupId - 插入位置
*/
public addGroup(groupId: string, beforeGroupId?: string) {
this.toolbar?.addGroup(groupId, beforeGroupId);
@@ -191,7 +484,7 @@ export class ConstructTreeManagerBtn extends BaseManager {
/**
* 添加按钮
* @param config 按钮配置
* @param config - 按钮配置
*/
public addButton(config: ButtonConfig) {
this.toolbar?.addButton(config);
@@ -200,8 +493,8 @@ export class ConstructTreeManagerBtn extends BaseManager {
/**
* 设置按钮可见性
* @param id 按钮 ID
* @param v 是否可见
* @param id - 按钮 ID
* @param v - 是否可见
*/
public setButtonVisibility(id: string, v: boolean) {
this.toolbar?.updateButtonVisibility(id, v);
@@ -209,7 +502,7 @@ export class ConstructTreeManagerBtn extends BaseManager {
/**
* 设置是否显示标签
* @param show 是否显示
* @param show - 是否显示
*/
public setShowLabel(show: boolean) {
this.toolbar?.setShowLabel(show);
@@ -217,7 +510,7 @@ export class ConstructTreeManagerBtn extends BaseManager {
/**
* 设置按钮组可见性
* @param visible 是否可见
* @param visible - 是否可见
*/
public setVisible(visible: boolean) {
if (this.toolbarContainer) {
@@ -227,7 +520,7 @@ export class ConstructTreeManagerBtn extends BaseManager {
/**
* 设置背景颜色
* @param color 颜色值
* @param color - 颜色值
*/
public setBackgroundColor(color: string) {
this.toolbar?.setBackgroundColor(color);
@@ -235,7 +528,7 @@ export class ConstructTreeManagerBtn extends BaseManager {
/**
* 设置按钮组颜色
* @param colors 颜色配置
* @param colors - 颜色配置
*/
public setColors(colors: ButtonGroupColors) {
this.toolbar?.setColors(colors);

View File

@@ -1,41 +1,18 @@
/**
* 对话框管理器
* 负责创建和管理所有对话框实例
*/
import { BimDialog } from '../components/dialog';
import { BimInfoDialog } from '../components/dialog/bimInfoDialog';
import type { DialogOptions } from '../components/dialog/index.type';
import type { ThemeConfig } from '../themes/types';
import { themeManager } from '../services/theme';
import { BaseManager } from '../core/base-manager';
/**
* 对话框管理器
* 统一管理对话框的创建、主题更新和销毁
*/
export class DialogManager extends BaseManager {
/** 容器元素 */
private container: HTMLElement;
/** 活跃的对话框列表 */
private activeDialogs: BimDialog[] = [];
constructor(container: HTMLElement) {
super();
this.container = container;
this.subscribe('ui:open-dialog', (payload) => {
console.log('[DialogManager] Received open-dialog event:', payload);
if (payload.id === 'info') {
this.showInfoDialog();
}
});
}
/**
* 创建对话框
* @param options 对话框配置选项
* @returns 对话框实例
*/
public create(options: Omit<DialogOptions, 'container'>): BimDialog {
const dialog = new BimDialog({
container: this.container,
@@ -51,15 +28,6 @@ export class DialogManager extends BaseManager {
return dialog;
}
/** 显示信息对话框 */
public showInfoDialog() {
new BimInfoDialog(this.container);
}
/**
* 更新所有对话框的主题
* @param theme 主题配置
*/
public updateTheme(theme: ThemeConfig) {
this.activeDialogs.forEach(dialog => {
if (dialog.setTheme) {
@@ -68,7 +36,6 @@ export class DialogManager extends BaseManager {
});
}
/** 销毁管理器和所有对话框 */
public destroy() {
this.activeDialogs.forEach(d => d.destroy());
this.activeDialogs = [];

View File

@@ -0,0 +1,55 @@
import { BaseDialogManager } from '../core/base-dialog-manager';
import { t } from '../services/locale';
import type { EngineInfo } from '../components/engine';
export class EngineInfoDialogManager extends BaseDialogManager {
protected get dialogId() { return 'engine-info-dialog'; }
protected get dialogTitle() { return 'info.dialogTitle'; }
protected get dialogWidth() { return 280; }
public init(): void {}
protected getDialogPosition() {
const container = this.registry.container;
if (!container) return { x: 100, y: 100 };
const containerWidth = container.clientWidth;
const containerHeight = container.clientHeight;
return {
x: (containerWidth - this.dialogWidth) / 2,
y: (containerHeight - 150) / 2
};
}
protected createContent(): HTMLElement {
const info: EngineInfo | null = this.registry.engine3d?.getEngineInfo() ?? null;
const content = document.createElement('div');
content.className = 'engine-info-content';
content.style.cssText = 'padding: 16px;';
const createRow = (label: string, value: number | string) => {
const row = document.createElement('div');
row.style.cssText = 'display: flex; justify-content: space-between; margin-bottom: 12px; font-size: 14px;';
const labelEl = document.createElement('span');
labelEl.style.cssText = 'color: var(--bim-text-secondary, #94a3b8);';
labelEl.textContent = label;
const valueEl = document.createElement('span');
valueEl.style.cssText = 'color: var(--bim-text-primary, #fff); font-weight: 500;';
valueEl.textContent = String(value);
row.appendChild(labelEl);
row.appendChild(valueEl);
return row;
};
content.appendChild(createRow(t('info.meshCount'), info?.meshCount ?? '-'));
content.appendChild(createRow(t('info.totalTriangles'), info?.totalTriangles ?? '-'));
content.appendChild(createRow(t('info.totalVertices'), info?.totalVertices ?? '-'));
return content;
}
}

View File

@@ -208,6 +208,7 @@ export class EngineManager extends BaseManager {
order: 8,
onClick: () => {
this.showAllModels();
this.emit('menu:show-all', {});
this.rightKey?.hide();
}
});
@@ -400,6 +401,28 @@ export class EngineManager extends BaseManager {
this.engineInstance.fitSectionBoxToModel();
}
/**
* 剖切盒适应(缩放到场景整体包围盒)
* @remarks 对接底层 clipping.scaleBox()
*/
public scaleSectionBox(): void {
if (!this.engineInstance) {
return;
}
this.engineInstance.scaleSectionBox();
}
/**
* 反向剖切
* @remarks 对接底层 clipping.reverse()
*/
public reverseSection(): void {
if (!this.engineInstance) {
return;
}
this.engineInstance.reverseSection();
}
/** 激活框选放大功能 */
public activateZoomBox(): void {
if (!this.engineInstance) {
@@ -569,13 +592,35 @@ export class EngineManager extends BaseManager {
}
/**
* 隐藏指定模型
* @param models 要隐藏的模型对象
* 高亮指定模型构件
*
* @param models - 要高亮的模型数组,格式: [{ url: string, ids: string[] }]
*
* @example
* manager.highlightModel([
* { url: 'https://xxx/models/xxx/', ids: [350518, 350520] }
* ]);
*/
public hideModels(models: any): void {
public highlightModel(models: { url: string; ids: number[] }[]): void {
this.engineInstance?.highlightModel(models);
}
public unhighlightAllModels(): void {
this.engineInstance?.unhighlightAllModels();
}
public viewScaleToModel(models: { url: string; ids: number[] }[]): void {
this.engineInstance?.viewScaleToModel(models);
}
public hideModels(models: { url: string; ids: number[] }[]): void {
this.engineInstance?.hideModels(models);
}
public showModel(models: { url: string; ids: number[] }[]): void {
this.engineInstance?.showModel(models);
}
/**
* 半透明指定模型
* @param models 要半透明的模型对象

View File

@@ -1,76 +0,0 @@
/**
* 地图对话框管理器
* 负责管理地图/平面图对话框的显示和交互
*/
import { BaseDialogManager } from '../core/base-dialog-manager';
import { MapPanel } from '../components/map-panel';
/**
* 地图对话框管理器
* 继承自 BaseDialogManager提供地图面板的对话框管理功能
*/
export class MapDialogManager extends BaseDialogManager {
/** 地图面板实例 */
private panel: MapPanel | null = null;
/** 对话框唯一标识 */
protected get dialogId() { return 'map-dialog'; }
/** 对话框标题(国际化 key */
protected get dialogTitle() { return 'map.dialogTitle'; }
/** 对话框宽度 */
protected get dialogWidth() { return 300; }
/** 对话框高度 */
protected get dialogHeight(): number { return 400; }
/** 初始化 */
public init(): void {}
/**
* 获取对话框位置
* 定位在容器左下角
*/
protected getDialogPosition() {
const container = this.registry.container;
if (!container) return { x: 20, y: 100 };
const paddingLeft = 20;
const paddingBottom = 20;
const containerHeight = container.clientHeight;
return {
x: paddingLeft,
y: containerHeight - this.dialogHeight - paddingBottom
};
}
/** 创建对话框内容 */
protected createContent(): HTMLElement {
this.panel = new MapPanel();
this.panel.init();
return this.panel.element;
}
/** 对话框创建后的回调 */
protected onDialogCreated(): void {
this.emit('map:opened', {});
}
/** 对话框关闭时的回调 */
protected onDialogClose(): void {
this.emit('map:closed', {});
}
/** 销毁前的清理 */
protected onBeforeDestroy(): void {
if (this.panel) {
this.panel.destroy();
this.panel = null;
}
}
/** 隐藏对话框 */
public hide(): void {
super.hide();
this.emit('map:closed', {});
}
}

View File

@@ -1,14 +1,15 @@
import { BaseDialogManager } from '../core/base-dialog-manager';
import { MeasurePanel } from '../components/measure-panel';
import type { MeasureConfig, MeasureResult } from '../components/measure-panel/types';
import { ENGINE_TYPE_TO_MODE, MEASURE_TYPES, type MeasureMode } from '../types/measure';
import { MEASURE_TYPES, getModeBycallBackType, getValueType, type MeasureMode, type CallBackType } from '../types/measure';
interface EngineMeasureData {
id: string;
point1?: { x: number; y: number; z: number };
point2?: { x: number; y: number; z: number };
text: number;
type: string;
textX?: number;
textY?: number;
textZ?: number;
type: CallBackType;
isSelect: boolean;
container: any;
}
@@ -80,20 +81,24 @@ export class MeasureDialogManager extends BaseDialogManager {
const engine = this.registry.engine3d?.getEngine();
if (engine?.events) {
const handler = (data: EngineMeasureData) => {
console.log('[MeasureDialogManager] 测量值变化:', data);
console.log('[MeasureDialogManager] 测量值回调:', data);
if (data && this.panel) {
this.handleMeasureChanged(data);
}
};
engine.events.on('measure-changed', handler);
this.unsubscribeMeasureChanged = () => engine.events.off('measure-changed', handler);
engine.events.on('measure-click', handler);
this.unsubscribeMeasureChanged = () => {
engine.events.off('measure-changed', handler);
engine.events.on('measure-click', handler);
}
}
}
private handleMeasureChanged(data: EngineMeasureData): void {
if (!this.panel) return;
const targetMode = ENGINE_TYPE_TO_MODE[data.type];
const targetMode = getModeBycallBackType(data.type);
if (!targetMode) {
console.warn('[MeasureDialogManager] 未知测量类型:', data.type);
return;
@@ -112,10 +117,12 @@ export class MeasureDialogManager extends BaseDialogManager {
const config = MEASURE_TYPES[mode];
const result: MeasureResult = {};
if (config.valueType === 'point' && data.point1) {
result.xyz = { x: data.point1.x, y: data.point1.y, z: data.point1.z };
if (getValueType(mode) === 'point') {
if (data.textX !== undefined && data.textY !== undefined && data.textZ !== undefined) {
result.xyz = { x: data.textX, y: data.textY, z: data.textZ };
}
} else {
(result as any)[config.resultField] = data.text;
(result as any)[config.callBackType] = data.text;
}
return result;

View File

@@ -57,14 +57,21 @@ export class SectionBoxDialogManager extends BaseDialogManager {
}
},
onReverseToggle: (isReversed) => {
// 底层暂不支持反向功能
console.log('[SectionBoxDialogManager] 反向切换(底层暂不支持):', isReversed);
console.log('[SectionBoxDialogManager] 反向切换:', isReversed);
// 底层 reverse() 为“切换一次”,这里不使用 isReversed 作为入参,只要用户点击就触发。
this.registry.engine3d?.reverseSection();
},
onFitToModel: () => {
console.log('[SectionBoxDialogManager] Fit to model not supported in new API');
// 对接底层 scaleBox():缩放剖切盒到场景整体包围盒
this.registry.engine3d?.scaleSectionBox();
},
onReset: () => {
console.log('[SectionBoxDialogManager] Reset not supported in new API');
// 重置定义:关闭剖切再打开剖切盒。
// UI 侧会自行将滑块强制恢复到 0-100并将隐藏/反向按钮恢复为关闭状态。
this.registry.engine3d?.deactivateSection();
this.registry.engine3d?.activeSection('box');
// 确保剖切可见(避免上一次处于隐藏状态导致“看起来没重置”)
this.registry.engine3d?.recoverSection();
},
onRangeChange: (range) => {
this.registry.engine3d?.setSectionBoxRange(range);

View File

@@ -41,12 +41,8 @@ export class WalkControlManager extends BaseManager {
this.panel = new WalkControlPanel({
onPlanViewToggle: (isActive) => {
console.log('[WalkControl] 地图:', isActive);
if (isActive) {
this.registry.map?.show();
} else {
this.registry.map?.hide();
}
console.log('[WalkControl] 地图:', isActive);
this.registry.engine3d?.toggleMiniMap();
this.emit('walk:plan-view-toggle', { isActive });
},
onPathModeToggle: (isActive) => {
@@ -94,17 +90,7 @@ export class WalkControlManager extends BaseManager {
});
this.panel.init();
if (this.registry.map?.isOpen()) {
this.panel.setPlanViewActive(true);
}
this.subscribe('map:opened', () => {
this.panel?.setPlanViewActive(true);
});
this.subscribe('map:closed', () => {
this.panel?.setPlanViewActive(false);
});
if (this.registry.container) {
this.panel.element.style.position = 'absolute';

View File

@@ -20,26 +20,26 @@ export class WalkPathDialogManager extends BaseDialogManager {
/** 对话框宽度 */
protected get dialogWidth() { return 300; }
/** 对话框高度 */
protected get dialogHeight(): number { return 400; }
protected get dialogHeight(): number { return 450; }
/** 初始化 */
public init(): void {}
/**
* 获取对话框位置
* 定位在容器右侧居中
* 定位在容器右上角
*/
protected getDialogPosition() {
const container = this.registry.container;
if (!container) return { x: 100, y: 100 };
const paddingRight = 20;
const paddingTop = 20;
const containerWidth = container.clientWidth;
const containerHeight = container.clientHeight;
return {
x: containerWidth - this.dialogWidth - paddingRight,
y: (containerHeight - this.dialogHeight) / 2
y: paddingTop
};
}