Refine rail assembly workflow and viewpoint strategies
This commit is contained in:
parent
c3b103c159
commit
2e0b26e032
50
AGENTS.md
50
AGENTS.md
@ -177,6 +177,56 @@
|
||||
|
||||
目标是做到:只看名字,就能知道这个方向、轴、向量到底属于宿主、内部、资产,还是对象自身局部轴。
|
||||
|
||||
### 4.1.3 对象局部轴业务映射
|
||||
|
||||
以后关于 `Local...`、前进轴、up 轴、side 轴,统一优先使用这个说法:
|
||||
|
||||
- **对象局部轴业务映射**
|
||||
- 它不是第四套全局坐标系
|
||||
- 也不是几何天然自带的“真理”
|
||||
- 它表示:在当前业务链路里,我们准备把对象自身哪根局部轴解释成:
|
||||
- `forward`
|
||||
- `up`
|
||||
- `side`
|
||||
|
||||
这个概念的边界必须非常明确:
|
||||
|
||||
- 它是**业务解释层**
|
||||
- 不是宿主坐标系
|
||||
- 不是内部 `Canonical Space`
|
||||
- 不是插件资源资产坐标系
|
||||
- 它不能脱离上下文单独存在
|
||||
- `up` 往往要结合宿主 `up` 语义来解释
|
||||
- `forward` 往往取决于:
|
||||
- fragment 代表姿态解释
|
||||
- 资源默认轴约定
|
||||
- 用户选择
|
||||
- 当前路径类型
|
||||
|
||||
为什么必须保留这层概念:
|
||||
|
||||
- 路径姿态求解必须回答:
|
||||
- 对象哪根局部轴去对齐路径 `forward`
|
||||
- 对象哪根局部轴去对齐目标 `up`
|
||||
- 如果没有这层映射,下面这些都无法稳定定义:
|
||||
- `Ground / Hoisting / Rail` 起点姿态
|
||||
- 逐帧姿态
|
||||
- `Rail` 角度修正
|
||||
- 通行空间尺寸投影
|
||||
- 终点原始姿态保持
|
||||
- `Rail` 平移模式的终点原位搬运
|
||||
|
||||
当前项目里的硬约束:
|
||||
|
||||
- 不要把“对象局部轴业务映射”写成“对象自身天然就有 forward/up 定义”
|
||||
- 对真实物体:
|
||||
- 不能简单把 `ModelItem.Transform` 当成可靠的局部轴真值
|
||||
- 应优先通过 fragment 代表姿态 + `Fragment默认Up` 去解释
|
||||
- 对虚拟物体:
|
||||
- 对象局部轴业务映射通常来自明确的资产轴约定
|
||||
- 如果只是讨论对象自身局部轴语义,允许继续使用 `Local...` 命名
|
||||
- 但文档、日志、设计讨论里优先说“对象局部轴业务映射”
|
||||
|
||||
### 4.1.1 Quaternion / Rotation3D 固定定义
|
||||
|
||||
这是项目级硬约束,不允许在任何修复中重新猜测、重新验证或临时改口:
|
||||
|
||||
@ -76,6 +76,8 @@
|
||||
<Compile Include="UnitTests\CoordinateSystem\PathTargetFrameResolverTests.cs" />
|
||||
<Compile Include="UnitTests\CoordinateSystem\AssemblyEndFaceAnalyzerTests.cs" />
|
||||
<Compile Include="UnitTests\CoordinateSystem\AssemblyInstallationReferenceBuilderTests.cs" />
|
||||
<Compile Include="UnitTests\CoordinateSystem\RailAssemblyWorkflowContextTests.cs" />
|
||||
<Compile Include="UnitTests\CoordinateSystem\ViewpointHelperTests.cs" />
|
||||
<Compile Include="UnitTests\CoordinateSystem\PathPointVisualizationTests.cs" />
|
||||
<Compile Include="UnitTests\CoordinateSystem\FragmentDefaultUpContextTests.cs" />
|
||||
<Compile Include="UnitTests\CoordinateSystem\VirtualGroundPoseCharacterizationTests.cs" />
|
||||
|
||||
@ -284,6 +284,7 @@
|
||||
<Compile Include="src\UI\WPF\ViewModels\ModelSettingsViewModel.cs" />
|
||||
<Compile Include="src\UI\WPF\ViewModels\AnimationControlViewModel.cs" />
|
||||
<Compile Include="src\UI\WPF\ViewModels\PathEditingViewModel.cs" />
|
||||
<Compile Include="src\UI\WPF\ViewModels\RailAssemblyWorkflowContext.cs" />
|
||||
<Compile Include="src\UI\WPF\ViewModels\SystemManagementViewModel.cs" />
|
||||
<Compile Include="src\UI\WPF\ViewModels\CollisionReportViewModel.cs" />
|
||||
<Compile Include="src\UI\WPF\ViewModels\PathAnalysisViewModel.cs" />
|
||||
|
||||
118
UnitTests/CoordinateSystem/RailAssemblyWorkflowContextTests.cs
Normal file
118
UnitTests/CoordinateSystem/RailAssemblyWorkflowContextTests.cs
Normal file
@ -0,0 +1,118 @@
|
||||
using Autodesk.Navisworks.Api;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using NavisworksTransport.UI.WPF.ViewModels;
|
||||
using System.Numerics;
|
||||
|
||||
namespace NavisworksTransport.UnitTests.CoordinateSystem
|
||||
{
|
||||
[TestClass]
|
||||
public class RailAssemblyWorkflowContextTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void ResetEndFaceAnalysis_ClearsAnalysisStateAndSeedPoints()
|
||||
{
|
||||
var context = CreateContext();
|
||||
context.HasEndFaceAnalysis = true;
|
||||
context.EndFaceCenterPoint = new Point3D(1.0, 2.0, 3.0);
|
||||
context.EndFaceNormal = new Vector3(1f, 0f, 0f);
|
||||
context.EndFaceSeedPoints.Add(new Point3D(4.0, 5.0, 6.0));
|
||||
|
||||
context.ResetEndFaceAnalysis();
|
||||
|
||||
Assert.IsFalse(context.HasEndFaceAnalysis);
|
||||
Assert.IsNull(context.EndFaceCenterPoint);
|
||||
Assert.AreEqual(default(Vector3), context.EndFaceNormal);
|
||||
Assert.AreEqual(0, context.EndFaceSeedPoints.Count);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ResetInstallationReference_RestoresDefaultOffsetAndClearsReferenceState()
|
||||
{
|
||||
var context = CreateContext();
|
||||
context.HasInstallationReference = true;
|
||||
context.InstallationPickPoint = new Point3D(1.0, 2.0, 3.0);
|
||||
context.InstallationBaseAnchorPoint = new Point3D(4.0, 5.0, 6.0);
|
||||
context.InstallationAnchorPoint = new Point3D(7.0, 8.0, 9.0);
|
||||
context.InstallationPlaneNormal = new Vector3(0f, 1f, 0f);
|
||||
context.InstallationPlaneSpanDirection = new Vector3(1f, 0f, 0f);
|
||||
context.InstallationOffsetDistanceInMeters = 2.5;
|
||||
context.InstallationReferenceRouteId = "route-1";
|
||||
context.InstallationSeedPoints.Add(new Point3D(9.0, 8.0, 7.0));
|
||||
context.AnchorVerticalOffsetInMeters = 3.5;
|
||||
|
||||
context.ResetInstallationReference();
|
||||
|
||||
Assert.IsFalse(context.HasInstallationReference);
|
||||
Assert.IsNull(context.InstallationPickPoint);
|
||||
Assert.IsNull(context.InstallationBaseAnchorPoint);
|
||||
Assert.IsNull(context.InstallationAnchorPoint);
|
||||
Assert.AreEqual(default(Vector3), context.InstallationPlaneNormal);
|
||||
Assert.AreEqual(default(Vector3), context.InstallationPlaneSpanDirection);
|
||||
Assert.AreEqual(0.0, context.InstallationOffsetDistanceInMeters, 1e-9);
|
||||
Assert.IsNull(context.InstallationReferenceRouteId);
|
||||
Assert.AreEqual(0, context.InstallationSeedPoints.Count);
|
||||
Assert.AreEqual(0.25, context.AnchorVerticalOffsetInMeters, 1e-9);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ResetSession_ClearsTransientStateButPreservesConfiguration()
|
||||
{
|
||||
var context = CreateContext();
|
||||
context.WorkflowMode = RailAssemblyWorkflowMode.EditSelectedRail;
|
||||
context.TerminalObjectName = "箱体A";
|
||||
context.TerminalObjectInfo = "info";
|
||||
context.ReferenceRodLengthInMeters = 12.0;
|
||||
context.ReferenceRodDiameterInMeters = 0.4;
|
||||
context.SphereCenterX = 10.0;
|
||||
context.SphereCenterY = 11.0;
|
||||
context.SphereCenterZ = 12.0;
|
||||
context.MountMode = RailMountMode.OverRail;
|
||||
context.HasTerminalObject = true;
|
||||
context.IsSelectingStartPoint = true;
|
||||
context.IsSelectingEndFacePoints = true;
|
||||
context.IsSelectingInstallationPoint = true;
|
||||
context.StartPoint = new Point3D(1.0, 2.0, 3.0);
|
||||
context.StartPointText = "(1,2,3)";
|
||||
context.HasEndFaceAnalysis = true;
|
||||
context.EndFaceCenterPoint = new Point3D(4.0, 5.0, 6.0);
|
||||
context.HasInstallationReference = true;
|
||||
context.InstallationReferenceRouteId = "route-2";
|
||||
context.AnchorVerticalOffsetInMeters = 1.5;
|
||||
context.EndFaceSeedPoints.Add(new Point3D(1.0, 0.0, 0.0));
|
||||
context.InstallationSeedPoints.Add(new Point3D(0.0, 1.0, 0.0));
|
||||
|
||||
context.ResetSession();
|
||||
|
||||
Assert.AreEqual(RailAssemblyWorkflowMode.None, context.WorkflowMode);
|
||||
Assert.IsFalse(context.HasTerminalObject);
|
||||
Assert.IsFalse(context.IsSelectingStartPoint);
|
||||
Assert.IsFalse(context.IsSelectingEndFacePoints);
|
||||
Assert.IsFalse(context.IsSelectingInstallationPoint);
|
||||
Assert.IsNull(context.TerminalObject);
|
||||
Assert.IsNotNull(context.StartPoint);
|
||||
Assert.AreEqual("未选择", context.TerminalObjectName);
|
||||
Assert.AreEqual("请选择终点处已安装箱体", context.TerminalObjectInfo);
|
||||
Assert.AreEqual("未选择", context.StartPointText);
|
||||
Assert.IsFalse(context.HasEndFaceAnalysis);
|
||||
Assert.IsFalse(context.HasInstallationReference);
|
||||
Assert.AreEqual(0.25, context.AnchorVerticalOffsetInMeters, 1e-9);
|
||||
Assert.AreEqual(12.0, context.ReferenceRodLengthInMeters, 1e-9);
|
||||
Assert.AreEqual(0.4, context.ReferenceRodDiameterInMeters, 1e-9);
|
||||
Assert.AreEqual(10.0, context.SphereCenterX, 1e-9);
|
||||
Assert.AreEqual(11.0, context.SphereCenterY, 1e-9);
|
||||
Assert.AreEqual(12.0, context.SphereCenterZ, 1e-9);
|
||||
Assert.AreEqual(RailMountMode.OverRail, context.MountMode);
|
||||
}
|
||||
|
||||
private static RailAssemblyWorkflowContext CreateContext()
|
||||
{
|
||||
return new RailAssemblyWorkflowContext(
|
||||
defaultReferenceRodLengthInMeters: 20.0,
|
||||
defaultReferenceRodDiameterInMeters: 0.3,
|
||||
defaultAnchorVerticalOffsetInMeters: 0.25,
|
||||
defaultSphereCenterX: 0.0,
|
||||
defaultSphereCenterY: 0.0,
|
||||
defaultSphereCenterZ: 0.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
80
UnitTests/CoordinateSystem/ViewpointHelperTests.cs
Normal file
80
UnitTests/CoordinateSystem/ViewpointHelperTests.cs
Normal file
@ -0,0 +1,80 @@
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using NavisworksTransport.Utils;
|
||||
using System.Numerics;
|
||||
|
||||
namespace NavisworksTransport.UnitTests.CoordinateSystem
|
||||
{
|
||||
[TestClass]
|
||||
public class ViewpointHelperTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void ResolvePathViewpointProfile_ShouldReturnExpectedDefaults()
|
||||
{
|
||||
var ground = ViewpointHelper.ResolvePathViewpointProfile(ViewpointHelper.ViewpointStrategy.PathGroundSelection);
|
||||
var hoisting = ViewpointHelper.ResolvePathViewpointProfile(ViewpointHelper.ViewpointStrategy.PathHoistingSelection);
|
||||
var rail = ViewpointHelper.ResolvePathViewpointProfile(ViewpointHelper.ViewpointStrategy.PathRailSelection);
|
||||
|
||||
Assert.AreEqual(1.0, ground.DistanceScale, 1e-9);
|
||||
Assert.AreEqual(12.0, ground.MinDistanceMeters, 1e-9);
|
||||
|
||||
Assert.AreEqual(1.2, hoisting.DistanceScale, 1e-9);
|
||||
Assert.AreEqual(8.0, hoisting.MinDistanceMeters, 1e-9);
|
||||
Assert.AreEqual(22.0, hoisting.ElevationDegrees, 1e-9);
|
||||
|
||||
Assert.AreEqual(0.9, rail.DistanceScale, 1e-9);
|
||||
Assert.AreEqual(6.0, rail.MinDistanceMeters, 1e-9);
|
||||
Assert.AreEqual(12.0, rail.ElevationDegrees, 1e-9);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ResolveCameraOffsetDirection_Hoisting_ShouldLookFromBehindAndAbove()
|
||||
{
|
||||
Vector3 hostUp = Vector3.UnitZ;
|
||||
Vector3 forward = Vector3.UnitX;
|
||||
|
||||
Vector3 offset = ViewpointHelper.ResolveCameraOffsetDirection(PathType.Hoisting, hostUp, forward);
|
||||
|
||||
Assert.IsTrue(offset.X < -0.8f);
|
||||
Assert.IsTrue(offset.Z > 0.3f);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ResolveCameraOffsetDirection_Rail_ShouldLookFromSideAndSlightlyAbove()
|
||||
{
|
||||
Vector3 hostUp = Vector3.UnitZ;
|
||||
Vector3 forward = Vector3.UnitX;
|
||||
|
||||
Vector3 offset = ViewpointHelper.ResolveCameraOffsetDirection(PathType.Rail, hostUp, forward);
|
||||
|
||||
Assert.AreEqual(0.0f, offset.X, 1e-5f);
|
||||
Assert.IsTrue(offset.Y > 0.9f);
|
||||
Assert.IsTrue(offset.Z > 0.15f);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ResolveHorizontalPathForward_ShouldFallbackWhenPathIsVertical()
|
||||
{
|
||||
Vector3 hostUp = Vector3.UnitZ;
|
||||
Vector3 rawVertical = Vector3.UnitZ;
|
||||
Vector3 fallback = Vector3.UnitY;
|
||||
|
||||
Vector3 resolved = ViewpointHelper.ResolveHorizontalPathForward(rawVertical, hostUp, fallback);
|
||||
|
||||
Assert.AreEqual(0.0f, resolved.Z, 1e-5f);
|
||||
Assert.AreEqual(1.0f, resolved.Y, 1e-5f);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ResolveFocusViewpointProfile_ShouldCentralizeNamedFocusStrategies()
|
||||
{
|
||||
var modelFocus = ViewpointHelper.ResolveFocusViewpointProfile(ViewpointHelper.ViewpointStrategy.ModelFocus);
|
||||
var collision = ViewpointHelper.ResolveFocusViewpointProfile(ViewpointHelper.ViewpointStrategy.CollisionCloseUp);
|
||||
|
||||
Assert.AreEqual(60.0, modelFocus.ViewAngleDegrees, 1e-9);
|
||||
Assert.AreEqual(0.25, modelFocus.TargetViewRatio, 1e-9);
|
||||
|
||||
Assert.AreEqual(60.0, collision.ViewAngleDegrees, 1e-9);
|
||||
Assert.AreEqual(0.25, collision.TargetViewRatio, 1e-9);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,12 +1,18 @@
|
||||
# 当前工程状态
|
||||
|
||||
更新时间:2026-03-25
|
||||
更新时间:2026-04-01
|
||||
|
||||
## 1. 当前稳定状态
|
||||
|
||||
- `Rail` 路径主链路已稳定:
|
||||
- `ZUp` 模型下,轨上/轨下路径的真实物体与虚拟物体起点、动画、终点贴合正确。
|
||||
- `YUp` 模型下,终端安装仿真、`Rail` 姿态、真实物体与虚拟物体通行空间、起点与动画主链路已基本跑通。
|
||||
- `Rail` “调整角度”窗口中的“平移”模式当前已重新收稳:
|
||||
- 以终点箱体当前原始位姿为真值;
|
||||
- 记录终点原位相对路径终点参考点的固定残差;
|
||||
- 把这份残差整体搬运到路径起点;
|
||||
- 动画全程保持终点原始姿态;
|
||||
- 动画结束时应精确回到终点箱体原位。
|
||||
- 地面路径在 `YUp` 模型下:
|
||||
- 虚拟物体起点姿态、动画姿态、转弯姿态已恢复正常。
|
||||
- 真实物体地面路径当前已恢复到可用状态:
|
||||
@ -20,16 +26,24 @@
|
||||
- 对象级前进轴统一按 `PositiveX` 解释;
|
||||
- 不再因为路径初始方向更偏 `Z` 就把对象前进轴自动切换成 `PositiveZ`。
|
||||
- 当前已禁止地面/吊装路径偷偷退回旧 `yaw` 链路。
|
||||
- 吊装路径真实物体的角度调整链路已重新稳定:
|
||||
- 起点“调整角度”后,预览、生成动画、播放前重置都复用同一份真实起点基姿态;
|
||||
- 清除并重新选择物体时,旧角度修正必须被清空,不能继承上一次调整状态。
|
||||
- 碰撞检测/恢复主链路已稳定:
|
||||
- `ClashDetective` 三维恢复不能再先 `ResetPermanentTransform`。
|
||||
- 碰撞恢复、自动报告、自动截图已重新对齐到动画主链路。
|
||||
- 虚拟物体资源问题已确认并修复:
|
||||
- 旧 `unit_cube.nwc` 局部几何中心不在原点,会导致虚拟物体中心偏差。
|
||||
- 新 `unit_cube.nwc` 已替换为几何中心在原点的版本。
|
||||
- 旋转适配入口当前稳定分流:
|
||||
- 虚拟物体继续走 `HostCoordinateAdapter` 的 `Legacy` 入口;
|
||||
- 真实物体走 `Direct` 入口;
|
||||
- 两者不能再强行共用同一条旋转转换链。
|
||||
- 旋转适配入口当前稳定分流:
|
||||
- 虚拟物体继续走 `HostCoordinateAdapter` 的 `Legacy` 入口;
|
||||
- 真实物体走 `Direct` 入口;
|
||||
- 两者不能再强行共用同一条旋转转换链。
|
||||
- `Rail` 终端安装仿真创建/编辑区当前已完成第一轮工作流拆分:
|
||||
- 新建 Rail 与编辑选中 Rail 不再共用同一套按钮命令入口;
|
||||
- 新建区和编辑区有独立的 `找端面 / 选安装点 / 取起点` 可用条件;
|
||||
- 两个区域都已有明确的“取消装配”退出入口;
|
||||
- 上方“路径编辑 -> 结束”在 Rail 装配工作流活跃时,也必须能真正退出 Rail 装配流程。
|
||||
|
||||
## 2. 当前坐标系架构
|
||||
|
||||
@ -212,6 +226,19 @@
|
||||
2. 再验证修正后 `forward` 是否仍沿 rail 切向
|
||||
3. 最后再看 Navisworks 应用层是否正确按三步增量姿态落位
|
||||
|
||||
### 4.7.1 Rail 平移模式
|
||||
|
||||
- `Rail` 的“平移”不是重新按轨上/轨下求一套新的安装姿态。
|
||||
- 当前稳定语义:
|
||||
- 终点箱体当前原始位姿是唯一真值;
|
||||
- 先计算终点箱体相对“路径终点参考点”的固定残差;
|
||||
- 再把这份残差搬运到路径起点;
|
||||
- 动画全程保持这套终点原始姿态和固定残差。
|
||||
- 当前规则:
|
||||
- 不能把“平移模式”误实现成重新按 `RailMountMode` 解目标 pose;
|
||||
- 不能锁到“参考姿态”而丢掉终点箱体当前真实几何姿态;
|
||||
- 终点保位误差如果异常,优先检查“残差是相对终点参考点采样,还是误相对起点参考点采样”。
|
||||
|
||||
### 4.8 Rail 真实物体必须复用统一姿态解释层
|
||||
|
||||
- `Rail` 不应再单独退回默认 `PositiveX / PositiveY` 轴约定。
|
||||
@ -243,6 +270,40 @@
|
||||
- `AnimationControlViewModel.UpdatePassageSpaceVisualization()` 中,真实物体的 `Rail` 和 `Hoisting` 必须分开消费
|
||||
- `Rail` 真实物体不能再复用 `Hoisting` 的法向/分段参数语义
|
||||
|
||||
### 4.10 Rail 终端安装仿真工作流
|
||||
|
||||
- 当前真实问题已经确认:
|
||||
- “创建 Rail 路径”区和“Rail 参数”编辑区之前共用同一套装配状态、命令入口和按钮可用条件;
|
||||
- 导致新建流程会误吃编辑态校验,或在切换路径后残留旧的装配状态。
|
||||
- 当前第一轮稳定修复:
|
||||
- 新建 Rail 与编辑 Rail 已拆成独立命令入口;
|
||||
- “找端面”在新建 Rail 时不再要求先选中一条现有 Rail 路径;
|
||||
- 新建区的“选安装点”不再受 `IsRailRouteSelected` 编辑态条件限制;
|
||||
- 安装点拾取完成后,拾取点、安装点、安装参考面、中心线必须保留可视化,不能在收尾时被一并清掉;
|
||||
- “隐藏辅助线”必须同时清理:
|
||||
- 参考杆与锚点
|
||||
- 球心到光轴参考点基准线
|
||||
- 端面分析可视化
|
||||
- 安装点、安装中心线、安装参考面
|
||||
- 新建区和编辑区都必须有明确的“取消装配”;
|
||||
- “路径编辑 -> 结束”在 Rail 装配流程活跃时,也必须能触发同一条取消/清理链路;
|
||||
- 切换路径时必须自动清空旧的 Rail 装配上下文,不能把旧状态带到下一条路径。
|
||||
- 当前仍要记住:
|
||||
- 这块只是“第一轮工作流拆分”,还不是彻底的数据结构重构;
|
||||
- 如果后续继续扩 Rail 终端安装仿真,优先方向应是把“创建态上下文”和“编辑态上下文”进一步拆成独立 context,而不是继续在 ViewModel 顶层堆共享字段和布尔分支。
|
||||
|
||||
### 4.11 吊装路径角度调整
|
||||
|
||||
- 当前真实根因已经确认:
|
||||
- 吊装真实物体在“起点调整角度后生成动画”时,如果基姿态缓存不跟着角度修正一起重建,就会出现:
|
||||
- 动画里没有沿用调整后的角度;
|
||||
- 甚至被错误立起或只抬高一点的异常现象。
|
||||
- 当前稳定规则:
|
||||
- 吊装路径必须缓存“真实起点基姿态 + 起始 yaw”;
|
||||
- 起点预览、动画生成、播放前重置,都要复用同一份基线;
|
||||
- 只要角度修正变化,就必须清掉旧缓存并重新 `MoveObjectToPathStart()`;
|
||||
- 清除物体或重新选择物体时,角度修正状态必须归零,不允许继承上一轮对象的角度状态。
|
||||
|
||||
## 5. 当前保留的日志策略
|
||||
|
||||
- 保留:
|
||||
@ -257,8 +318,16 @@
|
||||
- 已降级或删除:
|
||||
- 大量重复逐帧宿主姿态轴日志
|
||||
- 虚拟物体 `Transform` 即时读回日志(容易误导)
|
||||
- `Hoisting` 真实物体起点姿态与平面前进轴的刷屏 `Info` 日志,已降为 `Debug`
|
||||
|
||||
## 6. 当前 Navisworks 变换 API 结论
|
||||
## 6. 最近关键提交
|
||||
|
||||
- `c3b103c` `Add explicit exit for rail assembly workflow`
|
||||
- `c974469` `Separate rail creation and edit assembly flows`
|
||||
- `d4c49fc` `Stabilize hoisting pose adjustment flow`
|
||||
- `cb56737` `Preserve terminal pose when translating rail objects to path start`
|
||||
|
||||
## 7. 当前 Navisworks 变换 API 结论
|
||||
|
||||
- `ModelItem.Transform`
|
||||
- 只表示原始设计文件变换;
|
||||
@ -279,13 +348,13 @@
|
||||
- 清掉的是永久增量层;
|
||||
- 不会修改原始设计文件变换。
|
||||
|
||||
## 6. 当前还值得继续观察的点
|
||||
## 8. 当前还值得继续观察的点
|
||||
|
||||
- 空轨路径里仍有一个旧 warning:
|
||||
- `[空轨] 双轨几何中心线提取失败,退回 OBB 主轴中线`
|
||||
- 这与当前 `YUp` 地面路径问题无关,但后续可以单独处理。
|
||||
|
||||
## 7. 下一步建议
|
||||
## 9. 下一步建议
|
||||
|
||||
- 继续按“先框架、先测试、后接业务”的方式推进。
|
||||
- 新问题优先:
|
||||
@ -293,8 +362,12 @@
|
||||
2. 若日志不足,先补日志
|
||||
3. 再补针对性的回归测试
|
||||
4. 最后再改业务代码
|
||||
- `Rail` 终端安装仿真的下一轮优化优先级:
|
||||
1. 把创建态/编辑态从 ViewModel 顶层共享字段继续拆成独立 context
|
||||
2. 为“取消装配 / 切换路径 / 中途退出后重新开始”补单元测试或更轻量的状态回归验证
|
||||
3. 再考虑是否需要把创建区与编辑区在 UI 上做更明确的视觉分隔
|
||||
|
||||
## 8. 当前阶段的工作边界
|
||||
## 10. 当前阶段的工作边界
|
||||
|
||||
- 不要回到“直接补旧 `yaw` 链路”的方式。
|
||||
- 不要再增加隐藏错误的 fallback。
|
||||
|
||||
@ -2458,7 +2458,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
ModelHighlightHelper.HighlightItems(ManualTargetsHighlightCategory, items);
|
||||
|
||||
// 聚焦到对象(斜上方45度视角)
|
||||
ViewpointHelper.FocusOnModelItem(target.ModelItem, viewAngleDegrees: 60.0, targetViewRatio: 0.25);
|
||||
ViewpointHelper.FocusOnModelItem(target.ModelItem, ViewpointHelper.ViewpointStrategy.ModelFocus);
|
||||
|
||||
UpdateMainStatus($"已聚焦到: {target.DisplayName}");
|
||||
LogManager.Info($"聚焦到手工指定对象: {target.DisplayName}");
|
||||
@ -2484,7 +2484,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
ModelHighlightHelper.HighlightItems(ExcludedObjectsHighlightCategory, items);
|
||||
|
||||
// 聚焦到对象(斜上方45度视角)
|
||||
ViewpointHelper.FocusOnModelItem(excludedObject.ModelItem, viewAngleDegrees: 60.0, targetViewRatio: 0.25);
|
||||
ViewpointHelper.FocusOnModelItem(excludedObject.ModelItem, ViewpointHelper.ViewpointStrategy.ModelFocus);
|
||||
|
||||
UpdateMainStatus($"已聚焦到排除对象: {excludedObject.DisplayName}");
|
||||
LogManager.Info($"聚焦到排除对象: {excludedObject.DisplayName}");
|
||||
|
||||
@ -1597,7 +1597,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
try
|
||||
{
|
||||
// 聚焦到模型(斜上方60度视角)
|
||||
ViewpointHelper.FocusOnModelItem(logisticsModel.NavisworksItem, viewAngleDegrees: 60.0, targetViewRatio: 0.25);
|
||||
ViewpointHelper.FocusOnModelItem(logisticsModel.NavisworksItem, ViewpointHelper.ViewpointStrategy.ModelFocus);
|
||||
|
||||
UpdateMainStatus($"已聚焦到物流模型: {logisticsModel.Name}");
|
||||
LogManager.Info($"聚焦到物流模型: {logisticsModel.Name}, 类别: {logisticsModel.Category}");
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
154
src/UI/WPF/ViewModels/RailAssemblyWorkflowContext.cs
Normal file
154
src/UI/WPF/ViewModels/RailAssemblyWorkflowContext.cs
Normal file
@ -0,0 +1,154 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using Autodesk.Navisworks.Api;
|
||||
|
||||
namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
{
|
||||
internal enum RailAssemblyWorkflowMode
|
||||
{
|
||||
None,
|
||||
CreateNewRailPath,
|
||||
EditSelectedRail
|
||||
}
|
||||
|
||||
internal sealed class RailAssemblyWorkflowContext
|
||||
{
|
||||
private const string DefaultTerminalObjectName = "未选择";
|
||||
private const string DefaultTerminalObjectInfo = "请选择终点处已安装箱体";
|
||||
private const string DefaultAssemblyStartPointText = "未选择";
|
||||
|
||||
private readonly double _defaultReferenceRodLengthInMeters;
|
||||
private readonly double _defaultReferenceRodDiameterInMeters;
|
||||
private readonly double _defaultAnchorVerticalOffsetInMeters;
|
||||
private readonly double _defaultSphereCenterX;
|
||||
private readonly double _defaultSphereCenterY;
|
||||
private readonly double _defaultSphereCenterZ;
|
||||
|
||||
public RailAssemblyWorkflowContext(
|
||||
double defaultReferenceRodLengthInMeters,
|
||||
double defaultReferenceRodDiameterInMeters,
|
||||
double defaultAnchorVerticalOffsetInMeters,
|
||||
double defaultSphereCenterX,
|
||||
double defaultSphereCenterY,
|
||||
double defaultSphereCenterZ)
|
||||
{
|
||||
_defaultReferenceRodLengthInMeters = defaultReferenceRodLengthInMeters;
|
||||
_defaultReferenceRodDiameterInMeters = defaultReferenceRodDiameterInMeters;
|
||||
_defaultAnchorVerticalOffsetInMeters = defaultAnchorVerticalOffsetInMeters;
|
||||
_defaultSphereCenterX = defaultSphereCenterX;
|
||||
_defaultSphereCenterY = defaultSphereCenterY;
|
||||
_defaultSphereCenterZ = defaultSphereCenterZ;
|
||||
|
||||
TerminalObjectName = DefaultTerminalObjectName;
|
||||
TerminalObjectInfo = DefaultTerminalObjectInfo;
|
||||
ReferenceRodLengthInMeters = _defaultReferenceRodLengthInMeters;
|
||||
ReferenceRodDiameterInMeters = _defaultReferenceRodDiameterInMeters;
|
||||
AnchorVerticalOffsetInMeters = _defaultAnchorVerticalOffsetInMeters;
|
||||
SphereCenterX = _defaultSphereCenterX;
|
||||
SphereCenterY = _defaultSphereCenterY;
|
||||
SphereCenterZ = _defaultSphereCenterZ;
|
||||
StartPointText = DefaultAssemblyStartPointText;
|
||||
MountMode = RailMountMode.UnderRail;
|
||||
InstallationSeedPoints = new List<Point3D>();
|
||||
EndFaceSeedPoints = new List<Point3D>();
|
||||
}
|
||||
|
||||
public RailAssemblyWorkflowMode WorkflowMode { get; set; }
|
||||
|
||||
public string TerminalObjectName { get; set; }
|
||||
|
||||
public string TerminalObjectInfo { get; set; }
|
||||
|
||||
public double ReferenceRodLengthInMeters { get; set; }
|
||||
|
||||
public double ReferenceRodDiameterInMeters { get; set; }
|
||||
|
||||
public double AnchorVerticalOffsetInMeters { get; set; }
|
||||
|
||||
public double SphereCenterX { get; set; }
|
||||
|
||||
public double SphereCenterY { get; set; }
|
||||
|
||||
public double SphereCenterZ { get; set; }
|
||||
|
||||
public string StartPointText { get; set; }
|
||||
|
||||
public RailMountMode MountMode { get; set; }
|
||||
|
||||
public bool HasTerminalObject { get; set; }
|
||||
|
||||
public bool IsSelectingStartPoint { get; set; }
|
||||
|
||||
public bool IsSelectingEndFacePoints { get; set; }
|
||||
|
||||
public bool IsSelectingInstallationPoint { get; set; }
|
||||
|
||||
public bool HasEndFaceAnalysis { get; set; }
|
||||
|
||||
public bool HasInstallationReference { get; set; }
|
||||
|
||||
public ModelItem TerminalObject { get; set; }
|
||||
|
||||
public Point3D StartPoint { get; set; }
|
||||
|
||||
public Point3D EndFaceCenterPoint { get; set; }
|
||||
|
||||
public Vector3 EndFaceNormal { get; set; }
|
||||
|
||||
public Point3D InstallationPickPoint { get; set; }
|
||||
|
||||
public Point3D InstallationBaseAnchorPoint { get; set; }
|
||||
|
||||
public Point3D InstallationAnchorPoint { get; set; }
|
||||
|
||||
public Vector3 InstallationPlaneNormal { get; set; }
|
||||
|
||||
public Vector3 InstallationPlaneSpanDirection { get; set; }
|
||||
|
||||
public double InstallationOffsetDistanceInMeters { get; set; }
|
||||
|
||||
public string InstallationReferenceRouteId { get; set; }
|
||||
|
||||
public List<Point3D> InstallationSeedPoints { get; }
|
||||
|
||||
public List<Point3D> EndFaceSeedPoints { get; }
|
||||
|
||||
public void ResetEndFaceAnalysis()
|
||||
{
|
||||
HasEndFaceAnalysis = false;
|
||||
EndFaceCenterPoint = null;
|
||||
EndFaceNormal = default(Vector3);
|
||||
EndFaceSeedPoints.Clear();
|
||||
}
|
||||
|
||||
public void ResetInstallationReference()
|
||||
{
|
||||
HasInstallationReference = false;
|
||||
InstallationPickPoint = null;
|
||||
InstallationBaseAnchorPoint = null;
|
||||
InstallationAnchorPoint = null;
|
||||
InstallationPlaneNormal = default(Vector3);
|
||||
InstallationPlaneSpanDirection = default(Vector3);
|
||||
InstallationOffsetDistanceInMeters = 0.0;
|
||||
InstallationReferenceRouteId = null;
|
||||
InstallationSeedPoints.Clear();
|
||||
AnchorVerticalOffsetInMeters = _defaultAnchorVerticalOffsetInMeters;
|
||||
}
|
||||
|
||||
public void ResetSession()
|
||||
{
|
||||
WorkflowMode = RailAssemblyWorkflowMode.None;
|
||||
HasTerminalObject = false;
|
||||
IsSelectingStartPoint = false;
|
||||
IsSelectingEndFacePoints = false;
|
||||
IsSelectingInstallationPoint = false;
|
||||
TerminalObject = null;
|
||||
StartPoint = Point3D.Origin;
|
||||
TerminalObjectName = DefaultTerminalObjectName;
|
||||
TerminalObjectInfo = DefaultTerminalObjectInfo;
|
||||
StartPointText = DefaultAssemblyStartPointText;
|
||||
ResetEndFaceAnalysis();
|
||||
ResetInstallationReference();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -488,10 +488,6 @@ NavisworksTransport 路径编辑页签视图 - 采用与动画控制和分层管
|
||||
Command="{Binding SelectAssemblyInstallationPointForCreateCommand}"
|
||||
Style="{StaticResource SecondaryButtonStyle}"
|
||||
IsEnabled="{Binding CanSelectAssemblyInstallationPointForCreate}"/>
|
||||
<Button Content="生成辅助线"
|
||||
Command="{Binding GenerateAssemblyReferenceRodCommand}"
|
||||
Style="{StaticResource ActionButtonStyle}"
|
||||
IsEnabled="{Binding CanGenerateAssemblyReferenceRod}"/>
|
||||
<Button Content="取起点"
|
||||
Command="{Binding SelectAssemblyStartPointForCreateCommand}"
|
||||
Style="{StaticResource ActionButtonStyle}"
|
||||
|
||||
@ -50,7 +50,7 @@ namespace NavisworksTransport.Utils
|
||||
ModelHighlightHelper.HighlightItems(ModelHighlightHelper.ClashDetectiveResultsCategory, new[] { hitObject });
|
||||
|
||||
// 聚焦到被撞对象
|
||||
ViewpointHelper.FocusOnModelItem(hitObject, viewAngleDegrees: 60.0, targetViewRatio: 0.25);
|
||||
ViewpointHelper.FocusOnModelItem(hitObject, ViewpointHelper.ViewpointStrategy.CollisionCloseUp);
|
||||
|
||||
LogManager.Info(string.Format("聚焦到被撞对象: {0}", hitObject.DisplayName));
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Autodesk.Navisworks.Api;
|
||||
using NavisworksTransport.Utils.CoordinateSystem;
|
||||
|
||||
@ -12,6 +13,41 @@ namespace NavisworksTransport.Utils
|
||||
/// </summary>
|
||||
public static class ViewpointHelper
|
||||
{
|
||||
public enum ViewpointStrategy
|
||||
{
|
||||
PathGroundSelection,
|
||||
PathHoistingSelection,
|
||||
PathRailSelection,
|
||||
ModelFocus,
|
||||
CollisionCloseUp
|
||||
}
|
||||
|
||||
internal struct PathViewpointProfile
|
||||
{
|
||||
public PathViewpointProfile(double distanceScale, double minDistanceMeters, double elevationDegrees)
|
||||
{
|
||||
DistanceScale = distanceScale;
|
||||
MinDistanceMeters = minDistanceMeters;
|
||||
ElevationDegrees = elevationDegrees;
|
||||
}
|
||||
|
||||
public double DistanceScale { get; }
|
||||
public double MinDistanceMeters { get; }
|
||||
public double ElevationDegrees { get; }
|
||||
}
|
||||
|
||||
internal struct FocusViewpointProfile
|
||||
{
|
||||
public FocusViewpointProfile(double viewAngleDegrees, double targetViewRatio)
|
||||
{
|
||||
ViewAngleDegrees = viewAngleDegrees;
|
||||
TargetViewRatio = targetViewRatio;
|
||||
}
|
||||
|
||||
public double ViewAngleDegrees { get; }
|
||||
public double TargetViewRatio { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 包围盒扩展系数(用于 ZoomBox 调整视野范围)
|
||||
/// </summary>
|
||||
@ -33,6 +69,34 @@ namespace NavisworksTransport.Utils
|
||||
AdjustViewpointToPathCenter(path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 按路径类型调整视角。
|
||||
/// Ground 使用俯视;
|
||||
/// Hoisting 使用后上方视角;
|
||||
/// Rail 使用侧面轻俯视角。
|
||||
/// </summary>
|
||||
public static void AdjustViewpointForPath(PathRoute path)
|
||||
{
|
||||
if (path == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
switch (path.PathType)
|
||||
{
|
||||
case PathType.Hoisting:
|
||||
AdjustDirectionalViewpointToPath(path, ResolvePathViewpointProfile(ViewpointStrategy.PathHoistingSelection));
|
||||
return;
|
||||
case PathType.Rail:
|
||||
AdjustDirectionalViewpointToPath(path, ResolvePathViewpointProfile(ViewpointStrategy.PathRailSelection));
|
||||
return;
|
||||
case PathType.Ground:
|
||||
default:
|
||||
AdjustViewpointToPathCenter(path);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 调整视角到路径中心,确保整个路径都在视野内
|
||||
/// </summary>
|
||||
@ -67,6 +131,60 @@ namespace NavisworksTransport.Utils
|
||||
AdjustViewpointToBoundingBox(startEndBoundingBox, pathBoundingBox, baseDimension);
|
||||
}
|
||||
|
||||
private static void AdjustDirectionalViewpointToPath(PathRoute path, PathViewpointProfile profile)
|
||||
{
|
||||
Document doc = Application.ActiveDocument;
|
||||
if (doc == null || doc.IsClear)
|
||||
{
|
||||
throw new InvalidOperationException("没有活动的文档");
|
||||
}
|
||||
|
||||
if (path?.Points == null || path.Points.Count == 0)
|
||||
{
|
||||
throw new ArgumentException("路径为空或没有路径点", nameof(path));
|
||||
}
|
||||
|
||||
Point3D startPoint = path.Points[0].Position;
|
||||
Point3D endPoint = path.Points[path.Points.Count - 1].Position;
|
||||
BoundingBox3D focusBoundingBox = CreateBoundingBoxFromPoints(startPoint, endPoint);
|
||||
BoundingBox3D viewBoundingBox = CalculatePathBoundingBox(path);
|
||||
Point3D focusCenter = GetBoundingBoxCenter(focusBoundingBox);
|
||||
|
||||
var adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
|
||||
Vector3 hostUp = ToVector3(adapter.HostUpVector);
|
||||
Vector3 rawPathDirection = new Vector3(
|
||||
(float)(endPoint.X - startPoint.X),
|
||||
(float)(endPoint.Y - startPoint.Y),
|
||||
(float)(endPoint.Z - startPoint.Z));
|
||||
Vector3 fallbackForward = ToVector3(ProjectVectorOntoPlane(doc.FrontRightTopViewVector, adapter.HostUpVector));
|
||||
Vector3 horizontalForward = ResolveHorizontalPathForward(rawPathDirection, hostUp, fallbackForward);
|
||||
Vector3 cameraOffsetDirection = ResolveCameraOffsetDirection(path.PathType, hostUp, horizontalForward);
|
||||
|
||||
double maxDimensionModel = GetMaxDimension(viewBoundingBox);
|
||||
double minDistanceModel = UnitsConverter.ConvertFromMeters(profile.MinDistanceMeters);
|
||||
double cameraDistance = Math.Max(maxDimensionModel * profile.DistanceScale, minDistanceModel);
|
||||
Point3D cameraPosition = new Point3D(
|
||||
focusCenter.X + cameraOffsetDirection.X * (float)cameraDistance,
|
||||
focusCenter.Y + cameraOffsetDirection.Y * (float)cameraDistance,
|
||||
focusCenter.Z + cameraOffsetDirection.Z * (float)cameraDistance);
|
||||
|
||||
double expansionMargin = Math.Max(
|
||||
maxDimensionModel * (VIEW_BOUNDING_BOX_EXPANSION_FACTOR - 1.0) / 2.0,
|
||||
UnitsConverter.ConvertFromMeters(2.0));
|
||||
|
||||
ApplyViewpointWithZoomBox(
|
||||
cameraPosition,
|
||||
focusCenter,
|
||||
new Vector3D(hostUp.X, hostUp.Y, hostUp.Z),
|
||||
viewBoundingBox,
|
||||
expansionMargin);
|
||||
|
||||
LogManager.Info(
|
||||
$"视角已调整完成: 类型={path.PathType}, 焦点=({focusCenter.X:F2},{focusCenter.Y:F2},{focusCenter.Z:F2}), " +
|
||||
$"相机=({cameraPosition.X:F2},{cameraPosition.Y:F2},{cameraPosition.Z:F2}), " +
|
||||
$"距离={UnitsConverter.ConvertToMeters(cameraDistance):F2}米, elevation={profile.ElevationDegrees:F1}°");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 调整视角到指定的包围盒(通用方法)
|
||||
/// 设置俯视位置,确保包围盒完整显示在视野中
|
||||
@ -162,6 +280,146 @@ namespace NavisworksTransport.Utils
|
||||
return bounds;
|
||||
}
|
||||
|
||||
internal static PathViewpointProfile ResolvePathViewpointProfile(PathType pathType)
|
||||
{
|
||||
switch (pathType)
|
||||
{
|
||||
case PathType.Hoisting:
|
||||
return ResolvePathViewpointProfile(ViewpointStrategy.PathHoistingSelection);
|
||||
case PathType.Rail:
|
||||
return ResolvePathViewpointProfile(ViewpointStrategy.PathRailSelection);
|
||||
case PathType.Ground:
|
||||
default:
|
||||
return ResolvePathViewpointProfile(ViewpointStrategy.PathGroundSelection);
|
||||
}
|
||||
}
|
||||
|
||||
internal static PathViewpointProfile ResolvePathViewpointProfile(ViewpointStrategy strategy)
|
||||
{
|
||||
switch (strategy)
|
||||
{
|
||||
case ViewpointStrategy.PathHoistingSelection:
|
||||
return new PathViewpointProfile(distanceScale: 1.2, minDistanceMeters: 8.0, elevationDegrees: 22.0);
|
||||
case ViewpointStrategy.PathRailSelection:
|
||||
return new PathViewpointProfile(distanceScale: 0.9, minDistanceMeters: 6.0, elevationDegrees: 12.0);
|
||||
case ViewpointStrategy.PathGroundSelection:
|
||||
default:
|
||||
return new PathViewpointProfile(distanceScale: 1.0, minDistanceMeters: 12.0, elevationDegrees: 90.0);
|
||||
}
|
||||
}
|
||||
|
||||
internal static FocusViewpointProfile ResolveFocusViewpointProfile(ViewpointStrategy strategy)
|
||||
{
|
||||
switch (strategy)
|
||||
{
|
||||
case ViewpointStrategy.CollisionCloseUp:
|
||||
return new FocusViewpointProfile(viewAngleDegrees: 60.0, targetViewRatio: 0.25);
|
||||
case ViewpointStrategy.ModelFocus:
|
||||
default:
|
||||
return new FocusViewpointProfile(viewAngleDegrees: 60.0, targetViewRatio: 0.25);
|
||||
}
|
||||
}
|
||||
|
||||
internal static Vector3 ResolveHorizontalPathForward(Vector3 rawPathDirection, Vector3 hostUp, Vector3 fallbackForward)
|
||||
{
|
||||
Vector3 projected = ProjectOntoPlane(rawPathDirection, hostUp);
|
||||
if (projected.LengthSquared() > 1e-8f)
|
||||
{
|
||||
return Vector3.Normalize(projected);
|
||||
}
|
||||
|
||||
Vector3 fallbackProjected = ProjectOntoPlane(fallbackForward, hostUp);
|
||||
if (fallbackProjected.LengthSquared() > 1e-8f)
|
||||
{
|
||||
return Vector3.Normalize(fallbackProjected);
|
||||
}
|
||||
|
||||
Vector3 canonicalFallback = Math.Abs(Vector3.Dot(Vector3.UnitX, hostUp)) > 0.9f
|
||||
? Vector3.UnitY
|
||||
: Vector3.UnitX;
|
||||
return Vector3.Normalize(ProjectOntoPlane(canonicalFallback, hostUp));
|
||||
}
|
||||
|
||||
internal static Vector3 ResolveCameraOffsetDirection(PathType pathType, Vector3 hostUp, Vector3 horizontalForward)
|
||||
{
|
||||
PathViewpointProfile profile = ResolvePathViewpointProfile(pathType);
|
||||
float elevationRadians = (float)(profile.ElevationDegrees * Math.PI / 180.0);
|
||||
float horizontalWeight = (float)Math.Cos(elevationRadians);
|
||||
float verticalWeight = (float)Math.Sin(elevationRadians);
|
||||
|
||||
Vector3 horizontalDirection;
|
||||
switch (pathType)
|
||||
{
|
||||
case PathType.Hoisting:
|
||||
horizontalDirection = -horizontalForward;
|
||||
break;
|
||||
case PathType.Rail:
|
||||
horizontalDirection = Vector3.Cross(hostUp, horizontalForward);
|
||||
if (horizontalDirection.LengthSquared() <= 1e-8f)
|
||||
{
|
||||
horizontalDirection = -horizontalForward;
|
||||
}
|
||||
break;
|
||||
case PathType.Ground:
|
||||
default:
|
||||
horizontalDirection = hostUp;
|
||||
break;
|
||||
}
|
||||
|
||||
Vector3 offset = horizontalDirection * horizontalWeight + hostUp * verticalWeight;
|
||||
return Vector3.Normalize(offset);
|
||||
}
|
||||
|
||||
private static BoundingBox3D CreateBoundingBoxFromPoints(Point3D pointA, Point3D pointB)
|
||||
{
|
||||
return new BoundingBox3D(
|
||||
new Point3D(
|
||||
Math.Min(pointA.X, pointB.X),
|
||||
Math.Min(pointA.Y, pointB.Y),
|
||||
Math.Min(pointA.Z, pointB.Z)),
|
||||
new Point3D(
|
||||
Math.Max(pointA.X, pointB.X),
|
||||
Math.Max(pointA.Y, pointB.Y),
|
||||
Math.Max(pointA.Z, pointB.Z)));
|
||||
}
|
||||
|
||||
private static Point3D GetBoundingBoxCenter(BoundingBox3D boundingBox)
|
||||
{
|
||||
return new Point3D(
|
||||
(boundingBox.Min.X + boundingBox.Max.X) / 2.0,
|
||||
(boundingBox.Min.Y + boundingBox.Max.Y) / 2.0,
|
||||
(boundingBox.Min.Z + boundingBox.Max.Z) / 2.0);
|
||||
}
|
||||
|
||||
private static double GetMaxDimension(BoundingBox3D boundingBox)
|
||||
{
|
||||
return Math.Max(
|
||||
boundingBox.Max.X - boundingBox.Min.X,
|
||||
Math.Max(
|
||||
boundingBox.Max.Y - boundingBox.Min.Y,
|
||||
boundingBox.Max.Z - boundingBox.Min.Z));
|
||||
}
|
||||
|
||||
private static Vector3D ProjectVectorOntoPlane(Vector3D vector, Vector3D planeNormal)
|
||||
{
|
||||
double dot = vector.X * planeNormal.X + vector.Y * planeNormal.Y + vector.Z * planeNormal.Z;
|
||||
return new Vector3D(
|
||||
vector.X - planeNormal.X * dot,
|
||||
vector.Y - planeNormal.Y * dot,
|
||||
vector.Z - planeNormal.Z * dot);
|
||||
}
|
||||
|
||||
private static Vector3 ProjectOntoPlane(Vector3 vector, Vector3 planeNormal)
|
||||
{
|
||||
float dot = Vector3.Dot(vector, planeNormal);
|
||||
return vector - planeNormal * dot;
|
||||
}
|
||||
|
||||
private static Vector3 ToVector3(Vector3D vector)
|
||||
{
|
||||
return new Vector3((float)vector.X, (float)vector.Y, (float)vector.Z);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算所有碰撞位置的总包围盒
|
||||
/// </summary>
|
||||
@ -401,6 +659,12 @@ namespace NavisworksTransport.Utils
|
||||
}
|
||||
}
|
||||
|
||||
public static void FocusOnModelItem(ModelItem modelItem, ViewpointStrategy strategy)
|
||||
{
|
||||
FocusViewpointProfile profile = ResolveFocusViewpointProfile(strategy);
|
||||
FocusOnModelItem(modelItem, profile.ViewAngleDegrees, profile.TargetViewRatio);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 聚焦到多个模型元素(如碰撞中的两个对象)
|
||||
/// </summary>
|
||||
@ -468,6 +732,12 @@ namespace NavisworksTransport.Utils
|
||||
}
|
||||
}
|
||||
|
||||
public static void FocusOnCollision(ModelItem item1, ModelItem item2, ViewpointStrategy strategy)
|
||||
{
|
||||
FocusViewpointProfile profile = ResolveFocusViewpointProfile(strategy);
|
||||
FocusOnCollision(item1, item2, profile.ViewAngleDegrees, profile.TargetViewRatio);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 聚焦到指定位置和尺寸
|
||||
/// </summary>
|
||||
@ -499,6 +769,12 @@ namespace NavisworksTransport.Utils
|
||||
}
|
||||
}
|
||||
|
||||
public static void FocusOnPosition(Point3D center, double targetSize, ViewpointStrategy strategy)
|
||||
{
|
||||
FocusViewpointProfile profile = ResolveFocusViewpointProfile(strategy);
|
||||
FocusOnPosition(center, targetSize, profile.ViewAngleDegrees, profile.TargetViewRatio);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算相机位置(使用模型标准前右上视角)
|
||||
/// 基于 Navisworks 的标准 FrontRightTopViewVector 确定相机方向
|
||||
|
||||
Loading…
Reference in New Issue
Block a user