feat: add camera switch, fix click event/dialog resize/map state sync
- fix(engine): adapt click handler to base engine array format data[0].url/ids - feat(toolbar): add perspective/orthographic camera switch button with dynamic icon - fix(dialog): clamp resize to container bounds to prevent overflow - feat(demo): add auto-combine feature with robust URL parsing and validation - fix(walk): close minimap on walk exit and sync map state between toolbar and walk panel - fix(engine): correct MiniMap getstate() casing to match base engine API - build: rebuild demo libs
This commit is contained in:
@@ -739,6 +739,25 @@ export class BimButtonGroup implements IBimComponent {
|
||||
|
||||
private getIcon(icon?: string): string { return icon || this.DEFAULT_ICON; }
|
||||
|
||||
/**
|
||||
* 更新指定按钮的图标
|
||||
* @param id 按钮 ID
|
||||
* @param icon 新的 SVG 图标字符串
|
||||
*/
|
||||
public updateButtonIcon(id: string, icon: string): void {
|
||||
const btnEl = this.btnRefs.get(id);
|
||||
if (!btnEl) return;
|
||||
const iconEl = btnEl.querySelector('.opt-btn-icon');
|
||||
if (iconEl) {
|
||||
iconEl.innerHTML = this.getIcon(icon);
|
||||
}
|
||||
// 同步更新 button 对象的 icon 属性
|
||||
const button = this.findButtonById(id);
|
||||
if (button) {
|
||||
button.icon = icon;
|
||||
}
|
||||
}
|
||||
|
||||
public updateButtonVisibility(id: string, visible: boolean): void {
|
||||
if (!this.options.visibility) this.options.visibility = {};
|
||||
this.options.visibility[id] = visible;
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import type { ButtonConfig } from '../../../index.type';
|
||||
import { getIcon } from '../../../../../utils/icon-manager';
|
||||
import type { ManagerRegistry } from '../../../../../core/manager-registry';
|
||||
|
||||
export const createCameraSwitchButton = (registry: ManagerRegistry): ButtonConfig => {
|
||||
return {
|
||||
id: 'camera-switch',
|
||||
groupId: 'group-1',
|
||||
type: 'button',
|
||||
label: 'toolbar.cameraSwitch',
|
||||
icon: getIcon('透视相机'),
|
||||
keepActive: false,
|
||||
onClick: () => {
|
||||
const engineComponent = registry.engine3d?.getEngineComponent();
|
||||
if (!engineComponent) return;
|
||||
|
||||
engineComponent.switchCamera();
|
||||
|
||||
// 切换后更新图标
|
||||
const newType = engineComponent.getCameraType();
|
||||
const newIcon = getIcon(newType === 'orthographic' ? '正交相机' : '透视相机');
|
||||
registry.toolbar?.updateButtonIcon('camera-switch', newIcon);
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -15,6 +15,9 @@ export const createMapButton = (registry: ManagerRegistry): ButtonConfig => {
|
||||
icon: getIcon('地图'),
|
||||
onClick: () => {
|
||||
registry.engine3d?.getEngineComponent()?.toggleMiniMap();
|
||||
// 同步漫游面板的小地图按钮状态
|
||||
const mapState = registry.engine3d?.getEngineComponent()?.getMiniMapState() ?? false;
|
||||
registry.walkControl?.panel?.setPlanViewActive(mapState);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -21,10 +21,12 @@ export class Toolbar extends BimButtonGroup {
|
||||
const { createSectionAxisButton } = await import('./buttons/section/section-axis');
|
||||
const { createSectionBoxButton } = await import('./buttons/section/section-box');
|
||||
const { createAiChatButton } = await import('./buttons/ai-chat');
|
||||
const { createCameraSwitchButton } = await import('./buttons/camera-switch');
|
||||
|
||||
this.addGroup('group-1');
|
||||
|
||||
this.addButton(createHomeButton(registry));
|
||||
this.addButton(createCameraSwitchButton(registry));
|
||||
this.addButton(createZoomBoxButton(registry));
|
||||
this.addButton(createMeasureButton(registry));
|
||||
this.addButton(createSectionMenuButton(registry));
|
||||
|
||||
@@ -425,6 +425,10 @@ export class BimDialog implements IBimComponent {
|
||||
let startY = 0;
|
||||
let startW = 0;
|
||||
let startH = 0;
|
||||
let containerW = 0;
|
||||
let containerH = 0;
|
||||
let elLeft = 0;
|
||||
let elTop = 0;
|
||||
|
||||
const onMouseDown = (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
@@ -434,6 +438,12 @@ export class BimDialog implements IBimComponent {
|
||||
startW = this.element.offsetWidth;
|
||||
startH = this.element.offsetHeight;
|
||||
|
||||
// 缓存容器尺寸和弹窗位置,用于计算最大可缩放范围
|
||||
containerW = this.container.clientWidth;
|
||||
containerH = this.container.clientHeight;
|
||||
elLeft = this.element.offsetLeft;
|
||||
elTop = this.element.offsetTop;
|
||||
|
||||
// 关键:使用 capture: true
|
||||
document.addEventListener('mousemove', onMouseMove, { capture: true });
|
||||
document.addEventListener('mouseup', onMouseUp, { capture: true });
|
||||
@@ -449,8 +459,12 @@ export class BimDialog implements IBimComponent {
|
||||
const dx = e.clientX - startX;
|
||||
const dy = e.clientY - startY;
|
||||
|
||||
const newW = Math.max(this.options.minWidth || 100, startW + dx);
|
||||
const newH = Math.max(this.options.minHeight || 50, startH + dy);
|
||||
// 最大宽高:不超出容器右边界/下边界
|
||||
const maxW = containerW - elLeft;
|
||||
const maxH = containerH - elTop;
|
||||
|
||||
const newW = Math.min(maxW, Math.max(this.options.minWidth || 100, startW + dx));
|
||||
const newH = Math.min(maxH, Math.max(this.options.minHeight || 50, startH + dy));
|
||||
|
||||
this.element.style.width = `${newW}px`;
|
||||
this.element.style.height = `${newH}px`;
|
||||
|
||||
@@ -7,8 +7,8 @@ import type { MeasureUnit, MeasurePrecision } from '../measure-panel/types';
|
||||
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 { 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, ModelLoadOptions, EngineInfo };
|
||||
@@ -131,12 +131,13 @@ export class Engine implements IBimComponent {
|
||||
this.setTheme(themeManager.getTheme());
|
||||
|
||||
// 监听构件点击事件
|
||||
this.engine.events.on('click', (hit: any) => {
|
||||
// 底层 interactionModule trigger 格式: [{url: string, ids: string[]}]
|
||||
this.engine.events.on('click', (data: any) => {
|
||||
const registry = this.registry;
|
||||
if (hit && hit.object) {
|
||||
if (data && Array.isArray(data) && data.length > 0 && data[0].url) {
|
||||
this.selectedComponent = {
|
||||
url: hit.object.url,
|
||||
id: hit.object.name
|
||||
url: data[0].url,
|
||||
id: data[0].ids?.[0]
|
||||
};
|
||||
console.log('[Engine] 构件选中:', this.selectedComponent);
|
||||
registry.emit('component:selected', this.selectedComponent);
|
||||
@@ -593,6 +594,34 @@ export class Engine implements IBimComponent {
|
||||
|
||||
// ==================== 结束:剖切功能 ====================
|
||||
|
||||
// ==================== 相机切换 ====================
|
||||
|
||||
/**
|
||||
* 切换相机类型(透视/正交)
|
||||
* @remarks 底层调用 cameraModule.switchCurrentCamera(),保留当前相机位置
|
||||
*/
|
||||
public switchCamera(): void {
|
||||
if (!this._isInitialized || !this.engine?.cameraModule) {
|
||||
console.warn('[Engine] Cannot switch camera: engine not initialized.');
|
||||
return;
|
||||
}
|
||||
this.engine.cameraModule.switchCurrentCamera();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前相机类型
|
||||
* @returns 'perspective' | 'orthographic'
|
||||
*/
|
||||
public getCameraType(): 'perspective' | 'orthographic' {
|
||||
if (!this._isInitialized || !this.engine?.cameraModule) {
|
||||
return 'perspective';
|
||||
}
|
||||
// 底层 CameraType enum: PERSPECTIVE = 0, ORTHOGRAPHIC = 1
|
||||
const type = this.engine.cameraModule.getCameraType();
|
||||
return type === 1 ? 'orthographic' : 'perspective';
|
||||
}
|
||||
|
||||
// ==================== 结束:相机切换 ====================
|
||||
// ==================== 渲染模式 ====================
|
||||
|
||||
/**
|
||||
@@ -943,7 +972,7 @@ export class Engine implements IBimComponent {
|
||||
if (!this._isInitialized || !this.engine) {
|
||||
return false;
|
||||
}
|
||||
return this.engine.minMap?.getState() ?? false;
|
||||
return this.engine.minMap?.getstate() ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -25,7 +25,8 @@ export const enUS: TranslationDictionary = {
|
||||
section: 'Section',
|
||||
sectionPlane: 'Plane Section',
|
||||
sectionAxis: 'Axis Section',
|
||||
sectionBox: 'Section Box'
|
||||
sectionBox: 'Section Box',
|
||||
cameraSwitch: 'Camera',
|
||||
},
|
||||
dialog: {
|
||||
testTitle: 'Test Dialog',
|
||||
|
||||
@@ -29,6 +29,7 @@ export interface TranslationDictionary {
|
||||
sectionPlane: string;
|
||||
sectionAxis: string;
|
||||
sectionBox: string;
|
||||
cameraSwitch: string;
|
||||
};
|
||||
panel: {
|
||||
property: {
|
||||
|
||||
@@ -25,7 +25,8 @@ export const zhCN: TranslationDictionary = {
|
||||
section: '剖切',
|
||||
sectionPlane: '拾取面剖切',
|
||||
sectionAxis: '轴向剖切',
|
||||
sectionBox: '剖切盒'
|
||||
sectionBox: '剖切盒',
|
||||
cameraSwitch: '相机切换',
|
||||
},
|
||||
dialog: {
|
||||
testTitle: '测试弹窗',
|
||||
|
||||
@@ -112,6 +112,15 @@ export class ToolbarManager extends BaseManager {
|
||||
this.toolbar?.setBtnActive(id, active);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新按钮图标
|
||||
* @param id 按钮 ID
|
||||
* @param icon 新的 SVG 图标字符串
|
||||
*/
|
||||
public updateButtonIcon(id: string, icon: string) {
|
||||
this.toolbar?.updateButtonIcon(id, icon);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置工具栏可见性
|
||||
* @param visible 是否可见
|
||||
|
||||
@@ -44,6 +44,8 @@ export class WalkControlManager extends BaseManager {
|
||||
onPlanViewToggle: (isActive) => {
|
||||
console.log('[WalkControl] 小地图:', isActive);
|
||||
this.engineComponent?.toggleMiniMap();
|
||||
// 同步工具栏地图按钮的激活状态
|
||||
this.registry.toolbar?.setBtnActive('map', isActive);
|
||||
this.emit('walk:plan-view-toggle', { isActive });
|
||||
},
|
||||
onPathModeToggle: (isActive) => {
|
||||
@@ -91,7 +93,9 @@ export class WalkControlManager extends BaseManager {
|
||||
});
|
||||
this.panel.init();
|
||||
|
||||
|
||||
// 同步当前地图状态到漫游面板
|
||||
const mapState = this.engineComponent?.getMiniMapState() ?? false;
|
||||
this.panel.setPlanViewActive(mapState);
|
||||
|
||||
if (this.registry.container) {
|
||||
this.panel.element.style.position = 'absolute';
|
||||
@@ -110,6 +114,11 @@ export class WalkControlManager extends BaseManager {
|
||||
public hide(): void {
|
||||
this.pathManager?.hide();
|
||||
|
||||
// 如果小地图开着,先关闭它
|
||||
if (this.engineComponent?.getMiniMapState()) {
|
||||
this.engineComponent.toggleMiniMap();
|
||||
}
|
||||
|
||||
console.log('[WalkControl] 关闭漫游面板,退出第一人称模式');
|
||||
this.engineComponent?.deactivateFirstPersonMode();
|
||||
|
||||
@@ -118,6 +127,9 @@ export class WalkControlManager extends BaseManager {
|
||||
this.panel = null;
|
||||
}
|
||||
|
||||
// 同步工具栏地图按钮的激活状态
|
||||
this.registry.toolbar?.setBtnActive('map', false);
|
||||
|
||||
if (this.registry.toolbar) {
|
||||
this.registry.toolbar.show();
|
||||
}
|
||||
|
||||
@@ -65,6 +65,10 @@ const ICONS: Record<string, string> = {
|
||||
loader: '<svg width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M12 4V2A10 10 0 0 0 2 12h2a8 8 0 0 1 8-8Z"><animateTransform attributeName="transform" dur="0.75s" repeatCount="indefinite" type="rotate" values="0 12 12;360 12 12"/></path></svg>',
|
||||
send: '<svg width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M2 21l21-9L2 3v7l15 2l-15 2v7z"/></svg>',
|
||||
|
||||
// ========== 相机切换图标 (48x48) ==========
|
||||
透视相机: '<svg width="48" height="48" viewBox="0 0 48 48"><path fill="currentColor" d="M24 4L4 14v20l20 10l20-10V14L24 4zm0 4.5l14 7v14l-14 7l-14-7v-14l14-7zM24 18a6 6 0 100 12a6 6 0 000-12z"/></svg>',
|
||||
正交相机: '<svg width="48" height="48" viewBox="0 0 48 48"><path fill="currentColor" d="M6 6h36v36H6V6zm4 4v28h28V10H10zm4 4h20v20H14V14z"/></svg>',
|
||||
|
||||
// ========== 默认图标 ==========
|
||||
default: '<svg width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10s10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8s8 3.59 8 8s-3.59 8-8 8zm-1-13h2v6h-2zm0 8h2v2h-2z"/></svg>',
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user