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:
yuding
2026-02-02 18:18:36 +08:00
parent 316f42ae6b
commit 044cd0e034
14 changed files with 5566 additions and 5539 deletions

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

View File

@@ -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);
}

View File

@@ -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 };
}
}

View File

@@ -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;
/** 体积(单位: */
volumeM3?: number;
/** 激光测距单位mm */
laserDistanceMm?: number;
/** 角度(单位:deg */
angleDeg?: number;
/** 面积单位m² */
areaM2?: number;
/** 坡度(单位:% */
slopePercent?: number;
/** 空间体积单位 */
spaceVolumeM3?: number;
/** 可选:展示测量点/结果点坐标(单位由引擎侧定义,这里只负责显示) */
/** 坐标测量点(单位由引擎侧定义 */
xyz?: MeasureXYZ;
}
@@ -102,6 +99,12 @@ export interface MeasurePanelOptions {
* 说明:用于让外部(如 Dialog重新计算尺寸。
*/
onExpandedChange?: (expanded: boolean) => void;
/**
* 设置保存回调
* 说明:用户点击"保存设置"时触发,用于同步到引擎
*/
onConfigSave?: (config: MeasureConfig) => void;
}

View File

@@ -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',
}

View File

@@ -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;
};

View File

@@ -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: '取消',
}

View File

@@ -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.');

View File

@@ -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();

View File

@@ -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();

View File

@@ -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': {};

View File

@@ -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>;