版本: 0.4.1 | 日期: 2026-04-06 | 状态: Phase 4 完成,架构稳定期
基于 OCCT(几何内核) + VSG(Vulkan 渲染) + VTK(分析可视化) + QML(UI 框架) 构建海上油气/风电平台管道系统参数化建模与应力分析软件。
核心设计理念: 以 管点(PipePoint) 为中心的数据模型 — 管点是带坐标和类型的文档对象,管件几何由管点序列 + 管线特性(PipeSpec) 推导生成。管点按段(Segment)组织,段按树状结构组成路由(Route)。
采用 8 层工程分层(7 个核心库层 + 1 个应用入口层),QML 通过 FBO 嵌入 VSG 3D 视口,仅法兰连接,STEP 格式数据交换。
2026-04-06 同步说明: src/lib 与 src/apps 已成为唯一构建入口;命令编辑流程统一使用 CommandStack + CommandRegistry + CommandContext;当前基线为测试 46/46 全通过。构建脚本 (scripts/) 已移除,统一通过 pixi 任务执行构建与测试。
| 项目 | 决策 |
|---|---|
| 核心数据模型 | 管点(PipePoint) — 坐标+类型,管件几何由管点序列推导 |
| 管点类型 | Run(管段)、Bend(弯头)、Reducer(异径)、Tee(三通)、Valve(阀门)、FlexJoint(柔性接头) |
| 管点命名 | 字母=段ID + 数字=序号,如 A00→A01→A02;三通处分叉新段 B00→... |
| 管线特性(PipeSpec) | 文档对象,管点级别引用;核心字段: OD/WT/材料等,支持自定义扩展 |
| Bend 特殊管点 | 4个: 交点(Axx)、近端N、中点M、远端F;弯曲半径=OD×倍数(1.5D/2D/5D) |
| 几何推导 | 管件几何尺寸由管点坐标+PipeSpec决定 |
| 段与路由 | 路由=段的树状集合,三通处分叉 |
| 附属构件 | 固定点/支架/法兰/垫片/密封圈 — 独立文档对象 |
| 梁(Beam) | 独立文档对象,引用两个管点 |
| 连接方式 | 仅法兰连接 |
| UI | QML (表格输入管点) + VSG 渲染到纹理(FBO) |
| 数据格式 | 工程文件用 JSON(只存参数); STEP 仅作为几何导出格式 |
| VTK | AnalysisWorkbench 采用 VTK 渲染,支持实体/线条(梁单元)双渲染模式 |
| 工作台 | 三工作台: Specification(规格管理) / Design(路由设计) / Analysis(应力分析) |
| 规模 | 海上平台, 5k-30k 构件 |
| 单位制 | 初期全公制(mm/deg/MPa),内部统一公制存储,单位转换后续增加 |
DocumentObject (基类: ID + name, 无坐标)
│
├─── SpatialObject (有坐标基类: + position:gp_Pnt)
│ ├── PipePoint 管点 (类型枚举 + PipeSpec引用 + 类型参数)
│ ├── Accessory 附属构件基类 (关联管点引用 + offset + subType)
│ │ ├── Support 支撑件 (subType: Rigid/Spring/Constant/Shoe/Trunnion/Guide)
│ │ ├── Restraint 限位件 (subType: Anchor/LineStop/Snubber)
│ │ ├── Flange 法兰 (subType: WeldNeck/SlipOn/Blind)
│ │ └── Gasket 密封件 (subType: SpiralWound/RingJoint)
│ └── Beam 梁 (双端管点引用 + 截面参数)
│
├─── PropertyObject (无坐标基类: 属性/配置文档)
│ ├── PipeSpec 管线特性 (可扩展字段: OD/WT/材料...)
│ ├── ProjectConfig 工程配置 (名称/作者/标准/单位制/重力方向)
│ ├── Load 载荷基类 (作用范围 + 载荷类型标识)
│ │ ├── DeadWeightLoad 自重载荷
│ │ ├── ThermalLoad 热载荷 (安装温度/操作温度)
│ │ ├── PressureLoad 压力载荷 (内压/外压)
│ │ ├── WindLoad 风载荷 (风速/方向)
│ │ ├── SeismicLoad 地震载荷 (加速度/方向)
│ │ ├── DisplacementLoad 位移载荷 (强制位移/转角)
│ │ └── UserDefinedLoad 自定义载荷 (集中力/力矩)
│ ├── LoadCase 基本工况 (一种物理状态下的载荷集合)
│ └── LoadCombination 组合工况 (按规范对基本工况进行代数运算)
│
└─── ContainerObject (无坐标: 组织容器, 关联起始管点)
├── Segment 段 (有序管点列表 + 段ID + 关联起始管点)
└── Route 路由 (段的树状集合 + 关联起始管点)
PipePoint {
id: UUID
name: string // "A00", "A06", "A06N", "A06M", "A06F"
position: gp_Pnt // 3D坐标 (仅位置,方向由相邻点推导)
type: enum // Run | Bend | Reducer | Tee | Valve | FlexJoint
pipeSpec: ref<PipeSpec> // 引用管线特性
// 类型特有参数 (variant/map存储)
// Bend: bendMultiplier (1.5 | 2.0 | 5.0)
// Valve: valveType, bodyDimensions
// FlexJoint: flexParams
// Run/Reducer/Tee: 无额外参数(由管点序列+PipeSpec推导)
}
A05 ----[A06N]---arc---[A06M]---arc---[A06F]---- A07
\ /
\ [A06] /
\ (交点,虚拟) /
\_________________/
A06 = 弯头管点(交点,前后管段中心线延长交点)
A06N = Near端(靠近A05)
A06M = Mid中点(弧中点)
A06F = Far端(靠近A07)
弯曲半径 R = OD × bendMultiplier
弯头角度 θ = π - angle(A05→A06, A06→A07)
关键特性: 弯头上的 4 个管点(A06/A06N/A06M/A06F)都是 可选择的 SpatialObject,各自拥有精确坐标。这对 AnalysisWorkbench 至关重要 — 弯头上的每个管点可以独立添加载荷(如在 A06N 上施加集中力、在 A06M 上挂载支撑等)。在 3D 视口中,这 4 个管点均可被拾取和高亮。
PipeSpec {
id: UUID
name: string // "6inch-Sch40"
fields: map<string, Variant> // 可扩展字段字典
// 初期核心字段:
// "OD" 外径 (mm)
// "wallThickness" 壁厚 (mm)
// "material" 材料名称 (string)
// "designPressure" 设计压力 (MPa) [可选]
// "designTemp" 设计温度 (℃) [可选]
}
ProjectConfig {
id: UUID
name: string // 工程名称
author: string
standard: string // "ASME B31.3"
unitSystem: enum // SI | Imperial
gravityAxis: enum // X | Y | Z
}
| 管点类型 | 几何推导方式 |
|---|---|
| Run | 圆柱壳(外径-内径), 长度=|pos[i+1]-pos[i]|, 方向=normalize(pos[i+1]-pos[i]) |
| Bend | 弯管壳, R=OD×mult, 角度由交点和前后方向计算, N/M/F由R和角度导出 |
| Reducer | 锥壳, 大端OD=前段PipeSpec.OD, 小端OD=后段PipeSpec.OD |
| Tee | 主管+支管融合, 主管OD=当前PipeSpec.OD, 支管OD=分支段PipeSpec.OD |
| Valve | 参数化阀体, 端口OD=PipeSpec.OD, 阀体尺寸由valveSubType+OD查ComponentCatalog模板生成 |
| FlexJoint | 波纹管壳, OD=PipeSpec.OD, 长度=两点距离 |
管道软件与通用 CAD 的核心差异:构件形状拓扑固定,尺寸按管径参数化缩放。不需要草图系统或 STEP 文件库,而是参数化生成规则的代码库。
ComponentCatalog (单例, Layer 4: engine/)
│
├── FittingTemplate (管线构件模板基类)
│ ├── PipeTemplate 直管 — 圆柱壳
│ ├── ElbowTemplate 弯头 — 弯管壳
│ ├── TeeTemplate 三通 — 主管+支管融合
│ ├── ReducerTemplate 异径管 — 锥壳
│ ├── ValveTemplate 阀门
│ │ ├── GateValve 闸阀 — 阀体+手轮
│ │ ├── GlobeValve 截止阀 — 球形阀体
│ │ ├── BallValve 球阀 — 扁圆阀体+操作手柄
│ │ ├── CheckValve 止回阀 — 非对称阀体
│ │ └── ButterflyValve 蝶阀 — 薄盘片阀体
│ └── FlangeTemplate 法兰
│ ├── WeldNeckFlange 对焘法兰
│ ├── SlipOnFlange 平焘法兰
│ └── BlindFlange 盲板法兰
│
└── AccessoryTemplate (附属构件模板基类)
├── SupportTemplate 支撑件
│ ├── RigidSupport 刚性支撑 — 简单底板+立柱
│ ├── SpringHanger 弹簧支吊架 — 弹簧体+吊杆
│ ├── ConstantHanger 恒力支吊架 — 恒力机构
│ ├── PipeShoe 管鞋/管托 — U形或鞍形
│ ├── Trunnion 耳轴 — 圆管+底板
│ └── Guide 导向 — 两侧挡板
├── RestraintTemplate 限位
│ ├── Anchor 锚固 — 全约束块
│ ├── LineStop 限位挡 — 单向/双向挡板
│ └── Snubber 减振器 — 液压缸体
└── GasketTemplate 密封
├── SpiralWound 缠绕垫片 — 扁环
└── RingJoint 环连接垫片 — 八角截面环
每个模板定义三样东西:
1. 参数表 — 由管径 OD 驱动,所有尺寸都是 OD 的函数:
struct ComponentParams {
double od; // 管外径 (mm) — 主驱动参数
double wallThickness; // 壁厚 (mm)
double bodyLength; // 构件长度
double bodyWidth; // 构件宽度
double bodyHeight; // 构件高度
// ... 各类型有不同参数
};2. 尺寸比例规则 — 保证任何管径下构件外观协调:
// 示例: 闸阀的参数化规则
ComponentParams GateValveTemplate::deriveParams(double od, double wt) {
return {
.od = od,
.wallThickness = wt,
.bodyLength = od * 1.2, // 阀体长 ≈ 1.2×OD
.bodyWidth = od * 1.5, // 阀体宽 ≈ 1.5×OD
.bodyHeight = od * 3.0, // 含手轮高 ≈ 3.0×OD
.stemDia = od * 0.15, // 阀杆直径
.handwheelDia = od * 1.8, // 手轮直径
};
}3. 固定拓扑的几何生成 — 形状拓扑写死,尺寸参数化:
class ComponentTemplate {
public:
virtual ~ComponentTemplate() = default;
virtual std::string templateId() const = 0;
virtual ComponentParams deriveParams(double od, double wt) const = 0;
virtual TopoDS_Shape buildShape(const ComponentParams& p) const = 0;
};class ComponentCatalog {
public:
static ComponentCatalog& instance();
void registerTemplate(std::unique_ptr<ComponentTemplate> tpl);
ComponentTemplate* getTemplate(const std::string& templateId) const;
std::vector<std::string> allTemplateIds() const;
};初始化时注册所有内置模板,GeometryDeriver 通过 templateId 查表获取对应模板:
用户操作: 插入闸阀 → PipePoint.type=Valve, valveSubType="GateValve"
→ GeometryDeriver: catalog.getTemplate("GateValve")
→ deriveParams(OD=168.3, WT=7.11)
→ buildShape(params) → TopoDS_Shape
→ OcctToVsg → 3D 渲染
现有的 *Builder 类是模板的雏形。演进路径:
| 现状 | 演进后 |
|---|---|
ValveBuilder::build(point, spec) — 生成一种阀门 |
ComponentCatalog::getTemplate("GateValve") → buildShape(params) |
AccessoryBuilder::build(acc) — 生成一种支撑 |
ComponentCatalog::getTemplate("SpringHanger") → buildShape(params) |
| 参数硬编码在 Builder 里 | 提取为独立的 deriveParams() 规则 |
| 无统一注册/查询机制 | ComponentCatalog 单例查表 |
GeometryDeriver 仍为统一入口,内部从 ComponentCatalog 获取模板而非硬编码分发。
| 方案 | 适合场景 | 本项目选择 |
|---|---|---|
| STEP 文件库 — 预存各种尺寸的 3D 文件 | 通用零件库(McMaster-Carr) | ✘ 组合爆炸 |
| 参数化代码模板 — 代码写死拓扑,运行时按 OD 生成 | PDS/PDMS/E3D 的标准做法 | ✔ 采用 |
| 脚本化模板 — DSL/Python 定义构件 | 需要用户自定义构件时 | 后续可扩展 |
┌─────────────────────────────────────────────┐
│ Layer 8: App Entry Layer │
│ (main.cpp / QML 注册 / 命令连线 / 启动) │
├─────────────────────────────────────────────┤
│ Layer 7: QML UI Layer │
│ (表格编辑器/属性面板/段树/3D视口) │
├─────────────────────────────────────────────┤
│ Layer 6: Application Layer │
│ (文档管理/命令栈/选择管理/工作台系统) │
├─────────────────────────────────────────────┤
│ Layer 5: Visualization Bridge │
│ (ViewManager/VSG场景/VTK场景/拾取/LOD) │
├─────────────────────────────────────────────┤
│ Layer 4: Pipeline Domain Engine │
│ (几何推导/路由拓扑/约束求解/校验) │
├─────────────────────────────────────────────┤
│ Layer 3: Document Model │
│ (PipePoint/PipeSpec/Segment/Route/ │
│ Accessory/Beam — 所有文档对象) │
├─────────────────────────────────────────────┤
│ Layer 2: Geometry Kernel Wrapper │
│ (OCCT封装: BRep/Mesh/STEP/Transform) │
├─────────────────────────────────────────────┤
│ Layer 1: Foundation │
│ (类型系统/ID/数学工具/日志/配置) │
└─────────────────────────────────────────────┘
Layer 3 是"文档模型层",构件不持有几何生成逻辑;Layer 4 "管道领域引擎"负责从管点序列+PipeSpec推导几何。VTK 可视化以 Layer 5b 支线存在,由 ViewManager 在工作台切换时路由。
| 工具 | 作用 | 来源 |
|---|---|---|
| pixi | 包管理/环境隔离 | prefix.dev |
| CMake 3.24+ | 构建配置 | pixi (conda-forge) |
| Ninja | 并行构建后端 | pixi (conda-forge) |
通用依赖通过 pixi (conda-forge) 安装。OCCT 和 VSG 使用本地预编译库 (lib/),后续自建 conda recipe 迁入 pixi。Vulkan 依赖由系统提供。
| 包名 | 来源 | 备注 |
|---|---|---|
cmake |
pixi ✅ | 构建工具 |
ninja |
pixi ✅ | 构建后端 |
qt6-main |
pixi ✅ | Qt6 Quick/QML |
nlohmann_json |
pixi ✅ | JSON 序列化 |
gtest |
pixi ✅ | 单元测试 |
vtk |
pixi ✅ | CAE 分析可视化 |
glslang + spirv-tools |
pixi ✅ | SPIR-V 着色器编译 |
| Vulkan | 系统 RPM | vulkan-loader / vulkan-headers 由操作系统提供 |
occt (8.0.0) |
本地 lib/occt/ | 后续迁入 pixi |
vsg (1.1.13) |
本地 lib/vsg/ | 后续迁入 pixi |
[workspace]
name = "qml-vsg-occt"
version = "0.1.0"
description = "Pipeline parametric modeling software"
channels = ["conda-forge"]
platforms = ["linux-64"]
[dependencies]
cmake = ">=3.24"
ninja = "*"
pkg-config = "*"
nlohmann_json = "*"
gtest = "*"
qt6-main = ">=6.5"
glslang = "*"
spirv-tools = "*"
vtk = ">=9.6"
# Vulkan 由系统提供 (vulkan-loader / vulkan-headers 在系统 RPM 中)
# OCCT & VSG — 使用本地 lib/, 后续迁入 pixi:
# occt = ">=8.0.0"
# vulkanscenegraph = ">=1.1.0"
[target.linux-64.dependencies]
xorg-libx11 = "*"
xorg-libxcb = "*"
[feature.dev.dependencies]
clang-format = "*"
ccache = "*"
[environments.default]
features = ["dev"]
[tasks]
configure-debug = { cmd = "cmake -B build/debug -G Ninja -DCMAKE_BUILD_TYPE=Debug ..." }
configure-release = { cmd = "cmake -B build/release -G Ninja -DCMAKE_BUILD_TYPE=Release ..." }
build-debug = { cmd = "cmake --build build/debug", depends-on = ["configure-debug"] }
build-release = { cmd = "cmake --build build/release", depends-on = ["configure-release"] }
test = { cmd = "ctest --test-dir build/debug --output-on-failure", depends-on = ["build-debug"] }
clean = { cmd = "rm -rf build/" }cmake_minimum_required(VERSION 3.24)
project(qml-vsg-occt VERSION 0.1.0 LANGUAGES C CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
# Ninja 下链接阶段串行,避免资源峰值
if(CMAKE_GENERATOR MATCHES "Ninja")
set_property(GLOBAL PROPERTY JOB_POOLS link_pool=1)
set(CMAKE_JOB_POOL_LINK link_pool)
endif()
# 本地预编译库 fallback (OCCT/VSG)
list(APPEND CMAKE_PREFIX_PATH
${PROJECT_SOURCE_DIR}/lib/occt
${PROJECT_SOURCE_DIR}/lib/vsg
)
# 第三方库
find_package(OpenCASCADE REQUIRED)
find_package(vsg REQUIRED)
find_package(Freetype REQUIRED)
find_package(VTK 9.6 COMPONENTS CommonCore CommonDataModel RenderingCore)
find_package(Qt6 REQUIRED COMPONENTS Quick Qml Test)
find_package(nlohmann_json REQUIRED)
# 源码
add_subdirectory(src)
# 测试
enable_testing()
add_subdirectory(tests)当前采用 src/lib + src/apps 双根拓扑,关键目标关系如下:
lib_base (INTERFACE)
└─ lib_platform_occt (STATIC)
└─ geometry (ALIAS)
pipecad_app_model (STATIC)
└─ pipecad_app_engine (STATIC)
└─ engine (ALIAS)
lib_platform_vtk (STATIC) + vtk_visualization (STATIC)
lib_platform_vsg (STATIC) ──depends on── engine + lib_platform_occt + vtk_visualization
lib_runtime (STATIC) ──depends on── engine + nlohmann_json
lib_framework (STATIC) ──depends on── lib_runtime + visualization(ALIAS)
pipecad_lib (INTERFACE) ──forwards── app(ALIAS -> lib_framework)
pipecad_app (STATIC) ──aggregates── model/engine/workbench/ui/resources
pipecad (EXE) ──links── pipecad_app
兼容目标 foundation/geometry/model/engine/visualization/app/ui/command 仍可链接,但仅作为迁移兼容壳;实际构建入口与新实现目录均在 src/lib 与 src/apps。
pixi install # 安装所有依赖
pixi run configure-debug # CMake配置(Debug)
pixi run build-debug # 编译
pixi run test # 运行测试
pixi shell # 进入pixi环境AI Agent 构建时需将输出重定向到临时日志文件,命令完成后再读取日志判断结果:
mkdir -p tmp_build_logs
pixi run build-debug > tmp_build_logs/build.log 2>&1
pixi run test > tmp_build_logs/test.log 2>&1
tail -50 tmp_build_logs/build.log
tail -50 tmp_build_logs/test.log
rm -rf tmp_build_logs已完成: VTK 已从 lib/vtk/ 迁入 pixi conda-forge (vtk = ">=9.6")。
待完成 (OCCT/VSG → pixi):
- 编写 conda recipe (rattler-build),从源码或预编译包构建
- 发布到自定义 channel 或 conda-forge
- pixi.toml 中取消注释
occt/vulkanscenegraph依赖 - CMakeLists.txt 删除
lib/occtlib/vsg的 fallback - 删除
lib/目录
类似 FreeCAD 的 Workbench 机制 — 每个工作台定义自己的工具栏、面板组合和 3D 渲染模式。切换工作台改变 UI 工具集、面板布局和视口引擎,但文档模型(Layer 3)共享。
| 内部类名 | 中文专业名 | 英文名 | name() 返回值 |
视口引擎 |
|---|---|---|---|---|
SpecWorkbench |
管线规格管理 | Piping Specification | "Specification" |
VSG (可选预览) |
DesignWorkbench |
管线路由设计 | Piping Routing & Layout | "Design" |
VSG (Vulkan) |
AnalysisWorkbench |
管道应力分析 | Pipe Stress Analysis | "Analysis" |
VTK |
命名说明:
- Specification 是管道行业通用术语(对应 ASME/EN 的 Piping Spec 概念),涵盖规范选择、参数管理、Pipe Class 定义
- Design 是主力建模工作台,"Piping Routing & Layout" 是行业标准说法
- Pipe Stress Analysis 是管道应力分析的行业标准叫法(CAESAR II、AutoPIPE、ROHR2 等均采用),管道行业做的是梁单元应力分析
// 视口引擎类型
enum class ViewportType { Vsg, Vtk };
// 渲染模式(仅 AnalysisWorkbench 使用)
enum class RenderMode { Solid, Beam };
// Workbench (abstract, Layer 6: app/)
class Workbench {
virtual string name() = 0;
virtual void activate(Document&) = 0;
virtual void deactivate(Document&) = 0;
virtual vector<ToolbarAction> toolbarActions() = 0;
virtual vector<string> panelIds() = 0;
virtual ViewportType viewportType() = 0; // VSG | VTK
};
// WorkbenchManager (Layer 6: app/)
class WorkbenchManager {
void registerWorkbench(unique_ptr<Workbench>);
bool switchWorkbench(string name);
Workbench* activeWorkbench();
vector<string> workbenchNames();
};管理行业规范、企业标准、自定义参数、管线等级(Pipe Class)定义。作为全系统的规则与参数源头。当前支持自定义参数,后续接入行业规范标准。
工具栏:
| ID | 标签 | 说明 |
|---|---|---|
new-spec |
新建规格 | 创建新的 Piping Spec(管线等级) |
import-code |
导入规范 | 从行业标准模板导入(预留) |
add-material |
添加材料 | 管材、法兰、弯头等材料条目 |
add-component |
添加元件 | 管件元件定义(壁厚、压力等级等) |
validate |
校验 | 检查规格完整性与一致性 |
面板:
panelIds = { "SpecTree", "MaterialTable", "ComponentTable", "PropertyPanel" }
viewportType = Vsg // 可选:无视口或简单预览
基于已选规范与参数进行三维建模。这是主力建模工作台。
工具栏: 由 TopBar 区域显示,切换工作台时自动更换。
| ID | 标签 | 说明 |
|---|---|---|
new-segment |
新建段 | 新建管线段 |
add-point |
添加管点 | 添加路由控制点 |
measure |
测量 | 距离/角度测量 |
export-step |
STEP 导出 | 几何导出 |
面板:
panelIds = { "DesignTree", "Viewport3D", "ComponentToolStrip", "ParameterPanel" }
viewportType = Vsg
布局 (详见 §9.2):
┌────────────────────────────────────────────────────────────────────────────┐
│ TopBar (工作台切换 / 工具栏) │
├─────────┬──────────────────────────────────────────┬───┬───┬──────────────┤
│ │ │ │ │ │
│ 设计树 │ │ F │ A │ 参数化面板 │
│ Design │ 3D 视口 (VSG) │ i │ c │ Parameter │
│ Tree │ │ t │ c │ Panel │
│ │ │ t │ e │ │
│ ◄ 可 │ │ i │ s │ 可 ► │
│ 折叠 │ │ n │ s │ 折叠 │
│ │ │ g │ o │ │
│ │ │ s │ r │ │
│ │ │ │ y │ │
├─────────┴──────────────────────────────────────────┴───┴───┴──────────────┤
│ StatusBar │
└────────────────────────────────────────────────────────────────────────────┘
- 设计树 (DesignTree): 左侧,可折叠(←向左收起),展示管线段、管点、管件、附属构件的层级关系
- 参数化面板 (ParameterPanel): 右侧固定,可折叠(→向右收起),包含管点表格、属性编辑
- 元件插入工具条 (ComponentToolStrip): 贴在参数化面板内侧(靠近视口一侧)的双列图标条
- 设计树与参数化面板独立折叠/展开,折叠后视口自动填满
两列图标条,贴在参数化面板靠近视口一侧的边缘。每种元件一个独立的插入按钮。
Fittings 列(管件) — 紧贴视口侧:
| 图标 ID | 说明 |
|---|---|
insert-pipe |
插入直管段 |
insert-elbow |
插入弯头 |
insert-tee |
插入三通 |
insert-reducer |
插入异径管(大小头) |
insert-valve |
插入阀门 |
insert-flange |
插入法兰 |
Accessories 列(附属构件) — 紧贴参数化面板内容区:
| 图标 ID | 说明 |
|---|---|
insert-rigid-support |
刚性支撑/锚固 |
insert-spring-hanger |
弹簧支吊架 |
insert-guide |
导向约束 |
insert-restraint |
位移限位器 |
insert-beam |
结构梁/支架 |
Fittings 列始终紧贴视口(插入管件频率更高),Accessories 列始终紧贴参数化面板内容区。
在路由设计完成后进入,定义载荷、组合工况、执行分析与失效评估。使用 VTK 渲染引擎。
工具栏:
| ID | 标签 | 说明 |
|---|---|---|
toggle-render-mode |
切换渲染模式 | 实体 ↔ 线条(梁单元) |
add-load |
添加载荷 | 选择载荷类型并添加 |
manage-loadcase |
工况管理 | 创建/编辑/删除工况组合 |
run-analysis |
执行分析 | 运行当前工况计算(未来) |
show-results |
查看结果 | 应力/位移云图(未来) |
面板:
panelIds = { "AnalysisTree", "VtkViewport", "LoadTable", "LoadCaseTable", "PropertyPanel" }
viewportType = Vtk
| 模式 | 显示内容 | 技术实现 |
|---|---|---|
| 实体模式 (Solid) | 与设计工作台相同的 3D 管件外观 | VTK vtkPolyData + Surface 渲染,数据来自 OCCT→VTK 网格转换 |
| 线条模式 (Beam) | 管系中心线 + 节点(梁单元网格) | VTK vtkPolyLine,数据来自管路拓扑中心线提取 |
线条模式是管道应力分析的标准视图 — 每个管件对应一个或多个梁单元,节点在管件连接点上。
实体模式:
OCCT TopoDS_Shape → ShapeMesher::mesh() → MeshData → vtkPolyData → vtkActor (Surface)
线条模式:
管路拓扑 → 提取中心线端点 → vtkPoints + vtkCellArray(Lines) → vtkPolyData → vtkActor
+ 节点标记: vtkSphereSource / vtkGlyph3D → vtkActor
+ 约束/载荷符号: vtkGlyph3D 叠加显示
渲染模式切换通过 vtkActor 可见性控制,不需重建场景:
class AnalysisWorkbench : public Workbench {
void setRenderMode(RenderMode mode);
RenderMode renderMode() const;
};- TopBar 显示工作台切换标签(Specification / Design / Analysis)
- 切换时:
WorkbenchManager::switchWorkbench()→ 旧工作台deactivate()+ 新工作台activate()→workbenchChanged信号 → QML 根据panelIds()动态加载/卸载面板,根据toolbarActions()更换工具栏 - Document 在工作台之间共享
┌──────────────┐ PipingSpec ┌──────────────┐ Topology+Geometry ┌──────────────┐
│ Specification │ ───────────────→ │ Design │ ────────────────────→ │ Analysis │
│ Workbench │ 材料/壁厚/压力等级│ Workbench │ 管路拓扑+3D几何 │ Workbench │
└──────────────┘ └──────────────┘ └──────────────┘
- SpecWorkbench 写入:
PipeSpec对象(材料、元件、压力等级表) - DesignWorkbench 读取 PipeSpec,写入:
PipeSegment/PipePoint/附属构件拓扑 - AnalysisWorkbench 读取拓扑+几何,写入:
Load/LoadCase/LoadCombination
载荷(Load)、基本工况(LoadCase)、组合工况(LoadCombination) 均为 DocumentObject 子类,完整纳入文档基础设施(UUID、属性系统、Undo/Redo、序列化、依赖图)。
class Load : public DocumentObject {
public:
virtual std::string loadType() const = 0; // 类型标识,用于序列化/UI
virtual std::vector<UUID> affectedObjects() const; // 作用到哪些管段/管点
void addAffectedObject(const UUID& id);
void removeAffectedObject(const UUID& id);
protected:
std::vector<UUID> affectedObjectIds_;
};| 子类 | loadType | 特有参数 | 说明 |
|---|---|---|---|
DeadWeightLoad |
"DeadWeight" |
(无) | 自重由管件材料密度+壁厚自动计算 |
ThermalLoad |
"Thermal" |
installTemp, operatingTemp (°C) |
热膨胀载荷 |
PressureLoad |
"Pressure" |
pressure (MPa), isExternal (bool) |
内压/外压 |
WindLoad |
"Wind" |
speed (m/s), direction (Vec3) |
风载荷 |
SeismicLoad |
"Seismic" |
acceleration (g), direction (Vec3) |
地震载荷 |
DisplacementLoad |
"Displacement" |
translation (mm), rotation (deg) (Vec3) |
锚点强制位移 |
UserDefinedLoad |
"UserDefined" |
force (N), moment (N·mm) (Vec3) |
自定义集中力/力矩 |
一个基本工况 = 一种物理运行状态下的载荷集合。每个基本工况对应求解器的一次独立运算。
struct LoadEntry {
UUID loadId; // 指向 Load 文档对象
double factor; // 组合系数(通常 1.0)
};
class LoadCase : public DocumentObject {
std::string caseName_; // "W", "T1", "P1" 等
std::vector<LoadEntry> entries_;
};示例:
| 基本工况 | 包含载荷 |
|---|---|
| W(自重) | DeadWeightLoad × 1.0 |
| T1(热态1) | ThermalLoad(150°C) × 1.0 |
| P1(内压) | PressureLoad(2.5MPa) × 1.0 |
| WN(北风) | WindLoad(+X, 30m/s) × 1.0 |
| EQ(地震) | SeismicLoad(0.3g, +Z) × 1.0 |
组合工况 = 按规范公式将多个基本工况的结果进行代数运算,用于规范校核。
enum class CombineMethod {
Algebraic, // 代数叠加: Σ(factor × result)
Absolute, // 绝对值叠加: Σ|factor × result|(保守)
SRSS, // 平方和开方: √Σ(result²)(不相关载荷,如地震)
Envelope // 包络: max/min across cases
};
enum class StressCategory {
Sustained, // 持续应力 (SUS) — 自重+内压,不得超过 Sh
Expansion, // 热膨胀应力 (EXP) — 热态-冷态,不得超过 Sa
Occasional, // 偶然应力 (OCC) — 持续+风/地震,不得超过 1.33Sh
Operating, // 操作工况 (OPE) — 检查位移和力
Hydrotest // 水压试验 (HYD)
};
struct CaseEntry {
UUID caseId; // 指向 LoadCase
double factor; // 组合系数
};
class LoadCombination : public DocumentObject {
std::string comboName_;
StressCategory category_;
CombineMethod method_;
std::vector<CaseEntry> caseEntries_;
};示例(B31.3 典型配置):
| 组合工况 | 类别 | 方法 | 公式 |
|---|---|---|---|
| SUS | Sustained | Algebraic | W×1.0 + P1×1.0 |
| EXP | Expansion | Algebraic | T1×1.0 |
| OPE | Operating | Algebraic | W×1.0 + T1×1.0 + P1×1.0 |
| OCC1 | Occasional | Algebraic | W×1.0 + P1×1.0 + WN×1.0 |
| OCC2 | Occasional | SRSS | W×1.0 + P1×1.0 + EQ×1.0 |
| 方面 | 说明 |
|---|---|
| 依赖关系 | 严格 DAG: LoadCombination → LoadCase → Load → 管段/管点 |
| 求解分离 | 基本工况 = 独立求解任务,组合工况 = 后处理代数运算 |
| 规范校核 | StressCategory 自然属于 Combination 层 |
| 组合方法 | Algebraic/SRSS/Envelope 只在 Combination 层,语义清晰 |
| 复用 | 一个 LoadCase 可被多个 Combination 引用 |
📁 载荷 (Loads)
├── 🏋 自重 (DeadWeight)
├── 🌡 热载荷-操作 (Thermal 150°C)
├── 📐 内压 (Pressure 2.5MPa)
├── 💨 风载荷-北风 (Wind +X)
└── 🌊 地震 (Seismic 0.3g)
📁 基本工况 (Load Cases)
├── W — 自重
├── T1 — 热态操作
├── P1 — 内压
├── WN — 北风
└── EQ — 地震
📁 组合工况 (Combinations)
├── SUS — 持续应力 [W+P1]
├── EXP — 热膨胀应力 [T1]
├── OPE — 操作工况 [W+T1+P1]
├── OCC1 — 偶然-风 [W+P1+WN]
└── OCC2 — 偶然-地震 [W+P1+EQ] (SRSS)
全部纳入现有 DependencyGraph,支持级联重算:
| 触发变更 | 标脏范围 |
|---|---|
| Load 参数修改 | 引用该 Load 的所有 LoadCase |
| LoadCase 修改 | 引用该 LoadCase 的所有 LoadCombination |
| 管段/管点坐标修改 | 作用于此管段的所有 Load |
| PipeSpec 修改 | 影响的管点 → 关联的 Load |
- 单步操作=单命令: 每个用户编辑动作封装为一个
Command,对应一次 Undo/Redo 步骤 - 命令完成后统一重算:
CommandStack在执行、撤销、重做后发射 affectedIds,主线程统一触发脏传播与重算 - 支持宏命令: 复合操作通过
MacroCommand/CommandStack::openMacro()聚合为单个历史项 - 单线程: 所有文档变更与重算均在主线程执行
- 会话级: 命令历史仅在内存中,关闭应用后丢失;保存状态由
CommandStack::markClean()追踪
┌──────────────────────────────────────────────┐
│ CommandStack (Layer 6: app/) │
│ execute() / undo() / redo() / openMacro() │
│ commandCompleted / commandUndone / │
│ commandRedone / sceneRemoveRequested │
├──────────────────────────────────────────────┤
│ CommandRegistry + CommandContext │
│ 工厂注册 / 序列化 / 运行时依赖注入 │
├──────────────────────────────────────────────┤
│ DependencyGraph (Layer 6: app/) │
│ markDirty() / collectDirty() / clearDirty() │
├──────────────────────────────────────────────┤
│ RecomputeEngine (Layer 4: engine/) │
│ 收集脏对象 → 拓扑排序 → 批量重算Shape │
│ → SceneManager/VtkSceneManager 更新场景 │
└──────────────────────────────────────────────┘
1. UI 创建 `SetPropertyCommand` / `BatchSetPropertyCommand` / 结构命令
2. `Application::createCommandContext()` 构建 `CommandContext{document, dependencyGraph, topologyManager}`
3. `CommandStack::execute(cmd, ctx)` 执行命令,命令返回 `affectedIds` / `deletedIds`
4. `commandCompleted` 信号触发主线程重算处理器
5. 重算处理器执行:
├─ `DependencyGraph::markDirty(affectedIds)`
├─ `collectDirty()` 收集受影响对象
├─ `RecomputeEngine::recompute(dirtyIds)` 批量重算几何
├─ `SceneManager` / `VtkSceneManager` 更新节点
└─ `clearDirty()` 清空脏标记
undo(): `CommandStack::undo(ctx)` → `commandUndone(affectedIds)` → 标脏 → recompute()
redo(): `CommandStack::redo(ctx)` → `commandRedone(affectedIds)` → 标脏 → recompute()
| 触发变更 | 标脏范围 |
|---|---|
| PipeSpec 字段修改 | 所有引用该 PipeSpec 的 PipePoint |
| PipePoint 坐标修改 | 前后相邻 PipePoint |
| PipePoint 类型修改 | 自身 + 前后相邻 PipePoint |
| PipePoint 增删 | 所在 Segment 重组 + 关联 Accessory/Beam |
| Accessory 参数修改 | 仅自身 |
qml-vsg-occt/
├── CMakeLists.txt
├── pixi.toml
├── .github/ # AI 辅助开发配置
│ ├── instructions/ # VS Code Copilot 自动注入编码规范
│ └── prompts/ # 任务启动 slash 命令
├── src/
│ ├── CMakeLists.txt
│ ├── lib/ # 唯一架构构建入口
│ │ ├── base/
│ │ │ ├── foundation/
│ │ │ └── baseMod/
│ │ ├── platform/
│ │ │ ├── occt/geometry/
│ │ │ ├── vsg/visualization/
│ │ │ └── vtk/vtk-visualization/
│ │ ├── runtime/
│ │ │ ├── app/
│ │ │ ├── command/
│ │ │ ├── task/
│ │ │ └── runtimeMod/
│ │ └── framework/
│ │ ├── app/
│ │ └── frameworkMod/
│ ├── apps/ # 唯一业务构建入口
│ │ ├── CMakeLists.txt # PIPECAD_APP_LIST + 注册模板
│ │ └── pipecad/
│ │ ├── model/
│ │ ├── engine/
│ │ ├── workbench/
│ │ ├── ui/
│ │ ├── resources/
│ │ └── main.cpp
│ ├── foundation/ # 迁移期历史镜像(非构建入口)
│ ├── geometry/ # 迁移期历史镜像(非构建入口)
│ ├── model/ # 迁移期历史镜像(非构建入口)
│ ├── engine/ # 迁移期历史镜像(非构建入口)
│ ├── visualization/ # 迁移期历史镜像(非构建入口)
│ ├── vtk-visualization/ # 迁移期历史镜像(非构建入口)
│ ├── app/ # 迁移期历史镜像(非构建入口)
│ ├── command/ # 迁移期历史镜像(非构建入口)
│ ├── ui/ # 迁移期历史镜像(非构建入口)
│ └── main.cpp # 旧入口(保留,非构建入口)
├── docs/ # 项目文档
│ ├── architecture.md # 本文件
│ ├── tasks/ # 任务状态与交接
│ └── archive/ # 已完成阶段归档
├── ui/main.qml # QML 入口
└── tests/ # 全量测试(当前基线 46)
- 可执行入口为
src/apps/pipecad/main.cpp,构建产物为build/<type>/src/apps/pipecad/pipecad - 启动阶段创建
QGuiApplication,注册 QML 类型并初始化Application - 通过
CommandRegistry注册命令工厂,连线CommandStack与异步重算回调 - 配置
ViewManager,在 Design 工作台使用 VSG,在 Analysis 工作台切换 VTK - 加载
ui/main.qml并进入事件循环
现代 SaaS 风格(类似 Fusion 360 / Onshape) — 浅色主题, 简洁干净, 3D 视口为主体, 面板可收缩, 低密度留白。
┌──────────────────────────────────────────────────────────────┐
│ [≡] 工程名称 [工具栏图标组] [⚙] │ ← TopBar (48px)
├────────┬─────────────────────────────┬───────────────────────┤
│ │ │ │
│ 结构树 │ 3D 视口 (VSG) │ 管点表格 (可编辑) │
│(可收缩) │ │ (可收缩) │
│ │ │ │
│ 240px │ ← 视口主导最大面积 → │ ──────────────── │
│ │ │ 属性面板 (动态) │
│ │ │ 320px │
├────────┴─────────────────────────────┴───────────────────────┤
│ A06 Bend (3500.0, 1200.0, 0.0) [鼠标3D坐标] [缩放] │ ← StatusBar
└──────────────────────────────────────────────────────────────┘
- 视口主导: 3D 视口占中央最大面积
- 左右分割: 左=3D视口, 右=管点表格+属性面板, 可拖动分割条
- 左侧结构树: 可收缩, Route/Segment/PipeSpec/附属构件/梁树形展示
- 右侧上部: 管点表格 (Name|X|Y|Z|Type|PipeSpec),可直接编辑单元格
- 右侧下部: 属性面板,根据选中对象类型动态显示不同属性
- Bend 子点: N/M/F 行灰色背景只读,标记(auto)
右侧下部属性面板。根据选中对象类型动态渲染不同字段分组。
触发方式:
- 左键选中管点(3D/表格/树) → 面板内容切换
- 右键菜单→"查看管点属性" → 面板已展开时高亮闪烁动画提醒;面板已收缩时自动展开
面板结构 (以 Bend 管点 A06 为例):
┌─ PropertyPanel ──────────────────┐
│ 🔵 A06 Bend │ ← 标题: 名称+类型图标
│ ─────────────────────────────── │
│ ▸ 坐标 │ ← 分组1, 可折叠
│ X [ 3500.0 ] mm │
│ Y [ 1200.0 ] mm │
│ Z [ 0.0 ] mm │
│ ▸ 管线特性 │ ← 分组2
│ PipeSpec [ 6inch-Sch40 ▾] │
│ OD 168.3 mm │ 只读(来自PipeSpec)
│ 壁厚 7.11 mm │
│ 材料 A106-B │
│ ▸ Bend 参数 │ ← 分组3, 仅Bend可见
│ 弯曲倍数 [ 1.5D ▾] │
│ 弯曲半径 252.45 mm │ 只读(=OD×倍数)
│ 弯曲角度 90.0° │ 只读
│ ▸ 计算点 │ ← 分组4, 仅Bend可见
│ A06N (3247.6, 1200.0, 0.0) │ 只读
│ A06M (3320.8, 1072.4, 0.0) │
│ A06F (3500.0, 947.6, 0.0) │
│ ▸ 关联 │ ← 分组5
│ 所在段 Segment A │ 点击跳转
│ 前管点 A05 (Run) │
│ 后管点 A07 (Run) │
│ 附属构件 Flange-01 │
├──────────────────────────────────┤
│ [🔒 只读模式] / [✏️ 编辑模式] │ ← 模式切换按钮
└──────────────────────────────────┘
各管点类型分组差异:
| 类型 | 坐标 | 管线特性 | 类型参数 | 计算点 | 关联 |
|---|---|---|---|---|---|
| Run | ✅编辑 | ✅PipeSpec选择 | (无) | (无) | ✅ |
| Bend | ✅编辑(交点) | ✅PipeSpec选择 | 弯曲倍数(下拉) | N/M/F只读 | ✅ |
| Reducer | ✅编辑 | ✅PipeSpec选择 | (无) | 大端/小端OD只读 | ✅ |
| Tee | ✅编辑 | ✅PipeSpec选择 | 支管段引用(只读) | (无) | ✅+分支段 |
| Valve | ✅编辑 | ✅PipeSpec选择 | 阀门类型(下拉)+参数 | (无) | ✅ |
| FlexJoint | ✅编辑 | ✅PipeSpec选择 | 柔性参数 | (无) | ✅ |
编辑规则:
- 参数化面板最下方有一个 模式切换按钮,在“🔒 只读模式”和“✏️ 编辑模式”之间切换
- 编辑模式: 可编辑字段显示输入框/下拉菜单,修改即走命令(SetProperty/BatchSetProperty → CommandStack → recompute)
- 只读模式: 所有字段均为只读显示,无输入框,仅供查看
- 右键菜单“修改”自动切换到编辑模式,“查看”自动切换到只读模式
- 默认打开时为编辑模式
- 只读字段(如计算值、自动推导值)在两种模式下始终显示为灰色 #888888 无边框,带(auto)标签
- 关联项点击跳转联动,不受模式影响
| 属性 | 值 |
|---|---|
| 主背景 | #F5F5F5 |
| 面板背景 | #FFFFFF (白色卡片) |
| 3D视口背景 | 渐变 #E8E8E8 → #D0D0D0 |
| 强调色 | #0078D4 (蓝色) |
| 文字主色 | #333333 |
| 文字次色 | #888888 (只读/计算值) |
| 分割线 | #E0E0E0 |
| 圆角 | 6px |
| 表格行高 | 36px |
| 图标 | 线性风格, 1.5px 笔触 |
| 操作 | 响应 |
|---|---|
| 树节点选中 | 表格滚动到对应行 + 3D 视口高亮(管点:红色+, 管段:几何体高亮) |
| 表格行选中 | 树展开到对应节点 + 3D 视口高亮 |
| 3D 视口拾取(管点) | 树+表格同步选中该管点行,属性面板显示管点属性 |
| 3D 视口拾取(管段) | 树+表格同步选中该管段两端管点,属性面板显示管段信息 |
| 表格编辑坐标 | 命令执行→recompute→3D 视口实时更新 |
| 属性面板修改 PipeSpec | 命令执行→recompute→所有引用管点 3D 更新 |
- TopBar: 左侧工作台切换标签(Specification|Design|Analysis),当前活跃高亮。右侧工具栏图标组由活跃工作台的
toolbarActions()决定,切换工作台时工具栏自动更换。文件操作(新建/打开/保存/撤销/重做)始终可见。 - StatusBar: 分三个区域——
- 左区 (选中对象信息): 显示当前选中管件的
名称 类型 (X, Y, Z),例如A06 Bend (3500.0, 1200.0, 0.0);无选中时显示管点数/段数/路由数统计信息 - 中区 (鼠标3D坐标): 实时显示鼠标在3D视口中悬停位置的世界坐标
- 右区 (缩放比例): 当前视口缩放级别
- 左区 (选中对象信息): 显示当前选中管件的
| 操作 | 功能 |
|---|---|
| 滚轮滚动 | 缩放 (以鼠标位置为中心) |
| 滚轮按下拖动 | 平移视图 (Pan) |
| 左键点击 | 选择对象 (管点选择 或 管段选择,见下方规则) |
| 左键拖动框选 | 框选 (方向决定模式,见 §10.1.2) |
| Ctrl + 左键拖动 | 轨道旋转 (Orbit) |
| 右键点击 | 弹出上下文菜单 |
左键点击拾取后,PickHandler 根据点击位置与最近管点的距离决定选择类型:
点击位置 P → LineSegmentIntersector → 命中几何体
→ 反查所属管件,找到该管件两端管点
→ 计算 P 到最近管点的屏幕距离 d
→ if d < 阈值 (e.g. 20px): 管点选择
else: 管段选择
| 选择类型 | 触发条件 | 高亮表现 | SelectionManager 存储 |
|---|---|---|---|
| 管点选择 | 点击位置距管点 < 阈值 | 管点处显示 红色十字标记 (+) | {type: Point, id: pointUUID} |
| 管段选择 | 点击位置距管点 ≥ 阈值 | 整个管段几何体高亮(发光/边框) | {type: Segment, startId, endId} |
管点选择的红色十字标记:
- 在管点 3D 坐标处叠加一个十字形 Glyph(两条正交短线),颜色 #FF0000
- 尺寸与管径成比例(约 0.8×OD),保证不同管径下视觉协调
- 始终面向相机(billboard),任何角度都清晰可见
弯头管点的选择: 弯头上的 4 个管点(A06/N/M/F)均可独立拾取。点击弯头几何体时,找最近的弯头管点;距离小于阈值则选中该管点,否则选中整个弯头管段。
// SelectionManager 选择模型
enum class SelectionType { Point, Segment };
struct Selection {
SelectionType type;
UUID primaryId; // 管点选择: 管点ID; 管段选择: 起始管点ID
UUID secondaryId; // 管段选择: 终止管点ID; 管点选择: 无效
};左键拖动(非 Ctrl)形成矩形框选区域,拖动方向决定选择严格程度:
| 拖动方向 | 框线样式 | 选择逻辑 | 行业术语 |
|---|---|---|---|
| 左上→右下(正向) | 实线框 | 对象必须完全在框内才被选中 | Window 选择 |
| 右下→左上(反向) | 虚线框 | 对象部分在框内即被选中 | Crossing 选择 |
正向框选 (Window): 反向框选 (Crossing):
┌─────────────┐ ╭┄┄┄┄┄┄┄┄┄┄┄┄┄╮
│ ○ 选中 │ ┆ ○ 选中 ┆
│ ● 选中 │ 实线框 ┆ ●━━━选中 ┆ 虚线框
│ ◆━━━│━ 不选中 ┆ ◆━━━┆━ 选中(部分在框内)
└─────────────┘ ╰┄┄┄┄┄┄┄┄┄┄┄┄┄╯
判定规则:
- Window: 管点的屏幕投影在框内 → 管点选中;管段两端管点均在框内 → 管段选中
- Crossing: 管点的屏幕投影在框内 → 管点选中;管段几何体的屏幕投影与框相交或被包含 → 管段选中
- 框选结果为多选,所有命中对象同时高亮(管点显示红色+,管段显示高亮边框)
- 框选追加: Shift + 框选追加到已有选择集;不按 Shift 则替换
这与 AutoCAD/SolidWorks 的框选行为一致,管道设计师无学习成本。
3D 视口右上角视图切换按钮组,点击后相机平滑过渡(200ms ease-in-out):
| 视图 | 相机方向 | 快捷键 |
|---|---|---|
| 正视图 (Front) | -Y → +Y (XZ平面) | Numpad 1 |
| 右视图 (Right) | +X → -X (YZ平面) | Numpad 3 |
| 俯视图 (Top) | -Z → +Z (XY平面) | Numpad 7 |
| 轴测图 (Isometric) | (-1,-1,-1)→原点, 45°仰角 | Numpad 0 |
| Fit All | 自动包围全部对象 | F |
实现: CameraController::setViewPreset(ViewPreset) → 计算目标 LookAt → vsg::LookAt 插值动画
选中管件或管点后右键弹出,菜单项:
| 菜单项 | 快捷键 | 可用条件 | 行为 |
|---|---|---|---|
| 修改 | Enter | 有选中对象 | 参数化面板若已折叠则自动展开,属性面板进入编辑模式并聚焦到选中对象 |
| 查看 | — | 有选中对象 | 参数化面板若已折叠则自动展开,属性面板以只读模式显示选中对象信息 |
| 删除 | Delete | 有选中对象 | 弹出确认对话框,确认后执行删除命令 |
参数化面板弹出逻辑:
- 选择"修改"或"查看"时,如果右侧参数化面板已折叠 → 自动展开
- 如果参数化面板已展开 → 属性面板区域闪烁动画提醒用户注意
- 无选中对象时,三个菜单项均灰色禁用
ViewManager 是 Layer 5 的统一门面,协调 VSG 与 VTK 双视口的渲染与交互,上层 QML/工具栏只与 ViewManager 交互,无需感知底层渲染引擎。
架构定位:
WorkbenchManager ──切换通知──► ViewManager ──委派──► VSG Viewport (SceneManager + CameraController + SceneFurniture)
│ VTK Viewport (VtkSceneManager + VtkRenderer)
│
QML / ToolBar
管理状态:
class ViewManager {
public:
// === 视口路由 ===
enum class ActiveViewport { VSG, VTK };
void setActiveViewport(ActiveViewport vp); // 工作台切换时调用
ActiveViewport activeViewport() const;
// === 相机控制 (转发给当前活跃视口) ===
void fitAll();
void setViewPreset(ViewPreset preset); // Front/Right/Top/Iso
void saveViewState(const std::string& workbenchId);
void restoreViewState(const std::string& workbenchId);
// === 渲染模式 ===
enum class RenderMode { Solid, Wireframe, SolidWithEdges, Beam };
void setRenderMode(RenderMode mode);
RenderMode renderMode() const;
// === 可见性控制 ===
enum class Category { PipePoints, Segments, Accessories, Supports, Beams,
Annotations, LoadArrows, StressContour };
void setCategoryVisible(Category cat, bool visible);
bool isCategoryVisible(Category cat) const;
// === 场景装饰 ===
void setGridVisible(bool visible);
void setTriadVisible(bool visible);
void setAnnotationsVisible(bool visible);
// === LOD ===
enum class LodLevel { Draft, Normal, Fine };
void setLodLevel(LodLevel level);
LodLevel lodLevel() const;
// === StatusBar 数据 ===
gp_Pnt currentMouseWorldPos() const; // 鼠标 3D 坐标
// === 截图 ===
bool captureImage(const std::string& path); // 离屏渲染截图
private:
ActiveViewport activeVp_ = ActiveViewport::VSG;
RenderMode renderMode_ = RenderMode::Solid;
LodLevel lodLevel_ = LodLevel::Normal;
std::map<Category, bool> visibility_;
std::map<std::string, CameraState> viewStateCache_; // workbenchId → 相机状态
// 非拥有指针
SceneManager* vsgScene_ = nullptr;
CameraController* vsgCamera_ = nullptr;
SceneFurniture* vsgFurni_ = nullptr;
VtkSceneManager* vtkScene_ = nullptr;
};工作台切换流程:
WorkbenchManager::switchTo("Analysis")
→ ViewManager::saveViewState("Design") // 保存当前相机
→ ViewManager::setActiveViewport(VTK) // 切换活跃视口
→ ViewManager::restoreViewState("Analysis") // 恢复目标相机
→ ViewManager::setRenderMode(Beam) // Analysis 默认 Beam 模式
→ QML 层切换 Viewport3D.qml ↔ VtkViewport.qml
与各底层类的分工:
| 职责 | 负责类 | ViewManager 角色 |
|---|---|---|
| 场景图节点增删 | SceneManager / VtkSceneManager |
不涉及 |
| 相机数学(lookAt/投影) | CameraController |
转发 fitAll/setViewPreset |
| 坐标轴/网格/背景 | SceneFurniture |
转发显隐开关 |
| LOD 网格精度 | LodStrategy |
设置全局精度档位 |
| 3D 拾取 | PickHandler |
不涉及 |
| VSG↔VTK 视口切换 | — | ViewManager 独有 |
| 渲染模式/可见性 | — | ViewManager 独有 |
| 视图状态缓存 | — | ViewManager 独有 |
| 截图/离屏渲染 | — | ViewManager 独有 |
| 元素 | 描述 | 实现 |
|---|---|---|
| 坐标轴指示器 | 视口左下角 XYZ 小坐标轴 | vsg::MatrixTransform, 固定屏幕位置 |
| 地面网格 | XY 平面参考网格, 可开关 | vsg::Group + LineStrip |
| 渐变背景 | #E8E8E8 → #D0D0D0 | vsg::ClearValue / 全屏四边形 |
| Fit All | 自动调整相机包围全部对象 | ComputeBounds → 调整 vsg::LookAt |
- 单文档模式: 一次只打开一个工程文件
- 窗口标题:
{工程名} - PipeCAD/{工程名}* - PipeCAD(未保存修改加 *) - 键盘快捷键: Ctrl+S 保存 / Ctrl+Z 撤销 / Ctrl+Y 重做 / Ctrl+N 新建 / Ctrl+O 打开 / Delete 删除选中
Step 1 — 构建系统
pixi.toml: 声明所有依赖、平台、构建任务CMakeLists.txt(根): find_package, C++17, AUTOMOC/AUTORCCsrc/CMakeLists.txt: 各层子目录 targettests/CMakeLists.txt: GTest 测试.gitignore: build/, .pixi/- 验证:
pixi install+pixi run configure-debug+pixi run build-debug通过
Step 2 — Foundation 层
Types.h: UUID, Variant(double|int|string|enum), 单位枚举Math.h: 角度/弧度, 向量运算, 两线交点计算
Step 3 — Geometry Kernel Wrapper
ShapeBuilder: BRepPrimAPI_MakeCylinder/MakeTorus/MakeCone, BRepOffsetAPI_MakePipeShellShapeMesher: BRepMesh_IncrementalMesh → Poly_TriangulationBooleanOps: BRepAlgoAPI_Cut/FuseStepIO: STEPControl_Reader/WriterShapeTransform: gp_Trsf + BRepBuilderAPI_Transform
Step 4 — 核心文档对象
DocumentObject/SpatialObject/PropertyObject/ContainerObjectPipePoint/PipeSpec/ProjectConfig/Segment/Route
Step 5 — 附属对象
Accessory基类 + FixedPoint / Support / Flange / Gasket / SealRingBeam: 双端管点引用 + 截面参数
Step 6 — 弯头几何计算器 (核心算法)
BendCalculator: (A05, A06交点, A07, OD, bendMultiplier) → (N/M/F坐标 + 弯弧中心 + 起止角)
Step 7 — 各类型几何生成器
- RunBuilder / ReducerBuilder / TeeBuilder / ValveBuilder / FlexJointBuilder / BeamBuilder / AccessoryBuilder
GeometryDeriver: 统一入口, 按管点类型分发
Step 8 — 拓扑管理
TopologyManager: 段间关系, 管点增删时维护一致性
Step 9 — 约束求解与校验
ConstraintSolver: 端口口径匹配, Bend 角度约束PipelineValidator: 干涉检测, 未连接端口警告
Step 10 — OCCT→VSG 网格转换
OcctToVsg: TopoDS_Shape → BRepMesh → Poly_Triangulation → vsg::VertexIndexDraw
Step 11 — 场景管理
SceneManager: 文档对象 ID→VSG 节点映射, 增量更新PipePointNode/ComponentNode/LodStrategy
Step 12 — 3D 交互
CameraController: 自定义鼠标映射, Fit All, 视图预设PickHandler: 拾取→反查文档对象→SelectionManagerSelectionHighlight/SceneFurniture
Step 13 — VSG-QML 桥接
VsgQuickItem: QQuickFramebufferObject, VSG 离屏渲染→纹理→QML- 鼠标/键盘事件转发
Step 14 — 应用层
Document/DependencyGraph/RecomputeEngineCommand/CommandStack/CommandRegistry/CommandContextSelectionManager/Workbench+WorkbenchManagerDesignWorkbench(取代 CadWorkbench): 设计树+ComponentToolStrip+参数化面板WorkbenchController: C++→QML 桥接
Step 15 — QML UI (DesignWorkbench)
DesignTree.qml: 左侧可折叠设计树ParameterPanel.qml: 右侧可折叠参数化面板(含管点表格+属性面板)ComponentToolStrip.qml: 双列元件插入图标条Viewport3D.qml/ContextMenu.qml
Step 16 — STEP 导入/导出
- 导出: 遍历管点→GeometryDeriver→STEPControl_Writer
- 侧车 JSON: 管点序列/PipeSpec/段结构/附属构件
Step 17 — 管道校验
- 干涉检测, 规格校验, 未连接端口
Step 18 — SpecWorkbench (管线规格管理)
SpecWorkbench.h/cpp: 工具栏(新建规格/导入规范/添加材料/添加元件/校验)SpecTree.qml/MaterialTable.qml/ComponentTable.qml- 自定义参数管理,预留行业规范接入接口
Step 19 — 载荷与工况数据模型
Load.h/cpp基类 + 7个子类(DeadWeight/Thermal/Pressure/Wind/Seismic/Displacement/UserDefined)LoadCase.h/cpp: 基本工况LoadCombination.h/cpp: 组合工况(CombineMethod + StressCategory)- 序列化扩展: ProjectSerializer 支持载荷/工况的 JSON 读写
- 依赖图扩展: Load→LoadCase→LoadCombination 链
Step 20 — VTK 可视化桥接
OcctToVtk.h/cpp: OCCT TopoDS_Shape → vtkPolyDataBeamMeshBuilder.h/cpp: 管路拓扑中心线 → 梁单元线模型VtkSceneManager.h/cpp: VTK 场景管理, 实体/线条双模式 Actor 管理VtkViewport.h/cpp: VTK 渲染器 + QML 集成 (QVTKOpenGLNativeWidget 或 QQuickFramebufferObject)
Step 21 — AnalysisWorkbench (管道应力分析)
AnalysisWorkbench.h/cpp: 工具栏(渲染模式切换/添加载荷/工况管理)RenderMode::Solid↔RenderMode::Beam切换AnalysisTree.qml: 三级树结构(载荷/基本工况/组合工况)LoadTable.qml/LoadCaseTable.qml/VtkViewport.qml
Step 22 — 集成验证
- Spec→Design→Analysis 全流程端到端测试
- 载荷/工况 Undo/Redo 和 JSON round-trip 验证
- VTK 实体/线条模式切换验证
Step T0-T3 — 命令基础设施
Variant扩展bool/Vec3DocumentObject增加setProperty()/getProperty()虚接口Command/MacroCommand/PropertyApplier/CommandStack
Step T4-T7 — 属性命令与 UI 迁移
SetPropertyCommand/BatchSetPropertyCommandCommandRegistry统一工厂与 JSON 序列化Application与main.cpp接入命令信号AppController/PipePointTableModel/PipeSpecModel改为通过命令驱动编辑
Step T8-T10 — 结构命令与清理
CreatePipePointCommand/DeletePipePointCommand/InsertComponentCommand- 删除
TransactionManager src/command/源文件并入app静态库,消除循环依赖
BRepPrimAPI_MakeCylinder/MakeTorus/MakeCone→ 管/弯头/异径BRepAlgoAPI_Cut/Fuse→ 管壳/三通BRepOffsetAPI_MakePipeShell→ 梁扫掠BRepMesh_IncrementalMesh+Poly_Triangulation+BRep_Tool::Triangulation()→ 三角化STEPControl_Writer/Reader→ STEPgp_Trsf+BRepBuilderAPI_Transform→ 变换
vsg::VertexIndexDraw→ 渲染 OCCT 网格vsg::MatrixTransform/Group/StateGroup→ 场景组织vsg::Builder::createBox→ 管点正方体vsg::LOD→ 距离 LODvsg::LineSegmentIntersector→ 拾取
vtkPolyData→ 实体模式网格 (来自 OCCT 转换)vtkPolyLine+vtkCellArray→ 线条模式梁单元vtkGlyph3D+vtkSphereSource→ 节点标记vtkActor可见性切换 → 渲染模式切换vtkRenderer/vtkRenderWindow→ VTK 渲染管线
QQuickFramebufferObject→ VSG 嵌入 QMLQAbstractTableModel→ 管点表格QAbstractItemModel→ 段树
- Phase 1: test_geometry — 创建圆柱/圆环/锥 → 三角化 → 验证顶点数 > 0
- Phase 2: test_model — 创建 PipePoint/PipeSpec/Segment → 验证引用关系/查询
- Phase 3: test_engine — BendCalculator 验证 N/M/F 坐标;RunBuilder 验证 Shape 非空
- Phase 4: test_visualization — Shape→VSG 节点→验证顶点/索引非空
- Phase 5: 手动 — QML 启动→表格输入管点→3D 渲染→参数修改→几何更新
- Phase 6: 导出 STEP→FreeCAD 验证
- Phase 7: test_loads — 载荷/工况 CRUD + 依赖图 + 序列化 round-trip; VTK 渲染模式切换; 端到端多工作台流程
- Phase 8: test_command_base / test_command_stack / test_command_registry / test_property_commands / test_structural_commands / test_insert_component — 验证命令执行、撤销重做、序列化、结构编辑与 UI 迁移链路
| 决策 | 说明 |
|---|---|
| 管点为中心 | 管件不是独立对象,是管点类型+推导规则的结果 |
| PipeSpec 可扩展字段 | map<string,Variant> 字典,预留标准数据库接口 |
| 管点级别引用 PipeSpec | 异径接头自然由前后管点引用不同 PipeSpec 产生 |
| Bend 倍数存管点上 | 每个 Bend 可独立指定 1.5D/2D/5D |
| 仅法兰连接 | — |
| 工程文件=JSON | nlohmann/json 序列化参数,3D 几何不存储,打开时重建 |
| STEP 仅导出 | 用于与其他 CAD 软件交换最终几何 |
| 表格输入为主交互 | 管点通过表格输入坐标和参数 |
| 命令栈驱动编辑 | CommandStack / CommandRegistry 取代 TransactionManager,命令完成/撤销/重做后统一重算 |
| VTK 用于分析工作台 | 管道应力分析采用 VTK 渲染,支持实体/线条双模式 |
| 三工作台架构 | Specification(规格管理) → Design(路由设计) → Analysis(应力分析) |
| 载荷两层模型 | Load→LoadCase(基本工况,独立求解) → LoadCombination(组合工况,后处理代数运算) |
| 参数化构件模板库 | 形状拓扑固定+尺寸按OD缩放,不用草图/STEP文件库,ComponentCatalog代码模板 |
| 工作台系统 | FreeCAD 风格,CAD/CAE 两个工作台,文档模型共享 |
| 初期全公制 | 内部统一公制存储,单位转换后续增加 |
| pixi + CMake + Ninja | 通用依赖 pixi 管理,OCCT/VSG 本地 lib/ fallback |
| command 兼容别名 | src/command target 指向 app,避免 app↔command 循环依赖 |
| 单文档模式 | 一次只打开一个工程 |
| 3D 鼠标映射 | 滚轮=缩放, 滚轮拖=平移, Ctrl+左键拖=旋转, 左键=选择, 右键=菜单 |
| ViewManager 统一门面 | 视口路由+渲染状态+交互协调,QML 只与 ViewManager 交互,不感知 VSG/VTK |
| Application 中央单例 | 所有管理器由 Application 单例持有,使用 std::call_once 保证线程安全初始化,并提供 createCommandContext() 供 UI/命令调用 |
-
VSG-QML Vulkan 互操作: 初期方案 A(离屏渲染→CPU 拷贝→QML 纹理)快速原型;30k 构件帧率不足时升级方案 B(VK_KHR_external_memory 共享)
-
Bend N/M/F 点用户可见性: N/M/F 点是计算生成的,用户在表格中只输入交点坐标,N/M/F 自动计算显示。UI 区分"用户输入管点"和"自动计算管点"。
-
大规模性能: 实例化渲染(相同 PipeSpec+类型共享几何)、LOD、异步三角化、场景分区按段/路由分组可选加载。
-
工程文件打开重建流程: JSON→反序列化→全量依赖图构建→全量 Recompute→SceneManager 构建 VSG 场景。30k 管点可按 Segment 分批异步重建。
-
颜色/显示模式: 支持多种着色模式(按段/按 PipeSpec/按管点类型/自定义颜色)。
-
测量工具: 基础测量(两点距离/管段长度/路由总长)基于管点坐标计算,初期文本输出,后续 3D 标注。