提交代码
This commit is contained in:
@@ -16,8 +16,6 @@ import type { SectionBoxRange } from '../section-box-panel/types';
|
||||
import type { ManagerRegistry } from '../../core/manager-registry';
|
||||
// 导入第三方 SDK 的 createEngine 函数(从 npm 包引入)
|
||||
import { createEngine as createEngineSDK } from 'iflow-engine-base';
|
||||
//import { createEngine as createEngineSDK } from '../../../../bim_engine_base/dist/bim-engine-sdk.es';
|
||||
import "../../../../bim_engine_base/dist/iflow-engine-base.css"
|
||||
|
||||
export type {
|
||||
EngineOptions,
|
||||
@@ -177,6 +175,22 @@ export class Engine implements IBimComponent {
|
||||
console.warn('[Engine] 底层引擎不支持 events.on 方法,无法监听点击事件');
|
||||
}
|
||||
|
||||
const oneClickEncoding = (this.engine as any).oneClickEncoding;
|
||||
if (oneClickEncoding?.on) {
|
||||
oneClickEncoding.on('encoding-start', (data: any) => {
|
||||
console.log('[Engine] 底层 encoding-start 事件触发:', data);
|
||||
this.registry.emit('encoding:start', data);
|
||||
});
|
||||
oneClickEncoding.on('encoding-complete', (data: any) => {
|
||||
console.log('[Engine] 底层 encoding-complete 事件触发:', data);
|
||||
this.registry.emit('encoding:complete', data);
|
||||
});
|
||||
oneClickEncoding.on('encoding-error', (data: any) => {
|
||||
console.log('[Engine] 底层 encoding-error 事件触发:', data);
|
||||
this.registry.emit('encoding:error', data);
|
||||
});
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('[Engine] Failed to initialize engine:', error);
|
||||
this._isInitialized = false;
|
||||
@@ -252,6 +266,36 @@ export class Engine implements IBimComponent {
|
||||
this.engine?.events?.off(event, handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* 订阅一键编码事件
|
||||
* @param event 事件名称:'encoding-start' | 'encoding-complete' | 'encoding-error'
|
||||
* @param handler 事件处理函数
|
||||
*/
|
||||
public onOneClickEncodingEvent(event: string, handler: (...args: any[]) => void): void {
|
||||
this.engine?.oneClickEncoding?.on(event, handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消订阅一键编码事件
|
||||
* @param event 事件名称
|
||||
* @param handler 事件处理函数
|
||||
*/
|
||||
public offOneClickEncodingEvent(event: string, handler: (...args: any[]) => void): void {
|
||||
this.engine?.oneClickEncoding?.off(event, handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动一键编码
|
||||
* @remarks 调用底层 engine.oneClickEncoding.start(),需先订阅相关事件
|
||||
*/
|
||||
public startOneClickEncoding(): void {
|
||||
if (!this._isInitialized || !this.engine?.oneClickEncoding) {
|
||||
console.warn('[Engine] oneClickEncoding not available.');
|
||||
return;
|
||||
}
|
||||
this.engine.oneClickEncoding.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* 暂停渲染
|
||||
*/
|
||||
@@ -631,6 +675,32 @@ export class Engine implements IBimComponent {
|
||||
this.engine.clipping.reverse();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置剖切面填充开关
|
||||
* @param enabled 是否启用填充
|
||||
* @remarks 对接底层 `engine.clipping.setFillCutFace(enabled)`
|
||||
*/
|
||||
public setFillCutFace(enabled: boolean): void {
|
||||
if (!this._isInitialized || !this.engine?.clipping) {
|
||||
console.error('[Engine] Cannot set fill cut face: engine not initialized.');
|
||||
return;
|
||||
}
|
||||
console.log('[Engine] Setting fill cut face:', enabled);
|
||||
this.engine.clipping.setFillCutFace(enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取剖切面填充状态
|
||||
* @returns true=已启用填充,false=未启用
|
||||
* @remarks 对接底层 `engine.clipping.getFillCutFace()`
|
||||
*/
|
||||
public getFillCutFace(): boolean {
|
||||
if (!this._isInitialized || !this.engine?.clipping) {
|
||||
return false;
|
||||
}
|
||||
return this.engine.clipping.getFillCutFace?.() ?? false;
|
||||
}
|
||||
|
||||
// ==================== 结束:剖切功能 ====================
|
||||
|
||||
// ==================== 相机切换 ====================
|
||||
@@ -1767,6 +1837,49 @@ export class Engine implements IBimComponent {
|
||||
|
||||
// ==================== 结束:构件操作 ====================
|
||||
|
||||
/**
|
||||
* 读取所有已加载模型的缓存编码数据
|
||||
* @remarks 遍历 engine.models,对每个模型调用 oneClickEncoding.readModelCodeFormStoge(url)
|
||||
*/
|
||||
public readModelCodeFormStoge(): void {
|
||||
if (!this._isInitialized || !this.engine) {
|
||||
console.warn('[Engine] Cannot read model code form stoge: engine not initialized.');
|
||||
return;
|
||||
}
|
||||
let count = 0;
|
||||
this.engine.models.forEach((m: any) => {
|
||||
this.engine.oneClickEncoding.readModelCodeFormStoge(m.url);
|
||||
count++;
|
||||
});
|
||||
console.log('readModelCodeFormStoge-success:', count);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查所有已加载模型是否已有编码数据
|
||||
* @returns 当所有模型都存在编码时返回 true,否则返回 false
|
||||
*/
|
||||
public hasModelCode(): boolean {
|
||||
if (!this._isInitialized || !this.engine) {
|
||||
console.warn('[Engine] Cannot check model code: engine not initialized.');
|
||||
return false;
|
||||
}
|
||||
const models = (this.engine as any).models;
|
||||
if (!Array.isArray(models) || models.length === 0) {
|
||||
return false;
|
||||
}
|
||||
const oneClickEncoding = (this.engine as any).oneClickEncoding;
|
||||
if (!oneClickEncoding || typeof oneClickEncoding.exitModelCode !== 'function') {
|
||||
console.warn('[Engine] oneClickEncoding.exitModelCode is not available.');
|
||||
return false;
|
||||
}
|
||||
return models.every((m: any) => {
|
||||
if (!m || typeof m.url !== 'string') {
|
||||
return false;
|
||||
}
|
||||
return oneClickEncoding.exitModelCode(m.url) === true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁组件 (接口实现)
|
||||
* 清理资源、取消订阅、销毁引擎实例
|
||||
|
||||
@@ -25,29 +25,29 @@ export class RadialToolbar implements IBimComponent {
|
||||
private maxInteractiveRadius = 220;
|
||||
|
||||
private readonly closeDelay: number;
|
||||
private readonly itemsPerRing: number;
|
||||
|
||||
// 主按钮直径(px)
|
||||
private readonly MAIN_BUTTON_SIZE = 60;
|
||||
// 子按钮直径(px)
|
||||
private readonly SUB_BUTTON_SIZE = 46;
|
||||
// 第一环“子按钮中心”到“主按钮中心”的距离(px)
|
||||
// 不做防重叠兜底,允许在小半径时出现重叠。
|
||||
// 第一环"子按钮中心"到"主按钮中心"的距离(px)
|
||||
private readonly BASE_RADIUS = 80;
|
||||
// 多环时,相邻两环的中心半径差(px)
|
||||
private readonly RING_GAP = 40;
|
||||
// 扇形展开角度范围:当前 180~270(实际就是 90 度)
|
||||
private readonly RING_GAP = 60;
|
||||
// 扇形展开角度范围 (180为正左边/下,270为正上)
|
||||
private readonly FAN_START_DEG = 170;
|
||||
private readonly FAN_END_DEG = 280;
|
||||
// 扇形边缘留白(px),防止按钮贴边或裁切
|
||||
private readonly CANVAS_PADDING = 28;
|
||||
|
||||
// 【自定义每环数量】:你可以随意修改这里的数字!多出来的按钮会自动折叠到更外围的新环。
|
||||
private readonly FIXED_RING_CAPACITIES = [4, 6, 8];
|
||||
|
||||
constructor(options: RadialToolbarOptions) {
|
||||
this.container = options.container;
|
||||
this.items = options.items === undefined ? this.createDefaultItems() : [...options.items];
|
||||
this.mainButtonLabel = options.mainButtonLabel ?? 'toolbar.home';
|
||||
this.onMainButtonClick = options.onMainButtonClick;
|
||||
this.itemsPerRing = Math.max(3, options.itemsPerRing ?? 5);
|
||||
this.closeDelay = Math.max(100, options.closeDelay ?? 260);
|
||||
|
||||
this.wrapper = this.createWrapper();
|
||||
@@ -223,38 +223,69 @@ export class RadialToolbar implements IBimComponent {
|
||||
return btn;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据预设的数组计算容量(不足自动向后排)
|
||||
*/
|
||||
private getRingCapacities(total: number): number[] {
|
||||
const capacities: number[] = [];
|
||||
let remaining = total;
|
||||
|
||||
for (const cap of this.FIXED_RING_CAPACITIES) {
|
||||
if (remaining <= 0) break;
|
||||
const actualCount = Math.min(cap, remaining);
|
||||
capacities.push(actualCount);
|
||||
remaining -= actualCount;
|
||||
}
|
||||
|
||||
// 如果配置的环数不够用,剩下的全部折叠到最后一环
|
||||
if (remaining > 0) {
|
||||
capacities.push(remaining);
|
||||
}
|
||||
|
||||
return capacities;
|
||||
}
|
||||
|
||||
private updateItemPositions(): void {
|
||||
const total = this.itemElements.length;
|
||||
if (total === 0) return;
|
||||
|
||||
const fanSpan = this.FAN_END_DEG - this.FAN_START_DEG;
|
||||
const ringCapacities = this.getRingCapacities(total);
|
||||
|
||||
this.itemElements.forEach((btn, globalIndex) => {
|
||||
const ringIndex = Math.floor(globalIndex / this.itemsPerRing);
|
||||
const ringStart = ringIndex * this.itemsPerRing;
|
||||
const ringCount = Math.min(this.itemsPerRing, total - ringStart);
|
||||
const ringLocalIndex = globalIndex - ringStart;
|
||||
const ratio = ringCount === 1 ? 0.5 : ringLocalIndex / (ringCount - 1);
|
||||
const angleDeg = this.FAN_START_DEG + fanSpan * ratio;
|
||||
const angleRad = (angleDeg * Math.PI) / 180;
|
||||
const radius = this.getBaseRadius(ringCount) + ringIndex * this.RING_GAP;
|
||||
let globalIndex = 0;
|
||||
ringCapacities.forEach((ringCount, ringIndex) => {
|
||||
const radius = this.BASE_RADIUS + ringIndex * this.RING_GAP;
|
||||
|
||||
const x = Math.cos(angleRad) * radius;
|
||||
const y = Math.sin(angleRad) * radius;
|
||||
for (let ringLocalIndex = 0; ringLocalIndex < ringCount; ringLocalIndex++) {
|
||||
const btn = this.itemElements[globalIndex];
|
||||
|
||||
const openDelay = (ringLocalIndex + ringIndex * 0.5) * 0.045;
|
||||
const closeDelay = (ringCount - 1 - ringLocalIndex + ringIndex * 0.4) * 0.032;
|
||||
// 【核心逻辑】:首尾固定:第一个按钮在最下面(180),最后一个在最上面(270),中间按剩余空间均分
|
||||
// 如果该环只有1个按钮,让它待在180度的起始位置
|
||||
const ratio = ringCount <= 1 ? 0 : ringLocalIndex / (ringCount - 1);
|
||||
|
||||
btn.style.setProperty('--rt-x', `${x.toFixed(2)}px`);
|
||||
btn.style.setProperty('--rt-y', `${y.toFixed(2)}px`);
|
||||
btn.style.setProperty('--rt-open-delay', `${openDelay.toFixed(3)}s`);
|
||||
btn.style.setProperty('--rt-close-delay', `${closeDelay.toFixed(3)}s`);
|
||||
const angleDeg = this.FAN_START_DEG + fanSpan * ratio;
|
||||
const angleRad = (angleDeg * Math.PI) / 180;
|
||||
|
||||
const x = Math.cos(angleRad) * radius;
|
||||
const y = Math.sin(angleRad) * radius;
|
||||
|
||||
const openDelay = (ringLocalIndex + ringIndex * 0.5) * 0.045;
|
||||
const closeDelay = (ringCount - 1 - ringLocalIndex + ringIndex * 0.4) * 0.032;
|
||||
|
||||
btn.style.setProperty('--rt-x', `${x.toFixed(2)}px`);
|
||||
btn.style.setProperty('--rt-y', `${y.toFixed(2)}px`);
|
||||
btn.style.setProperty('--rt-open-delay', `${openDelay.toFixed(3)}s`);
|
||||
btn.style.setProperty('--rt-close-delay', `${closeDelay.toFixed(3)}s`);
|
||||
globalIndex++;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private updateLayoutMetrics(): void {
|
||||
const total = this.items.length;
|
||||
const ringCount = Math.max(1, Math.ceil(total / this.itemsPerRing));
|
||||
const maxItemsPerRing = Math.max(1, Math.min(total, this.itemsPerRing));
|
||||
const maxRadius = this.getBaseRadius(maxItemsPerRing) + (ringCount - 1) * this.RING_GAP;
|
||||
const ringCapacities = this.getRingCapacities(total);
|
||||
const ringCount = ringCapacities.length;
|
||||
const maxRadius = this.BASE_RADIUS + (ringCount - 1) * this.RING_GAP;
|
||||
const size = Math.ceil(maxRadius + this.MAIN_BUTTON_SIZE + this.SUB_BUTTON_SIZE + this.CANVAS_PADDING * 2);
|
||||
this.maxInteractiveRadius = maxRadius + this.SUB_BUTTON_SIZE * 0.7;
|
||||
|
||||
@@ -265,10 +296,6 @@ export class RadialToolbar implements IBimComponent {
|
||||
this.wrapper.style.setProperty('--rt-main-offset', `${(this.MAIN_BUTTON_SIZE - this.SUB_BUTTON_SIZE) / 2}px`);
|
||||
}
|
||||
|
||||
private getBaseRadius(_itemsInRing: number): number {
|
||||
return this.BASE_RADIUS;
|
||||
}
|
||||
|
||||
private scheduleCollapse(): void {
|
||||
if (this.timer) {
|
||||
clearTimeout(this.timer);
|
||||
@@ -354,7 +381,7 @@ export class RadialToolbar implements IBimComponent {
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
const text = t(item.label);
|
||||
const text = item.title ?? t(item.label);
|
||||
el.title = text;
|
||||
el.setAttribute('aria-label', text);
|
||||
this.applyItemActiveClass(el, item);
|
||||
@@ -362,7 +389,7 @@ export class RadialToolbar implements IBimComponent {
|
||||
if (!item.icon) {
|
||||
const iconEl = el.querySelector('.radial-sub-btn-icon');
|
||||
if (iconEl) {
|
||||
iconEl.textContent = this.getFallbackLabel(item.label);
|
||||
iconEl.textContent = this.getFallbackLabel(item.title ?? item.label);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -431,6 +458,12 @@ export class RadialToolbar implements IBimComponent {
|
||||
}
|
||||
}
|
||||
|
||||
public addItem(item: RadialMenuItem): void {
|
||||
this.items.push(item);
|
||||
this.renderItems();
|
||||
this.updateLayoutMetrics();
|
||||
}
|
||||
|
||||
public init(): void { }
|
||||
|
||||
public setLocales(): void {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
export interface RadialMenuItem {
|
||||
id: string;
|
||||
label: string;
|
||||
/** 直接显示的文本(优先级高于 label 的国际化翻译) */
|
||||
title?: string;
|
||||
icon?: string;
|
||||
onClick?: (item: RadialMenuItem) => void;
|
||||
isToggle?: boolean;
|
||||
@@ -14,6 +16,5 @@ export interface RadialToolbarOptions {
|
||||
mainButtonIcon?: string;
|
||||
mainButtonLabel?: string;
|
||||
onMainButtonClick?: () => void;
|
||||
itemsPerRing?: number;
|
||||
closeDelay?: number;
|
||||
}
|
||||
|
||||
@@ -15,15 +15,16 @@ export class SectionAxisPanel implements IBimComponent {
|
||||
public element: HTMLElement;
|
||||
private options: SectionAxisPanelOptions;
|
||||
|
||||
// 状态
|
||||
private isHidden: boolean = false;
|
||||
private isFilled: boolean = false;
|
||||
private activeAxis: SectionAxis = 'x';
|
||||
|
||||
// DOM 引用 - 第一行
|
||||
private hideBtn!: HTMLButtonElement;
|
||||
private reverseBtn!: HTMLButtonElement;
|
||||
private fillBtn!: HTMLButtonElement;
|
||||
private hideLabelEl!: HTMLElement;
|
||||
private reverseLabelEl!: HTMLElement;
|
||||
private fillLabelEl!: HTMLElement;
|
||||
|
||||
// DOM 引用 - 第二行
|
||||
private axisXBtn!: HTMLButtonElement;
|
||||
@@ -37,6 +38,7 @@ export class SectionAxisPanel implements IBimComponent {
|
||||
constructor(options: SectionAxisPanelOptions = {}) {
|
||||
this.options = options;
|
||||
this.isHidden = options.defaultHidden ?? false;
|
||||
this.isFilled = options.defaultFill ?? false;
|
||||
this.activeAxis = options.defaultAxis ?? 'x';
|
||||
this.element = this.createDom();
|
||||
}
|
||||
@@ -60,7 +62,7 @@ export class SectionAxisPanel implements IBimComponent {
|
||||
this.setTheme(themeManager.getTheme());
|
||||
|
||||
// 初始化按钮状态
|
||||
this.updateHideButtonState();
|
||||
this.updateButtonStates();
|
||||
this.updateAxisButtonsState();
|
||||
}
|
||||
|
||||
@@ -86,41 +88,39 @@ export class SectionAxisPanel implements IBimComponent {
|
||||
public setLocales(): void {
|
||||
this.hideLabelEl.textContent = t('sectionAxis.actions.hide');
|
||||
this.reverseLabelEl.textContent = t('sectionAxis.actions.reverse');
|
||||
// XYZ按钮的文字不需要国际化,保持为单个字母
|
||||
this.fillLabelEl.textContent = t('sectionAxis.actions.fill');
|
||||
|
||||
this.hideBtn.title = t('sectionAxis.actions.hide');
|
||||
this.reverseBtn.title = t('sectionAxis.actions.reverse');
|
||||
this.fillBtn.title = t('sectionAxis.actions.fill');
|
||||
this.axisXBtn.title = t('sectionAxis.actions.axisX');
|
||||
this.axisYBtn.title = t('sectionAxis.actions.axisY');
|
||||
this.axisZBtn.title = t('sectionAxis.actions.axisZ');
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置隐藏状态
|
||||
*/
|
||||
public setHiddenState(isHidden: boolean): void {
|
||||
this.isHidden = isHidden;
|
||||
this.updateHideButtonState();
|
||||
this.updateButtonStates();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取隐藏状态
|
||||
*/
|
||||
public getHiddenState(): boolean {
|
||||
return this.isHidden;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置激活的轴向
|
||||
*/
|
||||
public setFillState(isFilled: boolean): void {
|
||||
this.isFilled = isFilled;
|
||||
this.updateButtonStates();
|
||||
}
|
||||
|
||||
public getFillState(): boolean {
|
||||
return this.isFilled;
|
||||
}
|
||||
|
||||
public setActiveAxis(axis: SectionAxis): void {
|
||||
this.activeAxis = axis;
|
||||
this.updateAxisButtonsState();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取激活的轴向
|
||||
*/
|
||||
public getActiveAxis(): SectionAxis {
|
||||
return this.activeAxis;
|
||||
}
|
||||
@@ -151,6 +151,12 @@ export class SectionAxisPanel implements IBimComponent {
|
||||
const row1 = document.createElement('div');
|
||||
row1.className = 'section-axis-row-1';
|
||||
|
||||
this.fillBtn = this.createButton(
|
||||
'fill',
|
||||
getIcon('填充'),
|
||||
() => this.handleFillToggle()
|
||||
);
|
||||
|
||||
this.hideBtn = this.createButton(
|
||||
'hide',
|
||||
getIcon('隐藏'),
|
||||
@@ -163,6 +169,7 @@ export class SectionAxisPanel implements IBimComponent {
|
||||
() => this.handleReverse()
|
||||
);
|
||||
|
||||
row1.appendChild(this.fillBtn);
|
||||
row1.appendChild(this.hideBtn);
|
||||
row1.appendChild(this.reverseBtn);
|
||||
|
||||
@@ -188,7 +195,7 @@ export class SectionAxisPanel implements IBimComponent {
|
||||
* 创建按钮(带图标)
|
||||
*/
|
||||
private createButton(
|
||||
type: 'hide' | 'reverse',
|
||||
type: 'hide' | 'reverse' | 'fill',
|
||||
iconSvg: string,
|
||||
onClick: () => void
|
||||
): HTMLButtonElement {
|
||||
@@ -207,11 +214,12 @@ export class SectionAxisPanel implements IBimComponent {
|
||||
label.className = 'section-axis-btn-label';
|
||||
btn.appendChild(label);
|
||||
|
||||
// 保存 label 引用
|
||||
if (type === 'hide') {
|
||||
this.hideLabelEl = label;
|
||||
} else if (type === 'reverse') {
|
||||
this.reverseLabelEl = label;
|
||||
} else if (type === 'fill') {
|
||||
this.fillLabelEl = label;
|
||||
}
|
||||
|
||||
// 点击事件
|
||||
@@ -249,16 +257,22 @@ export class SectionAxisPanel implements IBimComponent {
|
||||
*/
|
||||
private handleHideToggle(): void {
|
||||
this.isHidden = !this.isHidden;
|
||||
this.updateHideButtonState();
|
||||
this.updateButtonStates();
|
||||
|
||||
if (this.options.onHideToggle) {
|
||||
this.options.onHideToggle(this.isHidden);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理反向按钮点击
|
||||
*/
|
||||
private handleFillToggle(): void {
|
||||
this.isFilled = !this.isFilled;
|
||||
this.updateButtonStates();
|
||||
|
||||
if (this.options.onFillToggle) {
|
||||
this.options.onFillToggle(this.isFilled);
|
||||
}
|
||||
}
|
||||
|
||||
private handleReverse(): void {
|
||||
if (this.options.onReverse) {
|
||||
this.options.onReverse();
|
||||
@@ -281,15 +295,9 @@ export class SectionAxisPanel implements IBimComponent {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新隐藏按钮状态
|
||||
*/
|
||||
private updateHideButtonState(): void {
|
||||
if (this.isHidden) {
|
||||
this.hideBtn.classList.add('active');
|
||||
} else {
|
||||
this.hideBtn.classList.remove('active');
|
||||
}
|
||||
private updateButtonStates(): void {
|
||||
this.hideBtn.classList.toggle('active', this.isHidden);
|
||||
this.fillBtn.classList.toggle('active', this.isFilled);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -7,30 +7,11 @@ export type SectionAxis = 'x' | 'y' | 'z';
|
||||
* 轴向剖切面板配置选项
|
||||
*/
|
||||
export interface SectionAxisPanelOptions {
|
||||
/**
|
||||
* 隐藏按钮切换回调
|
||||
* @param isHidden 是否隐藏
|
||||
*/
|
||||
onHideToggle?: (isHidden: boolean) => void;
|
||||
|
||||
/**
|
||||
* 反向按钮回调
|
||||
*/
|
||||
onFillToggle?: (isFilled: boolean) => void;
|
||||
onReverse?: () => void;
|
||||
|
||||
/**
|
||||
* 轴向切换回调
|
||||
* @param axis 当前激活的轴向
|
||||
*/
|
||||
onAxisChange?: (axis: SectionAxis) => void;
|
||||
|
||||
/**
|
||||
* 默认激活的轴向(默认 'x')
|
||||
*/
|
||||
defaultAxis?: SectionAxis;
|
||||
|
||||
/**
|
||||
* 初始隐藏状态(默认 false)
|
||||
*/
|
||||
defaultHidden?: boolean;
|
||||
defaultFill?: boolean;
|
||||
}
|
||||
|
||||
@@ -18,15 +18,18 @@ export class SectionBoxPanel implements IBimComponent {
|
||||
|
||||
private isHidden: boolean = false;
|
||||
private isReversed: boolean = false;
|
||||
private isFilled: boolean = false;
|
||||
private range: SectionBoxRange;
|
||||
|
||||
private hideBtn!: HTMLButtonElement;
|
||||
private fitBtn!: HTMLButtonElement;
|
||||
private resetBtn!: HTMLButtonElement;
|
||||
private fillBtn!: HTMLButtonElement;
|
||||
|
||||
private hideLabelEl!: HTMLElement;
|
||||
private fitLabelEl!: HTMLElement;
|
||||
private resetLabelEl!: HTMLElement;
|
||||
private fillLabelEl!: HTMLElement;
|
||||
private xLabelEl!: HTMLElement;
|
||||
private yLabelEl!: HTMLElement;
|
||||
private zLabelEl!: HTMLElement;
|
||||
@@ -61,6 +64,7 @@ export class SectionBoxPanel implements IBimComponent {
|
||||
this.options = options;
|
||||
this.isHidden = options.defaultHidden ?? false;
|
||||
this.isReversed = options.defaultReversed ?? false;
|
||||
this.isFilled = options.defaultFill ?? false;
|
||||
this.range = JSON.parse(JSON.stringify(options.defaultRange ?? DEFAULT_RANGE));
|
||||
}
|
||||
|
||||
@@ -96,6 +100,15 @@ export class SectionBoxPanel implements IBimComponent {
|
||||
return this.isReversed;
|
||||
}
|
||||
|
||||
public setFillState(isFilled: boolean): void {
|
||||
this.isFilled = isFilled;
|
||||
this.updateButtonStates();
|
||||
}
|
||||
|
||||
public getFillState(): boolean {
|
||||
return this.isFilled;
|
||||
}
|
||||
|
||||
public setRange(range: Partial<SectionBoxRange>): void {
|
||||
if (range.x) this.range.x = { ...this.range.x, ...range.x };
|
||||
if (range.y) this.range.y = { ...this.range.y, ...range.y };
|
||||
@@ -126,6 +139,12 @@ export class SectionBoxPanel implements IBimComponent {
|
||||
const buttonsContainer = document.createElement('div');
|
||||
buttonsContainer.className = 'section-box-row-buttons';
|
||||
|
||||
this.fillBtn = this.createButton('fill', t('sectionBox.actions.fill'), () => {
|
||||
this.isFilled = !this.isFilled;
|
||||
this.updateButtonStates();
|
||||
this.options.onFillToggle?.(this.isFilled);
|
||||
}, 'fill');
|
||||
|
||||
this.hideBtn = this.createButton('hide', t('sectionBox.actions.hide'), () => {
|
||||
this.isHidden = !this.isHidden;
|
||||
this.updateButtonStates();
|
||||
@@ -138,7 +157,7 @@ export class SectionBoxPanel implements IBimComponent {
|
||||
|
||||
this.resetBtn = this.createButton('reset', t('sectionBox.actions.reset'), () => this.reset(), 'reset');
|
||||
|
||||
[this.hideBtn, this.fitBtn, this.resetBtn].forEach(btn => buttonsContainer.appendChild(btn));
|
||||
[this.fillBtn, this.hideBtn, this.fitBtn, this.resetBtn].forEach(btn => buttonsContainer.appendChild(btn));
|
||||
|
||||
const slidersContainer = document.createElement('div');
|
||||
slidersContainer.className = 'section-box-sliders';
|
||||
@@ -164,7 +183,8 @@ export class SectionBoxPanel implements IBimComponent {
|
||||
hide: '隐藏',
|
||||
reverse: '反向',
|
||||
fit: '适应到模型',
|
||||
reset: '重置'
|
||||
reset: '重置',
|
||||
fill: '填充'
|
||||
};
|
||||
|
||||
const icon = document.createElement('div');
|
||||
@@ -178,6 +198,7 @@ export class SectionBoxPanel implements IBimComponent {
|
||||
if (ref === 'hide') this.hideLabelEl = labelEl;
|
||||
else if (ref === 'fit') this.fitLabelEl = labelEl;
|
||||
else if (ref === 'reset') this.resetLabelEl = labelEl;
|
||||
else if (ref === 'fill') this.fillLabelEl = labelEl;
|
||||
|
||||
btn.appendChild(icon);
|
||||
btn.appendChild(labelEl);
|
||||
@@ -320,12 +341,14 @@ export class SectionBoxPanel implements IBimComponent {
|
||||
|
||||
public setLocales(): void {
|
||||
if (!this.hideLabelEl) return;
|
||||
this.fillLabelEl.textContent = t('sectionBox.actions.fill');
|
||||
this.hideLabelEl.textContent = t('sectionBox.actions.hide');
|
||||
this.fitLabelEl.textContent = t('sectionBox.actions.fitToModel');
|
||||
this.resetLabelEl.textContent = t('sectionBox.actions.reset');
|
||||
this.xLabelEl.textContent = t('sectionBox.axes.x');
|
||||
this.yLabelEl.textContent = t('sectionBox.axes.y');
|
||||
this.zLabelEl.textContent = t('sectionBox.axes.z');
|
||||
this.fillBtn.title = t('sectionBox.actions.fill');
|
||||
this.hideBtn.title = t('sectionBox.actions.hide');
|
||||
this.fitBtn.title = t('sectionBox.actions.fitToModel');
|
||||
this.resetBtn.title = t('sectionBox.actions.reset');
|
||||
|
||||
@@ -24,46 +24,14 @@ export interface SectionBoxRange {
|
||||
* 剖切盒面板配置选项
|
||||
*/
|
||||
export interface SectionBoxPanelOptions {
|
||||
/**
|
||||
* 隐藏按钮切换回调
|
||||
* @param isHidden 是否隐藏剖切盒
|
||||
*/
|
||||
onHideToggle?: (isHidden: boolean) => void;
|
||||
|
||||
/**
|
||||
* 反向按钮切换回调
|
||||
* @param isReversed 是否反向
|
||||
*/
|
||||
onReverseToggle?: (isReversed: boolean) => void;
|
||||
|
||||
/**
|
||||
* 适应到模型按钮回调
|
||||
*/
|
||||
onFitToModel?: () => void;
|
||||
|
||||
/**
|
||||
* 重置按钮回调
|
||||
*/
|
||||
onReset?: () => void;
|
||||
|
||||
/**
|
||||
* 范围变化回调
|
||||
* @param range 当前范围值
|
||||
*/
|
||||
onRangeChange?: (range: SectionBoxRange) => void;
|
||||
|
||||
/**
|
||||
* 默认隐藏状态(默认 false)
|
||||
*/
|
||||
onFillToggle?: (isFilled: boolean) => void;
|
||||
defaultHidden?: boolean;
|
||||
|
||||
/**
|
||||
* 默认反向状态(默认 false)
|
||||
*/
|
||||
defaultReversed?: boolean;
|
||||
|
||||
/**
|
||||
* 默认范围值
|
||||
*/
|
||||
defaultFill?: boolean;
|
||||
defaultRange?: SectionBoxRange;
|
||||
}
|
||||
|
||||
@@ -11,16 +11,18 @@ export interface SectionDockPanelOptions {
|
||||
defaultType?: SectionDockType | null;
|
||||
defaultAxis?: SectionDockAxis;
|
||||
defaultHidden?: boolean;
|
||||
defaultFill?: boolean;
|
||||
onTypeChange?: (type: SectionDockType, axis: SectionDockAxis) => void;
|
||||
onAxisChange?: (axis: SectionDockAxis) => void;
|
||||
onHideToggle?: (hidden: boolean) => void;
|
||||
onReverse?: () => void;
|
||||
onReset?: (type: SectionDockType, axis: SectionDockAxis) => void;
|
||||
onFitToModel?: () => void;
|
||||
onFillToggle?: (isFilled: boolean) => void;
|
||||
}
|
||||
|
||||
interface ToolDefinition {
|
||||
key: 'hide' | 'reverse' | 'reset' | 'fit';
|
||||
key: 'hide' | 'reverse' | 'reset' | 'fit' | 'fill';
|
||||
iconName: string;
|
||||
textKey: string;
|
||||
onClick: () => void;
|
||||
@@ -40,12 +42,14 @@ export class SectionDockPanel implements IBimComponent {
|
||||
private activeType: SectionDockType | null;
|
||||
private activeAxis: SectionDockAxis;
|
||||
private isHidden: boolean;
|
||||
private isFilled: boolean;
|
||||
|
||||
constructor(options: SectionDockPanelOptions = {}) {
|
||||
this.options = options;
|
||||
this.activeType = options.defaultType ?? null;
|
||||
this.activeAxis = options.defaultAxis ?? 'x';
|
||||
this.isHidden = options.defaultHidden ?? false;
|
||||
this.isFilled = options.defaultFill ?? false;
|
||||
|
||||
const { root, toolContainer, axisPanel, divider } = this.createDom();
|
||||
this.element = root;
|
||||
@@ -112,6 +116,15 @@ export class SectionDockPanel implements IBimComponent {
|
||||
return this.activeAxis;
|
||||
}
|
||||
|
||||
public setFillState(isFilled: boolean): void {
|
||||
this.isFilled = isFilled;
|
||||
this.renderTools();
|
||||
}
|
||||
|
||||
public getFillState(): boolean {
|
||||
return this.isFilled;
|
||||
}
|
||||
|
||||
private createDom(): { root: HTMLElement; toolContainer: HTMLElement; axisPanel: HTMLElement; divider: HTMLElement } {
|
||||
const root = document.createElement('div');
|
||||
root.className = 'section-dock-panel';
|
||||
@@ -257,6 +270,18 @@ export class SectionDockPanel implements IBimComponent {
|
||||
}
|
||||
|
||||
private getToolsForType(type: SectionDockType): ToolDefinition[] {
|
||||
const fillTool: ToolDefinition = {
|
||||
key: 'fill',
|
||||
iconName: '填充',
|
||||
textKey: type === 'box' ? 'sectionBox.actions.fill' : type === 'axis' ? 'sectionAxis.actions.fill' : 'sectionPlane.actions.fill',
|
||||
isActive: this.isFilled,
|
||||
onClick: () => {
|
||||
this.isFilled = !this.isFilled;
|
||||
this.options.onFillToggle?.(this.isFilled);
|
||||
this.renderTools();
|
||||
}
|
||||
};
|
||||
|
||||
const hideTool: ToolDefinition = {
|
||||
key: 'hide',
|
||||
iconName: '隐藏',
|
||||
@@ -280,6 +305,7 @@ export class SectionDockPanel implements IBimComponent {
|
||||
|
||||
if (type === 'axis') {
|
||||
return [
|
||||
fillTool,
|
||||
hideTool,
|
||||
reverseTool,
|
||||
{
|
||||
@@ -297,6 +323,7 @@ export class SectionDockPanel implements IBimComponent {
|
||||
|
||||
if (type === 'box') {
|
||||
return [
|
||||
fillTool,
|
||||
hideTool,
|
||||
{
|
||||
key: 'fit',
|
||||
@@ -320,6 +347,7 @@ export class SectionDockPanel implements IBimComponent {
|
||||
}
|
||||
|
||||
return [
|
||||
fillTool,
|
||||
hideTool,
|
||||
reverseTool,
|
||||
{
|
||||
|
||||
@@ -15,14 +15,17 @@ export class SectionPlanePanel implements IBimComponent {
|
||||
private options: SectionPlanePanelOptions;
|
||||
|
||||
private isHidden: boolean = false;
|
||||
private isFilled: boolean = false;
|
||||
|
||||
// DOM 引用
|
||||
private hideBtn!: HTMLButtonElement;
|
||||
private reverseBtn!: HTMLButtonElement;
|
||||
private resetBtn!: HTMLButtonElement;
|
||||
private fillBtn!: HTMLButtonElement;
|
||||
private hideLabelEl!: HTMLElement;
|
||||
private reverseLabelEl!: HTMLElement;
|
||||
private resetLabelEl!: HTMLElement;
|
||||
private fillLabelEl!: HTMLElement;
|
||||
|
||||
// 订阅清理
|
||||
private unsubscribeLocale: (() => void) | null = null;
|
||||
@@ -31,6 +34,7 @@ export class SectionPlanePanel implements IBimComponent {
|
||||
constructor(options: SectionPlanePanelOptions = {}) {
|
||||
this.options = options;
|
||||
this.isHidden = options.defaultHidden ?? false;
|
||||
this.isFilled = options.defaultFill ?? false;
|
||||
this.element = this.createDom();
|
||||
}
|
||||
|
||||
@@ -43,8 +47,18 @@ export class SectionPlanePanel implements IBimComponent {
|
||||
return this.isHidden;
|
||||
}
|
||||
|
||||
public setFillState(isFilled: boolean): void {
|
||||
this.isFilled = isFilled;
|
||||
this.updateButtonStates();
|
||||
}
|
||||
|
||||
public getFillState(): boolean {
|
||||
return this.isFilled;
|
||||
}
|
||||
|
||||
private updateButtonStates(): void {
|
||||
this.hideBtn?.classList.toggle('active', this.isHidden);
|
||||
this.fillBtn?.classList.toggle('active', this.isFilled);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -88,10 +102,12 @@ export class SectionPlanePanel implements IBimComponent {
|
||||
this.hideLabelEl.textContent = t('sectionPlane.actions.hide');
|
||||
this.reverseLabelEl.textContent = t('sectionPlane.actions.reverse');
|
||||
this.resetLabelEl.textContent = t('sectionPlane.actions.reset');
|
||||
this.fillLabelEl.textContent = t('sectionPlane.actions.fill');
|
||||
|
||||
this.hideBtn.title = t('sectionPlane.actions.hide');
|
||||
this.reverseBtn.title = t('sectionPlane.actions.reverse');
|
||||
this.resetBtn.title = t('sectionPlane.actions.reset');
|
||||
this.fillBtn.title = t('sectionPlane.actions.fill');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -116,6 +132,17 @@ export class SectionPlanePanel implements IBimComponent {
|
||||
const root = document.createElement('div');
|
||||
root.className = 'section-plane-panel';
|
||||
|
||||
// 填充按钮
|
||||
this.fillBtn = this.createButton(
|
||||
'fill',
|
||||
getIcon('填充'),
|
||||
() => {
|
||||
this.isFilled = !this.isFilled;
|
||||
this.updateButtonStates();
|
||||
this.options.onFillToggle?.(this.isFilled);
|
||||
}
|
||||
);
|
||||
|
||||
// 隐藏按钮
|
||||
this.hideBtn = this.createButton(
|
||||
'hide',
|
||||
@@ -149,6 +176,7 @@ export class SectionPlanePanel implements IBimComponent {
|
||||
}
|
||||
);
|
||||
|
||||
root.appendChild(this.fillBtn);
|
||||
root.appendChild(this.hideBtn);
|
||||
root.appendChild(this.reverseBtn);
|
||||
root.appendChild(this.resetBtn);
|
||||
@@ -159,7 +187,7 @@ export class SectionPlanePanel implements IBimComponent {
|
||||
/**
|
||||
* 创建按钮
|
||||
*/
|
||||
private createButton(type: 'hide' | 'reverse' | 'reset', iconSvg: string, onClick: () => void): HTMLButtonElement {
|
||||
private createButton(type: 'hide' | 'reverse' | 'reset' | 'fill', iconSvg: string, onClick: () => void): HTMLButtonElement {
|
||||
const btn = document.createElement('button');
|
||||
btn.type = 'button';
|
||||
btn.className = 'section-plane-btn';
|
||||
@@ -175,11 +203,12 @@ export class SectionPlanePanel implements IBimComponent {
|
||||
label.className = 'section-plane-btn-label';
|
||||
btn.appendChild(label);
|
||||
|
||||
// 保存 label 引用
|
||||
if (type === 'hide') {
|
||||
this.hideLabelEl = label;
|
||||
} else if (type === 'reverse') {
|
||||
this.reverseLabelEl = label;
|
||||
} else if (type === 'fill') {
|
||||
this.fillLabelEl = label;
|
||||
} else {
|
||||
this.resetLabelEl = label;
|
||||
}
|
||||
|
||||
@@ -7,12 +7,23 @@ export interface SectionPlanePanelOptions {
|
||||
*/
|
||||
defaultHidden?: boolean;
|
||||
|
||||
/**
|
||||
* 初始填充状态
|
||||
*/
|
||||
defaultFill?: boolean;
|
||||
|
||||
/**
|
||||
* 隐藏状态切换回调
|
||||
* @param isHidden 是否隐藏
|
||||
*/
|
||||
onHideToggle?: (isHidden: boolean) => void;
|
||||
|
||||
/**
|
||||
* 填充状态切换回调
|
||||
* @param isFilled 是否填充
|
||||
*/
|
||||
onFillToggle?: (isFilled: boolean) => void;
|
||||
|
||||
/**
|
||||
* 反向按钮回调
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user