提交代码
This commit is contained in:
115
.opencode/package-lock.json
generated
Normal file
115
.opencode/package-lock.json
generated
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
{
|
||||||
|
"name": ".opencode",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"dependencies": {
|
||||||
|
"@opencode-ai/plugin": "1.4.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@opencode-ai/plugin": {
|
||||||
|
"version": "1.4.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@opencode-ai/plugin/-/plugin-1.4.3.tgz",
|
||||||
|
"integrity": "sha512-Ob/3tVSIeuMRJBr2O23RtrnC5djRe01Lglx+TwGEmjrH9yDBJ2tftegYLnNEjRoMuzITgq9LD8168p4pzv+U/A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@opencode-ai/sdk": "1.4.3",
|
||||||
|
"zod": "4.1.8"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@opentui/core": ">=0.1.97",
|
||||||
|
"@opentui/solid": ">=0.1.97"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@opentui/core": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@opentui/solid": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@opencode-ai/sdk": {
|
||||||
|
"version": "1.4.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@opencode-ai/sdk/-/sdk-1.4.3.tgz",
|
||||||
|
"integrity": "sha512-X0CAVbwoGAjTY2iecpWkx2B+GAa2jSaQKYpJ+xILopeF/OGKZUN15mjqci+L7cEuwLHV5wk3x2TStUOVCa5p0A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"cross-spawn": "7.0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cross-spawn": {
|
||||||
|
"version": "7.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||||
|
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"path-key": "^3.1.0",
|
||||||
|
"shebang-command": "^2.0.0",
|
||||||
|
"which": "^2.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/isexe": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
|
"node_modules/path-key": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||||
|
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/shebang-command": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"shebang-regex": "^3.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/shebang-regex": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/which": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"isexe": "^2.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"node-which": "bin/node-which"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/zod": {
|
||||||
|
"version": "4.1.8",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -229,6 +229,11 @@
|
|||||||
<button onclick="pauseRendering()">暂停渲染</button>
|
<button onclick="pauseRendering()">暂停渲染</button>
|
||||||
<button onclick="resumeRendering()">恢复渲染</button>
|
<button onclick="resumeRendering()">恢复渲染</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="btn-container" style="margin-top: 8px;">
|
||||||
|
<button onclick="readModelCodeFormStoge()">读取缓存编码</button>
|
||||||
|
<button onclick="startOneClickEncoding()">启动一键编码</button>
|
||||||
|
<button onclick="checkHasModelCode()">检查模型编码</button>
|
||||||
|
</div>
|
||||||
<div style="margin-top: 10px; font-size: 0.85rem; color: #666;">
|
<div style="margin-top: 10px; font-size: 0.85rem; color: #666;">
|
||||||
<div>状态: <span id="engine-status">未初始化</span></div>
|
<div>状态: <span id="engine-status">未初始化</span></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -339,7 +344,8 @@
|
|||||||
let unsubscribePresetSaved = null;
|
let unsubscribePresetSaved = null;
|
||||||
let unsubscribePresetChanged = null;
|
let unsubscribePresetChanged = null;
|
||||||
let unsubscribePresetDeleted = null;
|
let unsubscribePresetDeleted = null;
|
||||||
const DEFAULT_3D_MODEL_URL = 'https://lyz-1259524260.cos.ap-guangzhou.myqcloud.com/iflow/models/417664a3-76c8-4d94-9344-1337246a5d4e/';
|
// const DEFAULT_3D_MODEL_URL = 'https://pub-8092fd0db2e14822a9f742ad0050750c.r2.dev/e9603d6b-c885-4f1b-84b0-2589bc9dc44f';
|
||||||
|
const DEFAULT_3D_MODEL_URL = 'https://lyz-1259524260.cos.ap-guangzhou.myqcloud.com/iflow/models/417664a3-76c8-4d94-9344-1337246a5d4e';
|
||||||
//const DEFAULT_3D_MODEL_URL = 'https://pub-8092fd0db2e14822a9f742ad0050750c.r2.dev/66ad9a66-5ca8-47ac-9139-6aa8756069c1/';
|
//const DEFAULT_3D_MODEL_URL = 'https://pub-8092fd0db2e14822a9f742ad0050750c.r2.dev/66ad9a66-5ca8-47ac-9139-6aa8756069c1/';
|
||||||
let current3dModelUrl = DEFAULT_3D_MODEL_URL;
|
let current3dModelUrl = DEFAULT_3D_MODEL_URL;
|
||||||
|
|
||||||
@@ -698,6 +704,43 @@
|
|||||||
console.log('✅ 渲染已恢复');
|
console.log('✅ 渲染已恢复');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function readModelCodeFormStoge() {
|
||||||
|
if (!engine || !engine.engine || !engine.engine.isInitialized()) {
|
||||||
|
alert('请先初始化 3D 引擎!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
engine.engine.getEngineComponent()?.readModelCodeFormStoge();
|
||||||
|
console.log('✅ 读取缓存编码数据已触发');
|
||||||
|
}
|
||||||
|
|
||||||
|
function startOneClickEncoding() {
|
||||||
|
if (!engine || !engine.engine || !engine.engine.isInitialized()) {
|
||||||
|
alert('请先初始化 3D 引擎!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
engine.on('encoding:start', function (data) {
|
||||||
|
console.log('编码开始', data);
|
||||||
|
});
|
||||||
|
engine.on('encoding:complete', function (data) {
|
||||||
|
console.log('编码完成', data);
|
||||||
|
});
|
||||||
|
engine.on('encoding:error', function (data) {
|
||||||
|
console.log('编码失败', data);
|
||||||
|
});
|
||||||
|
engine.engine.getEngineComponent()?.startOneClickEncoding();
|
||||||
|
console.log('✅ 一键编码已启动');
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkHasModelCode() {
|
||||||
|
if (!engine || !engine.engine || !engine.engine.isInitialized()) {
|
||||||
|
alert('请先初始化 3D 引擎!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var result = engine.engine.getEngineComponent()?.hasModelCode();
|
||||||
|
console.log('✅ 检查模型编码结果:', result);
|
||||||
|
alert('模型编码检查结果: ' + (result ? '所有模型已编码' : '未全部编码'));
|
||||||
|
}
|
||||||
|
|
||||||
function switchModel() {
|
function switchModel() {
|
||||||
if (!engine || !engine.engine || !engine.engine.isInitialized()) {
|
if (!engine || !engine.engine || !engine.engine.isInitialized()) {
|
||||||
alert('请先初始化 3D 引擎!');
|
alert('请先初始化 3D 引擎!');
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -32,9 +32,10 @@
|
|||||||
| 管理器 | 职责 |
|
| 管理器 | 职责 |
|
||||||
|--------|------|
|
|--------|------|
|
||||||
| EngineManager | 3D 引擎管理 |
|
| EngineManager | 3D 引擎管理 |
|
||||||
| ToolbarManager | 工具栏管理 |
|
| ToolbarManager | 底部工具栏管理(传统线性布局,当前未使用) |
|
||||||
|
| **RadialToolbarManager** | **径向工具栏管理(当前主交互入口)** |
|
||||||
| DialogManager | 对话框管理 |
|
| DialogManager | 对话框管理 |
|
||||||
| ButtonGroupManager | 按钮组管理 |
|
| ButtonGroupManager | 按钮组管理(通用,当前未使用) |
|
||||||
| RightKeyManager | 右键菜单管理 |
|
| RightKeyManager | 右键菜单管理 |
|
||||||
|
|
||||||
### 功能管理器
|
### 功能管理器
|
||||||
@@ -75,6 +76,7 @@
|
|||||||
| BimTree | 树形控件 |
|
| BimTree | 树形控件 |
|
||||||
| BimMenu | 菜单组件 |
|
| BimMenu | 菜单组件 |
|
||||||
| BimButtonGroup | 按钮组 |
|
| BimButtonGroup | 按钮组 |
|
||||||
|
| **RadialToolbar** | **径向工具栏(圆形扇形菜单,当前主交互入口)** |
|
||||||
|
|
||||||
### 面板组件
|
### 面板组件
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
src/managers/
|
src/managers/
|
||||||
├── engine-manager.ts
|
├── engine-manager.ts
|
||||||
├── toolbar-manager.ts
|
├── toolbar-manager.ts
|
||||||
|
├── radial-toolbar-manager.ts
|
||||||
├── dialog-manager.ts
|
├── dialog-manager.ts
|
||||||
├── button-group-manager.ts
|
├── button-group-manager.ts
|
||||||
├── right-key-manager.ts
|
├── right-key-manager.ts
|
||||||
@@ -44,6 +45,7 @@ src/managers/
|
|||||||
BaseManager
|
BaseManager
|
||||||
├── EngineManager
|
├── EngineManager
|
||||||
├── ToolbarManager
|
├── ToolbarManager
|
||||||
|
├── **RadialToolbarManager**
|
||||||
├── DialogManager
|
├── DialogManager
|
||||||
├── ButtonGroupManager
|
├── ButtonGroupManager
|
||||||
├── RightKeyManager
|
├── RightKeyManager
|
||||||
@@ -127,7 +129,7 @@ class EngineManager extends BaseManager {
|
|||||||
| 类别 | 代表 Manager | 主要职责 |
|
| 类别 | 代表 Manager | 主要职责 |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| 核心入口 | `EngineManager` | 引擎生命周期与外部 API |
|
| 核心入口 | `EngineManager` | 引擎生命周期与外部 API |
|
||||||
| UI 容器 | `ToolbarManager`, `DialogManager`, `ButtonGroupManager` | UI 容器与通用交互 |
|
| UI 容器 | `RadialToolbarManager`, `ToolbarManager`, `DialogManager`, `ButtonGroupManager` | UI 容器与通用交互 |
|
||||||
| 业务编排 | `MeasureDialogManager`, `Section*DialogManager`, `WalkControlManager`, `SettingDialogManager` | 对话框/面板回调与引擎能力编排 |
|
| 业务编排 | `MeasureDialogManager`, `Section*DialogManager`, `WalkControlManager`, `SettingDialogManager` | 对话框/面板回调与引擎能力编排 |
|
||||||
| 数据/交互 | `ConstructTreeManagerBtn`, `ComponentDetailManager`, `RightKeyManager` | 构件树、属性、右键菜单 |
|
| 数据/交互 | `ConstructTreeManagerBtn`, `ComponentDetailManager`, `RightKeyManager` | 构件树、属性、右键菜单 |
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ src/components/
|
|||||||
├── tree/ # 树形控件组件
|
├── tree/ # 树形控件组件
|
||||||
├── menu/ # 菜单组件
|
├── menu/ # 菜单组件
|
||||||
├── button-group/ # 按钮组件
|
├── button-group/ # 按钮组件
|
||||||
|
├── radial-toolbar/ # 径向工具栏组件
|
||||||
├── collapse/ # 折叠面板组件
|
├── collapse/ # 折叠面板组件
|
||||||
├── tab/ # 标签页组件
|
├── tab/ # 标签页组件
|
||||||
├── description/ # 描述列表组件
|
├── description/ # 描述列表组件
|
||||||
@@ -408,6 +409,81 @@ class BimButtonGroup {
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## RadialToolbar(径向工具栏组件)
|
||||||
|
|
||||||
|
### 概述
|
||||||
|
|
||||||
|
圆形径向菜单组件,用于替代传统线性工具栏。支持多环布局、扇形展开、hover 触发和 toggle 状态。
|
||||||
|
|
||||||
|
### 配置
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface RadialToolbarOptions {
|
||||||
|
container: HTMLElement;
|
||||||
|
items?: RadialMenuItem[];
|
||||||
|
mainButtonIcon?: string;
|
||||||
|
mainButtonLabel?: string;
|
||||||
|
onMainButtonClick?: () => void;
|
||||||
|
itemsPerRing?: number;
|
||||||
|
closeDelay?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RadialMenuItem {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
/** 直接显示的文本(优先级高于 label 的国际化翻译) */
|
||||||
|
title?: string;
|
||||||
|
icon?: string;
|
||||||
|
onClick?: (item: RadialMenuItem) => void;
|
||||||
|
isToggle?: boolean;
|
||||||
|
isActive?: boolean;
|
||||||
|
onToggle?: (nextActive: boolean, item: RadialMenuItem) => void;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### API
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class RadialToolbar {
|
||||||
|
init(): void;
|
||||||
|
addItem(item: RadialMenuItem): void;
|
||||||
|
setItemActive(id: string, active: boolean): void;
|
||||||
|
setTheme(theme: ThemeConfig): void;
|
||||||
|
setLocales(): void;
|
||||||
|
destroy(): void;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 使用说明
|
||||||
|
|
||||||
|
- `label`:国际化键名(如 `'toolbar.home'`),SDK 内置按钮使用
|
||||||
|
- `title`:直接显示文本,优先级高于 `label`,适合外部动态添加按钮
|
||||||
|
- `itemsPerRing`:每环最多显示的按钮数,超出自动进入下一环
|
||||||
|
- `isToggle`:是否为切换按钮,配合 `onToggle` 和 `setItemActive` 使用
|
||||||
|
|
||||||
|
### 布局说明
|
||||||
|
|
||||||
|
**角度计算**:按钮中心均匀分布在扇形范围内(默认 180°~270°),通过 `(index + 0.5) / count` 计算位置,确保完整填满扇形而不贴边。
|
||||||
|
|
||||||
|
**环容量**:每环按钮数量写死在 `src/components/radial-toolbar/index.ts` 中(当前为 `[4, 6, 8]`),如需调整直接修改源码。
|
||||||
|
|
||||||
|
### 与 BottomDock 联动
|
||||||
|
|
||||||
|
`RadialToolbarManager` 会自动监听 `BottomDock` 状态变化,同步按钮激活状态:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 点击径向工具栏按钮展开/收起 Dock
|
||||||
|
onToggle: (active) => {
|
||||||
|
if (active) {
|
||||||
|
bimEngine.bottomDock?.open('measure');
|
||||||
|
} else {
|
||||||
|
bimEngine.bottomDock?.close('measure');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## BimCollapse(折叠面板组件)
|
## BimCollapse(折叠面板组件)
|
||||||
|
|
||||||
### 概述
|
### 概述
|
||||||
|
|||||||
160
docs/外部API总览.md
160
docs/外部API总览.md
@@ -172,3 +172,163 @@ bimEngine.engine?.getEngineComponent()?.isolateModels([
|
|||||||
{ url: modelUrl, ids: [350518] }
|
{ url: modelUrl, ids: [350518] }
|
||||||
]);
|
]);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6) 启动一键编码 `startOneClickEncoding()`
|
||||||
|
|
||||||
|
### 所属模块
|
||||||
|
|
||||||
|
- 调用入口:`bimEngine.engine?.getEngineComponent()`
|
||||||
|
- 源码位置:`src/components/engine/index.ts`
|
||||||
|
|
||||||
|
### 方法签名
|
||||||
|
|
||||||
|
```ts
|
||||||
|
startOneClickEncoding(): void
|
||||||
|
```
|
||||||
|
|
||||||
|
### 入参
|
||||||
|
|
||||||
|
- 无入参。
|
||||||
|
|
||||||
|
### 行为约定
|
||||||
|
|
||||||
|
- 调用前建议先订阅 `encoding:start`、`encoding:complete`、`encoding:error` 事件以获取编码进度和结果。
|
||||||
|
- 若引擎未初始化或底层 `oneClickEncoding` 模块不可用,会在控制台输出警告并静默返回。
|
||||||
|
- 多次调用将重复触发编码流程(底层行为由 `iflow-engine-base` 决定)。
|
||||||
|
|
||||||
|
### 调用示例
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const engineComp = bimEngine.engine?.getEngineComponent();
|
||||||
|
|
||||||
|
// 订阅编码事件
|
||||||
|
bimEngine.on('encoding:start', (data) => {
|
||||||
|
console.log('编码开始', data);
|
||||||
|
});
|
||||||
|
bimEngine.on('encoding:complete', (data) => {
|
||||||
|
console.log('编码完成', data);
|
||||||
|
});
|
||||||
|
bimEngine.on('encoding:error', (data) => {
|
||||||
|
console.log('编码失败', data);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 启动编码
|
||||||
|
engineComp?.startOneClickEncoding();
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7) 检查模型编码 `hasModelCode()`
|
||||||
|
|
||||||
|
### 所属模块
|
||||||
|
|
||||||
|
- 调用入口:`bimEngine.engine?.getEngineComponent()`
|
||||||
|
- 源码位置:`src/components/engine/index.ts`
|
||||||
|
|
||||||
|
### 方法签名
|
||||||
|
|
||||||
|
```ts
|
||||||
|
hasModelCode(): boolean
|
||||||
|
```
|
||||||
|
|
||||||
|
### 入参
|
||||||
|
|
||||||
|
- 无入参。
|
||||||
|
|
||||||
|
### 返回值
|
||||||
|
|
||||||
|
- `true`:所有已加载模型均已存在编码数据。
|
||||||
|
- `false`:至少有一个模型没有编码数据,或引擎未初始化、`oneClickEncoding` 模块不可用。
|
||||||
|
|
||||||
|
### 行为约定
|
||||||
|
|
||||||
|
- 遍历当前 `engine.models` 数组,对每个模型调用底层 `oneClickEncoding.exitModelCode(url)`。
|
||||||
|
- 仅当所有模型的返回值为 `true` 时,才返回 `true`。
|
||||||
|
- 若当前没有加载任何模型,返回 `false`。
|
||||||
|
|
||||||
|
### 调用示例
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const hasCode = bimEngine.engine?.getEngineComponent()?.hasModelCode();
|
||||||
|
console.log('模型是否已编码:', hasCode);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8) 读取缓存编码 `readModelCodeFormStoge()`
|
||||||
|
|
||||||
|
### 所属模块
|
||||||
|
|
||||||
|
- 调用入口:`bimEngine.engine?.getEngineComponent()`
|
||||||
|
- 源码位置:`src/components/engine/index.ts`
|
||||||
|
|
||||||
|
### 方法签名
|
||||||
|
|
||||||
|
```ts
|
||||||
|
readModelCodeFormStoge(): void
|
||||||
|
```
|
||||||
|
|
||||||
|
### 入参
|
||||||
|
|
||||||
|
- 无入参。
|
||||||
|
|
||||||
|
### 行为约定
|
||||||
|
|
||||||
|
- 遍历当前 `engine.models` 数组,对每个模型调用底层 `oneClickEncoding.readModelCodeFormStoge(url)`。
|
||||||
|
- 若引擎未初始化或底层模块不可用,会在控制台输出警告并静默返回。
|
||||||
|
- 无返回值,仅触发读取动作。
|
||||||
|
|
||||||
|
### 调用示例
|
||||||
|
|
||||||
|
```ts
|
||||||
|
bimEngine.engine?.getEngineComponent()?.readModelCodeFormStoge();
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9) 一键编码事件
|
||||||
|
|
||||||
|
### 所属模块
|
||||||
|
|
||||||
|
- 事件总线:`bimEngine`(通过 `ManagerRegistry` 桥接)
|
||||||
|
- 源码位置:`src/components/engine/index.ts`
|
||||||
|
|
||||||
|
### 事件列表
|
||||||
|
|
||||||
|
| 事件名 | 触发时机 | payload |
|
||||||
|
|--------|---------|---------|
|
||||||
|
| `encoding:start` | 编码流程开始时 | `{ data?: any }` |
|
||||||
|
| `encoding:complete` | 编码流程成功完成时 | `{ data?: any }` |
|
||||||
|
| `encoding:error` | 编码流程失败时 | `{ data?: any }` |
|
||||||
|
|
||||||
|
### 行为约定
|
||||||
|
|
||||||
|
- 事件由底层 `oneClickEncoding` 模块触发,经 `Engine` 组件桥接后冒泡到 `bimEngine` 事件总线。
|
||||||
|
- 订阅方式与 SDK 其他事件一致:使用 `bimEngine.on(event, handler)`,返回的函数可用于取消订阅。
|
||||||
|
- 建议在调用 `startOneClickEncoding()` 之前完成事件订阅,避免漏掉 `encoding:start` 事件。
|
||||||
|
|
||||||
|
### 调用示例
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const unsubStart = bimEngine.on('encoding:start', (data) => {
|
||||||
|
console.log('编码开始', data);
|
||||||
|
});
|
||||||
|
|
||||||
|
const unsubComplete = bimEngine.on('encoding:complete', (data) => {
|
||||||
|
console.log('编码完成', data);
|
||||||
|
});
|
||||||
|
|
||||||
|
const unsubError = bimEngine.on('encoding:error', (data) => {
|
||||||
|
console.log('编码失败', data);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 启动编码
|
||||||
|
bimEngine.engine?.getEngineComponent()?.startOneClickEncoding();
|
||||||
|
|
||||||
|
// 需要时取消订阅
|
||||||
|
// unsubStart();
|
||||||
|
// unsubComplete();
|
||||||
|
// unsubError();
|
||||||
|
```
|
||||||
|
|||||||
12
package-lock.json
generated
12
package-lock.json
generated
@@ -1,15 +1,15 @@
|
|||||||
{
|
{
|
||||||
"name": "iflow-engine",
|
"name": "iflow-engine",
|
||||||
"version": "2.5.9",
|
"version": "2.5.13",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "iflow-engine",
|
"name": "iflow-engine",
|
||||||
"version": "2.5.9",
|
"version": "2.5.13",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"iflow-engine-base": "^3.4.10",
|
"iflow-engine-base": "^3.5.0",
|
||||||
"three": "^0.182.0"
|
"three": "^0.182.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -1823,9 +1823,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/iflow-engine-base": {
|
"node_modules/iflow-engine-base": {
|
||||||
"version": "3.4.10",
|
"version": "3.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/iflow-engine-base/-/iflow-engine-base-3.4.10.tgz",
|
"resolved": "https://registry.npmjs.org/iflow-engine-base/-/iflow-engine-base-3.5.0.tgz",
|
||||||
"integrity": "sha512-sKgV1qWr8z3Cbli3yoGRolAKz0Za5Iuycu/OAbpA2/oE9LJ+Zb0CH63LrSR7ljRK6NBRw4P0fHrs6/KCWppG7w==",
|
"integrity": "sha512-d+jZQrD7nO1InfqVZfgvafUz4akFL8Kj0OBiw//A5Qs6yubiBER9Eey4mcMtM7F/yXFcNlJXvvmCo9J9tW6meQ==",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/three": "^0.181.0",
|
"@types/three": "^0.181.0",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "iflow-engine",
|
"name": "iflow-engine",
|
||||||
"version": "2.5.10",
|
"version": "2.6.0",
|
||||||
"description": "iFlow Engine SDK for Vue2, Vue3, React and HTML",
|
"description": "iFlow Engine SDK for Vue2, Vue3, React and HTML",
|
||||||
"main": "./dist/iflow-engine.umd.js",
|
"main": "./dist/iflow-engine.umd.js",
|
||||||
"module": "./dist/iflow-engine.es.js",
|
"module": "./dist/iflow-engine.es.js",
|
||||||
@@ -59,7 +59,7 @@
|
|||||||
"vite-plugin-dts": "^4.5.4"
|
"vite-plugin-dts": "^4.5.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"iflow-engine-base": "^3.4.10",
|
"iflow-engine-base": "^3.5.0",
|
||||||
"three": "^0.182.0"
|
"three": "^0.182.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -11,7 +11,7 @@ import { themeManager } from './services/theme';
|
|||||||
import type { LocaleType } from './locales/types';
|
import type { LocaleType } from './locales/types';
|
||||||
import type { ThemeType } from './themes/types';
|
import type { ThemeType } from './themes/types';
|
||||||
import type { EngineEvents } from './types/events';
|
import type { EngineEvents } from './types/events';
|
||||||
|
import './iflow-engine-base.css';
|
||||||
/**
|
/**
|
||||||
* BimEngine2d 构造选项
|
* BimEngine2d 构造选项
|
||||||
* 合并引擎配置与主题/语言设置
|
* 合并引擎配置与主题/语言设置
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { themeManager } from './services/theme';
|
|||||||
import type { LocaleType } from './locales/types';
|
import type { LocaleType } from './locales/types';
|
||||||
import type { ThemeType } from './themes/types';
|
import type { ThemeType } from './themes/types';
|
||||||
import type { EngineEvents } from './types/events';
|
import type { EngineEvents } from './types/events';
|
||||||
|
import './iflow-engine-base.css';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BimEngine720 构造选项
|
* BimEngine720 构造选项
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
declare const __APP_VERSION__: string;
|
declare const __APP_VERSION__: string;
|
||||||
import './bim-engine.css';
|
import './bim-engine.css';
|
||||||
|
import './iflow-engine-base.css';
|
||||||
import { DialogManager } from './managers/dialog-manager';
|
import { DialogManager } from './managers/dialog-manager';
|
||||||
import { EngineManager } from './managers/engine-manager';
|
import { EngineManager } from './managers/engine-manager';
|
||||||
import { RightKeyManager } from './managers/right-key-manager';
|
import { RightKeyManager } from './managers/right-key-manager';
|
||||||
|
|||||||
@@ -16,8 +16,6 @@ import type { SectionBoxRange } from '../section-box-panel/types';
|
|||||||
import type { ManagerRegistry } from '../../core/manager-registry';
|
import type { ManagerRegistry } from '../../core/manager-registry';
|
||||||
// 导入第三方 SDK 的 createEngine 函数(从 npm 包引入)
|
// 导入第三方 SDK 的 createEngine 函数(从 npm 包引入)
|
||||||
import { createEngine as createEngineSDK } from 'iflow-engine-base';
|
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 {
|
export type {
|
||||||
EngineOptions,
|
EngineOptions,
|
||||||
@@ -177,6 +175,22 @@ export class Engine implements IBimComponent {
|
|||||||
console.warn('[Engine] 底层引擎不支持 events.on 方法,无法监听点击事件');
|
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) {
|
} catch (error) {
|
||||||
console.error('[Engine] Failed to initialize engine:', error);
|
console.error('[Engine] Failed to initialize engine:', error);
|
||||||
this._isInitialized = false;
|
this._isInitialized = false;
|
||||||
@@ -252,6 +266,36 @@ export class Engine implements IBimComponent {
|
|||||||
this.engine?.events?.off(event, handler);
|
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();
|
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 maxInteractiveRadius = 220;
|
||||||
|
|
||||||
private readonly closeDelay: number;
|
private readonly closeDelay: number;
|
||||||
private readonly itemsPerRing: number;
|
|
||||||
|
|
||||||
// 主按钮直径(px)
|
// 主按钮直径(px)
|
||||||
private readonly MAIN_BUTTON_SIZE = 60;
|
private readonly MAIN_BUTTON_SIZE = 60;
|
||||||
// 子按钮直径(px)
|
// 子按钮直径(px)
|
||||||
private readonly SUB_BUTTON_SIZE = 46;
|
private readonly SUB_BUTTON_SIZE = 46;
|
||||||
// 第一环“子按钮中心”到“主按钮中心”的距离(px)
|
// 第一环"子按钮中心"到"主按钮中心"的距离(px)
|
||||||
// 不做防重叠兜底,允许在小半径时出现重叠。
|
|
||||||
private readonly BASE_RADIUS = 80;
|
private readonly BASE_RADIUS = 80;
|
||||||
// 多环时,相邻两环的中心半径差(px)
|
// 多环时,相邻两环的中心半径差(px)
|
||||||
private readonly RING_GAP = 40;
|
private readonly RING_GAP = 60;
|
||||||
// 扇形展开角度范围:当前 180~270(实际就是 90 度)
|
// 扇形展开角度范围 (180为正左边/下,270为正上)
|
||||||
private readonly FAN_START_DEG = 170;
|
private readonly FAN_START_DEG = 170;
|
||||||
private readonly FAN_END_DEG = 280;
|
private readonly FAN_END_DEG = 280;
|
||||||
// 扇形边缘留白(px),防止按钮贴边或裁切
|
// 扇形边缘留白(px),防止按钮贴边或裁切
|
||||||
private readonly CANVAS_PADDING = 28;
|
private readonly CANVAS_PADDING = 28;
|
||||||
|
|
||||||
|
// 【自定义每环数量】:你可以随意修改这里的数字!多出来的按钮会自动折叠到更外围的新环。
|
||||||
|
private readonly FIXED_RING_CAPACITIES = [4, 6, 8];
|
||||||
|
|
||||||
constructor(options: RadialToolbarOptions) {
|
constructor(options: RadialToolbarOptions) {
|
||||||
this.container = options.container;
|
this.container = options.container;
|
||||||
this.items = options.items === undefined ? this.createDefaultItems() : [...options.items];
|
this.items = options.items === undefined ? this.createDefaultItems() : [...options.items];
|
||||||
this.mainButtonLabel = options.mainButtonLabel ?? 'toolbar.home';
|
this.mainButtonLabel = options.mainButtonLabel ?? 'toolbar.home';
|
||||||
this.onMainButtonClick = options.onMainButtonClick;
|
this.onMainButtonClick = options.onMainButtonClick;
|
||||||
this.itemsPerRing = Math.max(3, options.itemsPerRing ?? 5);
|
|
||||||
this.closeDelay = Math.max(100, options.closeDelay ?? 260);
|
this.closeDelay = Math.max(100, options.closeDelay ?? 260);
|
||||||
|
|
||||||
this.wrapper = this.createWrapper();
|
this.wrapper = this.createWrapper();
|
||||||
@@ -223,38 +223,69 @@ export class RadialToolbar implements IBimComponent {
|
|||||||
return btn;
|
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 {
|
private updateItemPositions(): void {
|
||||||
const total = this.itemElements.length;
|
const total = this.itemElements.length;
|
||||||
|
if (total === 0) return;
|
||||||
|
|
||||||
const fanSpan = this.FAN_END_DEG - this.FAN_START_DEG;
|
const fanSpan = this.FAN_END_DEG - this.FAN_START_DEG;
|
||||||
|
const ringCapacities = this.getRingCapacities(total);
|
||||||
|
|
||||||
this.itemElements.forEach((btn, globalIndex) => {
|
let globalIndex = 0;
|
||||||
const ringIndex = Math.floor(globalIndex / this.itemsPerRing);
|
ringCapacities.forEach((ringCount, ringIndex) => {
|
||||||
const ringStart = ringIndex * this.itemsPerRing;
|
const radius = this.BASE_RADIUS + ringIndex * this.RING_GAP;
|
||||||
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;
|
|
||||||
|
|
||||||
const x = Math.cos(angleRad) * radius;
|
for (let ringLocalIndex = 0; ringLocalIndex < ringCount; ringLocalIndex++) {
|
||||||
const y = Math.sin(angleRad) * radius;
|
const btn = this.itemElements[globalIndex];
|
||||||
|
|
||||||
const openDelay = (ringLocalIndex + ringIndex * 0.5) * 0.045;
|
// 【核心逻辑】:首尾固定:第一个按钮在最下面(180),最后一个在最上面(270),中间按剩余空间均分
|
||||||
const closeDelay = (ringCount - 1 - ringLocalIndex + ringIndex * 0.4) * 0.032;
|
// 如果该环只有1个按钮,让它待在180度的起始位置
|
||||||
|
const ratio = ringCount <= 1 ? 0 : ringLocalIndex / (ringCount - 1);
|
||||||
|
|
||||||
btn.style.setProperty('--rt-x', `${x.toFixed(2)}px`);
|
const angleDeg = this.FAN_START_DEG + fanSpan * ratio;
|
||||||
btn.style.setProperty('--rt-y', `${y.toFixed(2)}px`);
|
const angleRad = (angleDeg * Math.PI) / 180;
|
||||||
btn.style.setProperty('--rt-open-delay', `${openDelay.toFixed(3)}s`);
|
|
||||||
btn.style.setProperty('--rt-close-delay', `${closeDelay.toFixed(3)}s`);
|
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 {
|
private updateLayoutMetrics(): void {
|
||||||
const total = this.items.length;
|
const total = this.items.length;
|
||||||
const ringCount = Math.max(1, Math.ceil(total / this.itemsPerRing));
|
const ringCapacities = this.getRingCapacities(total);
|
||||||
const maxItemsPerRing = Math.max(1, Math.min(total, this.itemsPerRing));
|
const ringCount = ringCapacities.length;
|
||||||
const maxRadius = this.getBaseRadius(maxItemsPerRing) + (ringCount - 1) * this.RING_GAP;
|
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);
|
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;
|
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`);
|
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 {
|
private scheduleCollapse(): void {
|
||||||
if (this.timer) {
|
if (this.timer) {
|
||||||
clearTimeout(this.timer);
|
clearTimeout(this.timer);
|
||||||
@@ -354,7 +381,7 @@ export class RadialToolbar implements IBimComponent {
|
|||||||
if (!item) {
|
if (!item) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const text = t(item.label);
|
const text = item.title ?? t(item.label);
|
||||||
el.title = text;
|
el.title = text;
|
||||||
el.setAttribute('aria-label', text);
|
el.setAttribute('aria-label', text);
|
||||||
this.applyItemActiveClass(el, item);
|
this.applyItemActiveClass(el, item);
|
||||||
@@ -362,7 +389,7 @@ export class RadialToolbar implements IBimComponent {
|
|||||||
if (!item.icon) {
|
if (!item.icon) {
|
||||||
const iconEl = el.querySelector('.radial-sub-btn-icon');
|
const iconEl = el.querySelector('.radial-sub-btn-icon');
|
||||||
if (iconEl) {
|
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 init(): void { }
|
||||||
|
|
||||||
public setLocales(): void {
|
public setLocales(): void {
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
export interface RadialMenuItem {
|
export interface RadialMenuItem {
|
||||||
id: string;
|
id: string;
|
||||||
label: string;
|
label: string;
|
||||||
|
/** 直接显示的文本(优先级高于 label 的国际化翻译) */
|
||||||
|
title?: string;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
onClick?: (item: RadialMenuItem) => void;
|
onClick?: (item: RadialMenuItem) => void;
|
||||||
isToggle?: boolean;
|
isToggle?: boolean;
|
||||||
@@ -14,6 +16,5 @@ export interface RadialToolbarOptions {
|
|||||||
mainButtonIcon?: string;
|
mainButtonIcon?: string;
|
||||||
mainButtonLabel?: string;
|
mainButtonLabel?: string;
|
||||||
onMainButtonClick?: () => void;
|
onMainButtonClick?: () => void;
|
||||||
itemsPerRing?: number;
|
|
||||||
closeDelay?: number;
|
closeDelay?: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,15 +15,16 @@ export class SectionAxisPanel implements IBimComponent {
|
|||||||
public element: HTMLElement;
|
public element: HTMLElement;
|
||||||
private options: SectionAxisPanelOptions;
|
private options: SectionAxisPanelOptions;
|
||||||
|
|
||||||
// 状态
|
|
||||||
private isHidden: boolean = false;
|
private isHidden: boolean = false;
|
||||||
|
private isFilled: boolean = false;
|
||||||
private activeAxis: SectionAxis = 'x';
|
private activeAxis: SectionAxis = 'x';
|
||||||
|
|
||||||
// DOM 引用 - 第一行
|
|
||||||
private hideBtn!: HTMLButtonElement;
|
private hideBtn!: HTMLButtonElement;
|
||||||
private reverseBtn!: HTMLButtonElement;
|
private reverseBtn!: HTMLButtonElement;
|
||||||
|
private fillBtn!: HTMLButtonElement;
|
||||||
private hideLabelEl!: HTMLElement;
|
private hideLabelEl!: HTMLElement;
|
||||||
private reverseLabelEl!: HTMLElement;
|
private reverseLabelEl!: HTMLElement;
|
||||||
|
private fillLabelEl!: HTMLElement;
|
||||||
|
|
||||||
// DOM 引用 - 第二行
|
// DOM 引用 - 第二行
|
||||||
private axisXBtn!: HTMLButtonElement;
|
private axisXBtn!: HTMLButtonElement;
|
||||||
@@ -37,6 +38,7 @@ export class SectionAxisPanel implements IBimComponent {
|
|||||||
constructor(options: SectionAxisPanelOptions = {}) {
|
constructor(options: SectionAxisPanelOptions = {}) {
|
||||||
this.options = options;
|
this.options = options;
|
||||||
this.isHidden = options.defaultHidden ?? false;
|
this.isHidden = options.defaultHidden ?? false;
|
||||||
|
this.isFilled = options.defaultFill ?? false;
|
||||||
this.activeAxis = options.defaultAxis ?? 'x';
|
this.activeAxis = options.defaultAxis ?? 'x';
|
||||||
this.element = this.createDom();
|
this.element = this.createDom();
|
||||||
}
|
}
|
||||||
@@ -60,7 +62,7 @@ export class SectionAxisPanel implements IBimComponent {
|
|||||||
this.setTheme(themeManager.getTheme());
|
this.setTheme(themeManager.getTheme());
|
||||||
|
|
||||||
// 初始化按钮状态
|
// 初始化按钮状态
|
||||||
this.updateHideButtonState();
|
this.updateButtonStates();
|
||||||
this.updateAxisButtonsState();
|
this.updateAxisButtonsState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,41 +88,39 @@ export class SectionAxisPanel implements IBimComponent {
|
|||||||
public setLocales(): void {
|
public setLocales(): void {
|
||||||
this.hideLabelEl.textContent = t('sectionAxis.actions.hide');
|
this.hideLabelEl.textContent = t('sectionAxis.actions.hide');
|
||||||
this.reverseLabelEl.textContent = t('sectionAxis.actions.reverse');
|
this.reverseLabelEl.textContent = t('sectionAxis.actions.reverse');
|
||||||
// XYZ按钮的文字不需要国际化,保持为单个字母
|
this.fillLabelEl.textContent = t('sectionAxis.actions.fill');
|
||||||
|
|
||||||
this.hideBtn.title = t('sectionAxis.actions.hide');
|
this.hideBtn.title = t('sectionAxis.actions.hide');
|
||||||
this.reverseBtn.title = t('sectionAxis.actions.reverse');
|
this.reverseBtn.title = t('sectionAxis.actions.reverse');
|
||||||
|
this.fillBtn.title = t('sectionAxis.actions.fill');
|
||||||
this.axisXBtn.title = t('sectionAxis.actions.axisX');
|
this.axisXBtn.title = t('sectionAxis.actions.axisX');
|
||||||
this.axisYBtn.title = t('sectionAxis.actions.axisY');
|
this.axisYBtn.title = t('sectionAxis.actions.axisY');
|
||||||
this.axisZBtn.title = t('sectionAxis.actions.axisZ');
|
this.axisZBtn.title = t('sectionAxis.actions.axisZ');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置隐藏状态
|
|
||||||
*/
|
|
||||||
public setHiddenState(isHidden: boolean): void {
|
public setHiddenState(isHidden: boolean): void {
|
||||||
this.isHidden = isHidden;
|
this.isHidden = isHidden;
|
||||||
this.updateHideButtonState();
|
this.updateButtonStates();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取隐藏状态
|
|
||||||
*/
|
|
||||||
public getHiddenState(): boolean {
|
public getHiddenState(): boolean {
|
||||||
return this.isHidden;
|
return this.isHidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public setFillState(isFilled: boolean): void {
|
||||||
* 设置激活的轴向
|
this.isFilled = isFilled;
|
||||||
*/
|
this.updateButtonStates();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getFillState(): boolean {
|
||||||
|
return this.isFilled;
|
||||||
|
}
|
||||||
|
|
||||||
public setActiveAxis(axis: SectionAxis): void {
|
public setActiveAxis(axis: SectionAxis): void {
|
||||||
this.activeAxis = axis;
|
this.activeAxis = axis;
|
||||||
this.updateAxisButtonsState();
|
this.updateAxisButtonsState();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取激活的轴向
|
|
||||||
*/
|
|
||||||
public getActiveAxis(): SectionAxis {
|
public getActiveAxis(): SectionAxis {
|
||||||
return this.activeAxis;
|
return this.activeAxis;
|
||||||
}
|
}
|
||||||
@@ -151,6 +151,12 @@ export class SectionAxisPanel implements IBimComponent {
|
|||||||
const row1 = document.createElement('div');
|
const row1 = document.createElement('div');
|
||||||
row1.className = 'section-axis-row-1';
|
row1.className = 'section-axis-row-1';
|
||||||
|
|
||||||
|
this.fillBtn = this.createButton(
|
||||||
|
'fill',
|
||||||
|
getIcon('填充'),
|
||||||
|
() => this.handleFillToggle()
|
||||||
|
);
|
||||||
|
|
||||||
this.hideBtn = this.createButton(
|
this.hideBtn = this.createButton(
|
||||||
'hide',
|
'hide',
|
||||||
getIcon('隐藏'),
|
getIcon('隐藏'),
|
||||||
@@ -163,6 +169,7 @@ export class SectionAxisPanel implements IBimComponent {
|
|||||||
() => this.handleReverse()
|
() => this.handleReverse()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
row1.appendChild(this.fillBtn);
|
||||||
row1.appendChild(this.hideBtn);
|
row1.appendChild(this.hideBtn);
|
||||||
row1.appendChild(this.reverseBtn);
|
row1.appendChild(this.reverseBtn);
|
||||||
|
|
||||||
@@ -188,7 +195,7 @@ export class SectionAxisPanel implements IBimComponent {
|
|||||||
* 创建按钮(带图标)
|
* 创建按钮(带图标)
|
||||||
*/
|
*/
|
||||||
private createButton(
|
private createButton(
|
||||||
type: 'hide' | 'reverse',
|
type: 'hide' | 'reverse' | 'fill',
|
||||||
iconSvg: string,
|
iconSvg: string,
|
||||||
onClick: () => void
|
onClick: () => void
|
||||||
): HTMLButtonElement {
|
): HTMLButtonElement {
|
||||||
@@ -207,11 +214,12 @@ export class SectionAxisPanel implements IBimComponent {
|
|||||||
label.className = 'section-axis-btn-label';
|
label.className = 'section-axis-btn-label';
|
||||||
btn.appendChild(label);
|
btn.appendChild(label);
|
||||||
|
|
||||||
// 保存 label 引用
|
|
||||||
if (type === 'hide') {
|
if (type === 'hide') {
|
||||||
this.hideLabelEl = label;
|
this.hideLabelEl = label;
|
||||||
} else if (type === 'reverse') {
|
} else if (type === 'reverse') {
|
||||||
this.reverseLabelEl = label;
|
this.reverseLabelEl = label;
|
||||||
|
} else if (type === 'fill') {
|
||||||
|
this.fillLabelEl = label;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 点击事件
|
// 点击事件
|
||||||
@@ -249,16 +257,22 @@ export class SectionAxisPanel implements IBimComponent {
|
|||||||
*/
|
*/
|
||||||
private handleHideToggle(): void {
|
private handleHideToggle(): void {
|
||||||
this.isHidden = !this.isHidden;
|
this.isHidden = !this.isHidden;
|
||||||
this.updateHideButtonState();
|
this.updateButtonStates();
|
||||||
|
|
||||||
if (this.options.onHideToggle) {
|
if (this.options.onHideToggle) {
|
||||||
this.options.onHideToggle(this.isHidden);
|
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 {
|
private handleReverse(): void {
|
||||||
if (this.options.onReverse) {
|
if (this.options.onReverse) {
|
||||||
this.options.onReverse();
|
this.options.onReverse();
|
||||||
@@ -281,15 +295,9 @@ export class SectionAxisPanel implements IBimComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private updateButtonStates(): void {
|
||||||
* 更新隐藏按钮状态
|
this.hideBtn.classList.toggle('active', this.isHidden);
|
||||||
*/
|
this.fillBtn.classList.toggle('active', this.isFilled);
|
||||||
private updateHideButtonState(): void {
|
|
||||||
if (this.isHidden) {
|
|
||||||
this.hideBtn.classList.add('active');
|
|
||||||
} else {
|
|
||||||
this.hideBtn.classList.remove('active');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -7,30 +7,11 @@ export type SectionAxis = 'x' | 'y' | 'z';
|
|||||||
* 轴向剖切面板配置选项
|
* 轴向剖切面板配置选项
|
||||||
*/
|
*/
|
||||||
export interface SectionAxisPanelOptions {
|
export interface SectionAxisPanelOptions {
|
||||||
/**
|
|
||||||
* 隐藏按钮切换回调
|
|
||||||
* @param isHidden 是否隐藏
|
|
||||||
*/
|
|
||||||
onHideToggle?: (isHidden: boolean) => void;
|
onHideToggle?: (isHidden: boolean) => void;
|
||||||
|
onFillToggle?: (isFilled: boolean) => void;
|
||||||
/**
|
|
||||||
* 反向按钮回调
|
|
||||||
*/
|
|
||||||
onReverse?: () => void;
|
onReverse?: () => void;
|
||||||
|
|
||||||
/**
|
|
||||||
* 轴向切换回调
|
|
||||||
* @param axis 当前激活的轴向
|
|
||||||
*/
|
|
||||||
onAxisChange?: (axis: SectionAxis) => void;
|
onAxisChange?: (axis: SectionAxis) => void;
|
||||||
|
|
||||||
/**
|
|
||||||
* 默认激活的轴向(默认 'x')
|
|
||||||
*/
|
|
||||||
defaultAxis?: SectionAxis;
|
defaultAxis?: SectionAxis;
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始隐藏状态(默认 false)
|
|
||||||
*/
|
|
||||||
defaultHidden?: boolean;
|
defaultHidden?: boolean;
|
||||||
|
defaultFill?: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,15 +18,18 @@ export class SectionBoxPanel implements IBimComponent {
|
|||||||
|
|
||||||
private isHidden: boolean = false;
|
private isHidden: boolean = false;
|
||||||
private isReversed: boolean = false;
|
private isReversed: boolean = false;
|
||||||
|
private isFilled: boolean = false;
|
||||||
private range: SectionBoxRange;
|
private range: SectionBoxRange;
|
||||||
|
|
||||||
private hideBtn!: HTMLButtonElement;
|
private hideBtn!: HTMLButtonElement;
|
||||||
private fitBtn!: HTMLButtonElement;
|
private fitBtn!: HTMLButtonElement;
|
||||||
private resetBtn!: HTMLButtonElement;
|
private resetBtn!: HTMLButtonElement;
|
||||||
|
private fillBtn!: HTMLButtonElement;
|
||||||
|
|
||||||
private hideLabelEl!: HTMLElement;
|
private hideLabelEl!: HTMLElement;
|
||||||
private fitLabelEl!: HTMLElement;
|
private fitLabelEl!: HTMLElement;
|
||||||
private resetLabelEl!: HTMLElement;
|
private resetLabelEl!: HTMLElement;
|
||||||
|
private fillLabelEl!: HTMLElement;
|
||||||
private xLabelEl!: HTMLElement;
|
private xLabelEl!: HTMLElement;
|
||||||
private yLabelEl!: HTMLElement;
|
private yLabelEl!: HTMLElement;
|
||||||
private zLabelEl!: HTMLElement;
|
private zLabelEl!: HTMLElement;
|
||||||
@@ -61,6 +64,7 @@ export class SectionBoxPanel implements IBimComponent {
|
|||||||
this.options = options;
|
this.options = options;
|
||||||
this.isHidden = options.defaultHidden ?? false;
|
this.isHidden = options.defaultHidden ?? false;
|
||||||
this.isReversed = options.defaultReversed ?? false;
|
this.isReversed = options.defaultReversed ?? false;
|
||||||
|
this.isFilled = options.defaultFill ?? false;
|
||||||
this.range = JSON.parse(JSON.stringify(options.defaultRange ?? DEFAULT_RANGE));
|
this.range = JSON.parse(JSON.stringify(options.defaultRange ?? DEFAULT_RANGE));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,6 +100,15 @@ export class SectionBoxPanel implements IBimComponent {
|
|||||||
return this.isReversed;
|
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 {
|
public setRange(range: Partial<SectionBoxRange>): void {
|
||||||
if (range.x) this.range.x = { ...this.range.x, ...range.x };
|
if (range.x) this.range.x = { ...this.range.x, ...range.x };
|
||||||
if (range.y) this.range.y = { ...this.range.y, ...range.y };
|
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');
|
const buttonsContainer = document.createElement('div');
|
||||||
buttonsContainer.className = 'section-box-row-buttons';
|
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.hideBtn = this.createButton('hide', t('sectionBox.actions.hide'), () => {
|
||||||
this.isHidden = !this.isHidden;
|
this.isHidden = !this.isHidden;
|
||||||
this.updateButtonStates();
|
this.updateButtonStates();
|
||||||
@@ -138,7 +157,7 @@ export class SectionBoxPanel implements IBimComponent {
|
|||||||
|
|
||||||
this.resetBtn = this.createButton('reset', t('sectionBox.actions.reset'), () => this.reset(), 'reset');
|
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');
|
const slidersContainer = document.createElement('div');
|
||||||
slidersContainer.className = 'section-box-sliders';
|
slidersContainer.className = 'section-box-sliders';
|
||||||
@@ -164,7 +183,8 @@ export class SectionBoxPanel implements IBimComponent {
|
|||||||
hide: '隐藏',
|
hide: '隐藏',
|
||||||
reverse: '反向',
|
reverse: '反向',
|
||||||
fit: '适应到模型',
|
fit: '适应到模型',
|
||||||
reset: '重置'
|
reset: '重置',
|
||||||
|
fill: '填充'
|
||||||
};
|
};
|
||||||
|
|
||||||
const icon = document.createElement('div');
|
const icon = document.createElement('div');
|
||||||
@@ -178,6 +198,7 @@ export class SectionBoxPanel implements IBimComponent {
|
|||||||
if (ref === 'hide') this.hideLabelEl = labelEl;
|
if (ref === 'hide') this.hideLabelEl = labelEl;
|
||||||
else if (ref === 'fit') this.fitLabelEl = labelEl;
|
else if (ref === 'fit') this.fitLabelEl = labelEl;
|
||||||
else if (ref === 'reset') this.resetLabelEl = labelEl;
|
else if (ref === 'reset') this.resetLabelEl = labelEl;
|
||||||
|
else if (ref === 'fill') this.fillLabelEl = labelEl;
|
||||||
|
|
||||||
btn.appendChild(icon);
|
btn.appendChild(icon);
|
||||||
btn.appendChild(labelEl);
|
btn.appendChild(labelEl);
|
||||||
@@ -320,12 +341,14 @@ export class SectionBoxPanel implements IBimComponent {
|
|||||||
|
|
||||||
public setLocales(): void {
|
public setLocales(): void {
|
||||||
if (!this.hideLabelEl) return;
|
if (!this.hideLabelEl) return;
|
||||||
|
this.fillLabelEl.textContent = t('sectionBox.actions.fill');
|
||||||
this.hideLabelEl.textContent = t('sectionBox.actions.hide');
|
this.hideLabelEl.textContent = t('sectionBox.actions.hide');
|
||||||
this.fitLabelEl.textContent = t('sectionBox.actions.fitToModel');
|
this.fitLabelEl.textContent = t('sectionBox.actions.fitToModel');
|
||||||
this.resetLabelEl.textContent = t('sectionBox.actions.reset');
|
this.resetLabelEl.textContent = t('sectionBox.actions.reset');
|
||||||
this.xLabelEl.textContent = t('sectionBox.axes.x');
|
this.xLabelEl.textContent = t('sectionBox.axes.x');
|
||||||
this.yLabelEl.textContent = t('sectionBox.axes.y');
|
this.yLabelEl.textContent = t('sectionBox.axes.y');
|
||||||
this.zLabelEl.textContent = t('sectionBox.axes.z');
|
this.zLabelEl.textContent = t('sectionBox.axes.z');
|
||||||
|
this.fillBtn.title = t('sectionBox.actions.fill');
|
||||||
this.hideBtn.title = t('sectionBox.actions.hide');
|
this.hideBtn.title = t('sectionBox.actions.hide');
|
||||||
this.fitBtn.title = t('sectionBox.actions.fitToModel');
|
this.fitBtn.title = t('sectionBox.actions.fitToModel');
|
||||||
this.resetBtn.title = t('sectionBox.actions.reset');
|
this.resetBtn.title = t('sectionBox.actions.reset');
|
||||||
|
|||||||
@@ -24,46 +24,14 @@ export interface SectionBoxRange {
|
|||||||
* 剖切盒面板配置选项
|
* 剖切盒面板配置选项
|
||||||
*/
|
*/
|
||||||
export interface SectionBoxPanelOptions {
|
export interface SectionBoxPanelOptions {
|
||||||
/**
|
|
||||||
* 隐藏按钮切换回调
|
|
||||||
* @param isHidden 是否隐藏剖切盒
|
|
||||||
*/
|
|
||||||
onHideToggle?: (isHidden: boolean) => void;
|
onHideToggle?: (isHidden: boolean) => void;
|
||||||
|
|
||||||
/**
|
|
||||||
* 反向按钮切换回调
|
|
||||||
* @param isReversed 是否反向
|
|
||||||
*/
|
|
||||||
onReverseToggle?: (isReversed: boolean) => void;
|
onReverseToggle?: (isReversed: boolean) => void;
|
||||||
|
|
||||||
/**
|
|
||||||
* 适应到模型按钮回调
|
|
||||||
*/
|
|
||||||
onFitToModel?: () => void;
|
onFitToModel?: () => void;
|
||||||
|
|
||||||
/**
|
|
||||||
* 重置按钮回调
|
|
||||||
*/
|
|
||||||
onReset?: () => void;
|
onReset?: () => void;
|
||||||
|
|
||||||
/**
|
|
||||||
* 范围变化回调
|
|
||||||
* @param range 当前范围值
|
|
||||||
*/
|
|
||||||
onRangeChange?: (range: SectionBoxRange) => void;
|
onRangeChange?: (range: SectionBoxRange) => void;
|
||||||
|
onFillToggle?: (isFilled: boolean) => void;
|
||||||
/**
|
|
||||||
* 默认隐藏状态(默认 false)
|
|
||||||
*/
|
|
||||||
defaultHidden?: boolean;
|
defaultHidden?: boolean;
|
||||||
|
|
||||||
/**
|
|
||||||
* 默认反向状态(默认 false)
|
|
||||||
*/
|
|
||||||
defaultReversed?: boolean;
|
defaultReversed?: boolean;
|
||||||
|
defaultFill?: boolean;
|
||||||
/**
|
|
||||||
* 默认范围值
|
|
||||||
*/
|
|
||||||
defaultRange?: SectionBoxRange;
|
defaultRange?: SectionBoxRange;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,16 +11,18 @@ export interface SectionDockPanelOptions {
|
|||||||
defaultType?: SectionDockType | null;
|
defaultType?: SectionDockType | null;
|
||||||
defaultAxis?: SectionDockAxis;
|
defaultAxis?: SectionDockAxis;
|
||||||
defaultHidden?: boolean;
|
defaultHidden?: boolean;
|
||||||
|
defaultFill?: boolean;
|
||||||
onTypeChange?: (type: SectionDockType, axis: SectionDockAxis) => void;
|
onTypeChange?: (type: SectionDockType, axis: SectionDockAxis) => void;
|
||||||
onAxisChange?: (axis: SectionDockAxis) => void;
|
onAxisChange?: (axis: SectionDockAxis) => void;
|
||||||
onHideToggle?: (hidden: boolean) => void;
|
onHideToggle?: (hidden: boolean) => void;
|
||||||
onReverse?: () => void;
|
onReverse?: () => void;
|
||||||
onReset?: (type: SectionDockType, axis: SectionDockAxis) => void;
|
onReset?: (type: SectionDockType, axis: SectionDockAxis) => void;
|
||||||
onFitToModel?: () => void;
|
onFitToModel?: () => void;
|
||||||
|
onFillToggle?: (isFilled: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ToolDefinition {
|
interface ToolDefinition {
|
||||||
key: 'hide' | 'reverse' | 'reset' | 'fit';
|
key: 'hide' | 'reverse' | 'reset' | 'fit' | 'fill';
|
||||||
iconName: string;
|
iconName: string;
|
||||||
textKey: string;
|
textKey: string;
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
@@ -40,12 +42,14 @@ export class SectionDockPanel implements IBimComponent {
|
|||||||
private activeType: SectionDockType | null;
|
private activeType: SectionDockType | null;
|
||||||
private activeAxis: SectionDockAxis;
|
private activeAxis: SectionDockAxis;
|
||||||
private isHidden: boolean;
|
private isHidden: boolean;
|
||||||
|
private isFilled: boolean;
|
||||||
|
|
||||||
constructor(options: SectionDockPanelOptions = {}) {
|
constructor(options: SectionDockPanelOptions = {}) {
|
||||||
this.options = options;
|
this.options = options;
|
||||||
this.activeType = options.defaultType ?? null;
|
this.activeType = options.defaultType ?? null;
|
||||||
this.activeAxis = options.defaultAxis ?? 'x';
|
this.activeAxis = options.defaultAxis ?? 'x';
|
||||||
this.isHidden = options.defaultHidden ?? false;
|
this.isHidden = options.defaultHidden ?? false;
|
||||||
|
this.isFilled = options.defaultFill ?? false;
|
||||||
|
|
||||||
const { root, toolContainer, axisPanel, divider } = this.createDom();
|
const { root, toolContainer, axisPanel, divider } = this.createDom();
|
||||||
this.element = root;
|
this.element = root;
|
||||||
@@ -112,6 +116,15 @@ export class SectionDockPanel implements IBimComponent {
|
|||||||
return this.activeAxis;
|
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 } {
|
private createDom(): { root: HTMLElement; toolContainer: HTMLElement; axisPanel: HTMLElement; divider: HTMLElement } {
|
||||||
const root = document.createElement('div');
|
const root = document.createElement('div');
|
||||||
root.className = 'section-dock-panel';
|
root.className = 'section-dock-panel';
|
||||||
@@ -257,6 +270,18 @@ export class SectionDockPanel implements IBimComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private getToolsForType(type: SectionDockType): ToolDefinition[] {
|
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 = {
|
const hideTool: ToolDefinition = {
|
||||||
key: 'hide',
|
key: 'hide',
|
||||||
iconName: '隐藏',
|
iconName: '隐藏',
|
||||||
@@ -280,6 +305,7 @@ export class SectionDockPanel implements IBimComponent {
|
|||||||
|
|
||||||
if (type === 'axis') {
|
if (type === 'axis') {
|
||||||
return [
|
return [
|
||||||
|
fillTool,
|
||||||
hideTool,
|
hideTool,
|
||||||
reverseTool,
|
reverseTool,
|
||||||
{
|
{
|
||||||
@@ -297,6 +323,7 @@ export class SectionDockPanel implements IBimComponent {
|
|||||||
|
|
||||||
if (type === 'box') {
|
if (type === 'box') {
|
||||||
return [
|
return [
|
||||||
|
fillTool,
|
||||||
hideTool,
|
hideTool,
|
||||||
{
|
{
|
||||||
key: 'fit',
|
key: 'fit',
|
||||||
@@ -320,6 +347,7 @@ export class SectionDockPanel implements IBimComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
fillTool,
|
||||||
hideTool,
|
hideTool,
|
||||||
reverseTool,
|
reverseTool,
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -15,14 +15,17 @@ export class SectionPlanePanel implements IBimComponent {
|
|||||||
private options: SectionPlanePanelOptions;
|
private options: SectionPlanePanelOptions;
|
||||||
|
|
||||||
private isHidden: boolean = false;
|
private isHidden: boolean = false;
|
||||||
|
private isFilled: boolean = false;
|
||||||
|
|
||||||
// DOM 引用
|
// DOM 引用
|
||||||
private hideBtn!: HTMLButtonElement;
|
private hideBtn!: HTMLButtonElement;
|
||||||
private reverseBtn!: HTMLButtonElement;
|
private reverseBtn!: HTMLButtonElement;
|
||||||
private resetBtn!: HTMLButtonElement;
|
private resetBtn!: HTMLButtonElement;
|
||||||
|
private fillBtn!: HTMLButtonElement;
|
||||||
private hideLabelEl!: HTMLElement;
|
private hideLabelEl!: HTMLElement;
|
||||||
private reverseLabelEl!: HTMLElement;
|
private reverseLabelEl!: HTMLElement;
|
||||||
private resetLabelEl!: HTMLElement;
|
private resetLabelEl!: HTMLElement;
|
||||||
|
private fillLabelEl!: HTMLElement;
|
||||||
|
|
||||||
// 订阅清理
|
// 订阅清理
|
||||||
private unsubscribeLocale: (() => void) | null = null;
|
private unsubscribeLocale: (() => void) | null = null;
|
||||||
@@ -31,6 +34,7 @@ export class SectionPlanePanel implements IBimComponent {
|
|||||||
constructor(options: SectionPlanePanelOptions = {}) {
|
constructor(options: SectionPlanePanelOptions = {}) {
|
||||||
this.options = options;
|
this.options = options;
|
||||||
this.isHidden = options.defaultHidden ?? false;
|
this.isHidden = options.defaultHidden ?? false;
|
||||||
|
this.isFilled = options.defaultFill ?? false;
|
||||||
this.element = this.createDom();
|
this.element = this.createDom();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,8 +47,18 @@ export class SectionPlanePanel implements IBimComponent {
|
|||||||
return this.isHidden;
|
return this.isHidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public setFillState(isFilled: boolean): void {
|
||||||
|
this.isFilled = isFilled;
|
||||||
|
this.updateButtonStates();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getFillState(): boolean {
|
||||||
|
return this.isFilled;
|
||||||
|
}
|
||||||
|
|
||||||
private updateButtonStates(): void {
|
private updateButtonStates(): void {
|
||||||
this.hideBtn?.classList.toggle('active', this.isHidden);
|
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.hideLabelEl.textContent = t('sectionPlane.actions.hide');
|
||||||
this.reverseLabelEl.textContent = t('sectionPlane.actions.reverse');
|
this.reverseLabelEl.textContent = t('sectionPlane.actions.reverse');
|
||||||
this.resetLabelEl.textContent = t('sectionPlane.actions.reset');
|
this.resetLabelEl.textContent = t('sectionPlane.actions.reset');
|
||||||
|
this.fillLabelEl.textContent = t('sectionPlane.actions.fill');
|
||||||
|
|
||||||
this.hideBtn.title = t('sectionPlane.actions.hide');
|
this.hideBtn.title = t('sectionPlane.actions.hide');
|
||||||
this.reverseBtn.title = t('sectionPlane.actions.reverse');
|
this.reverseBtn.title = t('sectionPlane.actions.reverse');
|
||||||
this.resetBtn.title = t('sectionPlane.actions.reset');
|
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');
|
const root = document.createElement('div');
|
||||||
root.className = 'section-plane-panel';
|
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(
|
this.hideBtn = this.createButton(
|
||||||
'hide',
|
'hide',
|
||||||
@@ -149,6 +176,7 @@ export class SectionPlanePanel implements IBimComponent {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
root.appendChild(this.fillBtn);
|
||||||
root.appendChild(this.hideBtn);
|
root.appendChild(this.hideBtn);
|
||||||
root.appendChild(this.reverseBtn);
|
root.appendChild(this.reverseBtn);
|
||||||
root.appendChild(this.resetBtn);
|
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');
|
const btn = document.createElement('button');
|
||||||
btn.type = 'button';
|
btn.type = 'button';
|
||||||
btn.className = 'section-plane-btn';
|
btn.className = 'section-plane-btn';
|
||||||
@@ -175,11 +203,12 @@ export class SectionPlanePanel implements IBimComponent {
|
|||||||
label.className = 'section-plane-btn-label';
|
label.className = 'section-plane-btn-label';
|
||||||
btn.appendChild(label);
|
btn.appendChild(label);
|
||||||
|
|
||||||
// 保存 label 引用
|
|
||||||
if (type === 'hide') {
|
if (type === 'hide') {
|
||||||
this.hideLabelEl = label;
|
this.hideLabelEl = label;
|
||||||
} else if (type === 'reverse') {
|
} else if (type === 'reverse') {
|
||||||
this.reverseLabelEl = label;
|
this.reverseLabelEl = label;
|
||||||
|
} else if (type === 'fill') {
|
||||||
|
this.fillLabelEl = label;
|
||||||
} else {
|
} else {
|
||||||
this.resetLabelEl = label;
|
this.resetLabelEl = label;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,12 +7,23 @@ export interface SectionPlanePanelOptions {
|
|||||||
*/
|
*/
|
||||||
defaultHidden?: boolean;
|
defaultHidden?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始填充状态
|
||||||
|
*/
|
||||||
|
defaultFill?: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 隐藏状态切换回调
|
* 隐藏状态切换回调
|
||||||
* @param isHidden 是否隐藏
|
* @param isHidden 是否隐藏
|
||||||
*/
|
*/
|
||||||
onHideToggle?: (isHidden: boolean) => void;
|
onHideToggle?: (isHidden: boolean) => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 填充状态切换回调
|
||||||
|
* @param isFilled 是否填充
|
||||||
|
*/
|
||||||
|
onFillToggle?: (isFilled: boolean) => void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 反向按钮回调
|
* 反向按钮回调
|
||||||
*/
|
*/
|
||||||
|
|||||||
1
src/iflow-engine-base.css
Normal file
1
src/iflow-engine-base.css
Normal file
File diff suppressed because one or more lines are too long
@@ -135,7 +135,8 @@ export const enUS: TranslationDictionary = {
|
|||||||
actions: {
|
actions: {
|
||||||
hide: 'Hide',
|
hide: 'Hide',
|
||||||
reverse: 'Reverse',
|
reverse: 'Reverse',
|
||||||
reset: 'Reset'
|
reset: 'Reset',
|
||||||
|
fill: 'Fill'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
sectionAxis: {
|
sectionAxis: {
|
||||||
@@ -146,7 +147,8 @@ export const enUS: TranslationDictionary = {
|
|||||||
reset: 'Reset',
|
reset: 'Reset',
|
||||||
axisX: 'X',
|
axisX: 'X',
|
||||||
axisY: 'Y',
|
axisY: 'Y',
|
||||||
axisZ: 'Z'
|
axisZ: 'Z',
|
||||||
|
fill: 'Fill'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
sectionBox: {
|
sectionBox: {
|
||||||
@@ -155,7 +157,8 @@ export const enUS: TranslationDictionary = {
|
|||||||
hide: 'Hide',
|
hide: 'Hide',
|
||||||
reverse: 'Reverse',
|
reverse: 'Reverse',
|
||||||
fitToModel: 'Fit',
|
fitToModel: 'Fit',
|
||||||
reset: 'Reset'
|
reset: 'Reset',
|
||||||
|
fill: 'Fill'
|
||||||
},
|
},
|
||||||
axes: {
|
axes: {
|
||||||
x: 'X',
|
x: 'X',
|
||||||
|
|||||||
@@ -147,6 +147,7 @@ export interface TranslationDictionary {
|
|||||||
hide: string;
|
hide: string;
|
||||||
reverse: string;
|
reverse: string;
|
||||||
reset: string;
|
reset: string;
|
||||||
|
fill: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
sectionAxis: {
|
sectionAxis: {
|
||||||
@@ -158,6 +159,7 @@ export interface TranslationDictionary {
|
|||||||
axisX: string;
|
axisX: string;
|
||||||
axisY: string;
|
axisY: string;
|
||||||
axisZ: string;
|
axisZ: string;
|
||||||
|
fill: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
sectionBox: {
|
sectionBox: {
|
||||||
@@ -167,6 +169,7 @@ export interface TranslationDictionary {
|
|||||||
reverse: string;
|
reverse: string;
|
||||||
fitToModel: string;
|
fitToModel: string;
|
||||||
reset: string;
|
reset: string;
|
||||||
|
fill: string;
|
||||||
};
|
};
|
||||||
axes: {
|
axes: {
|
||||||
x: string;
|
x: string;
|
||||||
|
|||||||
@@ -135,7 +135,8 @@ export const zhCN: TranslationDictionary = {
|
|||||||
actions: {
|
actions: {
|
||||||
hide: '隐藏',
|
hide: '隐藏',
|
||||||
reverse: '反向',
|
reverse: '反向',
|
||||||
reset: '重置'
|
reset: '重置',
|
||||||
|
fill: '填充'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
sectionAxis: {
|
sectionAxis: {
|
||||||
@@ -146,7 +147,8 @@ export const zhCN: TranslationDictionary = {
|
|||||||
reset: '重置',
|
reset: '重置',
|
||||||
axisX: 'X',
|
axisX: 'X',
|
||||||
axisY: 'Y',
|
axisY: 'Y',
|
||||||
axisZ: 'Z'
|
axisZ: 'Z',
|
||||||
|
fill: '填充'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
sectionBox: {
|
sectionBox: {
|
||||||
@@ -155,7 +157,8 @@ export const zhCN: TranslationDictionary = {
|
|||||||
hide: '隐藏',
|
hide: '隐藏',
|
||||||
reverse: '反向',
|
reverse: '反向',
|
||||||
fitToModel: '适应',
|
fitToModel: '适应',
|
||||||
reset: '重置'
|
reset: '重置',
|
||||||
|
fill: '填充'
|
||||||
},
|
},
|
||||||
axes: {
|
axes: {
|
||||||
x: 'X',
|
x: 'X',
|
||||||
|
|||||||
@@ -135,7 +135,8 @@ export const zhTW: TranslationDictionary = {
|
|||||||
actions: {
|
actions: {
|
||||||
hide: '隱藏',
|
hide: '隱藏',
|
||||||
reverse: '反向',
|
reverse: '反向',
|
||||||
reset: '重設'
|
reset: '重設',
|
||||||
|
fill: '填充'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
sectionAxis: {
|
sectionAxis: {
|
||||||
@@ -146,7 +147,8 @@ export const zhTW: TranslationDictionary = {
|
|||||||
reset: '重設',
|
reset: '重設',
|
||||||
axisX: 'X',
|
axisX: 'X',
|
||||||
axisY: 'Y',
|
axisY: 'Y',
|
||||||
axisZ: 'Z'
|
axisZ: 'Z',
|
||||||
|
fill: '填充'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
sectionBox: {
|
sectionBox: {
|
||||||
@@ -155,7 +157,8 @@ export const zhTW: TranslationDictionary = {
|
|||||||
hide: '隱藏',
|
hide: '隱藏',
|
||||||
reverse: '反向',
|
reverse: '反向',
|
||||||
fitToModel: '適應',
|
fitToModel: '適應',
|
||||||
reset: '重設'
|
reset: '重設',
|
||||||
|
fill: '填充'
|
||||||
},
|
},
|
||||||
axes: {
|
axes: {
|
||||||
x: 'X',
|
x: 'X',
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import { createWalkRadialButton } from '../components/radial-toolbar/buttons/wal
|
|||||||
|
|
||||||
export interface RadialToolbarManagerOptions {
|
export interface RadialToolbarManagerOptions {
|
||||||
items?: RadialMenuItem[];
|
items?: RadialMenuItem[];
|
||||||
itemsPerRing?: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class RadialToolbarManager extends BaseManager {
|
export class RadialToolbarManager extends BaseManager {
|
||||||
@@ -24,7 +23,6 @@ export class RadialToolbarManager extends BaseManager {
|
|||||||
this.toolbar = new RadialToolbar({
|
this.toolbar = new RadialToolbar({
|
||||||
container,
|
container,
|
||||||
items: options?.items ?? this.createDefaultItems(),
|
items: options?.items ?? this.createDefaultItems(),
|
||||||
itemsPerRing: options?.itemsPerRing ?? 4,
|
|
||||||
mainButtonIcon: getIcon('主视角'),
|
mainButtonIcon: getIcon('主视角'),
|
||||||
mainButtonLabel: 'toolbar.home',
|
mainButtonLabel: 'toolbar.home',
|
||||||
onMainButtonClick: () => {
|
onMainButtonClick: () => {
|
||||||
@@ -61,6 +59,10 @@ export class RadialToolbarManager extends BaseManager {
|
|||||||
this.toolbar.setItemActive('walk', dock.isOpen('walk'));
|
this.toolbar.setItemActive('walk', dock.isOpen('walk'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public addItem(item: RadialMenuItem): void {
|
||||||
|
this.toolbar?.addItem(item);
|
||||||
|
}
|
||||||
|
|
||||||
public destroy(): void {
|
public destroy(): void {
|
||||||
if (this.unsubscribeDockState) {
|
if (this.unsubscribeDockState) {
|
||||||
this.unsubscribeDockState();
|
this.unsubscribeDockState();
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ export class SectionAxisDialogManager extends BaseDialogManager {
|
|||||||
this.panel = new SectionAxisPanel({
|
this.panel = new SectionAxisPanel({
|
||||||
defaultAxis: 'x',
|
defaultAxis: 'x',
|
||||||
defaultHidden: false,
|
defaultHidden: false,
|
||||||
|
defaultFill: this.engineComponent?.getFillCutFace() ?? false,
|
||||||
onHideToggle: (isHidden) => {
|
onHideToggle: (isHidden) => {
|
||||||
console.log('[SectionAxisDialogManager] 隐藏切换:', isHidden);
|
console.log('[SectionAxisDialogManager] 隐藏切换:', isHidden);
|
||||||
if (isHidden) {
|
if (isHidden) {
|
||||||
@@ -69,6 +70,9 @@ export class SectionAxisDialogManager extends BaseDialogManager {
|
|||||||
this.engineComponent?.recoverSection();
|
this.engineComponent?.recoverSection();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onFillToggle: (isFilled) => {
|
||||||
|
this.engineComponent?.setFillCutFace(isFilled);
|
||||||
|
},
|
||||||
onReverse: () => {
|
onReverse: () => {
|
||||||
this.engineComponent?.reverseSection();
|
this.engineComponent?.reverseSection();
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ export class SectionBoxDialogManager extends BaseDialogManager {
|
|||||||
this.panel = new SectionBoxPanel({
|
this.panel = new SectionBoxPanel({
|
||||||
defaultHidden: false,
|
defaultHidden: false,
|
||||||
defaultReversed: false,
|
defaultReversed: false,
|
||||||
|
defaultFill: this.engineComponent?.getFillCutFace() ?? false,
|
||||||
onHideToggle: (isHidden) => {
|
onHideToggle: (isHidden) => {
|
||||||
console.log('[SectionBoxDialogManager] 隐藏切换:', isHidden);
|
console.log('[SectionBoxDialogManager] 隐藏切换:', isHidden);
|
||||||
if (isHidden) {
|
if (isHidden) {
|
||||||
@@ -61,6 +62,9 @@ export class SectionBoxDialogManager extends BaseDialogManager {
|
|||||||
this.engineComponent?.recoverSection();
|
this.engineComponent?.recoverSection();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onFillToggle: (isFilled) => {
|
||||||
|
this.engineComponent?.setFillCutFace(isFilled);
|
||||||
|
},
|
||||||
onFitToModel: () => {
|
onFitToModel: () => {
|
||||||
// 对接底层 scaleBox():缩放剖切盒到场景整体包围盒
|
// 对接底层 scaleBox():缩放剖切盒到场景整体包围盒
|
||||||
this.engineComponent?.scaleSectionBox();
|
this.engineComponent?.scaleSectionBox();
|
||||||
@@ -70,7 +74,7 @@ export class SectionBoxDialogManager extends BaseDialogManager {
|
|||||||
// UI 侧会自行将滑块强制恢复到 0-100,并将隐藏/反向按钮恢复为关闭状态。
|
// UI 侧会自行将滑块强制恢复到 0-100,并将隐藏/反向按钮恢复为关闭状态。
|
||||||
this.engineComponent?.deactivateSection();
|
this.engineComponent?.deactivateSection();
|
||||||
this.engineComponent?.activeSection('box');
|
this.engineComponent?.activeSection('box');
|
||||||
// 确保剖切可见(避免上一次处于隐藏状态导致“看起来没重置”)
|
// 确保剖切可见(避免上一次处于隐藏状态导致"看起来没重置")
|
||||||
this.engineComponent?.recoverSection();
|
this.engineComponent?.recoverSection();
|
||||||
},
|
},
|
||||||
onRangeChange: (range) => {
|
onRangeChange: (range) => {
|
||||||
|
|||||||
@@ -91,6 +91,10 @@ export class SectionDockManager extends BaseManager {
|
|||||||
},
|
},
|
||||||
onFitToModel: () => {
|
onFitToModel: () => {
|
||||||
this.engineComponent?.scaleSectionBox();
|
this.engineComponent?.scaleSectionBox();
|
||||||
|
},
|
||||||
|
defaultFill: this.engineComponent?.getFillCutFace() ?? false,
|
||||||
|
onFillToggle: (isFilled) => {
|
||||||
|
this.engineComponent?.setFillCutFace(isFilled);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.panel.init();
|
this.panel.init();
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ export class SectionPlaneDialogManager extends BaseDialogManager {
|
|||||||
protected createContent(): HTMLElement {
|
protected createContent(): HTMLElement {
|
||||||
this.panel = new SectionPlanePanel({
|
this.panel = new SectionPlanePanel({
|
||||||
defaultHidden: false,
|
defaultHidden: false,
|
||||||
|
defaultFill: this.engineComponent?.getFillCutFace() ?? false,
|
||||||
onHideToggle: (isHidden) => {
|
onHideToggle: (isHidden) => {
|
||||||
console.log('[SectionPlaneDialogManager] 隐藏切换:', isHidden);
|
console.log('[SectionPlaneDialogManager] 隐藏切换:', isHidden);
|
||||||
if (isHidden) {
|
if (isHidden) {
|
||||||
@@ -61,6 +62,9 @@ export class SectionPlaneDialogManager extends BaseDialogManager {
|
|||||||
this.engineComponent?.recoverSection();
|
this.engineComponent?.recoverSection();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onFillToggle: (isFilled) => {
|
||||||
|
this.engineComponent?.setFillCutFace(isFilled);
|
||||||
|
},
|
||||||
onReverse: () => {
|
onReverse: () => {
|
||||||
this.engineComponent?.reverseSection();
|
this.engineComponent?.reverseSection();
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -10,6 +10,10 @@ export interface EngineEvents {
|
|||||||
'engine:model-loading-completed': {};
|
'engine:model-loading-completed': {};
|
||||||
'engine:object-clicked': { objectId: string; position: { x: number; y: number; z: number } };
|
'engine:object-clicked': { objectId: string; position: { x: number; y: number; z: number } };
|
||||||
|
|
||||||
|
'encoding:start': { data?: any };
|
||||||
|
'encoding:complete': { data?: any };
|
||||||
|
'encoding:error': { data?: any };
|
||||||
|
|
||||||
// 树组件事件
|
// 树组件事件
|
||||||
'ui:tree-node-check': { id: string; checked: boolean; node: any };
|
'ui:tree-node-check': { id: string; checked: boolean; node: any };
|
||||||
'ui:tree-node-select': { id: string; selected: boolean; node: any };
|
'ui:tree-node-select': { id: string; selected: boolean; node: any };
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ const ICONS: Record<string, string> = {
|
|||||||
适应到模型: '<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="currentColor"><g transform="translate(-84.667 -84.667)"><path d="M87.667,97V87.667H97V85.333H86.733a1.4,1.4,0,0,0-1.4,1.4V97Zm0,23.333H85.333V130.6a1.4,1.4,0,0,0,1.4,1.4H97v-2.333H87.667v-9.333Zm32.667,9.333V132H130.6a1.4,1.4,0,0,0,1.4-1.4V120.333h-2.333v9.333h-9.333ZM129.667,97H132V86.733a1.4,1.4,0,0,0-1.4-1.4H120.333v2.333h9.333Z"/><path d="M270.857,243.5l11.3-6.652V223.387l-11.3,6.361V243.5Zm-1.8,0v-13.75l-11.3-6.361v13.456l11.3,6.652Zm-10.278-21.621,11.177,6.284,11.177-6.284L269.958,215.3l-11.177,6.573Zm11.622-8.426,13.1,7.709a.919.919,0,0,1,.448.793v15.419a.926.926,0,0,1-.448.793l-13.1,7.709a.887.887,0,0,1-.9,0l-13.1-7.709a.905.905,0,0,1-.448-.793V221.954a.919.919,0,0,1,.448-.793l13.1-7.709A.887.887,0,0,1,270.4,213.452Z" transform="translate(-161.292 -120.997)"/></g></svg>',
|
适应到模型: '<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="currentColor"><g transform="translate(-84.667 -84.667)"><path d="M87.667,97V87.667H97V85.333H86.733a1.4,1.4,0,0,0-1.4,1.4V97Zm0,23.333H85.333V130.6a1.4,1.4,0,0,0,1.4,1.4H97v-2.333H87.667v-9.333Zm32.667,9.333V132H130.6a1.4,1.4,0,0,0,1.4-1.4V120.333h-2.333v9.333h-9.333ZM129.667,97H132V86.733a1.4,1.4,0,0,0-1.4-1.4H120.333v2.333h9.333Z"/><path d="M270.857,243.5l11.3-6.652V223.387l-11.3,6.361V243.5Zm-1.8,0v-13.75l-11.3-6.361v13.456l11.3,6.652Zm-10.278-21.621,11.177,6.284,11.177-6.284L269.958,215.3l-11.177,6.573Zm11.622-8.426,13.1,7.709a.919.919,0,0,1,.448.793v15.419a.926.926,0,0,1-.448.793l-13.1,7.709a.887.887,0,0,1-.9,0l-13.1-7.709a.905.905,0,0,1-.448-.793V221.954a.919.919,0,0,1,.448-.793l13.1-7.709A.887.887,0,0,1,270.4,213.452Z" transform="translate(-161.292 -120.997)"/></g></svg>',
|
||||||
隐藏: '<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="currentColor"><path d="M61.274,76.178l0-.007-.007,0a32.458,32.458,0,0,0-7.014-9.981L51.53,68.91a28.382,28.382,0,0,1,6.124,8.635c-4.472,9.25-10.809,13.636-19.472,13.636A20.615,20.615,0,0,1,30.6,89.838l-2.937,2.933a23.916,23.916,0,0,0,10.514,2.277q15.472,0,23.088-16.116a3.229,3.229,0,0,0,0-2.755ZM57.839,58.351l-2.274-2.277a.429.429,0,0,0-.608,0l-6.265,6.262a23.775,23.775,0,0,0-10.51-2.281q-15.472,0-23.088,16.116v.007a3.236,3.236,0,0,0,0,2.765,32.617,32.617,0,0,0,7.014,9.985L16.7,94.32a.429.429,0,0,0,0,.608l2.274,2.277a.429.429,0,0,0,.608,0L57.839,58.949a.426.426,0,0,0,0-.6ZM32.113,78.915a6.011,6.011,0,0,1,7.22-7.22l-7.223,7.22Zm9.9-9.9A9.452,9.452,0,0,0,29.43,81.6l-4.6,4.6a28.424,28.424,0,0,1-6.124-8.635c4.475-9.25,10.816-13.636,19.472-13.636a20.615,20.615,0,0,1,7.577,1.343Zm-4.046,14.55a5.752,5.752,0,0,1-1.01-.086l-2.744,2.741A9.446,9.446,0,0,0,46.638,73.794L43.9,76.538a6.018,6.018,0,0,1-5.932,7.024Z" transform="translate(-14.182 -52.64)"/></svg>',
|
隐藏: '<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="currentColor"><path d="M61.274,76.178l0-.007-.007,0a32.458,32.458,0,0,0-7.014-9.981L51.53,68.91a28.382,28.382,0,0,1,6.124,8.635c-4.472,9.25-10.809,13.636-19.472,13.636A20.615,20.615,0,0,1,30.6,89.838l-2.937,2.933a23.916,23.916,0,0,0,10.514,2.277q15.472,0,23.088-16.116a3.229,3.229,0,0,0,0-2.755ZM57.839,58.351l-2.274-2.277a.429.429,0,0,0-.608,0l-6.265,6.262a23.775,23.775,0,0,0-10.51-2.281q-15.472,0-23.088,16.116v.007a3.236,3.236,0,0,0,0,2.765,32.617,32.617,0,0,0,7.014,9.985L16.7,94.32a.429.429,0,0,0,0,.608l2.274,2.277a.429.429,0,0,0,.608,0L57.839,58.949a.426.426,0,0,0,0-.6ZM32.113,78.915a6.011,6.011,0,0,1,7.22-7.22l-7.223,7.22Zm9.9-9.9A9.452,9.452,0,0,0,29.43,81.6l-4.6,4.6a28.424,28.424,0,0,1-6.124-8.635c4.475-9.25,10.816-13.636,19.472-13.636a20.615,20.615,0,0,1,7.577,1.343Zm-4.046,14.55a5.752,5.752,0,0,1-1.01-.086l-2.744,2.741A9.446,9.446,0,0,0,46.638,73.794L43.9,76.538a6.018,6.018,0,0,1-5.932,7.024Z" transform="translate(-14.182 -52.64)"/></svg>',
|
||||||
重置: '<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="currentColor"><path d="M32.936,32.658a15.914,15.914,0,0,1-9.6,3.064c-.451-.006-.9-.042-1.342-.077-.182-.018-.357-.047-.539-.077-.352-.047-.7-.089-1.037-.164-.211-.036-.422-.1-.621-.148-.334-.077-.668-.154-.99-.254-.153-.059-.3-.11-.457-.177-.381-.124-.755-.26-1.113-.414-.082-.036-.162-.071-.24-.1-.422-.2-.844-.408-1.248-.627-.017-.012-.035-.018-.054-.03a16.485,16.485,0,0,1-3.732-2.9c-.017-.018-.035-.042-.054-.059-.34-.355-.668-.721-.979-1.118-.065-.083-.123-.16-.194-.254A17.286,17.286,0,0,1,7.149,18.751h3.98L6.318,9.076,0,18.874H3.942a20.809,20.809,0,0,0,3.5,11.57.871.871,0,0,0,.075.14c.225.34.486.648.724.961.1.113.177.232.274.362.354.443.746.869,1.133,1.285.043.043.075.076.108.113a19.778,19.778,0,0,0,4.436,3.446c.043.027.08.043.128.076.467.259.949.5,1.432.719.124.055.242.113.359.164.419.184.847.335,1.277.493.2.076.4.14.611.21.378.113.762.21,1.153.3.257.065.509.129.772.184a2.252,2.252,0,0,0,.316.076c.37.07.735.1,1.1.151.134.027.27.049.4.065.66.065,1.314.11,1.974.11,4.006,0,8.852-1.3,11.85-4.177a1.908,1.908,0,0,0,.333-2.484,2.228,2.228,0,0,0-2.952.023ZM44.053,20.424a20.728,20.728,0,0,0-3.47-11.537c-.032-.055-.054-.113-.08-.164-.284-.405-.574-.778-.869-1.161a1.289,1.289,0,0,1-.1-.14A19.86,19.86,0,0,0,32.168,1.7c-.086-.032-.155-.076-.242-.11-.456-.195-.922-.362-1.394-.53C30.37,1,30.2.937,30.033.883,29.62.754,29.212.651,28.792.548c-.231-.055-.467-.113-.7-.164-.113-.022-.22-.055-.338-.081-.311-.055-.617-.081-.933-.124C26.6.151,26.392.119,26.18.1,25.655.042,25.135.021,24.616.016c-.1,0-.188-.016-.284-.016-.016,0-.032.005-.049.005A19.124,19.124,0,0,0,13.02,3.695a1.682,1.682,0,0,0-.466,2.515,1.777,1.777,0,0,0,2.309.3A15.251,15.251,0,0,1,24.374,3.45c.486.006.971.03,1.441.077.147.012.287.036.432.059a11.3,11.3,0,0,1,1.16.189c.162.03.334.077.493.11.381.089.744.178,1.107.3a2.577,2.577,0,0,1,.34.124c.422.142.832.29,1.23.467.047.012.082.047.123.059a16.492,16.492,0,0,1,6.182,4.8.367.367,0,0,0,.024.036A17.308,17.308,0,0,1,40.629,20.43H36.645l5.048,9.787L48,20.424Zm0,0" transform="translate(0 4.349)"/></svg>',
|
重置: '<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="currentColor"><path d="M32.936,32.658a15.914,15.914,0,0,1-9.6,3.064c-.451-.006-.9-.042-1.342-.077-.182-.018-.357-.047-.539-.077-.352-.047-.7-.089-1.037-.164-.211-.036-.422-.1-.621-.148-.334-.077-.668-.154-.99-.254-.153-.059-.3-.11-.457-.177-.381-.124-.755-.26-1.113-.414-.082-.036-.162-.071-.24-.1-.422-.2-.844-.408-1.248-.627-.017-.012-.035-.018-.054-.03a16.485,16.485,0,0,1-3.732-2.9c-.017-.018-.035-.042-.054-.059-.34-.355-.668-.721-.979-1.118-.065-.083-.123-.16-.194-.254A17.286,17.286,0,0,1,7.149,18.751h3.98L6.318,9.076,0,18.874H3.942a20.809,20.809,0,0,0,3.5,11.57.871.871,0,0,0,.075.14c.225.34.486.648.724.961.1.113.177.232.274.362.354.443.746.869,1.133,1.285.043.043.075.076.108.113a19.778,19.778,0,0,0,4.436,3.446c.043.027.08.043.128.076.467.259.949.5,1.432.719.124.055.242.113.359.164.419.184.847.335,1.277.493.2.076.4.14.611.21.378.113.762.21,1.153.3.257.065.509.129.772.184a2.252,2.252,0,0,0,.316.076c.37.07.735.1,1.1.151.134.027.27.049.4.065.66.065,1.314.11,1.974.11,4.006,0,8.852-1.3,11.85-4.177a1.908,1.908,0,0,0,.333-2.484,2.228,2.228,0,0,0-2.952.023ZM44.053,20.424a20.728,20.728,0,0,0-3.47-11.537c-.032-.055-.054-.113-.08-.164-.284-.405-.574-.778-.869-1.161a1.289,1.289,0,0,1-.1-.14A19.86,19.86,0,0,0,32.168,1.7c-.086-.032-.155-.076-.242-.11-.456-.195-.922-.362-1.394-.53C30.37,1,30.2.937,30.033.883,29.62.754,29.212.651,28.792.548c-.231-.055-.467-.113-.7-.164-.113-.022-.22-.055-.338-.081-.311-.055-.617-.081-.933-.124C26.6.151,26.392.119,26.18.1,25.655.042,25.135.021,24.616.016c-.1,0-.188-.016-.284-.016-.016,0-.032.005-.049.005A19.124,19.124,0,0,0,13.02,3.695a1.682,1.682,0,0,0-.466,2.515,1.777,1.777,0,0,0,2.309.3A15.251,15.251,0,0,1,24.374,3.45c.486.006.971.03,1.441.077.147.012.287.036.432.059a11.3,11.3,0,0,1,1.16.189c.162.03.334.077.493.11.381.089.744.178,1.107.3a2.577,2.577,0,0,1,.34.124c.422.142.832.29,1.23.467.047.012.082.047.123.059a16.492,16.492,0,0,1,6.182,4.8.367.367,0,0,0,.024.036A17.308,17.308,0,0,1,40.629,20.43H36.645l5.048,9.787L48,20.424Zm0,0" transform="translate(0 4.349)"/></svg>',
|
||||||
|
填充: '<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="currentColor"><path d="M24 6L10 22h10v18h8V22h10L24 6z" opacity="0.35"/><path d="M8 24h32v4H8z"/></svg>',
|
||||||
|
|
||||||
// ========== 测量相关图标 (32x32) ==========
|
// ========== 测量相关图标 (32x32) ==========
|
||||||
标高: '<svg width="32" height="32" viewBox="0 0 32 32"><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>',
|
标高: '<svg width="32" height="32" viewBox="0 0 32 32"><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>',
|
||||||
|
|||||||
Reference in New Issue
Block a user