NavisworksTransport/doc/design/2026/coordinate-system-canonical-space-design.md

15 KiB
Raw Blame History

坐标系统一架构设计方案

1. 背景

当前项目运行在 Navisworks 宿主环境中,程序直接读取和写入的都是 Navisworks 世界坐标:

  • Point3D
  • Vector3D
  • BoundingBox3D
  • Transform3D
  • 鼠标点击拾取点
  • Document.UpVector

这些坐标对程序来说属于外部坐标

项目目前的问题不是“完全没有坐标系抽象”,而是:

  • 有一部分代码已经开始抽象 Y-up / Z-up
  • 另一部分代码仍然直接写死世界 Z
  • 还有一部分代码把世界原点直接当作业务球心

结果是三种语义混在一起:

  1. Navisworks 外部坐标语义
  2. 程序内部几何计算语义
  3. 工程业务基准语义(球心、安装基准、轨道参考面)

这会导致:

  • Y-up / Z-up 项目切换后功能不一致
  • 终端安装仿真把世界原点误当成球心
  • Rail 姿态和渲染层偷用世界 Z
  • 不同模块对同一个点的解释不一致

因此,需要建立一套更成熟、更清晰的坐标系架构。

2. 设计目标

本方案的目标不是“到处加 if (isYUp)”,而是建立一个标准的分层架构:

  • Navisworks 世界坐标统一视为外部坐标
  • 程序内部统一使用一套规范内部坐标
  • 工程语义使用独立的业务基准坐标
  • 坐标转换只发生在少数边界入口
  • 业务逻辑禁止直接依赖宿主坐标语义

3. 总体方案

3.1 内部统一坐标

程序内部统一使用:

  • Canonical Space规范内部坐标
  • 固定为 Z-up

选择 Z-up 的原因:

  1. 当前项目大量成熟逻辑本身就是按 Z-up 语义构建的
  2. 动画、渲染、yaw 语义、很多 helper 都更接近 Z-up
  3. Z-up 作为内部标准,改造成本低于整体改成 Y-up

注意:

  • 这只是程序内部选择
  • 不代表客户模型必须是 Z-up
  • 客户仍可继续使用 Y-up 项目

3.2 三层坐标语义

flowchart LR
    A["Navisworks Host Space\n外部坐标"] --> B["Host Coordinate Adapter\n边界适配层"]
    B --> C["Canonical Space (Z-up)\n内部统一坐标"]
    C --> D["Project Reference Frame\n业务基准坐标"]

    C --> E["路径规划/几何计算"]
    C --> F["Rail 姿态/动画"]
    C --> G["碰撞检测/结果恢复"]
    C --> H["渲染/辅助线/通行空间"]

    D --> I["球心"]
    D --> J["终端安装基准"]
    D --> K["轨道参考面"]

    C --> B
    B --> L["Navisworks 输出\n渲染/移动/截图恢复"]

三层职责如下:

A. Navisworks Host Space外部坐标

宿主 API 直接提供的坐标。

特点:

  • 是程序的输入/输出坐标
  • 可能来自 Y-up 项目,也可能来自 Z-up 项目
  • 不应直接当成内部计算坐标

B. Canonical Space内部统一坐标

程序内部唯一允许进行几何计算、路径计算、姿态计算的坐标空间。

特点:

  • 固定为 Z-up
  • 只解决坐标轴和方向语义统一问题
  • 不承载业务基准含义

C. Project Reference Frame业务基准坐标

建立在内部统一坐标基础上的工程语义层。

负责表达:

  • 球心
  • 项目 up 方向的业务解释
  • 终端安装参考面
  • 轨道参考面
  • 业务锚点

这层不能偷用世界原点,也不能偷用宿主世界轴。

3.3 坐标系定义必须包含的语义

在本项目中,Y-up / Z-up 不能只被理解成“点坐标怎么换算”。

一个完整的坐标系定义,至少必须显式包含:

  • UpAxis
  • ElevationAxis
  • HorizontalPlane
  • 必要时的 Handedness

因此:

  • Y-up 的含义是:
    • Y 为 up 轴
    • Y 为高程轴
    • XZ 为水平平面
  • Z-up 的含义是:
    • Z 为 up 轴
    • Z 为高程轴
    • XY 为水平平面

后续凡是出现:

  • 高度
  • 底面
  • 顶面
  • 通行空间高度轴
  • 俯仰/法向

都必须基于这组定义来解释,不能继续偷用“世界 Z 就是 up”的旧假设。

3.4 模型局部轴约定是独立层

除了宿主坐标系和内部统一坐标系,还必须区分模型局部轴约定

这是另一层独立定义,至少包含:

  • LocalForwardAxis
  • LocalUpAxis

例如:

  • 虚拟物体资源通常按程序约定构建,可能是 Local X = Forward, Local Z = Up
  • 真实模型如果来自 Y-up 项目,则很可能是 Local X = Forward, Local Y = Up

这意味着:

  • 即使宿主坐标系转换已经正确
  • 如果模型局部轴约定没有显式处理
  • 动画和姿态仍然会出现“路径对了、通行空间对了、模型自己站歪了”的现象

因此,后续姿态系统必须显式区分:

  1. 宿主坐标系定义
  2. 内部统一坐标系定义
  3. 业务基准定义
  4. 模型局部轴约定

3.5 Rail 局部坐标系与动画跟踪点

Rail 路径不能再只理解成“沿世界 up 做上下偏移”。

Rail 来说,必须显式建立一套局部坐标系:

  • Forward
    • 沿轨道前进方向
  • Normal
    • 安装法向
    • 可以是任意空间角度
    • 但应尽量贴近项目 up
  • Lateral
    • Normal x Forward 或正交化后确定

后续所有 Rail 相关计算都应建立在这套 RailLocalFrame 之上:

  • 物体姿态
  • 通行空间姿态
  • 路径参考点到动画跟踪点的偏移
  • 终点贴合语义

3.6 动画跟踪点统一语义

动画系统内部不应混用:

  • 底面中心
  • 顶面中心
  • 包围盒中心
  • 路径参考点

统一规则应为:

  • 动画跟踪点 = 当前动画主链路使用的唯一位置语义
  • 当前阶段建议统一为:几何中心

这样做的原因是:

  • 中心点对三维旋转最中性
  • 不依赖“当前哪一面朝上”
  • 不依赖宿主 Y-up / Z-up
  • 物体姿态与碰撞恢复、截图回放更容易共用同一套语义

不同路径的业务需求通过“参考点 -> 跟踪中心点”的显式偏移来表达:

  • 地面/吊装:通常沿 +up / -up
  • Rail沿 RailLocalFrame.Normal

这样:

  • 业务层只表达“路径参考点”和“法向偏移”
  • 动画层只认“中心点 + 姿态”

4. 核心设计原则

4.1 Navisworks 世界坐标统一视为外部坐标

这是本方案的第一条硬规则。

对程序而言,以下数据统一视为外部输入:

  • Point3D
  • Vector3D
  • BoundingBox3D
  • Transform3D
  • ModelItem.BoundingBox()
  • Document.UpVector
  • 鼠标点击拾取点

无论当前 NWD/NWC 是客户原始模型,还是预先转换保存后的模型, 只要是从 Navisworks API 读出来的,它对程序来说就是外部坐标。

4.1.1 外部坐标不等于内部语义

外部坐标虽然来自 Navisworks但不能直接拿来推导内部业务语义。

特别是以下概念必须先经过坐标定义解释:

  • 哪个轴是 up
  • 哪个轴是高程
  • 哪个平面是水平面
  • 一个 BoundingBox 的“底面”到底是哪一面

也就是说:

  • 外部坐标是输入
  • 坐标定义决定如何解释这个输入
  • 业务代码不得跳过这一步

4.2 程序内部一律只认 Canonical Space

业务层不得直接消费 Navisworks 坐标。

禁止这样做:

// ❌ 错误:直接拿宿主包围盒结果开始做业务计算
var bounds = item.BoundingBox();
var center = bounds.Center;
var direction = new Vector3D(center.X, center.Y, center.Z);

正确做法应当是:

// ✅ 正确:先通过边界适配层转换到内部统一坐标
var hostBounds = item.BoundingBox();
var bounds = adapter.ToCanonicalBounds(hostBounds);
var center = bounds.Center;

4.3 坐标系层只解决轴变换,不解决业务语义

坐标适配层只负责:

  • 外部坐标到内部坐标的变换
  • 内部坐标到外部坐标的逆变换
  • up 轴、高程轴、水平面定义

不负责:

  • 球心是否在 (0,0,0)
  • 终端安装是否指向球心
  • 顶面/底面对接如何定义
  • 轨道参考面在哪里

这些都属于业务基准层。

4.4 项目基准点必须显式配置或显式求解

像“球心”这类工程点不属于坐标系本身。

不能因为某一批模型里球心恰好在世界原点,就长期把它写死。

应使用以下来源之一:

  • 项目配置
  • 明确的设计资料
  • 多条向心轴线拟合
  • 其他明确的业务求解方式

4.5 不在业务代码中散落 Y-up / Z-up 分支

不推荐:

// ❌ 错误
if (isYUp)
{
    ...
}
else
{
    ...
}

推荐:

  • 边界适配层统一处理 Host -> Canonical
  • 业务层只消费 Canonical 数据

4.6 渲染几何也必须完成坐标转换

坐标系改造不能只停留在:

  • 点坐标转换
  • 中心点偏移
  • 业务锚点转换

对于以下可视化对象,还必须同步改造它们的局部几何轴构造

  • 通行空间长方体
  • 辅助线杆体
  • 圆形/圆柱标记
  • 切向、法向、侧向相关的渲染面片

否则会出现一种典型错误:

  • 对象中心点已经在正确位置
  • 但渲染几何仍然按世界 Z-up 去构造 right/up/height
  • 最终看起来仍然像 Z-up,即使业务点位已经是对的

本项目已经出现过这一类问题:

  • Y-up 模型中,通行空间中心点偏移已经正确
  • 但长方体本体仍然按世界 XY + Z 构造
  • 导致通行空间整体看起来仍是 Z-up

因此,渲染层必须遵守以下规则:

  1. 凡是依赖 up/right/forward/normal 的渲染几何,必须基于统一坐标语义构造局部轴。
  2. 不允许只修改“中心点/偏移量”而保留旧的世界轴构造公式。
  3. 如果渲染输出面向 Navisworks 宿主,则应先在 Canonical Space 中完成几何语义计算,再转换回宿主坐标输出。
  4. 对长方体、圆柱体这类实体渲染,必须同时验证:
    • 中心点是否正确
    • 局部高度轴是否正确
    • 局部侧向轴是否正确
    • 法向/截面方向是否正确

5. 推荐的技术方案

5.1 使用完整变换矩阵作为适配基础

不推荐继续停留在:

  • GetElevation()
  • GetHorizontalCoords()
  • CreatePoint()

这类偏二维、局部的接口上。

对三维动画、Rail 姿态、碰撞恢复来说,更成熟的方案是:

  • 使用完整的空间变换对象
  • 以矩阵/旋转+平移为核心

即:

  • ExternalToCanonical
  • CanonicalToExternal

这两套变换应成为边界层的基础能力。

5.2 建议新增的核心组件

1. HostCoordinateAdapter

职责:

  • 统一处理 Navisworks 外部坐标到 Canonical Space 的转换
  • 统一处理 Canonical Space 到 Navisworks 外部坐标的反向转换

建议能力:

  • ToCanonicalPoint(...)
  • ToCanonicalVector(...)
  • ToCanonicalBounds(...)
  • ToCanonicalTransform(...)
  • FromCanonicalPoint(...)
  • FromCanonicalVector(...)
  • FromCanonicalBounds(...)
  • FromCanonicalTransform(...)

2. CanonicalTransform

职责:

  • 封装 ExternalToCanonical / CanonicalToExternal
  • 提供点、向量、姿态、包围盒的统一变换

3. ProjectReferenceFrame

职责:

  • 管理业务基准语义

建议包含:

  • SphereCenterInCanonical
  • ProjectUpInCanonical
  • RailReferencePlane
  • AssemblyReferenceFrame

5.3 业务层只消费 Canonical 对象

终端安装、Rail、动画、碰撞、渲染层不应直接使用

  • Navisworks Point3D
  • Navisworks BoundingBox3D
  • Navisworks Transform3D

而应通过适配层先转成内部统一对象后再计算。

6. 输入输出边界

6.1 必须拦截的输入入口

以下都是必须拦截的宿主输入点:

  • 鼠标点击获取路径点
  • ModelItem.BoundingBox()
  • ModelItem.Transform
  • Geometry.BoundingBox
  • Document.UpVector
  • PathClickToolPlugin 等交互工具返回的点

这些点一旦进入业务逻辑,就应先转换到 Canonical Space。

6.2 必须反向转换的输出入口

以下都是必须回写到宿主时做反向转换的输出点:

  • 3D 渲染点、线、法向
  • 动画对象位置与姿态
  • 碰撞点恢复
  • 碰撞报告截图定位
  • 辅助线/参考杆/通行空间可视化

7. 当前项目中的优先改造模块

以下模块优先级最高,因为它们既参与三维姿态,又直接暴露了世界坐标假设问题:

7.1 必改

7.2 次改

  • 自动路径规划相关的高度、坡度、网格构建
  • 历史二维 yaw 辅助逻辑
  • 旧视图辅助和截图辅助

这些部分可以后续逐步纳入统一架构,不必阻塞当前终端安装与 Rail 主线。

8. 迁移策略

不建议一次性全项目重构。

建议按以下顺序推进:

阶段 1

建立边界层:

  • HostCoordinateAdapter
  • CanonicalTransform
  • ProjectReferenceFrame

阶段 2

先接入以下主线功能:

  • 终端安装仿真
  • Rail 姿态
  • 动画播放
  • 碰撞恢复
  • 辅助线/通行空间渲染

阶段 2 的两个重要约束:

  1. 真实物体物理尺寸必须固定
  • 起点贴合、动画帧生成、通行空间尺寸、碰撞恢复
  • 必须共用同一份固定物理尺寸
  • 不允许在对象已经旋转后,再从当前世界 AABB 重新推导“真实高度”

否则会产生典型错误:

  • 起点贴合正确
  • 动画第一帧立刻出现固定间隙
  1. 渲染与业务计算必须同时改
  • 不能只改参考点、中心点、偏移量
  • 还必须同步改渲染几何局部轴:
    • right
    • up
    • normal
    • height axis

否则会出现:

  • 业务点位正确
  • 但通行空间/辅助杆/长方体仍按旧 Z-up 轴构造

阶段 3

再逐步改造:

  • 自动路径规划
  • 高度检测
  • 坡度分析
  • 旧二维路径辅助逻辑

部署约束

WPF 插件的最终部署,必须依赖完整主项目构建产物,而不应默认复用测试顺带生成的程序集。

原因:

  • DLL 时间戳正确,不代表插件可运行
  • 如果 TransportPlugin.g.resources 不完整Navisworks 在创建面板时仍会因缺少 .baml 崩溃

必须保证:

  • 主项目完整构建成功
  • 关键视图资源已经编入程序集

最低检查集:

  • LogisticsControlPanel.baml
  • PathEditingView.baml
  • AnimationControlView.baml
  • LayerManagementView.baml

9. 关键结论

本项目如果要长期稳定支持 Y-upZ-up 项目,正确方向不是:

  • 到处散落 if (isYUp)
  • 强行要求客户把模型先转成 Z-up
  • 继续混用世界原点和业务球心

而应该是:

  • Navisworks 世界坐标统一视为外部坐标
  • 程序内部统一使用 Canonical SpaceZ-up
  • 业务基准点单独建模
  • 坐标转换只发生在边界层

这是一条更接近业界三维软件成熟做法的路线。