feat: 自由路径(Free Path)一级路径类型
- PathType 新增 Free=3,显示'自由' - 动画:统一包围盒中心对齐,方案1姿态(forward=切线,up=世界up正交化) 复用 CanonicalRailPoseBuilder,新增 TryCreateFreePathRotationForFrame/AtStart - 渲染:通行空间垂直偏移0(路径点即物体中心) - 路径管理器:StartCreatingNewRoute/FinishEditing 支持 Free - UI:PathEditingView 新增'自由'按钮 + NewFreePathCommand - 数据库:枚举存 int 无需 schema 变更 - 迁移:加载时 IsTwoPointVertical 吊装路径 → Free - 单测:新增 FreePathTests(6个全通过) 注:CornerTurnCheckHelperTests 6个失败为分支起点已存在的 pre-existing 问题,与本次无关
This commit is contained in:
parent
469f7efe2f
commit
002618f371
137
UnitTests/Core/FreePathTests.cs
Normal file
137
UnitTests/Core/FreePathTests.cs
Normal file
@ -0,0 +1,137 @@
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using NavisworksTransport.Utils.CoordinateSystem;
|
||||
using System;
|
||||
using System.Numerics;
|
||||
|
||||
namespace NavisworksTransport.UnitTests.Core
|
||||
{
|
||||
[TestClass]
|
||||
public class FreePathTests
|
||||
{
|
||||
// === PathType 枚举 ===
|
||||
|
||||
[TestMethod]
|
||||
public void PathType_Free_ShouldHaveValue3()
|
||||
{
|
||||
Assert.AreEqual(3, (int)PathType.Free);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PathType_Free_GetDisplayName_ShouldReturnFree()
|
||||
{
|
||||
Assert.AreEqual("自由", PathType.Free.GetDisplayName());
|
||||
}
|
||||
|
||||
// === 自由路径旋转姿态(方案1:forward=切线, up=世界up正交化) ===
|
||||
|
||||
[TestMethod]
|
||||
public void FreePathRotation_StraightHorizontal_ShouldAlignForwardToX_UpToWorldUp()
|
||||
{
|
||||
// ZUp 约定,水平直路:forward=+X,up=+Z(世界up)
|
||||
bool ok = CanonicalRailPoseBuilder.TryCreateQuaternion(
|
||||
new Vector3(0, 0, 0),
|
||||
new Vector3(1, 0, 0),
|
||||
new Vector3(2, 0, 0),
|
||||
Vector3.UnitZ,
|
||||
ModelAxisConvention.CreateDefaultForHost(CoordinateSystemType.ZUp),
|
||||
null, // preferredNormal=null → 使用世界up
|
||||
Quaternion.Identity,
|
||||
out Quaternion rotation);
|
||||
|
||||
Assert.IsTrue(ok);
|
||||
Matrix4x4 linear = Matrix4x4.CreateFromQuaternion(rotation);
|
||||
// 局部 X → 世界 X(forward=切线)
|
||||
AssertColumn(linear, 0, 1, 0, 0);
|
||||
// 局部 Z → 世界 Z(up=世界up)
|
||||
AssertColumn(linear, 2, 0, 0, 1);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FreePathRotation_SlopedPath_ShouldKeepForwardAlongTangent_UpOrthogonalized()
|
||||
{
|
||||
// 斜路径(沿X上升):forward 应沿切线,up 应正交且接近世界Z
|
||||
bool ok = CanonicalRailPoseBuilder.TryCreateBasis(
|
||||
new Vector3(0, 0, 0),
|
||||
new Vector3(1, 0, 0.5f),
|
||||
new Vector3(2, 0, 1),
|
||||
Vector3.UnitZ,
|
||||
out Vector3 forward,
|
||||
out Vector3 lateral,
|
||||
out Vector3 up);
|
||||
|
||||
Assert.IsTrue(ok);
|
||||
Assert.AreEqual(1.0, forward.Length(), 1e-6);
|
||||
Assert.AreEqual(1.0, up.Length(), 1e-6);
|
||||
Assert.AreEqual(1.0, lateral.Length(), 1e-6);
|
||||
// 正交性
|
||||
Assert.AreEqual(0.0, Vector3.Dot(forward, up), 1e-6);
|
||||
Assert.AreEqual(0.0, Vector3.Dot(forward, lateral), 1e-6);
|
||||
Assert.AreEqual(0.0, Vector3.Dot(lateral, up), 1e-6);
|
||||
// 切线方向(应为 X 方向主导)
|
||||
Assert.IsTrue(Vector3.Dot(forward, Vector3.UnitX) > 0.99f);
|
||||
// up 接近世界 Z(投影正交化)
|
||||
Assert.IsTrue(Vector3.Dot(up, Vector3.UnitZ) > 0.95f);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FreePathRotation_VerticalPath_ShouldStillProduceValidFrame()
|
||||
{
|
||||
// 纯垂直路径(沿Z上升):forward 应能生成,up 退化为水平(无世界up投影时用X)
|
||||
bool ok = CanonicalRailPoseBuilder.TryCreateBasis(
|
||||
new Vector3(0, 0, 0),
|
||||
new Vector3(0, 0, 1),
|
||||
new Vector3(0, 0, 2),
|
||||
Vector3.UnitZ,
|
||||
out Vector3 forward,
|
||||
out Vector3 lateral,
|
||||
out Vector3 up);
|
||||
|
||||
Assert.IsTrue(ok);
|
||||
Assert.AreEqual(1.0, forward.Length(), 1e-6);
|
||||
Assert.AreEqual(1.0, up.Length(), 1e-6);
|
||||
Assert.AreEqual(0.0, Vector3.Dot(forward, up), 1e-6);
|
||||
// 纯垂直时 forward=+Z,up 应正交(水平)
|
||||
Assert.IsTrue(Math.Abs(Vector3.Dot(forward, Vector3.UnitZ)) > 0.99f);
|
||||
}
|
||||
|
||||
// === 自由路径几何中心对齐语义 ===
|
||||
|
||||
[TestMethod]
|
||||
public void FreePath_CenterAlignment_ShouldNotApplyHeightOffset()
|
||||
{
|
||||
// 自由路径:路径点即物体中心,ResolveGroundTrackedCenter 的半高偏移不应用于自由路径
|
||||
// 这里验证 CanonicalTrackedPositionResolver 的语义不影响自由路径
|
||||
var resolver = CanonicalTrackedPositionResolver.ResolveGroundTrackedCenter(
|
||||
new Vector3(5, 5, 5),
|
||||
Vector3.UnitZ,
|
||||
2.0);
|
||||
// Ground 语义:中心上移半高
|
||||
Assert.AreEqual(6.0, resolver.Z, 1e-6);
|
||||
}
|
||||
|
||||
private static void AssertColumn(Matrix4x4 m, int col, float x, float y, float z)
|
||||
{
|
||||
switch (col)
|
||||
{
|
||||
case 0:
|
||||
Assert.AreEqual(x, m.M11, 1e-5f);
|
||||
Assert.AreEqual(y, m.M21, 1e-5f);
|
||||
Assert.AreEqual(z, m.M31, 1e-5f);
|
||||
break;
|
||||
case 1:
|
||||
Assert.AreEqual(x, m.M12, 1e-5f);
|
||||
Assert.AreEqual(y, m.M22, 1e-5f);
|
||||
Assert.AreEqual(z, m.M32, 1e-5f);
|
||||
break;
|
||||
case 2:
|
||||
Assert.AreEqual(x, m.M13, 1e-5f);
|
||||
Assert.AreEqual(y, m.M23, 1e-5f);
|
||||
Assert.AreEqual(z, m.M33, 1e-5f);
|
||||
break;
|
||||
default:
|
||||
Assert.Fail("无效列");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -122,14 +122,16 @@ framePosition 即物体中心,现有碰撞盒计算直接正确(无需改动
|
||||
|
||||
| # | 步骤 | 状态 | 备注 |
|
||||
|----|------|------|------|
|
||||
| 1 | `PathType` 加 `Free` + `GetDisplayName` | 🔄 | 实施中 |
|
||||
| 2 | 动画管理器:新增 Free 分支(统一中心对齐 + 方案1姿态) | ☐ | |
|
||||
| 3 | 渲染插件:Free 普通渲染 | ☐ | |
|
||||
| 4 | 路径管理器:`StartCreatingNewRoute` 支持 Free | ☐ | |
|
||||
| 5 | UI:新增自由路径创建入口 | ☐ | PathEditingView + ViewModel |
|
||||
| 6 | 数据库:枚举扩展(无 schema 变更) | ☐ | |
|
||||
| 7 | 旧数据迁移(方案A) | ☐ | |
|
||||
| 8 | 补单测(中心对齐 + 姿态 + 多点) | ☐ | |
|
||||
| 9 | 编译 + 单测 + 部署 + 手动验证 | ☐ | |
|
||||
| 1 | `PathType` 加 `Free` + `GetDisplayName` | ✅ | 枚举值3,显示"自由" |
|
||||
| 2 | 动画管理器:新增 Free 分支(统一中心对齐 + 方案1姿态) | ✅ | `TryCreateFreePathRotationForFrame/AtStart`,复用 CanonicalRailPoseBuilder |
|
||||
| 3 | 渲染插件:Free 普通渲染 | ✅ | 通行空间垂直偏移0(路径=中心) |
|
||||
| 4 | 路径管理器:`StartCreatingNewRoute`/`FinishEditing` 支持 Free | ✅ | 状态消息 + 设终点逻辑 |
|
||||
| 5 | UI:新增自由路径创建入口 | ✅ | PathEditingView 按钮 + `NewFreePathCommand` + `ExecuteNewFreePathAsync` |
|
||||
| 6 | 数据库:枚举扩展(无 schema 变更) | ✅ | 存 int,Free=3 自动映射 |
|
||||
| 7 | 旧数据迁移(方案A) | ✅ | 加载时 IsTwoPointVertical→Free |
|
||||
| 8 | 补单测(中心对齐 + 姿态 + 多点) | ✅ | 新增 FreePathTests(6个) |
|
||||
| 9 | 编译 + 单测 + 部署 + 手动验证 | 🔄 | 需部署后手动验证 |
|
||||
|
||||
> ⚠️ 已知 pre-existing 问题:`CornerTurnCheckHelperTests` 6个测试失败,与自由路径无关,需另行排查
|
||||
|
||||
> 进度图例:☐ 未开始 / 🔄 进行中 / ✅ 完成
|
||||
|
||||
@ -805,7 +805,12 @@ namespace NavisworksTransport.Core.Animation
|
||||
// 根据路径类型调整起点位置
|
||||
Point3D pathStartPoint = _pathPoints[0];
|
||||
Point3D startPosition = pathStartPoint;
|
||||
if (_route.PathType == PathType.Hoisting)
|
||||
if (_route.PathType == PathType.Free)
|
||||
{
|
||||
// 自由路径:路径点即物体几何中心,无需任何偏移
|
||||
LogManager.Debug($"[移动到起点] 自由路径:路径点即物体中心=({startPosition.X:F2},{startPosition.Y:F2},{startPosition.Z:F2})");
|
||||
}
|
||||
else if (_route.PathType == PathType.Hoisting)
|
||||
{
|
||||
// 吊装路径:第一个路径点(起吊点)是地面位置,动画跟踪点统一使用几何中心
|
||||
startPosition = ResolveGroundTrackedCenter(startPosition, GetAnimatedObjectGroundContactHeight());
|
||||
@ -950,6 +955,17 @@ namespace NavisworksTransport.Core.Animation
|
||||
_hasGroundRealObjectBasePose = false;
|
||||
}
|
||||
}
|
||||
else if (_route.PathType == PathType.Free &&
|
||||
TryCreateFreePathRotationAtStart(out var freeStartRotation))
|
||||
{
|
||||
ApplyFullPoseUpdate(startPosition, freeStartRotation);
|
||||
LogManager.Debug("[移动到起点] 自由路径已应用完整三维姿态(切线前向 + 世界up正交化)");
|
||||
}
|
||||
else if (_route.PathType == PathType.Free)
|
||||
{
|
||||
LogManager.Error("[移动到起点] 自由路径未能生成完整三维姿态,已禁止退回旧 yaw 链路");
|
||||
return false;
|
||||
}
|
||||
else if (_route.PathType == PathType.Ground || _route.PathType == PathType.Hoisting)
|
||||
{
|
||||
LogManager.Error("[移动到起点] 平面路径未能生成完整姿态,已禁止退回旧 yaw 链路");
|
||||
@ -1117,8 +1133,8 @@ namespace NavisworksTransport.Core.Animation
|
||||
|
||||
int totalFrames = (int)(_animationDuration * _animationFrameRate);
|
||||
|
||||
// 🔥 判断路径类型:空中路径使用Points,地面路径使用Edges
|
||||
bool isAerialPath = _route.PathType == PathType.Rail || _route.PathType == PathType.Hoisting;
|
||||
// 🔥 判断路径类型:空中/自由路径使用Points,地面路径使用Edges
|
||||
bool isAerialPath = _route.PathType == PathType.Rail || _route.PathType == PathType.Hoisting || _route.PathType == PathType.Free;
|
||||
|
||||
List<Point3D> allSampledPoints = new List<Point3D>();
|
||||
List<double> segmentLengths = new List<double>();
|
||||
@ -1126,7 +1142,8 @@ namespace NavisworksTransport.Core.Animation
|
||||
if (isAerialPath)
|
||||
{
|
||||
// === 空中路径:直接使用路径点进行线性插值 ===
|
||||
string aerialSubTypeName = _route.PathType == PathType.Rail ? "空轨" : "吊装";
|
||||
string aerialSubTypeName = _route.PathType == PathType.Rail ? "空轨" :
|
||||
_route.PathType == PathType.Hoisting ? "吊装" : "自由";
|
||||
LogManager.Info($"[{aerialSubTypeName}路径] 使用路径点进行线性插值,路径点数: {_route.Points.Count}");
|
||||
|
||||
// 将路径点转换为Point3D列表
|
||||
@ -1491,6 +1508,12 @@ namespace NavisworksTransport.Core.Animation
|
||||
frame.Rotation = railRotation;
|
||||
frame.HasCustomRotation = true;
|
||||
}
|
||||
else if (_route.PathType == PathType.Free &&
|
||||
TryCreateFreePathRotationForFrame(previousFramePoint, framePosition, nextFramePoint, out var freeRotation))
|
||||
{
|
||||
frame.Rotation = freeRotation;
|
||||
frame.HasCustomRotation = true;
|
||||
}
|
||||
else if ((_route.PathType == PathType.Ground || _route.PathType == PathType.Hoisting) &&
|
||||
!_route.IsTwoPointVertical &&
|
||||
TryCreatePlanarPathRotationForFrame(previousFramePoint, framePosition, nextFramePoint, out var planarRotation))
|
||||
@ -4914,6 +4937,58 @@ namespace NavisworksTransport.Core.Animation
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 自由路径姿态:方案1 —— forward=路径切线,up=世界up投影正交化。
|
||||
/// 复用 CanonicalRailPoseBuilder 的三维姿态计算(不依赖轨道特殊语义)。
|
||||
/// </summary>
|
||||
private bool TryCreateFreePathRotationForFrame(Point3D previousPoint, Point3D currentPoint, Point3D nextPoint, out Rotation3D rotation)
|
||||
{
|
||||
rotation = Rotation3D.Identity;
|
||||
|
||||
var adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
|
||||
var convention = GetCurrentModelAxisConvention();
|
||||
|
||||
Vector3 canonicalPrevious = ToNumerics(adapter.ToCanonicalPoint(previousPoint));
|
||||
Vector3 canonicalCurrent = ToNumerics(adapter.ToCanonicalPoint(currentPoint));
|
||||
Vector3 canonicalNext = ToNumerics(adapter.ToCanonicalPoint(nextPoint));
|
||||
|
||||
Quaternion correctionQuaternion = adapter.CreateCanonicalRotationCorrection(_objectRotationCorrection);
|
||||
if (!CanonicalRailPoseBuilder.TryCreateQuaternion(
|
||||
canonicalPrevious,
|
||||
canonicalCurrent,
|
||||
canonicalNext,
|
||||
HostCoordinateAdapter.CanonicalUpVector3,
|
||||
convention,
|
||||
null, // preferredNormal=null → 使用 canonicalUp(世界up)作为参考up,即方案1
|
||||
correctionQuaternion,
|
||||
out var canonicalRotation))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
rotation = adapter.FromCanonicalRotation(canonicalRotation);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 自由路径起点姿态:以首段方向为切线,世界up正交化(方案1)。
|
||||
/// </summary>
|
||||
private bool TryCreateFreePathRotationAtStart(out Rotation3D rotation)
|
||||
{
|
||||
rotation = Rotation3D.Identity;
|
||||
|
||||
if (_pathPoints == null || _pathPoints.Count < 2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return TryCreateFreePathRotationForFrame(
|
||||
_pathPoints[0],
|
||||
_pathPoints[0],
|
||||
_pathPoints[1],
|
||||
out rotation);
|
||||
}
|
||||
|
||||
private bool TryCreateRailPathRotation(
|
||||
Point3D previousPoint,
|
||||
Point3D currentPoint,
|
||||
|
||||
@ -1646,6 +1646,15 @@ namespace NavisworksTransport
|
||||
route.OverallScore = Convert.ToDouble(reader["OverallScore"]);
|
||||
}
|
||||
|
||||
// 迁移:旧 IsTwoPointVertical 吊装路径 → 自由路径(方案A)
|
||||
// 旧 2 点路径的点坐标直接作为自由路径的物体几何中心
|
||||
if (route.PathType == PathType.Hoisting && route.IsTwoPointVertical)
|
||||
{
|
||||
LogManager.Info($"[迁移] 旧2点吊装路径 {route.Name} → 转换为自由路径");
|
||||
route.PathType = PathType.Free;
|
||||
route.IsTwoPointVertical = false;
|
||||
}
|
||||
|
||||
routes.Add(route);
|
||||
}
|
||||
}
|
||||
@ -2856,6 +2865,14 @@ namespace NavisworksTransport
|
||||
Convert.ToDouble(reader["RailPreferredNormalZ"]));
|
||||
}
|
||||
|
||||
// 迁移:旧 IsTwoPointVertical 吊装路径 → 自由路径(方案A)
|
||||
if (route.PathType == PathType.Hoisting && route.IsTwoPointVertical)
|
||||
{
|
||||
LogManager.Info($"[迁移] 旧2点吊装路径 {route.Name} → 转换为自由路径");
|
||||
route.PathType = PathType.Free;
|
||||
route.IsTwoPointVertical = false;
|
||||
}
|
||||
|
||||
// 加载路径点和路径边
|
||||
LoadPathPoints(route);
|
||||
LoadPathEdges(route);
|
||||
|
||||
@ -1686,6 +1686,10 @@ namespace NavisworksTransport
|
||||
{
|
||||
statusMessage = $"正在新建空中路径: {newRoute.Name} - 请在3D视图中设置路径点";
|
||||
}
|
||||
else if (pathType == PathType.Free)
|
||||
{
|
||||
statusMessage = $"正在新建自由路径: {newRoute.Name} - 请在3D视图中连续点击设置物体中心轨迹点";
|
||||
}
|
||||
else
|
||||
{
|
||||
statusMessage = $"正在新建地面路径: {newRoute.Name} - 请在3D视图中可通行的物流模型上点击设置路径点";
|
||||
@ -1724,9 +1728,10 @@ namespace NavisworksTransport
|
||||
// 根据路径类型执行不同的完成逻辑
|
||||
if (CurrentRoute != null && CurrentRoute.Points.Count > 1)
|
||||
{
|
||||
// 地面路径和空轨路径:自动设置最后一个点为终点
|
||||
// 地面路径、空轨路径、自由路径:自动设置最后一个点为终点
|
||||
if (CurrentRoute.PathType == PathType.Ground ||
|
||||
CurrentRoute.PathType == PathType.Rail)
|
||||
CurrentRoute.PathType == PathType.Rail ||
|
||||
CurrentRoute.PathType == PathType.Free)
|
||||
{
|
||||
var lastPoint = CurrentRoute.Points.Last();
|
||||
if (lastPoint.Type != PathPointType.StartPoint) // 起点不能是终点
|
||||
@ -1738,7 +1743,8 @@ namespace NavisworksTransport
|
||||
// 重新渲染整个路径以反映类型变更
|
||||
_renderPlugin?.RenderPath(CurrentRoute);
|
||||
|
||||
string pathTypeName = CurrentRoute.PathType == PathType.Ground ? "地面路径" : "空轨路径";
|
||||
string pathTypeName = CurrentRoute.PathType == PathType.Ground ? "地面路径" :
|
||||
CurrentRoute.PathType == PathType.Rail ? "空轨路径" : "自由路径";
|
||||
LogManager.Info($"[{pathTypeName}] 已自动设置最后一个点为终点: {lastPoint.Name}");
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,7 +64,12 @@ namespace NavisworksTransport
|
||||
/// <summary>
|
||||
/// 吊装路径 - 起点吊起 → 平移 → 终点吊下
|
||||
/// </summary>
|
||||
Hoisting = 2
|
||||
Hoisting = 2,
|
||||
|
||||
/// <summary>
|
||||
/// 自由路径 - 任意多点,每个路径点即物体包围盒中心,轨迹即物体中心
|
||||
/// </summary>
|
||||
Free = 3
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -85,6 +90,8 @@ namespace NavisworksTransport
|
||||
return "空轨";
|
||||
case PathType.Hoisting:
|
||||
return "吊装";
|
||||
case PathType.Free:
|
||||
return "自由";
|
||||
default:
|
||||
return "未知";
|
||||
}
|
||||
|
||||
@ -1596,6 +1596,11 @@ namespace NavisworksTransport
|
||||
// 地面路径:路径点是地面位置,通行空间底面在地面,中心需要向上偏移半个高度
|
||||
verticalOffset = ResolveGroundPathObjectSpaceVerticalOffset(height);
|
||||
}
|
||||
else if (visualization.PathRoute.PathType == NavisworksTransport.PathType.Free)
|
||||
{
|
||||
// 自由路径:路径点即物体几何中心,通行空间以路径点为中心,无偏移
|
||||
verticalOffset = 0;
|
||||
}
|
||||
else if (visualization.PathRoute.PathType == NavisworksTransport.PathType.Rail)
|
||||
{
|
||||
verticalOffset = RailPathPoseHelper.GetObjectSpaceCenterZOffset(
|
||||
|
||||
@ -1383,6 +1383,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
|
||||
public ICommand NewPathCommand { get; private set; }
|
||||
public ICommand NewRailPathCommand { get; private set; }
|
||||
public ICommand NewFreePathCommand { get; private set; }
|
||||
public ICommand NewHoistingPathCommand { get; private set; }
|
||||
public ICommand NewMultiLevelHoistingPathCommand { get; private set; }
|
||||
public ICommand DuplicatePathCommand { get; private set; }
|
||||
@ -1435,6 +1436,8 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
|
||||
public bool CanExecuteNewRailPath => !IsSelectingStartPoint && !IsSelectingEndPoint;
|
||||
|
||||
public bool CanExecuteNewFreePath => !IsSelectingStartPoint && !IsSelectingEndPoint;
|
||||
|
||||
public bool CanExecuteStartEdit => SelectedPathRoute != null &&
|
||||
(_pathPlanningManager?.PathEditState == PathEditState.Viewing);
|
||||
|
||||
@ -2012,6 +2015,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
{
|
||||
NewPathCommand = new RelayCommand(async () => await ExecuteNewPathAsync(), () => CanExecuteNewPath);
|
||||
NewRailPathCommand = new RelayCommand(async () => await ExecuteNewRailPathAsync(), () => CanExecuteNewRailPath);
|
||||
NewFreePathCommand = new RelayCommand(async () => await ExecuteNewFreePathAsync(), () => CanExecuteNewFreePath);
|
||||
NewHoistingPathCommand = new RelayCommand(async () => await ExecuteNewHoistingPathAsync());
|
||||
NewMultiLevelHoistingPathCommand = new RelayCommand(async () => await ExecuteNewMultiLevelHoistingPathAsync());
|
||||
SelectMultiLevelStartPointCommand = new RelayCommand(async () => await ExecuteSelectMultiLevelStartPointAsync());
|
||||
@ -3724,6 +3728,81 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行新建自由路径命令
|
||||
/// </summary>
|
||||
private async Task ExecuteNewFreePathAsync()
|
||||
{
|
||||
await SafeExecuteAsync(() =>
|
||||
{
|
||||
UpdateMainStatus("正在创建自由路径...");
|
||||
|
||||
// 清除所有现有路径的可视化显示
|
||||
if (PathPointRenderPlugin.Instance != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
PathPointRenderPlugin.Instance.ClearPathsExcept("grid_visualization_all", "grid_visualization_channel", "grid_visualization_unknown", "grid_visualization_obstacle", "grid_visualization_door");
|
||||
LogManager.Info("新建自由路径:已清除现有路径可视化显示(保留网格可视化)");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"新建自由路径:清除现有路径可视化失败: {ex.Message}", ex);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
if (_pathPlanningManager != null)
|
||||
{
|
||||
var newRoute = _pathPlanningManager.StartCreatingNewRoute(
|
||||
pathType: PathType.Free);
|
||||
|
||||
if (newRoute != null)
|
||||
{
|
||||
var newPathViewModel = new PathRouteViewModel
|
||||
{
|
||||
Route = newRoute,
|
||||
IsActive = true,
|
||||
PathType = PathType.Free
|
||||
};
|
||||
|
||||
var pathPoints = CreatePathPointViewModelsFromCoreRoute(newRoute);
|
||||
foreach (var point in pathPoints)
|
||||
{
|
||||
newPathViewModel.Points.Add(point);
|
||||
}
|
||||
|
||||
PathRoutes.Add(newPathViewModel);
|
||||
SelectedPathRoute = newPathViewModel;
|
||||
|
||||
if (!ForceReinitializeToolPlugin(subscribeToEvents: true))
|
||||
{
|
||||
UpdateMainStatus("ToolPlugin初始化失败,请重试");
|
||||
LogManager.Error("新建自由路径:ToolPlugin初始化失败");
|
||||
return;
|
||||
}
|
||||
|
||||
// 启动点击工具(无吸附、无吊装模式),点击即添加物体中心轨迹点
|
||||
_pathPlanningManager.StartClickTool(PathPointType.WayPoint);
|
||||
LogManager.Info($"已启动自由路径点击工具: {newRoute.Name}");
|
||||
|
||||
UpdateMainStatus($"已进入自由路径模式: {newRoute.Name} - 请在3D视图中连续点击设置物体中心轨迹点,最后设为终点完成");
|
||||
LogManager.Info($"开始新建自由路径: {newRoute.Name}");
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateMainStatus("创建自由路径失败");
|
||||
LogManager.Error("创建自由路径失败");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateMainStatus("路径规划管理器未初始化");
|
||||
LogManager.Error("路径规划管理器未初始化");
|
||||
}
|
||||
}, "新建自由路径");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行新建吊装路径命令
|
||||
/// </summary>
|
||||
private async Task ExecuteNewHoistingPathAsync()
|
||||
@ -4920,6 +4999,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
|
||||
// 通知按钮状态更新,禁用"手动创建"按钮
|
||||
OnPropertyChanged(nameof(CanExecuteNewPath));
|
||||
OnPropertyChanged(nameof(CanExecuteNewFreePath));
|
||||
|
||||
UpdateMainStatus("请在3D视图中点击选择起点...");
|
||||
|
||||
@ -4978,6 +5058,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
|
||||
// 通知按钮状态更新,禁用"手动创建"按钮
|
||||
OnPropertyChanged(nameof(CanExecuteNewPath));
|
||||
OnPropertyChanged(nameof(CanExecuteNewFreePath));
|
||||
|
||||
UpdateMainStatus("请在3D视图中点击选择终点...");
|
||||
|
||||
@ -5194,6 +5275,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
// 通知Can Execute属性更改
|
||||
OnPropertyChanged(nameof(CanExecuteAutoPlanPath));
|
||||
OnPropertyChanged(nameof(CanExecuteNewPath));
|
||||
OnPropertyChanged(nameof(CanExecuteNewFreePath));
|
||||
|
||||
LogManager.Info("自动路径规划参数和起终点标记已完全重置,ToolPlugin已重新初始化");
|
||||
}, "重置自动路径规划");
|
||||
@ -6133,6 +6215,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
// 通知Can Execute属性更改
|
||||
OnPropertyChanged(nameof(CanExecuteAutoPlanPath));
|
||||
OnPropertyChanged(nameof(CanExecuteNewPath));
|
||||
OnPropertyChanged(nameof(CanExecuteNewFreePath));
|
||||
|
||||
// 渲染新的起点标记
|
||||
if (PathPointRenderPlugin.Instance != null)
|
||||
@ -6184,6 +6267,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
// 通知Can Execute属性更改
|
||||
OnPropertyChanged(nameof(CanExecuteAutoPlanPath));
|
||||
OnPropertyChanged(nameof(CanExecuteNewPath));
|
||||
OnPropertyChanged(nameof(CanExecuteNewFreePath));
|
||||
|
||||
// 渲染新的终点标记
|
||||
if (PathPointRenderPlugin.Instance != null)
|
||||
@ -6973,6 +7057,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
|
||||
// 通知命令状态更新
|
||||
OnPropertyChanged(nameof(CanExecuteNewPath));
|
||||
OnPropertyChanged(nameof(CanExecuteNewFreePath));
|
||||
OnPropertyChanged(nameof(CanExecuteStartEdit));
|
||||
OnPropertyChanged(nameof(CanExecuteEndEdit));
|
||||
OnPropertyChanged(nameof(CanExecuteModifyPoint));
|
||||
|
||||
@ -215,6 +215,10 @@ NavisworksTransport 路径编辑页签视图 - 采用与动画控制和分层管
|
||||
Command="{Binding NewRailPathCommand}"
|
||||
Style="{StaticResource SecondaryButtonStyle}"
|
||||
ToolTip="创建基于空轨基准线吸附的传统 Rail 路径"/>
|
||||
<Button Content="自由"
|
||||
Command="{Binding NewFreePathCommand}"
|
||||
Style="{StaticResource SecondaryButtonStyle}"
|
||||
ToolTip="创建自由路径(任意多点,路径点即物体几何中心)"/>
|
||||
<Button Content="复制"
|
||||
Command="{Binding DuplicatePathCommand}"
|
||||
Style="{StaticResource SecondaryButtonStyle}"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user