feat: 迁移BimEngine到radial+dock并完善测量/剖切/漫游面板

This commit is contained in:
yuding
2026-03-30 15:56:18 +08:00
parent 2574a11284
commit 819992f331
26 changed files with 9575 additions and 8579 deletions

View File

@@ -1,11 +1,13 @@
declare const __APP_VERSION__: string;
import './bim-engine.css';
import { ToolbarManager } from './managers/toolbar-manager';
import { ButtonGroupManager } from './managers/button-group-manager';
import { DialogManager } from './managers/dialog-manager';
import { EngineManager } from './managers/engine-manager';
import { RightKeyManager } from './managers/right-key-manager';
import { ConstructTreeManagerBtn } from './managers/construct-tree-manager-btn';
import { RadialToolbarManager } from './managers/radial-toolbar-manager';
import { BottomDockManager } from './managers/bottom-dock-manager';
import { MeasureDockManager } from './managers/measure-dock-manager';
import { SectionDockManager } from './managers/section-dock-manager';
import { WalkDockManager } from './managers/walk-dock-manager';
import { MeasureDialogManager } from './managers/measure-dialog-manager';
import { SectionPlaneDialogManager } from './managers/section-plane-dialog-manager';
@@ -16,6 +18,7 @@ import { EngineInfoDialogManager } from './managers/engine-info-dialog-manager';
import { SettingDialogManager } from './managers/setting-dialog-manager';
import { ComponentDetailManager } from './managers/component-detail-manager';
import { AiChatManager } from './managers/ai-chat-manager';
import { ConstructTreeManagerBtn } from './managers/construct-tree-manager-btn';
import type { EngineOptions, ModelLoadOptions } from './components/engine';
import { localeManager } from './services/locale';
import { themeManager } from './services/theme';
@@ -35,12 +38,14 @@ export class BimEngine {
private lastSyncedHeight = -1;
private registry: ManagerRegistry;
public toolbar: ToolbarManager | null = null;
public constructTreeBtn: ConstructTreeManagerBtn | null = null;
public buttonGroup: ButtonGroupManager | null = null;
public dialog: DialogManager | null = null;
public engine: EngineManager | null = null;
public rightKey: RightKeyManager | null = null;
public radialToolbar: RadialToolbarManager | null = null;
public bottomDock: BottomDockManager | null = null;
public measureDock: MeasureDockManager | null = null;
public sectionDock: SectionDockManager | null = null;
public walkDock: WalkDockManager | null = null;
public measure: MeasureDialogManager | null = null;
public sectionPlane: SectionPlaneDialogManager | null = null;
@@ -51,6 +56,7 @@ export class BimEngine {
public componentDetail: ComponentDetailManager | null = null;
public aiChat: AiChatManager | null = null;
public setting: SettingDialogManager | null = null;
public constructTreeBtn: ConstructTreeManagerBtn | null = null;
private readonly handleWindowResize = () => {
this.updateClientSizeDisplay();
@@ -133,10 +139,23 @@ export class BimEngine {
this.engine = new EngineManager(this.wrapper, this.registry);
this.dialog = new DialogManager(this.wrapper, this.registry);
this.toolbar = new ToolbarManager(this.wrapper, this.registry);
this.buttonGroup = new ButtonGroupManager(this.wrapper, this.registry);
this.rightKey = new RightKeyManager(this.wrapper, this.registry);
this.constructTreeBtn = new ConstructTreeManagerBtn(this.wrapper, this.registry);
this.bottomDock = new BottomDockManager(this.wrapper, this.registry);
this.registry.bottomDock = this.bottomDock;
this.measureDock = new MeasureDockManager(this.registry);
this.registry.measureDock = this.measureDock;
this.measureDock.init();
this.sectionDock = new SectionDockManager(this.registry);
this.registry.sectionDock = this.sectionDock;
this.sectionDock.init();
this.walkDock = new WalkDockManager(this.registry);
this.registry.walkDock = this.walkDock;
this.walkDock.init();
this.radialToolbar = new RadialToolbarManager(this.wrapper, this.registry);
this.measure = new MeasureDialogManager(this.registry);
this.sectionPlane = new SectionPlaneDialogManager(this.registry);
@@ -149,10 +168,8 @@ export class BimEngine {
this.registry.engine3d = this.engine;
this.registry.dialog = this.dialog;
this.registry.toolbar = this.toolbar;
this.registry.buttonGroup = this.buttonGroup;
this.registry.rightKey = this.rightKey;
this.registry.constructTree = this.constructTreeBtn;
this.registry.radialToolbar = this.radialToolbar;
this.registry.measure = this.measure;
this.registry.sectionPlane = this.sectionPlane;
@@ -170,6 +187,9 @@ export class BimEngine {
this.registry.aiChat = this.aiChat;
this.aiChat.init();
this.constructTreeBtn = new ConstructTreeManagerBtn(this.wrapper, this.registry);
this.registry.constructTree = this.constructTreeBtn;
this.setting = new SettingDialogManager(this.registry);
this.registry.setting = this.setting;
this.setting.init();
@@ -236,8 +256,11 @@ export class BimEngine {
public destroy() {
this.unbindSizeObserver();
this.toolbar?.destroy();
this.buttonGroup?.destroy();
this.radialToolbar?.destroy();
this.measureDock?.destroy();
this.sectionDock?.destroy();
this.walkDock?.destroy();
this.bottomDock?.destroy();
this.engine?.destroy();
this.dialog?.destroy();
this.rightKey?.destroy();
@@ -247,6 +270,7 @@ export class BimEngine {
this.sectionAxis?.destroy();
this.sectionBox?.destroy();
this.walkControl?.destroy();
this.constructTreeBtn?.destroy();
this.aiChat?.destroy();
this.setting?.destroy();

View File

@@ -23,6 +23,12 @@
box-shadow: var(--bd-shadow, 0 2px 8px rgba(15, 23, 42, 0.1));
transition: transform 220ms ease, opacity 200ms ease;
overflow: visible;
z-index: 1;
}
.bottom-dock-panel:hover,
.bottom-dock-panel:focus-within {
z-index: 10;
}
.bottom-dock-panel.is-entering {

View File

@@ -94,6 +94,7 @@ export interface EngineSettingPreset {
settings: EngineSettings;
readonly?: boolean;
source?: 'sdk-default' | 'external' | 'user';
allowModify?: boolean;
}
export interface PresetListItem {

View File

@@ -4,7 +4,6 @@
padding: 0;
border-radius: 8px;
border: none;
background: color-mix(in srgb, var(--bim-bg-elevated, #e8ecf2) 92%, #ffffff 8%);
box-sizing: border-box;
color: var(--bim-text-secondary, #475569);
font-size: 13px;
@@ -187,7 +186,6 @@
.measure-dock-panel-mode-btn.is-active {
border-color: color-mix(in srgb, var(--bim-primary, #4f88ff) 70%, #9db9ff 30%);
background: color-mix(in srgb, var(--bim-primary-subtle, rgba(96, 140, 255, 0.18)) 72%, #ffffff 28%);
color: color-mix(in srgb, var(--bim-primary, #4f88ff) 78%, #6f9dff 22%);
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--bim-primary, #4f88ff) 35%, transparent 65%);
}
@@ -332,4 +330,4 @@
.measure-dock-panel-action-expand.is-expanded {
height: 74px;
}
}
}

View File

@@ -400,23 +400,21 @@ export class RadialToolbar implements IBimComponent {
style.setProperty('--bim-icon-inverse', theme.iconInverse);
style.setProperty('--bim-shadow-glow', theme.shadowGlow);
const isDark = theme.name === 'dark';
style.setProperty('--rt-main-bg', theme.bgBase);
style.setProperty('--rt-main-bg-hover', theme.bgBase);
style.setProperty('--rt-main-border', theme.floatingBtnBorder);
style.setProperty('--rt-main-shadow', theme.floatingBtnShadow);
style.setProperty('--rt-main-shadow-hover', theme.floatingBtnShadowHover);
style.setProperty('--rt-main-icon', theme.floatingIconColor);
style.setProperty('--rt-main-icon-hover', theme.floatingIconColorHover);
style.setProperty('--rt-main-bg', isDark ? 'rgba(55, 68, 86, 0.92)' : theme.floatingBtnBg);
style.setProperty('--rt-main-bg-hover', isDark ? 'rgba(66, 82, 104, 0.96)' : theme.floatingBtnBgHover);
style.setProperty('--rt-main-border', isDark ? 'rgba(117, 133, 154, 0.56)' : theme.floatingBtnBorder);
style.setProperty('--rt-main-shadow', isDark ? '0 2px 8px rgba(15, 23, 42, 0.32), 0 4px 12px rgba(15, 23, 42, 0.24)' : theme.floatingBtnShadow);
style.setProperty('--rt-main-shadow-hover', isDark ? '0 4px 12px rgba(15, 23, 42, 0.38), 0 6px 20px rgba(15, 23, 42, 0.3)' : theme.floatingBtnShadowHover);
style.setProperty('--rt-main-icon', isDark ? '#e2e8f0' : theme.floatingIconColor);
style.setProperty('--rt-main-icon-hover', isDark ? '#f8fafc' : theme.floatingIconColorHover);
style.setProperty('--rt-sub-bg', isDark ? 'rgba(55, 68, 86, 0.92)' : theme.floatingBtnBg);
style.setProperty('--rt-sub-bg-hover', isDark ? 'rgba(66, 82, 104, 0.96)' : theme.floatingBtnBgHover);
style.setProperty('--rt-sub-border', isDark ? 'rgba(117, 133, 154, 0.56)' : theme.floatingBtnBorder);
style.setProperty('--rt-sub-shadow', isDark ? '0 2px 8px rgba(15, 23, 42, 0.32), 0 4px 12px rgba(15, 23, 42, 0.24)' : theme.floatingBtnShadow);
style.setProperty('--rt-sub-shadow-hover', isDark ? '0 4px 12px rgba(15, 23, 42, 0.38), 0 6px 20px rgba(15, 23, 42, 0.3)' : theme.floatingBtnShadowHover);
style.setProperty('--rt-sub-icon', isDark ? '#e2e8f0' : theme.floatingIconColor);
style.setProperty('--rt-sub-icon-hover', isDark ? '#f8fafc' : theme.floatingIconColorHover);
style.setProperty('--rt-sub-bg', theme.bgBase);
style.setProperty('--rt-sub-bg-hover', theme.primaryHover);
style.setProperty('--rt-sub-border', theme.floatingBtnBorder);
style.setProperty('--rt-sub-shadow', theme.floatingBtnShadow);
style.setProperty('--rt-sub-shadow-hover', theme.floatingBtnShadowHover);
style.setProperty('--rt-sub-icon', theme.floatingIconColor);
style.setProperty('--rt-sub-icon-hover', theme.iconHover);
}
public setItemActive(id: string, active: boolean): void {

View File

@@ -0,0 +1,142 @@
.section-dock-panel {
width: fit-content;
max-width: 100%;
display: flex;
flex-direction: column;
gap: 10px;
color: var(--bim-text-secondary, #475569);
}
.section-dock-axis-panel {
display: none;
width: fit-content;
border-radius: 8px;
padding: 0;
gap: 10px;
}
.section-dock-axis-panel.is-visible {
display: flex;
}
.section-dock-axis-btn {
width: 32px;
height: 32px;
border-radius: 8px;
border: 1px solid rgba(148, 163, 184, 0.28);
background: color-mix(in srgb, var(--bim-bg-inset, #edf1f6) 92%, #ffffff 8%);
color: color-mix(in srgb, var(--bim-text-secondary, #64748b) 94%, #475569 6%);
font-size: 16px;
line-height: 1;
box-sizing: border-box;
padding: 0;
cursor: pointer;
transition: all 0.15s ease;
}
.section-dock-axis-btn:hover {
border-color: rgba(148, 163, 184, 0.5);
background: color-mix(in srgb, var(--bim-component-bg-hover, #dce5f2) 64%, #ffffff 36%);
}
.section-dock-axis-btn.is-active {
border-color: color-mix(in srgb, var(--bim-primary, #4f88ff) 70%, #9db9ff 30%);
background: color-mix(in srgb, var(--bim-primary-subtle, rgba(96, 140, 255, 0.18)) 72%, #ffffff 28%);
color: color-mix(in srgb, var(--bim-primary, #4f88ff) 78%, #6f9dff 22%);
}
.section-dock-main {
display: flex;
align-items: center;
gap: 10px;
padding: 0;
border-radius: 8px;
}
.section-dock-types,
.section-dock-tools {
display: flex;
align-items: center;
gap: 10px;
}
.section-dock-divider {
width: 1px;
height: 32px;
background: color-mix(in srgb, var(--bim-border-default, #cbd5e1) 84%, transparent 16%);
}
.section-dock-type-btn,
.section-dock-tool-btn {
width: 32px;
height: 32px;
border: 1px solid rgba(148, 163, 184, 0.28);
border-radius: 8px;
background: color-mix(in srgb, var(--bim-bg-inset, #edf1f6) 92%, #ffffff 8%);
color: color-mix(in srgb, var(--bim-text-secondary, #64748b) 94%, #475569 6%);
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
box-sizing: border-box;
padding: 0;
transition: all 0.15s ease;
}
.section-dock-type-btn:hover,
.section-dock-tool-btn:hover {
border-color: var(--bim-border-strong, rgba(100, 116, 139, 0.6));
background: color-mix(in srgb, var(--bim-component-bg-hover, #dce5f2) 64%, #ffffff 36%);
}
.section-dock-type-btn.is-active,
.section-dock-tool-btn.is-active {
border-color: color-mix(in srgb, var(--bim-primary, #4f88ff) 70%, #9db9ff 30%);
color: color-mix(in srgb, var(--bim-primary, #4f88ff) 78%, #6f9dff 22%);
}
.section-dock-type-icon,
.section-dock-tool-icon {
width: 20px;
height: 20px;
display: inline-flex;
align-items: center;
justify-content: center;
}
.section-dock-type-icon svg,
.section-dock-tool-icon svg {
width: 100%;
height: 100%;
fill: currentColor;
}
.section-dock-panel [data-tooltip] {
position: relative;
}
.section-dock-panel [data-tooltip]::after {
content: attr(data-tooltip);
position: absolute;
left: 50%;
bottom: calc(100% + 6px);
transform: translateX(-50%);
padding: 4px 8px;
border-radius: 6px;
background: rgba(15, 23, 42, 0.92);
color: #f8fafc;
font-size: 12px;
line-height: 1.2;
white-space: nowrap;
pointer-events: none;
opacity: 0;
visibility: hidden;
z-index: 8;
transition: opacity 60ms ease;
}
.section-dock-panel [data-tooltip]:hover::after,
.section-dock-panel [data-tooltip]:focus-visible::after {
opacity: 1;
visibility: visible;
}

View File

@@ -0,0 +1,330 @@
import './index.css';
import type { ThemeConfig } from '../../themes/types';
import { IBimComponent } from '../../types/component';
import { t } from '../../services/locale';
import { getIcon } from '../../utils/icon-manager';
export type SectionDockType = 'face' | 'axis' | 'box';
export type SectionDockAxis = 'x' | 'y' | 'z';
export interface SectionDockPanelOptions {
defaultType?: SectionDockType;
defaultAxis?: SectionDockAxis;
defaultHidden?: 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;
}
interface ToolDefinition {
key: 'hide' | 'reverse' | 'reset' | 'fit';
iconName: string;
textKey: string;
onClick: () => void;
isActive?: boolean;
}
export class SectionDockPanel implements IBimComponent {
public readonly element: HTMLElement;
private readonly options: SectionDockPanelOptions;
private readonly typeButtons: Map<SectionDockType, HTMLButtonElement> = new Map();
private readonly axisButtons: Map<SectionDockAxis, HTMLButtonElement> = new Map();
private readonly toolContainer: HTMLElement;
private readonly axisPanel: HTMLElement;
private activeType: SectionDockType;
private activeAxis: SectionDockAxis;
private isHidden: boolean;
constructor(options: SectionDockPanelOptions = {}) {
this.options = options;
this.activeType = options.defaultType ?? 'face';
this.activeAxis = options.defaultAxis ?? 'x';
this.isHidden = options.defaultHidden ?? false;
const { root, toolContainer, axisPanel } = this.createDom();
this.element = root;
this.toolContainer = toolContainer;
this.axisPanel = axisPanel;
}
public init(): void {
this.applyTypeState();
this.applyAxisState();
this.renderTools();
this.setLocales();
}
public setTheme(theme: ThemeConfig): void {
const style = this.element.style;
style.setProperty('--bim-text-primary', theme.textPrimary);
style.setProperty('--bim-text-secondary', theme.textSecondary);
style.setProperty('--bim-border-default', theme.borderDefault);
style.setProperty('--bim-border-strong', theme.borderStrong);
style.setProperty('--bim-bg-inset', theme.bgInset);
style.setProperty('--bim-bg-elevated', theme.bgElevated);
style.setProperty('--bim-primary', theme.primary);
style.setProperty('--bim-primary-subtle', theme.primarySubtle);
style.setProperty('--bim-component-bg-hover', theme.componentBgHover);
}
public setLocales(): void {
this.typeButtons.get('face')!.dataset.tooltip = t('toolbar.sectionPlane');
this.typeButtons.get('axis')!.dataset.tooltip = t('toolbar.sectionAxis');
this.typeButtons.get('box')!.dataset.tooltip = t('toolbar.sectionBox');
this.axisButtons.get('x')!.dataset.label = 'X';
this.axisButtons.get('y')!.dataset.label = 'Y';
this.axisButtons.get('z')!.dataset.label = 'Z';
this.typeButtons.forEach((button) => {
button.setAttribute('aria-label', button.dataset.tooltip ?? '');
});
this.axisButtons.forEach((button) => {
button.setAttribute('aria-label', button.dataset.label ?? '');
});
this.renderTools();
}
public destroy(): void {
this.element.remove();
}
public resetForOpen(): void {
this.activeType = 'face';
this.isHidden = false;
this.applyTypeState();
this.renderTools();
this.options.onTypeChange?.(this.activeType, this.activeAxis);
this.options.onHideToggle?.(false);
}
public getActiveType(): SectionDockType {
return this.activeType;
}
public getActiveAxis(): SectionDockAxis {
return this.activeAxis;
}
private createDom(): { root: HTMLElement; toolContainer: HTMLElement; axisPanel: HTMLElement } {
const root = document.createElement('div');
root.className = 'section-dock-panel';
const axisPanel = document.createElement('div');
axisPanel.className = 'section-dock-axis-panel';
(['x', 'y', 'z'] as SectionDockAxis[]).forEach((axis) => {
const button = document.createElement('button');
button.type = 'button';
button.className = 'section-dock-axis-btn';
button.textContent = axis.toUpperCase();
button.addEventListener('click', () => {
this.handleAxisChange(axis);
});
this.axisButtons.set(axis, button);
axisPanel.appendChild(button);
});
const main = document.createElement('div');
main.className = 'section-dock-main';
const left = document.createElement('div');
left.className = 'section-dock-types';
const faceBtn = this.createTypeButton('face', getIcon('拾曲面剖切'));
const axisBtn = this.createTypeButton('axis', getIcon('轴向剖切'));
const boxBtn = this.createTypeButton('box', getIcon('剖切盒'));
left.appendChild(faceBtn);
left.appendChild(axisBtn);
left.appendChild(boxBtn);
const divider = document.createElement('div');
divider.className = 'section-dock-divider';
const toolContainer = document.createElement('div');
toolContainer.className = 'section-dock-tools';
main.appendChild(left);
main.appendChild(divider);
main.appendChild(toolContainer);
root.appendChild(axisPanel);
root.appendChild(main);
return { root, toolContainer, axisPanel };
}
private createTypeButton(type: SectionDockType, iconSvg: string): HTMLButtonElement {
const button = document.createElement('button');
button.type = 'button';
button.className = 'section-dock-type-btn';
const icon = document.createElement('span');
icon.className = 'section-dock-type-icon';
icon.innerHTML = iconSvg;
button.appendChild(icon);
button.addEventListener('click', () => {
this.handleTypeChange(type);
});
this.typeButtons.set(type, button);
return button;
}
private handleTypeChange(type: SectionDockType): void {
if (this.activeType === type) {
return;
}
this.activeType = type;
this.applyTypeState();
this.renderTools();
if (this.isHidden) {
this.isHidden = false;
this.options.onHideToggle?.(false);
}
this.options.onTypeChange?.(type, this.activeAxis);
}
private handleAxisChange(axis: SectionDockAxis): void {
if (this.activeAxis === axis) {
return;
}
this.activeAxis = axis;
this.applyAxisState();
if (this.activeType === 'axis') {
this.options.onAxisChange?.(axis);
}
}
private applyTypeState(): void {
this.typeButtons.forEach((button, type) => {
button.classList.toggle('is-active', type === this.activeType);
});
const axisVisible = this.activeType === 'axis';
this.axisPanel.classList.toggle('is-visible', axisVisible);
}
private applyAxisState(): void {
this.axisButtons.forEach((button, axis) => {
button.classList.toggle('is-active', axis === this.activeAxis);
});
}
private renderTools(): void {
this.toolContainer.innerHTML = '';
this.getToolsForType(this.activeType).forEach((tool) => {
const button = document.createElement('button');
button.type = 'button';
button.className = 'section-dock-tool-btn';
if (tool.isActive) {
button.classList.add('is-active');
}
const icon = document.createElement('span');
icon.className = 'section-dock-tool-icon';
icon.innerHTML = getIcon(tool.iconName);
button.appendChild(icon);
const text = t(tool.textKey);
button.dataset.tooltip = text;
button.setAttribute('aria-label', text);
button.addEventListener('click', tool.onClick);
this.toolContainer.appendChild(button);
});
}
private getToolsForType(type: SectionDockType): ToolDefinition[] {
const hideTool: ToolDefinition = {
key: 'hide',
iconName: '隐藏',
textKey: type === 'box' ? 'sectionBox.actions.hide' : type === 'axis' ? 'sectionAxis.actions.hide' : 'sectionPlane.actions.hide',
isActive: this.isHidden,
onClick: () => {
this.isHidden = !this.isHidden;
this.options.onHideToggle?.(this.isHidden);
this.renderTools();
}
};
const reverseTool: ToolDefinition = {
key: 'reverse',
iconName: '反向',
textKey: type === 'axis' ? 'sectionAxis.actions.reverse' : type === 'box' ? 'sectionBox.actions.reverse' : 'sectionPlane.actions.reverse',
onClick: () => {
this.options.onReverse?.();
}
};
if (type === 'axis') {
return [
hideTool,
reverseTool,
{
key: 'reset',
iconName: '重置',
textKey: 'sectionAxis.actions.reset',
onClick: () => {
this.isHidden = false;
this.options.onReset?.(this.activeType, this.activeAxis);
this.renderTools();
}
}
];
}
if (type === 'box') {
return [
hideTool,
{
key: 'fit',
iconName: '适应到模型',
textKey: 'sectionBox.actions.fitToModel',
onClick: () => {
this.options.onFitToModel?.();
}
},
{
key: 'reset',
iconName: '重置',
textKey: 'sectionBox.actions.reset',
onClick: () => {
this.isHidden = false;
this.options.onReset?.(this.activeType, this.activeAxis);
this.renderTools();
}
}
];
}
return [
hideTool,
reverseTool,
{
key: 'reset',
iconName: '重置',
textKey: 'sectionPlane.actions.reset',
onClick: () => {
this.isHidden = false;
this.options.onReset?.(this.activeType, this.activeAxis);
this.renderTools();
}
}
];
}
}

View File

@@ -0,0 +1,107 @@
.walk-control-panel.walk-dock-panel {
gap: 10px;
padding: 0;
background: transparent;
border: none;
border-radius: 8px;
box-shadow: none;
color: var(--bim-text-secondary, #64748b);
}
.walk-control-panel.walk-dock-panel .walk-divider {
height: 32px;
}
.walk-control-panel.walk-dock-panel .walk-control-left,
.walk-control-panel.walk-dock-panel .walk-control-settings {
gap: 10px;
}
.walk-control-panel.walk-dock-panel .walk-icon-btn {
width: 32px;
height: 32px;
padding: 0;
border-radius: 8px;
border: 1px solid rgba(148, 163, 184, 0.28);
background: color-mix(in srgb, var(--bim-bg-inset, #edf1f6) 92%, #ffffff 8%);
color: color-mix(in srgb, var(--bim-text-secondary, #64748b) 94%, #475569 6%);
}
.walk-control-panel.walk-dock-panel .walk-icon-btn:hover {
border-color: rgba(148, 163, 184, 0.5);
background: color-mix(in srgb, var(--bim-component-bg-hover, #dce5f2) 64%, #ffffff 36%);
}
.walk-control-panel.walk-dock-panel .walk-icon-btn.active {
border-color: color-mix(in srgb, var(--bim-primary, #4f88ff) 70%, #9db9ff 30%);
background: color-mix(in srgb, var(--bim-primary-subtle, rgba(96, 140, 255, 0.18)) 72%, #ffffff 28%);
color: color-mix(in srgb, var(--bim-primary, #4f88ff) 78%, #6f9dff 22%);
}
.walk-control-panel.walk-dock-panel .walk-icon-btn svg {
width: 20px;
height: 20px;
}
.walk-control-panel.walk-dock-panel .walk-icon-btn.active svg {
fill: currentColor;
}
.walk-control-panel.walk-dock-panel .walk-speed-control {
gap: 8px;
}
.walk-control-panel.walk-dock-panel .walk-speed-label,
.walk-control-panel.walk-dock-panel .walk-checkbox-label,
.walk-control-panel.walk-dock-panel .walk-select-label {
font-size: 12px;
}
.walk-control-panel.walk-dock-panel .walk-speed-group {
padding: 2px;
border: 1px solid rgba(148, 163, 184, 0.28);
background: color-mix(in srgb, var(--bim-bg-inset, #edf1f6) 92%, #ffffff 8%);
}
.walk-control-panel.walk-dock-panel .walk-speed-btn {
width: 24px;
height: 24px;
font-size: 14px;
line-height: 1;
padding: 0;
display: inline-flex;
align-items: center;
justify-content: center;
border: 1px solid rgba(148, 163, 184, 0.28);
background: color-mix(in srgb, var(--bim-bg-elevated, #f8fafc) 88%, #ffffff 12%);
color: var(--bim-text-primary, #0f172a);
}
.walk-control-panel.walk-dock-panel .walk-speed-display {
min-width: 30px;
font-size: 12px;
color: var(--bim-text-primary, #0f172a);
}
.walk-control-panel.walk-dock-panel .walk-checkbox {
width: 14px;
height: 14px;
accent-color: var(--bim-primary, #3b82f6);
}
.walk-control-panel.walk-dock-panel .walk-select {
min-width: 90px;
height: 24px;
padding: 2px 8px;
font-size: 12px;
border: 1px solid rgba(148, 163, 184, 0.28);
background: color-mix(in srgb, var(--bim-bg-inset, #edf1f6) 92%, #ffffff 8%);
color: var(--bim-text-primary, #0f172a);
}
.walk-control-panel.walk-dock-panel .walk-exit-btn {
height: 32px;
padding: 0 12px;
border-radius: 8px;
font-size: 12px;
}

View File

@@ -0,0 +1,39 @@
import './index.css';
import { IBimComponent } from '../../types/component';
import { WalkControlPanel } from '../walk-control-panel';
import type { WalkControlPanelOptions, WalkControlState } from '../walk-control-panel/types';
import type { ThemeConfig } from '../../themes/types';
export class WalkDockPanel implements IBimComponent {
public readonly element: HTMLElement;
private readonly panel: WalkControlPanel;
constructor(options: WalkControlPanelOptions = {}) {
this.panel = new WalkControlPanel(options);
this.panel.init();
this.element = this.panel.element;
this.element.classList.add('walk-dock-panel');
}
public init(): void {}
public setPlanViewActive(active: boolean): void {
this.panel.setPlanViewActive(active);
}
public setLocales(): void {
this.panel.setLocales();
}
public setTheme(theme: ThemeConfig): void {
this.panel.setTheme(theme);
}
public getState(): WalkControlState {
return this.panel.getState();
}
public destroy(): void {
this.panel.destroy();
}
}

View File

@@ -79,7 +79,7 @@ export class WalkPathPanel implements IBimComponent {
*/
private render(): void {
this.element.innerHTML = '';
// 渲染路径设置区域
const settings = this.createSettingsSection();
this.element.appendChild(settings);
@@ -104,13 +104,13 @@ export class WalkPathPanel implements IBimComponent {
// ===== 漫游时间 =====
const durationGroup = document.createElement('div');
durationGroup.className = 'walk-path-form-group';
const durationLabel = document.createElement('label');
durationLabel.textContent = t('walkControl.path.duration');
const durationWrapper = document.createElement('div');
durationWrapper.className = 'walk-path-input-wrapper';
const durationInput = document.createElement('input');
durationInput.type = 'number';
durationInput.className = 'walk-path-input';
@@ -121,11 +121,11 @@ export class WalkPathPanel implements IBimComponent {
const val = parseInt((e.target as HTMLInputElement).value) || 1;
this.duration = val * 1000;
};
const durationUnit = document.createElement('span');
durationUnit.className = 'walk-path-unit';
durationUnit.textContent = t('walkControl.path.durationUnit');
durationWrapper.appendChild(durationInput);
durationWrapper.appendChild(durationUnit);
durationGroup.appendChild(durationLabel);
@@ -134,7 +134,7 @@ export class WalkPathPanel implements IBimComponent {
// ===== 循环播放 =====
const loopGroup = document.createElement('div');
loopGroup.className = 'walk-path-form-group walk-path-form-group-inline';
const loopCheckbox = document.createElement('input');
loopCheckbox.type = 'checkbox';
loopCheckbox.id = 'walk-path-loop-checkbox';
@@ -144,11 +144,11 @@ export class WalkPathPanel implements IBimComponent {
// 更新循环播放状态
this.loop = (e.target as HTMLInputElement).checked;
};
const loopLabel = document.createElement('label');
loopLabel.htmlFor = 'walk-path-loop-checkbox';
loopLabel.textContent = t('walkControl.path.loop');
loopGroup.appendChild(loopCheckbox);
loopGroup.appendChild(loopLabel);
@@ -390,7 +390,7 @@ export class WalkPathPanel implements IBimComponent {
if (!this.element) return;
// 设置 CSS 变量
this.element.style.setProperty('--bim-text-primary', theme.textPrimary ?? '#fff');
this.element.style.setProperty('--bim-text-secondary', theme.textSecondary ?? '#94a3b8');
this.element.style.setProperty('--bim-text-secondary', theme.textPrimary ?? '#fff');
this.element.style.setProperty('--bim-bg-elevated', theme.bgElevated ?? '#1f2d3e');
this.element.style.setProperty('--bim-border-default', theme.borderDefault ?? '#334155');
this.element.style.setProperty('--bim-primary', theme.primary ?? '#3b82f6');

View File

@@ -26,6 +26,8 @@ import type { AiChatManager } from '../managers/ai-chat-manager';
import type { RadialToolbarManager } from '../managers/radial-toolbar-manager';
import type { BottomDockManager } from '../managers/bottom-dock-manager';
import type { MeasureDockManager } from '../managers/measure-dock-manager';
import type { SectionDockManager } from '../managers/section-dock-manager';
import type { WalkDockManager } from '../managers/walk-dock-manager';
/**
* Manager 注册表 - 实例模式
@@ -79,6 +81,8 @@ export class ManagerRegistry {
public radialToolbar: RadialToolbarManager | null = null;
public bottomDock: BottomDockManager | null = null;
public measureDock: MeasureDockManager | null = null;
public sectionDock: SectionDockManager | null = null;
public walkDock: WalkDockManager | null = null;
constructor() {}
@@ -107,6 +111,8 @@ export class ManagerRegistry {
this.radialToolbar = null;
this.bottomDock = null;
this.measureDock = null;
this.sectionDock = null;
this.walkDock = null;
}
/**

View File

@@ -6,6 +6,8 @@ import { RightKeyManager } from './managers/right-key-manager';
import { RadialToolbarManager } from './managers/radial-toolbar-manager';
import { BottomDockManager } from './managers/bottom-dock-manager';
import { MeasureDockManager } from './managers/measure-dock-manager';
import { SectionDockManager } from './managers/section-dock-manager';
import { WalkDockManager } from './managers/walk-dock-manager';
import { MeasureDialogManager } from './managers/measure-dialog-manager';
import { SectionPlaneDialogManager } from './managers/section-plane-dialog-manager';
@@ -46,6 +48,8 @@ export class CusBimEngine {
public radialToolbar: RadialToolbarManager | null = null;
public bottomDock: BottomDockManager | null = null;
public measureDock: MeasureDockManager | null = null;
public sectionDock: SectionDockManager | null = null;
public walkDock: WalkDockManager | null = null;
public measure: MeasureDialogManager | null = null;
public sectionPlane: SectionPlaneDialogManager | null = null;
@@ -150,6 +154,14 @@ export class CusBimEngine {
this.registry.measureDock = this.measureDock;
this.measureDock.init();
this.sectionDock = new SectionDockManager(this.registry);
this.registry.sectionDock = this.sectionDock;
this.sectionDock.init();
this.walkDock = new WalkDockManager(this.registry);
this.registry.walkDock = this.walkDock;
this.walkDock.init();
this.radialToolbar = new RadialToolbarManager(this.wrapper, this.registry);
this.measure = new MeasureDialogManager(this.registry);
@@ -252,6 +264,8 @@ export class CusBimEngine {
this.radialToolbar?.destroy();
this.measureDock?.destroy();
this.sectionDock?.destroy();
this.walkDock?.destroy();
this.bottomDock?.destroy();
this.engine?.destroy();
this.dialog?.destroy();

View File

@@ -143,6 +143,7 @@ export const enUS: TranslationDictionary = {
actions: {
hide: 'Hide',
reverse: 'Reverse',
reset: 'Reset',
axisX: 'X',
axisY: 'Y',
axisZ: 'Z'

View File

@@ -154,6 +154,7 @@ export interface TranslationDictionary {
actions: {
hide: string;
reverse: string;
reset: string;
axisX: string;
axisY: string;
axisZ: string;

View File

@@ -143,6 +143,7 @@ export const zhCN: TranslationDictionary = {
actions: {
hide: '隐藏',
reverse: '反向',
reset: '重置',
axisX: 'X',
axisY: 'Y',
axisZ: 'Z'

View File

@@ -143,6 +143,7 @@ export const zhTW: TranslationDictionary = {
actions: {
hide: '隱藏',
reverse: '反向',
reset: '重設',
axisX: 'X',
axisY: 'Y',
axisZ: 'Z'

View File

@@ -144,13 +144,25 @@ export class BottomDockManager extends BaseManager {
this.register({
id: 'section',
title: '剖切',
createContent: () => this.stack.createPlaceholderContent('剖切面板占位')
createContent: () => {
const sectionPanel = this.registry.sectionDock?.getPanelElement();
if (sectionPanel) {
return sectionPanel;
}
return this.stack.createPlaceholderContent('剖切面板占位');
}
});
this.register({
id: 'walk',
title: '漫游',
createContent: () => this.stack.createPlaceholderContent('漫游面板占位')
createContent: () => {
const walkPanel = this.registry.walkDock?.getPanelElement();
if (walkPanel) {
return walkPanel;
}
return this.stack.createPlaceholderContent('漫游面板占位');
}
});
}
}

View File

@@ -0,0 +1,123 @@
import { BaseManager } from '../core/base-manager';
import { ManagerRegistry } from '../core/manager-registry';
import { SectionDockPanel, type SectionDockAxis, type SectionDockType } from '../components/section-dock-panel';
import { themeManager } from '../services/theme';
import { localeManager } from '../services/locale';
export class SectionDockManager extends BaseManager {
private panel: SectionDockPanel | null = null;
private unsubscribeTheme: (() => void) | null = null;
private unsubscribeLocale: (() => void) | null = null;
private unsubscribeDockState: (() => void) | null = null;
constructor(registry: ManagerRegistry) {
super(registry);
}
public init(): void {
if (!this.unsubscribeTheme) {
this.unsubscribeTheme = themeManager.subscribe(() => {
this.applyPresentation();
});
}
if (!this.unsubscribeLocale) {
this.unsubscribeLocale = localeManager.subscribe(() => {
this.applyPresentation();
});
}
if (!this.unsubscribeDockState) {
this.unsubscribeDockState = this.registry.bottomDock?.onStateChange((state) => {
if (state.id !== 'section') {
return;
}
if (!state.open) {
console.log("deactivateSection")
this.engineComponent?.deactivateSection();
return;
}
this.panel?.resetForOpen();
}) ?? null;
}
}
public destroy(): void {
if (this.unsubscribeTheme) {
this.unsubscribeTheme();
this.unsubscribeTheme = null;
}
if (this.unsubscribeLocale) {
this.unsubscribeLocale();
this.unsubscribeLocale = null;
}
if (this.unsubscribeDockState) {
this.unsubscribeDockState();
this.unsubscribeDockState = null;
}
this.panel?.destroy();
this.panel = null;
super.destroy();
}
public getPanelElement(): HTMLElement {
if (!this.panel) {
this.panel = new SectionDockPanel({
defaultType: 'face',
defaultAxis: 'x',
defaultHidden: false,
onTypeChange: (type, axis) => {
this.activateByType(type, axis);
},
onAxisChange: (axis) => {
if (this.panel?.getActiveType() === 'axis') {
this.engineComponent?.activeSection(axis);
}
},
onHideToggle: (hidden) => {
if (hidden) {
this.engineComponent?.hideSection();
return;
}
this.engineComponent?.recoverSection();
},
onReverse: () => {
this.engineComponent?.reverseSection();
},
onReset: (type, axis) => {
this.engineComponent?.deactivateSection();
this.activateByType(type, axis);
},
onFitToModel: () => {
this.engineComponent?.scaleSectionBox();
}
});
this.panel.init();
}
this.panel.resetForOpen();
this.applyPresentation();
return this.panel.element;
}
private activateByType(type: SectionDockType, axis: SectionDockAxis): void {
if (type === 'face') {
this.engineComponent?.activeSection('face');
return;
}
if (type === 'box') {
this.engineComponent?.activeSection('box');
return;
}
this.engineComponent?.activeSection(axis);
}
private applyPresentation(): void {
if (!this.panel) {
return;
}
this.panel.setTheme(themeManager.getTheme());
this.panel.setLocales();
}
}

View File

@@ -100,8 +100,9 @@ export class SettingDialogManager extends BaseDialogManager {
}
private ensurePresetListWithInternalDefault(): void {
const builtin = this.createInternalDefaultPreset();
const withoutInternal = this.presetList.filter((item) => item.id !== SettingDialogManager.INTERNAL_DEFAULT_PRESET_ID);
const hasCustomDefault = withoutInternal.some((item) => item.isDefault);
const builtin = this.createInternalDefaultPreset(!hasCustomDefault);
this.presetList = [...withoutInternal, builtin];
if (!this.selectedPresetId || !this.presetList.some((item) => item.id === this.selectedPresetId)) {
this.selectedPresetId = builtin.id;
@@ -118,21 +119,24 @@ export class SettingDialogManager extends BaseDialogManager {
this.ensurePresetListWithInternalDefault();
}
private createInternalDefaultPreset(): EngineSettingPreset {
private createInternalDefaultPreset(isDefault: boolean = true): EngineSettingPreset {
return {
id: SettingDialogManager.INTERNAL_DEFAULT_PRESET_ID,
presetName: t('setting.defaultPresetLabel'),
isDefault: true,
isDefault,
settings: structuredClone(DEFAULT_SETTINGS),
source: 'sdk-default',
readonly: false,
allowModify: false,
};
}
public setPresetList(presets: EngineSettingPreset[]): void {
this.presetList = this.normalizePresetList(presets);
this.ensurePresetListWithInternalDefault();
this.selectedPresetId = this.resolveDefaultPresetId(this.presetList);
if (!this.selectedPresetId && this.presetList.length > 0) {
this.selectedPresetId = this.presetList[0].id;
}
@@ -169,11 +173,11 @@ export class SettingDialogManager extends BaseDialogManager {
this.ensureSliderStyle();
const content = document.createElement('div');
content.style.cssText = 'max-height: 520px; display: flex; flex-direction: column;';
content.style.cssText = 'height: 600px; display: flex; flex-direction: column;';
const scrollBody = document.createElement('div');
scrollBody.className = 'setting-scroll-body';
scrollBody.style.cssText = 'padding: 14px 16px 12px 16px; overflow-y: auto; display: flex; flex-direction: column; gap: 6px;';
scrollBody.style.cssText = 'padding: 14px 16px 12px 16px; overflow-y: auto; display: flex; flex-direction: column; gap: 6px; flex: 1; min-height: 0;';
scrollBody.appendChild(this.createRenderModeSection());
scrollBody.appendChild(this.createSliderSection(
@@ -228,7 +232,11 @@ export class SettingDialogManager extends BaseDialogManager {
const engine = this.engineComponent;
if (!engine) return;
const savedSelectedId = this.selectedPresetId;
this.ensurePresetListWithInternalDefault();
if (savedSelectedId && this.presetList.some((item) => item.id === savedSelectedId)) {
this.selectedPresetId = savedSelectedId;
}
this.presetLists = engine.getPresetLists();
this.settings = engine.getSettings();
this.isDirty = this.getCurrentSettingsDirtyState(this.settings);
@@ -314,23 +322,32 @@ export class SettingDialogManager extends BaseDialogManager {
const actions = document.createElement('div');
const canDelete = this.isSelectedPresetDeletable();
actions.style.cssText = canDelete
? 'display: grid; grid-template-columns: minmax(0, 1.55fr) 40px minmax(0, 0.7fr) minmax(0, 0.95fr); gap: 8px; align-items: center;'
: 'display: grid; grid-template-columns: minmax(0, 1.55fr) minmax(0, 0.7fr) minmax(0, 0.95fr); gap: 8px; align-items: center;';
const allowModify = this.isSelectedPresetAllowModify();
actions.style.cssText = 'display: grid; grid-template-columns: minmax(0, 1.55fr) 40px minmax(0, 0.7fr) minmax(0, 0.95fr); gap: 8px; align-items: center;';
actions.appendChild(this.createBottomPresetSelect());
if (canDelete) {
if (allowModify && canDelete) {
actions.appendChild(this.createDeleteIconButton());
} else {
const placeholder = document.createElement('div');
placeholder.style.cssText = 'width: 40px; height: 36px;';
actions.appendChild(placeholder);
}
actions.appendChild(this.createFooterActionButton(t('setting.savePreset'), {
background: PRIMARY,
color: TEXT_INVERSE,
border: 'none',
onClick: () => this.handleSaveCurrentPreset(),
compact: true,
}));
if (allowModify) {
actions.appendChild(this.createFooterActionButton(t('setting.savePreset'), {
background: PRIMARY,
color: TEXT_INVERSE,
border: 'none',
onClick: () => this.handleSaveCurrentPreset(),
compact: true,
}));
} else {
const placeholder = document.createElement('div');
actions.appendChild(placeholder);
}
actions.appendChild(this.createFooterActionButton(t('setting.saveAsNewPreset'), {
background: PRIMARY,
@@ -371,9 +388,17 @@ export class SettingDialogManager extends BaseDialogManager {
private isSelectedPresetDeletable(): boolean {
if (!this.selectedPresetId) return false;
if (this.selectedPresetId === SettingDialogManager.INTERNAL_DEFAULT_PRESET_ID) return false;
const preset = this.presetList.find((item) => item.id === this.selectedPresetId);
if (preset?.allowModify === false) return false;
return this.presetList.some((item) => item.id === this.selectedPresetId);
}
private isSelectedPresetAllowModify(): boolean {
if (!this.selectedPresetId) return true;
const preset = this.presetList.find((item) => item.id === this.selectedPresetId);
return preset?.allowModify !== false;
}
private createBottomPresetSelect(): HTMLElement {
const options: SelectOption[] = [];
if (this.presetList.length === 0) {
@@ -875,6 +900,9 @@ export class SettingDialogManager extends BaseDialogManager {
}
private handleSaveCurrentPreset(): void {
if (!this.isSelectedPresetAllowModify()) {
return;
}
if (!this.selectedPresetId) {
window.alert(t('setting.selectPresetFirst'));
return;
@@ -886,14 +914,20 @@ export class SettingDialogManager extends BaseDialogManager {
return;
}
const savedPresetId = this.selectedPresetId;
const currentSettings = this.engineComponent?.getSettings() ?? structuredClone(this.settings);
const updatedPreset: EngineSettingPreset = {
...this.presetList[index],
settings: structuredClone(currentSettings),
isDefault: true,
};
this.presetList = this.presetList.map((item, i) => (i === index ? updatedPreset : item));
this.presetList = this.presetList.map((item, i) => ({
...(i === index ? updatedPreset : item),
isDefault: i === index,
}));
this.ensurePresetListWithInternalDefault();
this.selectedPresetId = savedPresetId;
this.settings = structuredClone(currentSettings);
this.isDirty = false;
this.updateUndoRestoreVisibility();
@@ -922,14 +956,14 @@ export class SettingDialogManager extends BaseDialogManager {
const preset: EngineSettingPreset = {
id: `user-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
presetName: trimmedName,
isDefault: false,
isDefault: true,
settings: structuredClone(currentSettings),
source: 'user',
readonly: false,
};
const withoutInternal = this.presetList.filter((item) => item.id !== SettingDialogManager.INTERNAL_DEFAULT_PRESET_ID);
this.presetList = [...withoutInternal, preset];
this.presetList = [...withoutInternal.map((item) => ({ ...item, isDefault: false })), preset];
this.ensurePresetListWithInternalDefault();
this.selectedPresetId = preset.id;
this.settings = structuredClone(currentSettings);
@@ -945,7 +979,7 @@ export class SettingDialogManager extends BaseDialogManager {
}
private openDeletePresetConfirmModal(): void {
if (!this.isSelectedPresetDeletable()) {
if (!this.isSelectedPresetAllowModify() || !this.isSelectedPresetDeletable()) {
return;
}
this.closeDeletePresetConfirmModal();

View File

@@ -0,0 +1,125 @@
import { BaseManager } from '../core/base-manager';
import { ManagerRegistry } from '../core/manager-registry';
import { WalkDockPanel } from '../components/walk-dock-panel';
import { WalkPathDialogManager } from './walk-path-dialog-manager';
export class WalkDockManager extends BaseManager {
private panel: WalkDockPanel | null = null;
private pathManager: WalkPathDialogManager | null = null;
private unsubscribeDockState: (() => void) | null = null;
constructor(registry: ManagerRegistry) {
super(registry);
}
public init(): void {
this.pathManager = new WalkPathDialogManager(this.registry);
this.pathManager.init();
if (!this.unsubscribeDockState) {
this.unsubscribeDockState = this.registry.bottomDock?.onStateChange((state) => {
if (state.id !== 'walk') {
return;
}
if (state.open) {
this.onOpen();
return;
}
this.onClose();
}) ?? null;
}
}
public destroy(): void {
if (this.unsubscribeDockState) {
this.unsubscribeDockState();
this.unsubscribeDockState = null;
}
this.onClose();
this.pathManager?.destroy();
this.pathManager = null;
super.destroy();
}
public getPanelElement(): HTMLElement {
if (!this.panel) {
this.panel = new WalkDockPanel({
onPlanViewToggle: (isActive) => {
this.engineComponent?.toggleMiniMap();
this.registry.toolbar?.setBtnActive('map', isActive);
this.emit('walk:plan-view-toggle', { isActive });
},
onPathModeToggle: (isActive) => {
if (isActive) {
this.pathManager?.show();
} else {
this.pathManager?.hide();
}
this.emit('walk:path-mode-toggle', { isActive });
},
onWalkModeToggle: (isActive) => {
if (isActive) {
this.pathManager?.hide();
}
this.emit('walk:walk-mode-toggle', { isActive });
},
onSpeedChange: (speed) => {
const engineSpeed = speed * 0.1;
this.engineComponent?.setWalkSpeed(engineSpeed);
this.emit('walk:speed-change', { speed });
},
onGravityToggle: (enabled) => {
this.engineComponent?.setWalkGravity(enabled);
this.emit('walk:gravity-toggle', { enabled });
},
onCollisionToggle: (enabled) => {
this.engineComponent?.setWalkCollision(enabled);
this.emit('walk:collision-toggle', { enabled });
},
onCharacterModelChange: (model) => {
void model;
},
onWalkModeChange: (mode) => {
void mode;
},
onExit: () => {
this.registry.bottomDock?.close('walk');
}
});
this.panel.init();
}
this.syncMapState();
return this.panel.element;
}
private onOpen(): void {
this.engineComponent?.activateFirstPersonMode();
this.syncMapState();
}
private onClose(): void {
this.pathManager?.hide();
if (this.engineComponent?.getMiniMapState()) {
this.engineComponent.toggleMiniMap();
}
this.engineComponent?.deactivateFirstPersonMode();
if (this.panel) {
this.panel.destroy();
this.panel = null;
}
this.registry.toolbar?.setBtnActive('map', false);
}
private syncMapState(): void {
const mapState = this.engineComponent?.getMiniMapState() ?? false;
this.panel?.setPlanViewActive(mapState);
}
}