refactor(measure): centralize measure type config and migrate API
- Add unified MEASURE_TYPES config in types/measure.ts - Export MEASURE_MODES_ORDERED, ENGINE_TYPE_TO_MODE, MODE_TO_ENGINE_TYPE - Refactor measure-dialog-manager to use centralized config - Refactor measure-panel to use MEASURE_TYPES for icons/order/valueType - Simplify engine/index.ts measureMap with dynamic key access - Add measure settings API (saveMeasureSetting) with cache sync - Add direct engine event listening for measure-changed and section-move - Update i18n keys for 8 measure modes
This commit is contained in:
@@ -3,6 +3,7 @@ import { IBimComponent } from '../../types/component';
|
||||
import { themeManager } from '../../services/theme';
|
||||
import type { EngineOptions, ModelLoadOptions } from './types';
|
||||
import type { MeasureMode } from '../../types/measure';
|
||||
import type { MeasureUnit, MeasurePrecision } from '../measure-panel/types';
|
||||
import type { SectionBoxRange } from '../section-box-panel/types';
|
||||
import { ManagerRegistry } from '../../core/manager-registry';
|
||||
// 导入第三方 SDK 的 createEngine 函数(从 npm 包引入)
|
||||
@@ -142,12 +143,6 @@ export class Engine implements IBimComponent {
|
||||
}
|
||||
});
|
||||
|
||||
// 监听剖切值变化事件
|
||||
this.engine.events.on('section-move', (data: any) => {
|
||||
console.log('[Engine] 剖切值变化:', data);
|
||||
const registry = ManagerRegistry.getInstance();
|
||||
registry.emit('section:move', data);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[Engine] Failed to initialize engine:', error);
|
||||
this._isInitialized = false;
|
||||
@@ -246,189 +241,92 @@ export class Engine implements IBimComponent {
|
||||
|
||||
// ==================== 测量功能方法 ====================
|
||||
|
||||
// ==================== 测量功能(统一 API) ====================
|
||||
|
||||
/**
|
||||
* 激活具体测量类型的统一入口(私有方法)
|
||||
* @param type 测量类型
|
||||
* @param activateFunc 第三方引擎的激活函数
|
||||
* 激活测量功能
|
||||
* @param mode 测量类型
|
||||
* - 'clearHeight': 净高测量
|
||||
* - 'clearDistance': 净距测量
|
||||
* - 'distance': 距离测量 y
|
||||
* - 'elevation': 标高测量 y
|
||||
* - 'point': 坐标测量
|
||||
* - 'angle': 角度测量 y
|
||||
* - 'area': 面积测量
|
||||
* - 'slope': 坡度测量 y
|
||||
*/
|
||||
private activateMeasureType(type: MeasureMode, activateFunc: () => void): void {
|
||||
// 1. 检查引擎是否初始化
|
||||
if (!this._isInitialized || !this.engine) {
|
||||
console.error('Cannot activate measure: engine not initialized.');
|
||||
public activateMeasure(mode: MeasureMode): void {
|
||||
if (!this._isInitialized || !this.engine?.measure) {
|
||||
console.error('[Engine] Cannot activate measure: engine not initialized.');
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 检查 measure 模块是否存在
|
||||
if (!this.engine.measure) {
|
||||
console.error('Measure module not available.');
|
||||
if (this.currentMeasureType === mode) {
|
||||
console.log(`[Engine] Measure ${mode} already active, skipping.`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 先激活测量模块
|
||||
if (!this.isMeasureActive) {
|
||||
console.log('激活测量功能');
|
||||
console.log('[Engine] Activating measure module');
|
||||
this.engine.measure.active();
|
||||
this.isMeasureActive = true;
|
||||
}
|
||||
|
||||
// 4. 激活具体的测量类型(直接切换,不需要停用前一个)
|
||||
activateFunc();
|
||||
this.currentMeasureType = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* 激活距离测量
|
||||
*/
|
||||
public activateDistanceMeasure(): void {
|
||||
this.activateMeasureType('distance', () => {
|
||||
console.log(`激活距离测量`);
|
||||
this.engine.measure.distanceMeasure.active();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 激活最小距离测量
|
||||
*/
|
||||
public activateMinDistanceMeasure(): void {
|
||||
this.activateMeasureType('minDistance', () => {
|
||||
console.log(`激活最小距离测量`);
|
||||
this.engine.measure.clearDistanceMeasure.active();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 激活角度测量
|
||||
*/
|
||||
public activateAngleMeasure(): void {
|
||||
this.activateMeasureType('angle', () => {
|
||||
console.log(`激活角度测量`);
|
||||
this.engine.measure.angleMeasure.active();
|
||||
console.log('[Engine] Angle measure activated (placeholder)');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 激活标高测量
|
||||
*/
|
||||
public activateElevationMeasure(): void {
|
||||
this.activateMeasureType('elevation', () => {
|
||||
console.log(`激活标高测量`);
|
||||
this.engine.measure.elevationMeasure.active();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 激活体积测量
|
||||
*/
|
||||
public activateVolumeMeasure(): void {
|
||||
this.activateMeasureType('volume', () => {
|
||||
console.log(`激活体积测量`);
|
||||
this.engine.measure.areaMeasure.active();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 激活激光测距
|
||||
*/
|
||||
public activateLaserDistanceMeasure(): void {
|
||||
this.activateMeasureType('laserDistance', () => {
|
||||
// TODO: 调用第三方引擎方法(当前先空着)
|
||||
// this.engine.measure.laserDistanceMeasure.active();
|
||||
console.log('[Engine] Laser distance measure activated (placeholder)');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 激活坡度测量
|
||||
*/
|
||||
public activateSlopeMeasure(): void {
|
||||
this.activateMeasureType('slope', () => {
|
||||
console.log(`激活坡度测量`);
|
||||
this.engine.measure.slopeMeasure.active();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 激活空间体积测量
|
||||
*/
|
||||
public activateSpaceVolumeMeasure(): void {
|
||||
this.activateMeasureType('spaceVolume', () => {
|
||||
// TODO: 调用第三方引擎方法(当前先空着)
|
||||
// this.engine.measure.spaceVolumeMeasure.active();
|
||||
console.log('[Engine] Space volume measure activated (placeholder)');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 激活测量功能(根据类型统一入口)
|
||||
* @param mode 测量类型
|
||||
*/
|
||||
public activateMeasure(mode: MeasureMode): void {
|
||||
switch (mode) {
|
||||
case 'distance':
|
||||
this.activateDistanceMeasure();
|
||||
break;
|
||||
case 'minDistance':
|
||||
this.activateMinDistanceMeasure();
|
||||
break;
|
||||
case 'angle':
|
||||
this.activateAngleMeasure();
|
||||
break;
|
||||
case 'elevation':
|
||||
this.activateElevationMeasure();
|
||||
break;
|
||||
case 'volume':
|
||||
this.activateVolumeMeasure();
|
||||
break;
|
||||
case 'laserDistance':
|
||||
this.activateLaserDistanceMeasure();
|
||||
break;
|
||||
case 'slope':
|
||||
this.activateSlopeMeasure();
|
||||
break;
|
||||
case 'spaceVolume':
|
||||
this.activateSpaceVolumeMeasure();
|
||||
break;
|
||||
console.log(`[Engine] Activating measure: ${mode}`);
|
||||
const measureKey = `${mode}Measure`;
|
||||
const measureInstance = (this.engine.measure as any)[measureKey];
|
||||
if (measureInstance && typeof measureInstance.active === 'function') {
|
||||
measureInstance.active();
|
||||
this.currentMeasureType = mode;
|
||||
} else {
|
||||
console.error(`[Engine] Measure type ${mode} not available.`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 停用测量功能(关闭测量时调用)
|
||||
*/
|
||||
public deactivateMeasure(): void {
|
||||
if (!this._isInitialized || !this.engine?.measure) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.isMeasureActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('停用测量功能');
|
||||
this.engine.measure.disActive();
|
||||
this.isMeasureActive = false;
|
||||
this.currentMeasureType = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前激活的测量类型
|
||||
* @returns 当前测量类型,如果未激活则返回 null
|
||||
* @returns 当前测量类型,未激活时返回 null
|
||||
*/
|
||||
public getCurrentMeasureType(): MeasureMode | null {
|
||||
return this.currentMeasureType;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除所有测量标注
|
||||
* 停用测量功能
|
||||
* @remarks 关闭测量时调用,会同时清除所有测量标注
|
||||
*/
|
||||
public deactivateMeasure(): void {
|
||||
if (!this._isInitialized || !this.engine?.measure) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[Engine] Deactivating measure');
|
||||
this.engine.measure.disActive();
|
||||
this.engine.measure.clearAll();
|
||||
this.isMeasureActive = false;
|
||||
this.currentMeasureType = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除所有测量标注(不停用测量模式)
|
||||
*/
|
||||
public clearAllMeasures(): void {
|
||||
if (!this._isInitialized || !this.engine?.measure) {
|
||||
return;
|
||||
}
|
||||
console.log('清除所有测量标注');
|
||||
console.log('[Engine] Clearing all measures');
|
||||
this.engine.measure.clearAll();
|
||||
}
|
||||
|
||||
// ==================== 结束:测量功能方法 ====================
|
||||
public saveMeasureSetting(setting: { unit: MeasureUnit; precision: MeasurePrecision }): void {
|
||||
if (!this._isInitialized || !this.engine?.measure) {
|
||||
return;
|
||||
}
|
||||
this.engine.measure.saveSetting?.(setting);
|
||||
}
|
||||
|
||||
// ==================== 结束:测量功能 ====================
|
||||
|
||||
// ==================== 剖切功能(统一 API) ====================
|
||||
|
||||
@@ -503,7 +401,6 @@ export class Engine implements IBimComponent {
|
||||
console.error('[Engine] Cannot set section box range: engine not initialized.');
|
||||
return;
|
||||
}
|
||||
console.log('[Engine] Setting section box range:', range);
|
||||
this.engine.clipping.updateClippingValue(range);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,26 +3,8 @@ import type { ThemeConfig } from '../../themes/types';
|
||||
import { IBimComponent } from '../../types/component';
|
||||
import { localeManager, t } from '../../services/locale';
|
||||
import { themeManager } from '../../services/theme';
|
||||
import type { MeasureConfig, MeasureMode, MeasurePanelOptions, MeasurePrecision, MeasureResult, MeasureUnit } from './types';
|
||||
|
||||
/**
|
||||
* 测量方式图标(SVG)
|
||||
*
|
||||
* 说明:
|
||||
* - 你上传的 SVG 原文件放在 `src/assets/icons/` 目录
|
||||
* - 原始 SVG 含 defs/clipPath/style/背景 rect,直接内联时容易出现渲染/裁剪异常(尤其多个图标同时出现)
|
||||
* - 这里把图标“瘦身”为纯 path,并统一使用 currentColor,确保稳定渲染
|
||||
*/
|
||||
const MEASURE_MODE_ICON_SVGS: Record<MeasureMode, string> = {
|
||||
distance: `<svg viewBox="0 0 32 32" aria-hidden="true"><g transform="translate(0 4.197)"><path fill="currentColor" d="M29.692,3.03,27.55.919a.529.529,0,0,1-.014-.756A.549.549,0,0,1,28.3.15l.014.013,3.067,3.023a.529.529,0,0,1,0,.756L28.317,6.966a.549.549,0,0,1-.767.013.529.529,0,0,1-.014-.756l.014-.013L29.692,4.1H2.31L4.452,6.21a.528.528,0,0,1,.013.756.547.547,0,0,1-.766.013l-.014-.013L.616,3.942a.531.531,0,0,1,0-.756L3.685.163a.548.548,0,0,1,.767.014.528.528,0,0,1,0,.742L2.31,3.03ZM24.136,15.055H23.051V18H21.966v-2.94H20.882V18H19.8v-2.94H18.712V18H17.627v-2.94H16.543v5.078H15.458V15.055H14.373V18H13.288v-2.94H12.2V18H11.119v-2.94H10.034V18H8.949v-2.94H7.865V18H6.78v-2.94H5.7v5.078H4.61V15.055H1.9a.27.27,0,0,0-.272.268v6.413A.269.269,0,0,0,1.9,22H30.1a.268.268,0,0,0,.271-.267V15.323a.269.269,0,0,0-.271-.268H27.39v5.078H26.305V15.055H25.221V18H24.136Zm5.966-1.6A1.884,1.884,0,0,1,32,15.323v6.413a1.885,1.885,0,0,1-1.9,1.871H1.9A1.885,1.885,0,0,1,0,21.736V15.323a1.885,1.885,0,0,1,1.9-1.871Z"/></g></svg>`,
|
||||
minDistance: `<svg viewBox="0 0 32 32" aria-hidden="true"><path fill="currentColor" d="M-5.839,24.8H-34.16A1.875,1.875,0,0,1-36,22.933V16.52a1.887,1.887,0,0,1,1.9-1.871H-5.9A1.887,1.887,0,0,1-4,16.52v6.412A1.875,1.875,0,0,1-5.839,24.8ZM-34.1,16.252a.27.27,0,0,0-.272.268v6.412a.27.27,0,0,0,.272.267H-5.9a.269.269,0,0,0,.271-.267V16.52a.27.27,0,0,0-.271-.268H-8.61V21.33H-9.695V16.252h-1.085v2.939h-1.085V16.252h-1.085v2.939h-1.085V16.252h-1.084v2.939H-16.2V16.252h-1.085v2.939h-1.085V16.252h-1.084V21.33h-1.084V16.252h-1.085v2.939h-1.085V16.252H-23.8v2.939h-1.085V16.252h-1.085v2.939h-1.085V16.252h-1.084v2.939H-29.22V16.252H-30.3V21.33H-31.39V16.252Z" transform="translate(36 2)"/><path fill="currentColor" d="M23.716,7.947V4.875c0-.8-.232-1.085-.765-1.085a1.573,1.573,0,0,0-1.133.585V7.947H20.4V2.75h1.163l.1.687H21.7a2.547,2.547,0,0,1,1.763-.817c1.172,0,1.676.78,1.676,2.089V7.947Zm-7.26,0V2.62h1.58V7.947Zm-3.8,0V4.875c0-.8-.243-1.085-.76-1.085a1.606,1.606,0,0,0-1.049.585V7.947H9.421V4.875c0-.8-.243-1.085-.758-1.085a1.608,1.608,0,0,0-1.05.585V7.947H6.194V2.75H7.36l.1.7H7.5A2.326,2.326,0,0,1,9.169,2.62a1.486,1.486,0,0,1,1.5.91A2.445,2.445,0,0,1,12.4,2.62c1.156,0,1.691.78,1.691,2.089V7.947Zm3.8-6.849a.79.79,0,0,1,1.58,0,.79.79,0,0,1-1.58,0Z" transform="translate(0.333 3.053)"/></svg>`,
|
||||
angle: `<svg viewBox="0 0 32 32" aria-hidden="true"><path fill="currentColor" d="M39.587,50.766h13.7a1,1,0,0,1,0,2H23.171a1,1,0,0,1,0-2h1.418l6.582-7.006v-.006a.517.517,0,0,1,.14-.357.456.456,0,0,1,.337-.144l12.1-12.876a.451.451,0,0,1,.665,0,.524.524,0,0,1,0,.708L32.883,43.355a8.3,8.3,0,0,1,6.7,7.411Zm-.949,0a7.254,7.254,0,0,0-6.611-6.5l-6.108,6.5Z" transform="translate(-22.229 -26.489)"/></svg>`,
|
||||
elevation: `<svg viewBox="0 0 32 32" aria-hidden="true"><path fill="currentColor" d="M84.131,193.119a1.056,1.056,0,0,1,1.116.982v7.857a1.056,1.056,0,0,1-1.116.982H54.367a1.056,1.056,0,0,1-1.116-.982V194.1a1.056,1.056,0,0,1,1.116-.982Zm-1.116,1.964H55.483v5.893H83.015Zm1.116-13.749a1.064,1.064,0,0,1,1.114.935,1.032,1.032,0,0,1-1.007,1.025l-.107,0H71.2l-7.858,6.914a1.227,1.227,0,0,1-1.578,0l-8.185-7.2-.018-.016-.032-.031.049.047a1.107,1.107,0,0,1-.092-.092l-.011-.014a.869.869,0,0,1-.182-.857l0-.008L53.31,182l.012-.029.02-.045.019-.035a1.1,1.1,0,0,1,.891-.552h.007q.053,0,.107,0ZM68.043,183.3H57.06l5.492,4.831Z" transform="translate(-53.247 -176.136)"/></svg>`,
|
||||
volume: `<svg viewBox="0 0 32 32" aria-hidden="true"><path fill="currentColor" d="M94.74,86.658V71.189a.371.371,0,0,1,.2-.329l13.869-7.22a.371.371,0,0,1,.344,0l13.053,6.891h0l.819.431a.371.371,0,0,1,.2.328v15.3a.371.371,0,0,1-.2.328l-13.872,7.255a.371.371,0,0,1-.342,0L94.94,86.987a.371.371,0,0,1-.2-.329Zm2.119-.837,11.2,5.8a.024.024,0,0,0,.035-.022V79.483a.371.371,0,0,0-.2-.328l-11.2-5.909a.024.024,0,0,0-.035.021V85.492A.371.371,0,0,0,96.859,85.821Zm13.151-6.459v12a.12.12,0,0,0,.176.106L114,89.474l3.334-1.745,3.771-1.978a.371.371,0,0,0,.2-.328V73.5a.193.193,0,0,0-.284-.171l-10.812,5.708A.371.371,0,0,0,110.01,79.362ZM97.925,71.725l10.839,5.72a.371.371,0,0,0,.346,0L119.8,71.808a.214.214,0,0,0,0-.378l-10.649-5.621a.371.371,0,0,0-.344,0L97.925,71.47A.144.144,0,0,0,97.925,71.725Z" transform="translate(-92.982 -62.907)"/></svg>`,
|
||||
laserDistance: `<svg viewBox="0 0 32 32" aria-hidden="true"><g transform="translate(0 -1.293)"><path fill="currentColor" d="M0,1.293v31.96H32V1.293ZM30.97,32.182H1.03V2.323H30.97Z"/><path fill="currentColor" d="M160.026,291.9l1.6,1.6,7.305-7.305-7.305-7.305-1.6,1.6,4.794,4.566h-6.392v2.283h6.392Zm-5.251,0-4.566-4.566h6.164v-2.283H150.21l4.566-4.566-1.37-1.6L146.1,286.19l7.305,7.305Z" transform="translate(-141.535 -268.917)"/></g></svg>`,
|
||||
slope: `<svg viewBox="0 0 32 32" aria-hidden="true"><path fill="currentColor" d="M202.1,188.337l2.629-2.191-8.447-3.106,1.533,8.871,2.629-2.194,9.341,11.209,1.656-1.379Zm-13.726-.435a1.075,1.075,0,0,0-1.07-.341,1.057,1.057,0,0,0-.5.277l-5.11,4.08a1.08,1.08,0,0,0-.406.84l-.007,17.386a1.079,1.079,0,0,0,1.077,1.077L205.7,211.2a1.078,1.078,0,0,0,.822-1.774Zm-4.934,21.164.007-15.788,3.968-3.171,15.974,18.941Z" transform="translate(-180.36 -181.131)"/></svg>`,
|
||||
spaceVolume: `<svg viewBox="0 0 32 32" aria-hidden="true"><g transform="translate(-106.35 -97.661)"><path fill="currentColor" d="M125.977,128.829l13.076-7.363v-13.6l-13.076,6.8Zm-3.126-15.655a.565.565,0,0,1-.258-.064L109.3,106.323a.567.567,0,0,1-.011-1L122.578,98a.567.567,0,0,1,.55,0l13.288,7.325a.567.567,0,0,1-.011,1l-13.292,6.79A.63.63,0,0,1,122.851,113.174ZM110.773,105.8l12.078,6.172,12.078-6.172-12.078-6.657Z" transform="translate(-1.922)"/><path fill="currentColor" d="M120.649,322.52a.58.58,0,0,1-.262-.064l-13.08-6.8a.573.573,0,0,1-.307-.5V301a.566.566,0,0,1,.273-.486.573.573,0,0,1,.558-.019l13.076,6.8a.573.573,0,0,1,.307.5v14.161a.57.57,0,0,1-.565.569Zm-12.511-7.708,11.942,6.206V308.136l-11.942-6.206Zm15.917,9.408a.585.585,0,0,1-.288-.076.567.567,0,0,1-.281-.489V309.49a.562.562,0,0,1,.307-.5l13.076-6.8a.573.573,0,0,1,.558.019.562.562,0,0,1,.273.486v13.6a.568.568,0,0,1-.288.493l-13.076,7.359A.557.557,0,0,1,124.055,324.22Zm.569-14.385V322.68l11.942-6.722V303.629Z" transform="translate(0 -194.822)"/></g></svg>`
|
||||
};
|
||||
import type { MeasureConfig, MeasurePanelOptions, MeasurePrecision, MeasureResult, MeasureUnit } from './types';
|
||||
import { MEASURE_TYPES, MEASURE_MODES_ORDERED, type MeasureMode } from '../../types/measure';
|
||||
|
||||
/**
|
||||
* 测量面板组件(只做 UI,不实现真实测量)
|
||||
@@ -242,36 +224,23 @@ export class MeasurePanel implements IBimComponent {
|
||||
return this.activeMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换测量方式(你要求的“切换类型的方法”)
|
||||
* @param mode 目标测量方式
|
||||
*/
|
||||
public switchMode(mode: MeasureMode): void {
|
||||
this.setActiveMode(mode);
|
||||
public switchMode(mode: MeasureMode, triggerCallback: boolean = true): void {
|
||||
this.setActiveMode(mode, triggerCallback);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前测量方式
|
||||
* @param mode 目标测量方式
|
||||
*/
|
||||
public setActiveMode(mode: MeasureMode): void {
|
||||
public setActiveMode(mode: MeasureMode, triggerCallback: boolean = true): void {
|
||||
if (this.activeMode === mode) return;
|
||||
this.activeMode = mode;
|
||||
this.applyActiveModeState();
|
||||
|
||||
// 切换方式后,主值 label 也需要更新
|
||||
this.mainValueLabelEl.textContent = t(this.getModeValueLabelI18nKey(this.activeMode));
|
||||
|
||||
// 通知外部(如果需要)
|
||||
if (this.options.onModeChange) {
|
||||
if (triggerCallback && this.options.onModeChange) {
|
||||
this.options.onModeChange(mode);
|
||||
}
|
||||
|
||||
// 模式切换后,结果展示也应刷新(例如某些字段显示为 --)
|
||||
this.renderResult();
|
||||
|
||||
// 切换模式会影响结果区高度(例如 distance 显示 xyz,其它不显示)
|
||||
// 复用 onExpandedChange 来通知外部重新计算 Dialog 高度(不额外扩展回调,保持接口简单)
|
||||
if (this.options.onExpandedChange) {
|
||||
this.options.onExpandedChange(this.isExpanded);
|
||||
}
|
||||
@@ -386,38 +355,23 @@ export class MeasurePanel implements IBimComponent {
|
||||
const grid = document.createElement('div');
|
||||
grid.className = 'bim-measure-tool-grid';
|
||||
|
||||
// 8 种测量方式(顺序严格按你给的)
|
||||
const modes: MeasureMode[] = [
|
||||
'distance',
|
||||
'minDistance',
|
||||
'angle',
|
||||
'elevation',
|
||||
'volume',
|
||||
'laserDistance',
|
||||
'slope',
|
||||
'spaceVolume'
|
||||
];
|
||||
|
||||
// 图标:优先使用你上传的 SVG 文件内容(已内联到 MEASURE_MODE_ICON_SVGS)
|
||||
// 兜底:如果某个 mode 没有配置图标,则使用圆形占位(防止页面空白)
|
||||
const fallbackCircleIconSvg = `
|
||||
<svg viewBox="0 0 24 24" aria-hidden="true">
|
||||
<circle cx="12" cy="12" r="9"></circle>
|
||||
</svg>
|
||||
`;
|
||||
|
||||
// 逐个创建按钮
|
||||
for (let i = 0; i < modes.length; i++) {
|
||||
const mode = modes[i];
|
||||
for (let i = 0; i < MEASURE_MODES_ORDERED.length; i++) {
|
||||
const mode = MEASURE_MODES_ORDERED[i];
|
||||
const config = MEASURE_TYPES[mode];
|
||||
const btn = document.createElement('button');
|
||||
btn.type = 'button';
|
||||
btn.className = 'bim-measure-tool-btn';
|
||||
btn.dataset.mode = mode;
|
||||
|
||||
// icon
|
||||
const icon = document.createElement('span');
|
||||
icon.className = 'bim-measure-tool-icon';
|
||||
icon.innerHTML = MEASURE_MODE_ICON_SVGS[mode] || fallbackCircleIconSvg;
|
||||
icon.innerHTML = config.icon || fallbackCircleIconSvg;
|
||||
btn.appendChild(icon);
|
||||
|
||||
// 点击切换模式
|
||||
@@ -680,13 +634,10 @@ export class MeasurePanel implements IBimComponent {
|
||||
this.view = 'main';
|
||||
this.applyViewState();
|
||||
|
||||
// 配置变化会影响显示
|
||||
this.renderResult();
|
||||
|
||||
// 高度变化(设置面板 -> 主面板)也需要通知外部
|
||||
if (this.options.onExpandedChange) {
|
||||
this.options.onExpandedChange(this.isExpanded);
|
||||
}
|
||||
this.options.onConfigSave?.(next);
|
||||
this.options.onExpandedChange?.(this.isExpanded);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -822,26 +773,14 @@ export class MeasurePanel implements IBimComponent {
|
||||
// - 坡度:--%
|
||||
// - 空间体积:--mm³(单位随设置变动,即 unit³)
|
||||
|
||||
// 1.1) 主行:默认显示 label + value(数值/单位拆分)
|
||||
// 激光测距:只显示文字,因此隐藏 label/单位
|
||||
if (this.activeMode === 'laserDistance') {
|
||||
this.mainValueLabelEl.style.display = 'none';
|
||||
this.mainNumberEl.textContent = t(this.getModeI18nKey('laserDistance'));
|
||||
this.mainUnitEl.textContent = '';
|
||||
// 激光测距:你要求不使用黄色主数据
|
||||
this.mainNumberEl.classList.add('is-laser-text');
|
||||
} else {
|
||||
this.mainValueLabelEl.style.display = '';
|
||||
this.mainValueLabelEl.textContent = t(this.getModeValueLabelI18nKey(this.activeMode));
|
||||
const parts = this.formatMainValueParts(this.activeMode, this.result);
|
||||
this.mainNumberEl.textContent = parts.numberText;
|
||||
this.mainUnitEl.textContent = parts.unitText;
|
||||
// 其它模式:恢复黄色主数据
|
||||
this.mainNumberEl.classList.remove('is-laser-text');
|
||||
}
|
||||
this.mainValueLabelEl.style.display = '';
|
||||
this.mainValueLabelEl.textContent = t(this.getModeValueLabelI18nKey(this.activeMode));
|
||||
const parts = this.formatMainValueParts(this.activeMode, this.result);
|
||||
this.mainNumberEl.textContent = parts.numberText;
|
||||
this.mainUnitEl.textContent = parts.unitText;
|
||||
|
||||
// 1.2) XYZ:只有“距离”需要展示
|
||||
if (this.activeMode === 'distance') {
|
||||
const showXyz = this.activeMode === 'distance' || this.activeMode === 'point';
|
||||
if (showXyz) {
|
||||
this.xyzBoxEl.style.display = '';
|
||||
const xyz = this.result?.xyz;
|
||||
if (!xyz) {
|
||||
@@ -856,13 +795,9 @@ export class MeasurePanel implements IBimComponent {
|
||||
return;
|
||||
}
|
||||
|
||||
// 非 distance:隐藏 xyz
|
||||
this.xyzBoxEl.style.display = 'none';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取模式名称的国际化 key
|
||||
*/
|
||||
private getModeI18nKey(mode: MeasureMode): string {
|
||||
return `measure.modes.${mode}`;
|
||||
}
|
||||
@@ -887,103 +822,47 @@ export class MeasurePanel implements IBimComponent {
|
||||
|
||||
// 注意:旧的 formatLengthWithConfig 已被 formatLengthParts 替代。
|
||||
|
||||
private convertMmToUnit(mm: number, unit: MeasureUnit): number {
|
||||
switch (unit) {
|
||||
case 'mm':
|
||||
return mm;
|
||||
case 'cm':
|
||||
return mm / 10;
|
||||
case 'm':
|
||||
return mm / 1000;
|
||||
case 'km':
|
||||
return mm / 1_000_000;
|
||||
default:
|
||||
return mm;
|
||||
}
|
||||
}
|
||||
|
||||
private getUnitI18nKey(unit: MeasureUnit): string {
|
||||
return `measure.units.${unit}`;
|
||||
}
|
||||
|
||||
// 注意:旧的 formatElevationFixedMeters / formatVolumeWithConfig 已被 formatMainValueParts 替代。
|
||||
|
||||
private convertMm3ToUnit3(mm3: number, unit: MeasureUnit): number {
|
||||
// 先把 mm³ -> 对应 unit³
|
||||
// mm -> cm: /10,因此 mm³ -> cm³: /1000
|
||||
// mm -> m : /1000,因此 mm³ -> m³ : /1e9
|
||||
// mm -> km: /1e6,因此 mm³ -> km³: /1e18
|
||||
switch (unit) {
|
||||
case 'mm':
|
||||
return mm3;
|
||||
case 'cm':
|
||||
return mm3 / 1000;
|
||||
case 'm':
|
||||
return mm3 / 1_000_000_000;
|
||||
case 'km':
|
||||
return mm3 / 1_000_000_000_000_000_000;
|
||||
default:
|
||||
return mm3;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 主数据拆分:返回 { 数值文本, 单位文本 }
|
||||
* 规则:
|
||||
* - 没数据时:必须展示 `-- 单位`(而不是只展示 `--`)
|
||||
* - 单位随模式变化:
|
||||
* - 距离/最小距离:单位随设置变动
|
||||
* - 角度:°
|
||||
* - 标高:固定 m
|
||||
* - 体积/空间体积:单位³(随设置变动)
|
||||
* - 坡度:%
|
||||
*/
|
||||
private formatMainValueParts(mode: MeasureMode, result: MeasureResult | null): { numberText: string; unitText: string } {
|
||||
if (mode === 'laserDistance') return { numberText: t(this.getModeI18nKey('laserDistance')), unitText: '' };
|
||||
|
||||
// 没有数据:显示 `-- 单位`
|
||||
if (!result) {
|
||||
return this.getEmptyValuePartsByMode(mode);
|
||||
}
|
||||
|
||||
switch (mode) {
|
||||
case 'distance':
|
||||
return this.formatLengthParts(result.distanceMm);
|
||||
case 'minDistance':
|
||||
return this.formatLengthParts(result.minDistanceMm);
|
||||
const config = MEASURE_TYPES[mode];
|
||||
const value = (result as any)[config.resultField];
|
||||
|
||||
switch (config.valueType) {
|
||||
case 'length':
|
||||
case 'area':
|
||||
return this.formatMeasureValue(value, config.valueType);
|
||||
case 'angle':
|
||||
return this.formatFixedUnitParts(result.angleDeg, t('measure.units.deg'));
|
||||
case 'elevation':
|
||||
// 标高固定 m(外部注入值约定为 mm)
|
||||
return this.formatFixedUnitParts(
|
||||
result.elevationMm === undefined ? undefined : result.elevationMm / 1000,
|
||||
t('measure.units.m')
|
||||
);
|
||||
case 'volume':
|
||||
return this.formatVolumeParts(result.volumeM3);
|
||||
case 'slope':
|
||||
return this.formatFixedUnitParts(result.slopePercent, t('measure.units.percent'));
|
||||
case 'spaceVolume':
|
||||
return this.formatVolumeParts(result.spaceVolumeM3);
|
||||
return this.formatFixedUnitParts(value, t('measure.units.deg'));
|
||||
case 'percent':
|
||||
return this.formatFixedUnitParts(value, t('measure.units.percent'));
|
||||
case 'point':
|
||||
return { numberText: '--', unitText: '' };
|
||||
default:
|
||||
return { numberText: '--', unitText: '' };
|
||||
}
|
||||
}
|
||||
|
||||
private getEmptyValuePartsByMode(mode: MeasureMode): { numberText: string; unitText: string } {
|
||||
switch (mode) {
|
||||
case 'distance':
|
||||
case 'minDistance':
|
||||
const config = MEASURE_TYPES[mode];
|
||||
|
||||
switch (config.valueType) {
|
||||
case 'length':
|
||||
return { numberText: '--', unitText: t(this.getUnitI18nKey(this.config.unit)) };
|
||||
case 'area':
|
||||
return { numberText: '--', unitText: `${this.config.unit}²` };
|
||||
case 'angle':
|
||||
return { numberText: '--', unitText: t('measure.units.deg') };
|
||||
case 'elevation':
|
||||
return { numberText: '--', unitText: t('measure.units.m') };
|
||||
case 'volume':
|
||||
case 'spaceVolume':
|
||||
return { numberText: '--', unitText: `${this.config.unit}³` };
|
||||
case 'slope':
|
||||
case 'percent':
|
||||
return { numberText: '--', unitText: t('measure.units.percent') };
|
||||
case 'point':
|
||||
return { numberText: '--', unitText: '' };
|
||||
default:
|
||||
return { numberText: '--', unitText: '' };
|
||||
}
|
||||
@@ -996,22 +875,32 @@ export class MeasurePanel implements IBimComponent {
|
||||
return { numberText: this.formatNumberWithPrecision(value, this.config.precision), unitText };
|
||||
}
|
||||
|
||||
private formatLengthParts(valueMm: number | undefined): { numberText: string; unitText: string } {
|
||||
const unitText = t(this.getUnitI18nKey(this.config.unit));
|
||||
if (valueMm === null || valueMm === undefined || Number.isNaN(valueMm)) {
|
||||
private formatMeasureValue(value: number | undefined, type: 'length' | 'area'): { numberText: string; unitText: string } {
|
||||
const unit = this.config.unit;
|
||||
const unitText = type === 'area' ? `${unit}²` : t(this.getUnitI18nKey(unit));
|
||||
|
||||
if (value === null || value === undefined || Number.isNaN(value)) {
|
||||
return { numberText: '--', unitText };
|
||||
}
|
||||
const converted = this.convertMmToUnit(valueMm, this.config.unit);
|
||||
return { numberText: this.formatNumberWithPrecision(converted, this.config.precision), unitText };
|
||||
}
|
||||
|
||||
private formatVolumeParts(valueMm3: number | undefined): { numberText: string; unitText: string } {
|
||||
const unitText = `${this.config.unit}³`;
|
||||
if (valueMm3 === null || valueMm3 === undefined || Number.isNaN(valueMm3)) {
|
||||
return { numberText: '--', unitText };
|
||||
let converted: number;
|
||||
if (type === 'length') {
|
||||
switch (unit) {
|
||||
case 'mm': converted = value * 1000; break;
|
||||
case 'cm': converted = value * 100; break;
|
||||
case 'km': converted = value / 1000; break;
|
||||
default: converted = value;
|
||||
}
|
||||
} else {
|
||||
switch (unit) {
|
||||
case 'mm': converted = value * 1000 * 1000; break;
|
||||
case 'cm': converted = value * 100 * 100; break;
|
||||
case 'km': converted = value / 1000 / 1000; break;
|
||||
default: converted = value;
|
||||
}
|
||||
}
|
||||
const converted = this.convertMm3ToUnit3(valueMm3, this.config.unit);
|
||||
return { numberText: this.formatNumberWithPrecision(converted, this.config.precision), unitText };
|
||||
|
||||
return { numberText: converted.toFixed(this.config.precision), unitText };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -50,24 +50,21 @@ export interface MeasureXYZ {
|
||||
* - 不同测量方式对应不同字段;未传入则 UI 显示 “--”。
|
||||
*/
|
||||
export interface MeasureResult {
|
||||
/** 净高(单位:mm) */
|
||||
clearHeightMm?: number;
|
||||
/** 净距(单位:mm) */
|
||||
clearDistanceMm?: number;
|
||||
/** 距离(单位:mm) */
|
||||
distanceMm?: number;
|
||||
/** 最小距离(单位:mm) */
|
||||
minDistanceMm?: number;
|
||||
/** 角度(单位:deg) */
|
||||
angleDeg?: number;
|
||||
/** 标高(单位:mm) */
|
||||
elevationMm?: number;
|
||||
/** 体积(单位:m³) */
|
||||
volumeM3?: number;
|
||||
/** 激光测距(单位:mm) */
|
||||
laserDistanceMm?: number;
|
||||
/** 角度(单位:deg) */
|
||||
angleDeg?: number;
|
||||
/** 面积(单位:m²) */
|
||||
areaM2?: number;
|
||||
/** 坡度(单位:%) */
|
||||
slopePercent?: number;
|
||||
/** 空间体积(单位:m³) */
|
||||
spaceVolumeM3?: number;
|
||||
|
||||
/** 可选:展示测量点/结果点坐标(单位由引擎侧定义,这里只负责显示) */
|
||||
/** 坐标测量点(单位由引擎侧定义) */
|
||||
xyz?: MeasureXYZ;
|
||||
}
|
||||
|
||||
@@ -102,6 +99,12 @@ export interface MeasurePanelOptions {
|
||||
* 说明:用于让外部(如 Dialog)重新计算尺寸。
|
||||
*/
|
||||
onExpandedChange?: (expanded: boolean) => void;
|
||||
|
||||
/**
|
||||
* 设置保存回调
|
||||
* 说明:用户点击"保存设置"时触发,用于同步到引擎
|
||||
*/
|
||||
onConfigSave?: (config: MeasureConfig) => void;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -76,14 +76,14 @@ export const enUS: TranslationDictionary = {
|
||||
btnName: 'Measure',
|
||||
dialogTitle: 'Measure',
|
||||
modes: {
|
||||
clearHeight: 'Clear Height',
|
||||
clearDistance: 'Clear Distance',
|
||||
distance: 'Distance',
|
||||
minDistance: 'Min Distance',
|
||||
angle: 'Angle',
|
||||
elevation: 'Elevation',
|
||||
volume: 'Volume',
|
||||
laserDistance: 'Laser Distance',
|
||||
point: 'Point',
|
||||
angle: 'Angle',
|
||||
area: 'Area',
|
||||
slope: 'Slope',
|
||||
spaceVolume: 'Space Volume',
|
||||
},
|
||||
actions: {
|
||||
expand: 'Expand',
|
||||
@@ -97,14 +97,14 @@ export const enUS: TranslationDictionary = {
|
||||
y: 'Y:',
|
||||
z: 'Z:',
|
||||
value: {
|
||||
clearHeight: 'Clear Height:',
|
||||
clearDistance: 'Clear Distance:',
|
||||
distance: 'Distance:',
|
||||
minDistance: 'Min Distance:',
|
||||
angle: 'Angle:',
|
||||
elevation: 'Elevation:',
|
||||
volume: 'Volume:',
|
||||
laserDistance: 'Laser Distance:',
|
||||
point: 'Point:',
|
||||
angle: 'Angle:',
|
||||
area: 'Area:',
|
||||
slope: 'Slope:',
|
||||
spaceVolume: 'Space Volume:',
|
||||
}
|
||||
},
|
||||
units: {
|
||||
@@ -113,14 +113,14 @@ export const enUS: TranslationDictionary = {
|
||||
m: 'm',
|
||||
km: 'km',
|
||||
deg: '°',
|
||||
m3: 'm³',
|
||||
m2: 'm²',
|
||||
percent: '%',
|
||||
},
|
||||
settings: {
|
||||
title: 'Settings',
|
||||
unit: 'Unit:',
|
||||
precision: 'Precision:',
|
||||
hint: 'Distance, min distance and elevation use this unit by default; angle and volume use their own units.',
|
||||
hint: 'Distance, clear distance, clear height and elevation use this unit by default; angle and area use their own units.',
|
||||
save: 'Save',
|
||||
cancel: 'Cancel',
|
||||
}
|
||||
|
||||
@@ -79,60 +79,45 @@ export interface TranslationDictionary {
|
||||
btnName: string;
|
||||
dialogTitle: string;
|
||||
|
||||
/**
|
||||
* 8 种测量方式名称(用于 UI 按钮 tooltip、当前方式显示等)
|
||||
*/
|
||||
modes: {
|
||||
clearHeight: string;
|
||||
clearDistance: string;
|
||||
distance: string;
|
||||
minDistance: string;
|
||||
angle: string;
|
||||
elevation: string;
|
||||
volume: string;
|
||||
laserDistance: string;
|
||||
point: string;
|
||||
angle: string;
|
||||
area: string;
|
||||
slope: string;
|
||||
spaceVolume: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* 操作按钮文案
|
||||
*/
|
||||
actions: {
|
||||
expand: string;
|
||||
collapse: string;
|
||||
clearAll: string;
|
||||
settings: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* 结果区标签
|
||||
*/
|
||||
labels: {
|
||||
currentMode: string;
|
||||
x: string;
|
||||
y: string;
|
||||
z: string;
|
||||
value: {
|
||||
clearHeight: string;
|
||||
clearDistance: string;
|
||||
distance: string;
|
||||
minDistance: string;
|
||||
angle: string;
|
||||
elevation: string;
|
||||
volume: string;
|
||||
laserDistance: string;
|
||||
point: string;
|
||||
angle: string;
|
||||
area: string;
|
||||
slope: string;
|
||||
spaceVolume: string;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 单位(也走国际化,避免硬编码)
|
||||
*/
|
||||
units: {
|
||||
mm: string;
|
||||
cm: string;
|
||||
m: string;
|
||||
km: string;
|
||||
deg: string;
|
||||
m3: string;
|
||||
m2: string;
|
||||
percent: string;
|
||||
};
|
||||
|
||||
|
||||
@@ -76,14 +76,14 @@ export const zhCN: TranslationDictionary = {
|
||||
btnName: '测量',
|
||||
dialogTitle: '测量',
|
||||
modes: {
|
||||
clearHeight: '净高',
|
||||
clearDistance: '净距',
|
||||
distance: '距离',
|
||||
minDistance: '最小距离',
|
||||
angle: '角度',
|
||||
elevation: '标高',
|
||||
volume: '体积',
|
||||
laserDistance: '激光测距',
|
||||
point: '坐标',
|
||||
angle: '角度',
|
||||
area: '面积',
|
||||
slope: '坡度',
|
||||
spaceVolume: '空间体积',
|
||||
},
|
||||
actions: {
|
||||
expand: '展开',
|
||||
@@ -97,14 +97,14 @@ export const zhCN: TranslationDictionary = {
|
||||
y: 'Y:',
|
||||
z: 'Z:',
|
||||
value: {
|
||||
clearHeight: '净高:',
|
||||
clearDistance: '净距:',
|
||||
distance: '距离:',
|
||||
minDistance: '最小距离:',
|
||||
angle: '角度:',
|
||||
elevation: '标高:',
|
||||
volume: '体积:',
|
||||
laserDistance: '激光测距:',
|
||||
point: '坐标:',
|
||||
angle: '角度:',
|
||||
area: '面积:',
|
||||
slope: '坡度:',
|
||||
spaceVolume: '空间体积:',
|
||||
}
|
||||
},
|
||||
units: {
|
||||
@@ -113,14 +113,14 @@ export const zhCN: TranslationDictionary = {
|
||||
m: 'm',
|
||||
km: 'km',
|
||||
deg: '°',
|
||||
m3: 'm³',
|
||||
m2: 'm²',
|
||||
percent: '%',
|
||||
},
|
||||
settings: {
|
||||
title: '设置',
|
||||
unit: '单位:',
|
||||
precision: '精度:',
|
||||
hint: '距离、最小距离和标高默认使用该单位;角度和体积有各自默认单位。',
|
||||
hint: '距离、净距、净高和标高默认使用该单位;角度和面积有各自默认单位。',
|
||||
save: '保存设置',
|
||||
cancel: '取消',
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Engine, type EngineOptions, type ModelLoadOptions } from '../components
|
||||
import { BaseManager } from '../core/base-manager';
|
||||
import { RightKeyManager } from './right-key-manager';
|
||||
import type { MeasureMode } from '../types/measure';
|
||||
import type { MeasureUnit, MeasurePrecision } from '../components/measure-panel/types';
|
||||
import type { SectionBoxRange } from '../components/section-box-panel/types';
|
||||
import type { MenuItemConfig } from '../components/menu/item';
|
||||
import { ManagerRegistry } from '../core/manager-registry';
|
||||
@@ -246,7 +247,6 @@ export class EngineManager extends BaseManager {
|
||||
return this.engineInstance.getCurrentMeasureType();
|
||||
}
|
||||
|
||||
/** 清除所有测量标注 */
|
||||
public clearAllMeasures(): void {
|
||||
if (!this.engineInstance) {
|
||||
return;
|
||||
@@ -254,11 +254,13 @@ export class EngineManager extends BaseManager {
|
||||
this.engineInstance.clearAllMeasures();
|
||||
}
|
||||
|
||||
/**
|
||||
* 激活剖切功能(统一入口)
|
||||
* @param mode 剖切模式: 'x' | 'y' | 'z' (轴向剖切) | 'box' (剖切盒) | 'face' (拾取面剖切)
|
||||
* @remarks 代理调用 Engine.activeSection(mode)
|
||||
*/
|
||||
public saveMeasureSetting(setting: { unit: MeasureUnit; precision: MeasurePrecision }): void {
|
||||
if (!this.engineInstance) {
|
||||
return;
|
||||
}
|
||||
this.engineInstance.saveMeasureSetting(setting);
|
||||
}
|
||||
|
||||
public activeSection(mode: 'x' | 'y' | 'z' | 'box' | 'face'): void {
|
||||
if (!this.engineInstance) {
|
||||
console.warn('[EngineManager] 3D Engine not initialized.');
|
||||
|
||||
@@ -1,40 +1,35 @@
|
||||
/**
|
||||
* 测量对话框管理器
|
||||
* 负责管理测量工具对话框的显示、隐藏和测量面板的交互
|
||||
*/
|
||||
import { BaseDialogManager } from '../core/base-dialog-manager';
|
||||
import { MeasurePanel } from '../components/measure-panel';
|
||||
import type { MeasureConfig, MeasureMode, MeasureResult } from '../components/measure-panel/types';
|
||||
import type { MeasureConfig, MeasureResult } from '../components/measure-panel/types';
|
||||
import { ENGINE_TYPE_TO_MODE, MEASURE_TYPES, type MeasureMode } 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;
|
||||
isSelect: boolean;
|
||||
container: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* 测量对话框管理器
|
||||
* 继承自 BaseDialogManager,提供测量工具的对话框管理功能
|
||||
*/
|
||||
export class MeasureDialogManager extends BaseDialogManager {
|
||||
/** 测量面板实例 */
|
||||
private panel: MeasurePanel | null = null;
|
||||
/** 测量配置(单位、精度等) */
|
||||
private config: MeasureConfig | null = null;
|
||||
private unsubscribeMeasureChanged: (() => void) | null = null;
|
||||
|
||||
/** 对话框唯一标识 */
|
||||
protected get dialogId(): string {
|
||||
return 'measure-dialog';
|
||||
}
|
||||
|
||||
/** 对话框标题(国际化 key) */
|
||||
protected get dialogTitle(): string {
|
||||
return 'measure.dialogTitle';
|
||||
}
|
||||
|
||||
/** 对话框宽度 */
|
||||
protected get dialogWidth(): number {
|
||||
return 250;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建对话框内容
|
||||
* 初始化测量面板并设置回调
|
||||
*/
|
||||
protected createContent(): HTMLElement {
|
||||
this.panel = new MeasurePanel({
|
||||
defaultMode: 'distance',
|
||||
@@ -52,10 +47,25 @@ export class MeasureDialogManager extends BaseDialogManager {
|
||||
},
|
||||
onExpandedChange: () => {
|
||||
this.dialog?.fitHeight(false);
|
||||
},
|
||||
onConfigSave: (config) => {
|
||||
console.log('[MeasureDialogManager] 保存设置:', config);
|
||||
this.registry.engine3d?.saveMeasureSetting({
|
||||
unit: config.unit,
|
||||
precision: config.precision
|
||||
});
|
||||
}
|
||||
});
|
||||
this.panel.init();
|
||||
|
||||
this.config = this.panel.getConfig();
|
||||
if (this.config) {
|
||||
console.log('[MeasureDialogManager] 同步缓存设置到引擎:', this.config);
|
||||
this.registry.engine3d?.saveMeasureSetting({
|
||||
unit: this.config.unit,
|
||||
precision: this.config.precision
|
||||
});
|
||||
}
|
||||
|
||||
const panelWrapper = document.createElement('div');
|
||||
panelWrapper.style.padding = '12px';
|
||||
@@ -64,18 +74,66 @@ export class MeasureDialogManager extends BaseDialogManager {
|
||||
return panelWrapper;
|
||||
}
|
||||
|
||||
/** 对话框创建后的回调,自适应高度 */
|
||||
protected onDialogCreated(): void {
|
||||
this.dialog?.fitHeight(false);
|
||||
|
||||
const engine = this.registry.engine3d?.getEngine();
|
||||
if (engine?.events) {
|
||||
const handler = (data: EngineMeasureData) => {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
private handleMeasureChanged(data: EngineMeasureData): void {
|
||||
if (!this.panel) return;
|
||||
|
||||
const targetMode = ENGINE_TYPE_TO_MODE[data.type];
|
||||
if (!targetMode) {
|
||||
console.warn('[MeasureDialogManager] 未知测量类型:', data.type);
|
||||
return;
|
||||
}
|
||||
|
||||
const currentMode = this.panel.getActiveMode();
|
||||
if (currentMode !== targetMode) {
|
||||
this.panel.switchMode(targetMode, false);
|
||||
}
|
||||
|
||||
const result = this.convertToMeasureResult(data, targetMode);
|
||||
this.panel.setResult(result);
|
||||
}
|
||||
|
||||
private convertToMeasureResult(data: EngineMeasureData, mode: MeasureMode): MeasureResult {
|
||||
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 };
|
||||
} else {
|
||||
(result as any)[config.resultField] = data.text;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/** 对话框关闭时的回调,取消工具栏按钮激活状态 */
|
||||
protected onDialogClose(): void {
|
||||
if (this.unsubscribeMeasureChanged) {
|
||||
this.unsubscribeMeasureChanged();
|
||||
this.unsubscribeMeasureChanged = null;
|
||||
}
|
||||
this.registry.toolbar?.setBtnActive('measure', false);
|
||||
}
|
||||
|
||||
/** 销毁前的清理,停用测量功能并销毁面板 */
|
||||
protected onBeforeDestroy(): void {
|
||||
if (this.unsubscribeMeasureChanged) {
|
||||
this.unsubscribeMeasureChanged();
|
||||
this.unsubscribeMeasureChanged = null;
|
||||
}
|
||||
if (this.registry.engine3d) {
|
||||
this.registry.engine3d.deactivateMeasure();
|
||||
}
|
||||
@@ -85,36 +143,20 @@ export class MeasureDialogManager extends BaseDialogManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前激活的测量模式
|
||||
* @returns 当前测量模式,如 'distance'、'angle' 等
|
||||
*/
|
||||
public getActiveMode(): MeasureMode | null {
|
||||
return this.panel ? this.panel.getActiveMode() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换测量模式
|
||||
* @param mode 目标测量模式
|
||||
*/
|
||||
public switchMode(mode: MeasureMode): void {
|
||||
if (!this.panel) return;
|
||||
this.panel.switchMode(mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置测量结果
|
||||
* @param result 测量结果对象
|
||||
*/
|
||||
public setMeasureResult(result: MeasureResult | null): void {
|
||||
if (!this.panel) return;
|
||||
this.panel.setResult(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取测量配置
|
||||
* @returns 测量配置副本
|
||||
*/
|
||||
public getConfig(): MeasureConfig | null {
|
||||
if (this.panel) {
|
||||
this.config = this.panel.getConfig();
|
||||
@@ -122,11 +164,6 @@ export class MeasureDialogManager extends BaseDialogManager {
|
||||
return this.config ? { ...this.config } : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置测量配置
|
||||
* @param partial 部分配置
|
||||
* @param persist 是否持久化
|
||||
*/
|
||||
public setConfig(partial: Partial<MeasureConfig>, persist: boolean = true): void {
|
||||
if (this.panel) {
|
||||
this.panel.setConfig(partial, persist);
|
||||
@@ -143,13 +180,11 @@ export class MeasureDialogManager extends BaseDialogManager {
|
||||
this.config = next;
|
||||
}
|
||||
|
||||
/** 清除所有测量结果 */
|
||||
public clearAll(): void {
|
||||
if (!this.panel) return;
|
||||
this.panel.clearAll();
|
||||
}
|
||||
|
||||
/** 打开测量设置面板 */
|
||||
public openSettings(): void {
|
||||
if (!this.panel) return;
|
||||
this.panel.openSettings();
|
||||
|
||||
@@ -1,18 +1,10 @@
|
||||
/**
|
||||
* 剖切盒对话框管理器
|
||||
* 负责管理剖切盒工具对话框的显示、隐藏和剖切盒面板的交互
|
||||
*/
|
||||
import { BaseDialogManager } from '../core/base-dialog-manager';
|
||||
import { SectionBoxPanel } from '../components/section-box-panel';
|
||||
import type { SectionBoxRange } from '../components/section-box-panel/types';
|
||||
|
||||
/**
|
||||
* 剖切盒对话框管理器
|
||||
* 继承自 BaseDialogManager,提供六面体剖切盒的对话框管理功能
|
||||
*/
|
||||
export class SectionBoxDialogManager extends BaseDialogManager {
|
||||
/** 剖切盒面板实例 */
|
||||
private panel: SectionBoxPanel | null = null;
|
||||
private unsubscribeSectionMove: (() => void) | null = null;
|
||||
|
||||
/** 对话框唯一标识 */
|
||||
protected get dialogId(): string {
|
||||
@@ -82,19 +74,35 @@ export class SectionBoxDialogManager extends BaseDialogManager {
|
||||
return this.panel.element;
|
||||
}
|
||||
|
||||
/** 对话框创建后的回调,激活剖切盒并自适应高度 */
|
||||
protected onDialogCreated(): void {
|
||||
this.registry.engine3d?.activeSection('box');
|
||||
this.dialog?.fitHeight(false);
|
||||
|
||||
const engine = this.registry.engine3d?.getEngine();
|
||||
if (engine?.events) {
|
||||
const handler = (data: any) => {
|
||||
if (data && this.panel) {
|
||||
this.panel.setRange(data as Partial<SectionBoxRange>);
|
||||
}
|
||||
};
|
||||
engine.events.on('section-move', handler);
|
||||
this.unsubscribeSectionMove = () => engine.events.off('section-move', handler);
|
||||
}
|
||||
}
|
||||
|
||||
/** 对话框关闭时的回调,取消工具栏按钮激活状态 */
|
||||
protected onDialogClose(): void {
|
||||
if (this.unsubscribeSectionMove) {
|
||||
this.unsubscribeSectionMove();
|
||||
this.unsubscribeSectionMove = null;
|
||||
}
|
||||
this.registry.toolbar?.setBtnActive('section-box', false);
|
||||
}
|
||||
|
||||
/** 销毁前的清理,停用剖切盒并销毁面板实例 */
|
||||
protected onBeforeDestroy(): void {
|
||||
if (this.unsubscribeSectionMove) {
|
||||
this.unsubscribeSectionMove();
|
||||
this.unsubscribeSectionMove = null;
|
||||
}
|
||||
this.registry.engine3d?.deactivateSection();
|
||||
if (this.panel) {
|
||||
this.panel.destroy();
|
||||
|
||||
@@ -38,6 +38,9 @@ export interface EngineEvents {
|
||||
// 剖切事件
|
||||
'section:move': { x?: { min: number; max: number }; y?: { min: number; max: number }; z?: { min: number; max: number } };
|
||||
|
||||
// 测量事件
|
||||
'measure:changed': { type: string; value: number; unit: string; points?: { x: number; y: number; z: number }[] };
|
||||
|
||||
// AI 聊天事件
|
||||
'aiChat:opened': {};
|
||||
'aiChat:closed': {};
|
||||
|
||||
@@ -1,24 +1,110 @@
|
||||
/**
|
||||
* 测量相关的通用类型定义
|
||||
*
|
||||
* 说明:
|
||||
* - 这些类型定义在通用 types 目录下,避免组件间直接耦合
|
||||
* - Engine 组件、MeasurePanel 组件、Manager 都可以引用这些类型
|
||||
*/
|
||||
|
||||
/**
|
||||
* 测量方式(8 种)
|
||||
*
|
||||
* 说明:
|
||||
* - id 采用英文驼峰/小写,便于程序内部使用
|
||||
* - 显示名称必须通过国际化 key 获取(见 locales)
|
||||
*/
|
||||
export type MeasureMode =
|
||||
| 'distance' // 距离
|
||||
| 'minDistance' // 最小距离
|
||||
| 'angle' // 角度
|
||||
| 'elevation' // 标高
|
||||
| 'volume' // 体积
|
||||
| 'laserDistance' // 激光测距
|
||||
| 'slope' // 坡度
|
||||
| 'spaceVolume'; // 空间体积
|
||||
| 'clearHeight'
|
||||
| 'clearDistance'
|
||||
| 'distance'
|
||||
| 'elevation'
|
||||
| 'point'
|
||||
| 'angle'
|
||||
| 'area'
|
||||
| 'slope';
|
||||
|
||||
export type MeasureValueType = 'length' | 'area' | 'angle' | 'percent' | 'point';
|
||||
|
||||
export interface MeasureTypeConfig {
|
||||
key: MeasureMode;
|
||||
engineKey: string;
|
||||
valueType: MeasureValueType;
|
||||
icon: string;
|
||||
order: number;
|
||||
resultField: string;
|
||||
}
|
||||
|
||||
const ICONS = {
|
||||
clearHeight: `<svg viewBox="0 0 32 32" aria-hidden="true"><text x="16" y="20" text-anchor="middle" fill="currentColor" font-size="12" font-family="system-ui, sans-serif">净高</text></svg>`,
|
||||
clearDistance: `<svg viewBox="0 0 32 32" aria-hidden="true"><text x="16" y="20" text-anchor="middle" fill="currentColor" font-size="12" font-family="system-ui, sans-serif">净距</text></svg>`,
|
||||
distance: `<svg viewBox="0 0 32 32" aria-hidden="true"><g transform="translate(0 4.197)"><path fill="currentColor" d="M29.692,3.03,27.55.919a.529.529,0,0,1-.014-.756A.549.549,0,0,1,28.3.15l.014.013,3.067,3.023a.529.529,0,0,1,0,.756L28.317,6.966a.549.549,0,0,1-.767.013.529.529,0,0,1-.014-.756l.014-.013L29.692,4.1H2.31L4.452,6.21a.528.528,0,0,1,.013.756.547.547,0,0,1-.766.013l-.014-.013L.616,3.942a.531.531,0,0,1,0-.756L3.685.163a.548.548,0,0,1,.767.014.528.528,0,0,1,0,.742L2.31,3.03ZM24.136,15.055H23.051V18H21.966v-2.94H20.882V18H19.8v-2.94H18.712V18H17.627v-2.94H16.543v5.078H15.458V15.055H14.373V18H13.288v-2.94H12.2V18H11.119v-2.94H10.034V18H8.949v-2.94H7.865V18H6.78v-2.94H5.7v5.078H4.61V15.055H1.9a.27.27,0,0,0-.272.268v6.413A.269.269,0,0,0,1.9,22H30.1a.268.268,0,0,0,.271-.267V15.323a.269.269,0,0,0-.271-.268H27.39v5.078H26.305V15.055H25.221V18H24.136Zm5.966-1.6A1.884,1.884,0,0,1,32,15.323v6.413a1.885,1.885,0,0,1-1.9,1.871H1.9A1.885,1.885,0,0,1,0,21.736V15.323a1.885,1.885,0,0,1,1.9-1.871Z"/></g></svg>`,
|
||||
elevation: `<svg viewBox="0 0 32 32" aria-hidden="true"><path fill="currentColor" d="M84.131,193.119a1.056,1.056,0,0,1,1.116.982v7.857a1.056,1.056,0,0,1-1.116.982H54.367a1.056,1.056,0,0,1-1.116-.982V194.1a1.056,1.056,0,0,1,1.116-.982Zm-1.116,1.964H55.483v5.893H83.015Zm1.116-13.749a1.064,1.064,0,0,1,1.114.935,1.032,1.032,0,0,1-1.007,1.025l-.107,0H71.2l-7.858,6.914a1.227,1.227,0,0,1-1.578,0l-8.185-7.2-.018-.016-.032-.031.049.047a1.107,1.107,0,0,1-.092-.092l-.011-.014a.869.869,0,0,1-.182-.857l0-.008L53.31,182l.012-.029.02-.045.019-.035a1.1,1.1,0,0,1,.891-.552h.007q.053,0,.107,0ZM68.043,183.3H57.06l5.492,4.831Z" transform="translate(-53.247 -176.136)"/></svg>`,
|
||||
point: `<svg viewBox="0 0 32 32" aria-hidden="true"><text x="16" y="20" text-anchor="middle" fill="currentColor" font-size="12" font-family="system-ui, sans-serif">坐标</text></svg>`,
|
||||
angle: `<svg viewBox="0 0 32 32" aria-hidden="true"><path fill="currentColor" d="M39.587,50.766h13.7a1,1,0,0,1,0,2H23.171a1,1,0,0,1,0-2h1.418l6.582-7.006v-.006a.517.517,0,0,1,.14-.357.456.456,0,0,1,.337-.144l12.1-12.876a.451.451,0,0,1,.665,0,.524.524,0,0,1,0,.708L32.883,43.355a8.3,8.3,0,0,1,6.7,7.411Zm-.949,0a7.254,7.254,0,0,0-6.611-6.5l-6.108,6.5Z" transform="translate(-22.229 -26.489)"/></svg>`,
|
||||
area: `<svg viewBox="0 0 32 32" aria-hidden="true"><text x="16" y="20" text-anchor="middle" fill="currentColor" font-size="12" font-family="system-ui, sans-serif">面积</text></svg>`,
|
||||
slope: `<svg viewBox="0 0 32 32" aria-hidden="true"><path fill="currentColor" d="M202.1,188.337l2.629-2.191-8.447-3.106,1.533,8.871,2.629-2.194,9.341,11.209,1.656-1.379Zm-13.726-.435a1.075,1.075,0,0,0-1.07-.341,1.057,1.057,0,0,0-.5.277l-5.11,4.08a1.08,1.08,0,0,0-.406.84l-.007,17.386a1.079,1.079,0,0,0,1.077,1.077L205.7,211.2a1.078,1.078,0,0,0,.822-1.774Zm-4.934,21.164.007-15.788,3.968-3.171,15.974,18.941Z" transform="translate(-180.36 -181.131)"/></svg>`,
|
||||
};
|
||||
|
||||
export const MEASURE_TYPES: Record<MeasureMode, MeasureTypeConfig> = {
|
||||
clearHeight: {
|
||||
key: 'clearHeight',
|
||||
engineKey: 'clear-height',
|
||||
valueType: 'length',
|
||||
icon: ICONS.clearHeight,
|
||||
order: 0,
|
||||
resultField: 'clearHeightMm',
|
||||
},
|
||||
clearDistance: {
|
||||
key: 'clearDistance',
|
||||
engineKey: 'clear-distance',
|
||||
valueType: 'length',
|
||||
icon: ICONS.clearDistance,
|
||||
order: 1,
|
||||
resultField: 'clearDistanceMm',
|
||||
},
|
||||
distance: {
|
||||
key: 'distance',
|
||||
engineKey: 'distance',
|
||||
valueType: 'length',
|
||||
icon: ICONS.distance,
|
||||
order: 2,
|
||||
resultField: 'distanceMm',
|
||||
},
|
||||
elevation: {
|
||||
key: 'elevation',
|
||||
engineKey: 'elevation',
|
||||
valueType: 'length',
|
||||
icon: ICONS.elevation,
|
||||
order: 3,
|
||||
resultField: 'elevationMm',
|
||||
},
|
||||
point: {
|
||||
key: 'point',
|
||||
engineKey: 'point',
|
||||
valueType: 'point',
|
||||
icon: ICONS.point,
|
||||
order: 4,
|
||||
resultField: 'xyz',
|
||||
},
|
||||
angle: {
|
||||
key: 'angle',
|
||||
engineKey: 'angle',
|
||||
valueType: 'angle',
|
||||
icon: ICONS.angle,
|
||||
order: 5,
|
||||
resultField: 'angleDeg',
|
||||
},
|
||||
area: {
|
||||
key: 'area',
|
||||
engineKey: 'area',
|
||||
valueType: 'area',
|
||||
icon: ICONS.area,
|
||||
order: 6,
|
||||
resultField: 'areaM2',
|
||||
},
|
||||
slope: {
|
||||
key: 'slope',
|
||||
engineKey: 'slope',
|
||||
valueType: 'percent',
|
||||
icon: ICONS.slope,
|
||||
order: 7,
|
||||
resultField: 'slopePercent',
|
||||
},
|
||||
};
|
||||
|
||||
export const MEASURE_MODES_ORDERED: MeasureMode[] = Object.values(MEASURE_TYPES)
|
||||
.sort((a, b) => a.order - b.order)
|
||||
.map((t) => t.key);
|
||||
|
||||
export const ENGINE_TYPE_TO_MODE: Record<string, MeasureMode> = Object.fromEntries(
|
||||
Object.values(MEASURE_TYPES).map((t) => [t.engineKey, t.key])
|
||||
) as Record<string, MeasureMode>;
|
||||
|
||||
export const MODE_TO_ENGINE_TYPE: Record<MeasureMode, string> = Object.fromEntries(
|
||||
Object.values(MEASURE_TYPES).map((t) => [t.key, t.engineKey])
|
||||
) as Record<MeasureMode, string>;
|
||||
|
||||
Reference in New Issue
Block a user