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

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