diff --git a/NavisworksTransportPlugin.csproj b/NavisworksTransportPlugin.csproj
index 1098c34..976af06 100644
--- a/NavisworksTransportPlugin.csproj
+++ b/NavisworksTransportPlugin.csproj
@@ -145,6 +145,8 @@
+
+
@@ -183,6 +185,7 @@
+
LogisticsControlPanel.xaml
@@ -232,6 +235,9 @@
EditCoordinatesWindow.xaml
+
+ HoistingHeightDialog.xaml
+
@@ -345,6 +351,10 @@
Designer
MSBuild:Compile
+
+ Designer
+ MSBuild:Compile
+
Designer
diff --git a/src/Commands/CommandManager.cs b/src/Commands/CommandManager.cs
index e9f842c..2f9662a 100644
--- a/src/Commands/CommandManager.cs
+++ b/src/Commands/CommandManager.cs
@@ -302,7 +302,7 @@ namespace NavisworksTransport.Commands
{
if (args.Length < 1 || !(args[0] is PathRoute route))
throw new ArgumentException("StartAnimation 需要 PathRoute 参数");
-
+
var parameters = new StartAnimationParameters
{
TargetRoute = route,
@@ -310,10 +310,19 @@ namespace NavisworksTransport.Commands
Loop = args.Length > 2 && args[2] is bool v1 && v1,
AutoStart = args.Length <= 3 || !(args[3] is bool v2) || v2
};
-
+
return new StartAnimationCommand(parameters);
});
+ // 6. 注册创建吊装路径命令
+ RegisterCommand("CreateHoistingPath", (object[] args) =>
+ {
+ if (args.Length < 1 || !(args[0] is HoistingPathParameters parameters))
+ throw new ArgumentException("CreateHoistingPath 需要 HoistingPathParameters 参数");
+
+ return new CreateHoistingPathCommand(parameters);
+ });
+
LogManager.Info("新的业务Commands注册完成");
}
diff --git a/src/Commands/CreateHoistingPathCommand.cs b/src/Commands/CreateHoistingPathCommand.cs
new file mode 100644
index 0000000..c69f6b2
--- /dev/null
+++ b/src/Commands/CreateHoistingPathCommand.cs
@@ -0,0 +1,110 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Autodesk.Navisworks.Api;
+using NavisworksTransport.Core;
+using NavisworksTransport.PathPlanning;
+using NavisworksTransport.Utils;
+using NavisworksTransport.Core.Config;
+
+namespace NavisworksTransport.Commands
+{
+ ///
+ /// 创建吊装路径命令
+ ///
+ public class CreateHoistingPathCommand : CommandBase
+ {
+ private readonly HoistingPathParameters _parameters;
+ private readonly PathPlanningManager _pathPlanningManager;
+
+ ///
+ /// 构造函数
+ ///
+ /// 吊装路径参数
+ public CreateHoistingPathCommand(HoistingPathParameters parameters)
+ {
+ _parameters = parameters ?? throw new ArgumentNullException(nameof(parameters));
+ _pathPlanningManager = PathPlanningManager.Instance;
+ }
+
+ ///
+ /// 执行命令
+ ///
+ protected override Task ExecuteInternalAsync(CancellationToken cancellationToken)
+ {
+ try
+ {
+ // 验证参数
+ var validationResult = ValidateParameters();
+ if (!validationResult.IsSuccess)
+ {
+ return Task.FromResult(validationResult);
+ }
+
+ LogManager.Info("开始创建吊装路径...");
+
+ // 创建路径生成器
+ var generator = new HoistingPathGenerator();
+ var pathPoints = generator.GenerateHoistingPath(
+ _parameters.EndPoint,
+ _parameters.StartHeightMeters);
+
+ var startPoint = pathPoints[0];
+ var endPoint = pathPoints[1];
+
+ LogManager.Info($"吊装路径: 起点=({startPoint.X:F2},{startPoint.Y:F2},{startPoint.Z:F2}), 终点=({endPoint.X:F2},{endPoint.Y:F2},{endPoint.Z:F2}), 起点高度={_parameters.StartHeightMeters:F2}米");
+
+ // 创建路径对象
+ var route = new PathRoute($"吊装路径_{DateTime.Now:HHmmss}")
+ {
+ PathType = PathType.Hoisting,
+ Points = pathPoints,
+ CreatedTime = DateTime.Now,
+ LastModified = DateTime.Now
+ };
+
+ // 设置车辆参数(从配置获取)
+ var config = ConfigManager.Instance.Current;
+ route.MaxVehicleLength = config.PathEditing.VehicleLengthMeters;
+ route.MaxVehicleWidth = config.PathEditing.VehicleWidthMeters;
+ route.MaxVehicleHeight = config.PathEditing.VehicleHeightMeters;
+ route.SafetyMargin = config.PathEditing.SafetyMarginMeters;
+
+ // 计算总长度
+ double distanceInModelUnits = GeometryHelper.CalculatePointDistance(
+ new Point3D(startPoint.X, startPoint.Y, startPoint.Z),
+ new Point3D(endPoint.X, endPoint.Y, endPoint.Z));
+ route.TotalLength = UnitsConverter.ConvertToMeters(distanceInModelUnits);
+
+ LogManager.Info($"吊装路径创建成功: {route.Name}, 路径点数: {pathPoints.Count}, 总长度: {route.TotalLength:F2}米");
+
+ // 保存路径到数据库
+ _pathPlanningManager.AddRoute(route);
+
+ var genericResult = PathPlanningResult.Success(route, "吊装路径创建成功");
+ return Task.FromResult(genericResult);
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"创建吊装路径失败: {ex.Message}", ex);
+ var genericResult = PathPlanningResult.Failure($"创建吊装路径失败: {ex.Message}", ex);
+ return Task.FromResult(genericResult);
+ }
+ }
+
+ ///
+ /// 验证参数
+ ///
+ protected override PathPlanningResult ValidateParameters()
+ {
+ if (_parameters.EndPoint == null)
+ return PathPlanningResult.ValidationFailure("终点不能为空");
+
+ if (_parameters.StartHeightMeters <= 0)
+ return PathPlanningResult.ValidationFailure("起点高度必须大于0");
+
+ return PathPlanningResult.Success("参数验证通过");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Commands/HoistingPathParameters.cs b/src/Commands/HoistingPathParameters.cs
new file mode 100644
index 0000000..d050afd
--- /dev/null
+++ b/src/Commands/HoistingPathParameters.cs
@@ -0,0 +1,21 @@
+using Autodesk.Navisworks.Api;
+
+namespace NavisworksTransport.Commands
+{
+ ///
+ /// 吊装路径参数
+ ///
+ public class HoistingPathParameters
+ {
+ ///
+ /// 终点(下层目标位置)
+ ///
+ public Point3D EndPoint { get; set; }
+
+ ///
+ /// 起点高度(米,相对于终点向上)
+ /// 如果为0,则使用默认高度(3米)
+ ///
+ public double StartHeightMeters { get; set; } = 3.0;
+ }
+}
\ No newline at end of file
diff --git a/src/Core/Animation/PathAnimationManager.cs b/src/Core/Animation/PathAnimationManager.cs
index d12d6c6..f6f8a3d 100644
--- a/src/Core/Animation/PathAnimationManager.cs
+++ b/src/Core/Animation/PathAnimationManager.cs
@@ -411,10 +411,15 @@ namespace NavisworksTransport.Core.Animation
// 计算朝向(使用前两个路径点的方向)
double yaw = Math.Atan2(_pathPoints[1].Y - _pathPoints[0].Y, _pathPoints[1].X - _pathPoints[0].X);
-
+
// 根据路径类型调整起点位置
Point3D startPosition = _pathPoints[0];
- if (_route?.PathType == PathType.Rail)
+ if (_route?.PathType == PathType.Hoisting)
+ {
+ // 吊装路径:路径点就是设备中心位置,无需调整
+ LogManager.Debug($"[移动到起点] 吊装路径: pos=({startPosition.X:F2},{startPosition.Y:F2},{startPosition.Z:F2})");
+ }
+ else if (_route?.PathType == PathType.Rail)
{
// 空轨路径:points[0] 是空轨下表面位置,车辆顶面应该在这里
// 所以车辆底面 = points[0] - 车辆高度
@@ -431,7 +436,15 @@ namespace NavisworksTransport.Core.Animation
// 使用 UpdateObjectPosition 统一处理移动和旋转
UpdateObjectPosition(startPosition, yaw);
- LogManager.Info($"物体已初始化到路径起点并对齐朝向: pos=({startPosition.X:F2},{startPosition.Y:F2},{startPosition.Z:F2}), yaw={yaw:F3}rad, 路径类型={(_route?.PathType == PathType.Rail ? "空轨" : "地面")}");
+ string pathTypeName;
+ if (_route?.PathType == PathType.Hoisting)
+ pathTypeName = "吊装";
+ else if (_route?.PathType == PathType.Rail)
+ pathTypeName = "空轨";
+ else
+ pathTypeName = "地面";
+
+ LogManager.Info($"物体已初始化到路径起点并对齐朝向: pos=({startPosition.X:F2},{startPosition.Y:F2},{startPosition.Z:F2}), yaw={yaw:F3}rad, 路径类型={pathTypeName}");
}
catch (Exception ex)
{
@@ -450,13 +463,33 @@ namespace NavisworksTransport.Core.Animation
int totalFrames = (int)(_animationDuration * _animationFrameRate);
- // 🔥 判断路径类型:空轨路径使用Points,地面路径使用Edges
+ // 🔥 判断路径类型:吊装路径、空轨路径使用Points,地面路径使用Edges
+ bool isHoistingPath = _route.PathType == PathType.Hoisting;
bool isRailPath = _route.PathType == PathType.Rail;
List allSampledPoints = new List();
List segmentLengths = new List();
- if (isRailPath)
+ if (isHoistingPath)
+ {
+ // === 吊装路径:直接使用路径点进行线性插值 ===
+ LogManager.Info($"[吊装路径] 使用路径点进行线性插值,路径点数: {_route.Points.Count}");
+
+ // 将路径点转换为Point3D列表
+ foreach (var point in _route.Points)
+ {
+ allSampledPoints.Add(point.Position);
+ }
+
+ // 计算每段长度
+ for (int i = 0; i < allSampledPoints.Count - 1; i++)
+ {
+ double lengthInModelUnits = GeometryHelper.CalculatePointDistance(allSampledPoints[i], allSampledPoints[i + 1]);
+ double lengthInMeters = UnitsConverter.ConvertToMeters(lengthInModelUnits);
+ segmentLengths.Add(lengthInMeters);
+ }
+ }
+ else if (isRailPath)
{
// === 空轨路径:直接使用路径点进行线性插值 ===
LogManager.Info($"[空轨路径] 使用路径点进行线性插值,路径点数: {_route.Points.Count}");
@@ -495,7 +528,15 @@ namespace NavisworksTransport.Core.Animation
}
double totalLength = _route.TotalLength;
- LogManager.Info($"路径总长度: {totalLength:F2}米, 采样点数: {allSampledPoints.Count}, 路径类型: {(isRailPath ? "空轨" : "地面")}");
+ string pathTypeName;
+ if (_route.PathType == PathType.Hoisting)
+ pathTypeName = "吊装";
+ else if (_route.PathType == PathType.Rail)
+ pathTypeName = "空轨";
+ else
+ pathTypeName = "地面";
+
+ LogManager.Info($"路径总长度: {totalLength:F2}米, 采样点数: {allSampledPoints.Count}, 路径类型: {pathTypeName}");
// 2. 按帧数采样生成动画帧
_animationFrames = new List();
@@ -579,7 +620,35 @@ namespace NavisworksTransport.Core.Animation
Point3D framePosition;
double yawRadians;
- if (isRailPath)
+ if (isHoistingPath)
+ {
+ // === 吊装路径:在路径点之间进行线性插值 ===
+ int segmentIndex = FindSegmentForDistance(targetDistance, segmentLengths);
+ if (segmentIndex < 0 || segmentIndex >= segmentLengths.Count)
+ {
+ LogManager.Warning($"[吊装路径] 无法找到线段,目标距离:{targetDistance:F2}米");
+ continue;
+ }
+
+ double accumulatedLength = segmentLengths.Take(segmentIndex).Sum();
+ double segmentProgress = (targetDistance - accumulatedLength) / segmentLengths[segmentIndex];
+
+ // 线性插值
+ Point3D p1 = allSampledPoints[segmentIndex];
+ Point3D p2 = allSampledPoints[segmentIndex + 1];
+ framePosition = new Point3D(
+ p1.X + segmentProgress * (p2.X - p1.X),
+ p1.Y + segmentProgress * (p2.Y - p1.Y),
+ p1.Z + segmentProgress * (p2.Z - p1.Z)
+ );
+
+ // 计算朝向(使用当前线段的方向)
+ yawRadians = Math.Atan2(p2.Y - p1.Y, p2.X - p1.X);
+
+ // 吊装路径:路径点就是设备中心位置,无需调整
+ LogManager.Debug($"[吊装路径] 设备位置: ({framePosition.X:F2},{framePosition.Y:F2},{framePosition.Z:F2})");
+ }
+ else if (isRailPath)
{
// === 空轨路径:在路径点之间进行线性插值 ===
int segmentIndex = FindSegmentForDistance(targetDistance, segmentLengths);
@@ -691,8 +760,21 @@ namespace NavisworksTransport.Core.Animation
);
}
+ // 吊装路径:排除下层楼面(不排除孔洞,需要检测设备能否通过孔洞)
+ ModelItem lowerFloorModel = null;
+ if (_route?.PathType == PathType.Hoisting)
+ {
+ lowerFloorModel = _route.LowerFloorModel;
+ }
+
foreach (var collider in nearbyObjects)
{
+ // 吊装路径:跳过下层楼面模型
+ if (lowerFloorModel != null && ReferenceEquals(collider, lowerFloorModel))
+ {
+ continue;
+ }
+
var colliderBox = collider.BoundingBox();
// 使用包围盒距离检测方法
diff --git a/src/Core/PathCurveEngine.cs b/src/Core/PathCurveEngine.cs
index 4c98dc0..9a7da6c 100644
--- a/src/Core/PathCurveEngine.cs
+++ b/src/Core/PathCurveEngine.cs
@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Autodesk.Navisworks.Api;
+using NavisworksTransport.Utils;
namespace NavisworksTransport
{
@@ -503,7 +504,7 @@ namespace NavisworksTransport
return;
}
- route.TotalLength = route.Edges.Sum(e => e.PhysicalLength);
+ route.TotalLength = UnitsConverter.ConvertToMeters(route.Edges.Sum(e => e.PhysicalLength));
}
}
}
\ No newline at end of file
diff --git a/src/Core/PathPlanningManager.cs b/src/Core/PathPlanningManager.cs
index fe238fd..e9c28d2 100644
--- a/src/Core/PathPlanningManager.cs
+++ b/src/Core/PathPlanningManager.cs
@@ -50,6 +50,9 @@ namespace NavisworksTransport
// 空轨吸附模式标志
private bool _enableRailSnapping = false;
+ // 吊装模式标志
+ private bool _enableHoistingMode = false;
+
// 路径点3D标记管理
private List _pathPointMarkers;
@@ -339,6 +342,11 @@ namespace NavisworksTransport
[Obsolete("请使用新的强类型事件接口")]
public event EventHandler PathPointsListUpdated_Legacy;
+ ///
+ /// 吊装终点选择事件
+ ///
+ public event EventHandler HoistingEndPointSelected;
+
#endregion
#region 事件触发方法
@@ -852,7 +860,7 @@ namespace NavisworksTransport
/// 点击类型
public void StartClickTool(PathPointType pointType)
{
- StartClickTool(pointType, enableRailSnapping: false);
+ StartClickTool(pointType, enableRailSnapping: false, enableHoistingMode: false);
}
///
@@ -861,19 +869,31 @@ namespace NavisworksTransport
/// 点击类型
/// 是否启用空轨吸附
public void StartClickTool(PathPointType pointType, bool enableRailSnapping)
+ {
+ StartClickTool(pointType, enableRailSnapping, enableHoistingMode: false);
+ }
+
+ ///
+ /// 启动点击工具(支持空轨吸附和吊装模式)
+ ///
+ /// 点击类型
+ /// 是否启用空轨吸附
+ /// 是否启用吊装模式
+ public void StartClickTool(PathPointType pointType, bool enableRailSnapping, bool enableHoistingMode)
{
try
{
CurrentPointType = pointType;
_enableRailSnapping = enableRailSnapping;
+ _enableHoistingMode = enableHoistingMode;
// 检查是否在自动路径模式 - 如果是,则不订阅PathPlanningManager的事件
bool shouldSubscribeToEvents = !IsInAutoPathMode;
- LogManager.Info($"StartClickTool - 自动路径模式: {IsInAutoPathMode}, 订阅事件: {shouldSubscribeToEvents}, 空轨吸附: {enableRailSnapping}");
+ LogManager.Info($"StartClickTool - 自动路径模式: {IsInAutoPathMode}, 订阅事件: {shouldSubscribeToEvents}, 空轨吸附: {enableRailSnapping}, 吊装模式: {enableHoistingMode}");
ActivateToolPlugin(shouldSubscribeToEvents);
PathEditState = PathEditState.AddingPoints;
- LogManager.Info($"点击工具已启动,类型: {pointType},事件订阅: {shouldSubscribeToEvents},空轨吸附: {enableRailSnapping}");
+ LogManager.Info($"点击工具已启动,类型: {pointType},事件订阅: {shouldSubscribeToEvents},空轨吸附: {enableRailSnapping},吊装模式: {enableHoistingMode}");
}
catch (Exception ex)
{
@@ -1533,6 +1553,9 @@ namespace NavisworksTransport
/// 完成当前编辑并保存
///
/// 是否成功完成编辑
+ ///
+ /// 完成路径编辑
+ ///
public bool FinishEditing()
{
if (!IsInEditableState)
@@ -1543,20 +1566,29 @@ namespace NavisworksTransport
try
{
- // 自动设置最后一个点为终点
+ // 根据路径类型执行不同的完成逻辑
if (CurrentRoute != null && CurrentRoute.Points.Count > 1)
{
- var lastPoint = CurrentRoute.Points.Last();
- if (lastPoint.Type != PathPointType.StartPoint) // 起点不能是终点
+ // 地面路径:自动设置最后一个点为终点
+ if (CurrentRoute.PathType == PathType.Ground || CurrentRoute.PathType == PathType.Rail)
{
- lastPoint.Type = PathPointType.EndPoint;
- lastPoint.Name = GeneratePointName(PathPointType.EndPoint);
+ var lastPoint = CurrentRoute.Points.Last();
+ if (lastPoint.Type != PathPointType.StartPoint) // 起点不能是终点
+ {
+ lastPoint.Type = PathPointType.EndPoint;
+ lastPoint.Name = GeneratePointName(PathPointType.EndPoint);
- // 更新3D路径可视化
- // 重新渲染整个路径以反映类型变更
- _renderPlugin?.RenderPath(CurrentRoute);
+ // 更新3D路径可视化
+ // 重新渲染整个路径以反映类型变更
+ _renderPlugin?.RenderPath(CurrentRoute);
- LogManager.Info($"已自动设置最后一个点为终点: {lastPoint.Name}");
+ LogManager.Info($"[地面路径] 已自动设置最后一个点为终点: {lastPoint.Name}");
+ }
+ }
+ // 吊装路径:已有明确的起点和终点,不需要修改
+ else if (CurrentRoute.PathType == PathType.Hoisting)
+ {
+ LogManager.Info($"[吊装路径] 路径已包含明确的起点和终点,无需修改");
}
}
@@ -2694,8 +2726,17 @@ namespace NavisworksTransport
{
try
{
- // 如果启用了空轨吸附,先吸附到基准路径
Point3D clickedPoint = pickResult.Point;
+
+ // 吊装模式处理
+ if (_enableHoistingMode)
+ {
+ LogManager.Debug("[吊装模式] 处理吊装终点选择");
+ HoistingEndPointSelected?.Invoke(this, clickedPoint);
+ return;
+ }
+
+ // 如果启用了空轨吸附,先吸附到基准路径
if (_enableRailSnapping)
{
clickedPoint = SnapToRailBaseline(clickedPoint);
diff --git a/src/Core/PathPlanningModels.cs b/src/Core/PathPlanningModels.cs
index d404370..9c0f359 100644
--- a/src/Core/PathPlanningModels.cs
+++ b/src/Core/PathPlanningModels.cs
@@ -58,7 +58,12 @@ namespace NavisworksTransport
///
/// 空轨路径 - 车辆悬挂在空轨下方运行
///
- Rail = 1
+ Rail = 1,
+
+ ///
+ /// 吊装路径 - 设备从孔洞垂直下降
+ ///
+ Hoisting = 2
}
///
@@ -567,6 +572,18 @@ namespace NavisworksTransport
///
public PathType PathType { get; set; } = PathType.Ground;
+ ///
+ /// 吊装孔洞模型(仅吊装路径使用)
+ ///
+ [XmlIgnore]
+ public ModelItem HoleModel { get; set; }
+
+ ///
+ /// 下层楼面模型(仅吊装路径使用,用于碰撞排除)
+ ///
+ [XmlIgnore]
+ public ModelItem LowerFloorModel { get; set; }
+
// 数据库分析相关属性
///
/// 碰撞数量(从数据库加载)
diff --git a/src/Core/PathPointRenderPlugin.cs b/src/Core/PathPointRenderPlugin.cs
index 53d0547..3838e75 100644
--- a/src/Core/PathPointRenderPlugin.cs
+++ b/src/Core/PathPointRenderPlugin.cs
@@ -123,7 +123,12 @@ namespace NavisworksTransport
///
/// 空轨基准路径样式(浅红色)
///
- RailBaseline
+ RailBaseline,
+
+ ///
+ /// 吊装路径样式(紫色)
+ ///
+ HoistingLine
}
///
@@ -1239,11 +1244,17 @@ namespace NavisworksTransport
// 地面路径使用曲线化后的路径(Edges)
// 空轨路径只显示控制点连线,不显示路径连线
+ // 吊装路径使用控制点连线,不显示路径连线
if (visualization.PathRoute.PathType == NavisworksTransport.PathType.Rail)
{
// 空轨路径:只显示控制点连线,不构建路径连线
LogManager.Debug($"[路径渲染] 空轨路径只显示控制点连线,不显示路径连线");
}
+ else if (visualization.PathRoute.PathType == NavisworksTransport.PathType.Hoisting)
+ {
+ // 吊装路径:只显示控制点连线,不构建路径连线
+ LogManager.Debug($"[路径渲染] 吊装路径只显示控制点连线,不显示路径连线");
+ }
else if (visualization.PathRoute.Edges != null && visualization.PathRoute.Edges.Count > 0)
{
// 地面路径:使用曲线化后的路径
@@ -1280,6 +1291,13 @@ namespace NavisworksTransport
/// 排序后的路径点列表
private void BuildControlLines(PathVisualization visualization, List sortedPoints)
{
+ // 根据路径类型选择连线样式
+ RenderStyleName lineStyleName = RenderStyleName.PreviewLine;
+ if (visualization.PathRoute.PathType == NavisworksTransport.PathType.Hoisting)
+ {
+ lineStyleName = RenderStyleName.HoistingLine;
+ }
+
// 构建控制点之间的连线(所有相邻控制点)
for (int i = 0; i < sortedPoints.Count - 1; i++)
{
@@ -1290,7 +1308,7 @@ namespace NavisworksTransport
{
StartPoint = currentPoint.Position,
EndPoint = nextPoint.Position,
- Color = GetRenderStyle(RenderStyleName.PreviewLine).Color,
+ Color = GetRenderStyle(lineStyleName).Color,
Radius = GetLineRadius() * 0.5, // 比实际路径细
SegmentType = PathSegmentType.Straight,
FromIndex = currentPoint.Index,
@@ -1676,6 +1694,9 @@ namespace NavisworksTransport
case RenderStyleName.RailBaseline:
return new RenderStyle(Color.FromByteRGB(255, 138, 128), 0.7); // 浅红色,30%透明
+ case RenderStyleName.HoistingLine:
+ return new RenderStyle(Color.FromByteRGB(156, 39, 176), 0.8); // Material Purple吊装路径,20%透明
+
default:
return new RenderStyle(Color.White, 1.0); // 默认白色,完全不透明
}
diff --git a/src/PathPlanning/HoistingPathGenerator.cs b/src/PathPlanning/HoistingPathGenerator.cs
new file mode 100644
index 0000000..2a0fd91
--- /dev/null
+++ b/src/PathPlanning/HoistingPathGenerator.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Collections.Generic;
+using Autodesk.Navisworks.Api;
+using NavisworksTransport.Utils;
+using NavisworksTransport.Core.Config;
+
+namespace NavisworksTransport.PathPlanning
+{
+ ///
+ /// 吊装路径生成器
+ /// 用于生成垂直吊装路径,只包含起点和终点两个点
+ ///
+ public class HoistingPathGenerator
+ {
+ ///
+ /// 生成吊装路径点(只包含起点和终点)
+ ///
+ /// 终点(下层目标位置)
+ /// 起点高度(米,相对于终点向上)
+ /// 路径点列表(仅包含起点和终点)
+ public List GenerateHoistingPath(
+ Point3D endPoint,
+ double startHeightMeters)
+ {
+ if (endPoint == null)
+ throw new ArgumentNullException(nameof(endPoint), "终点不能为空");
+
+ if (startHeightMeters <= 0)
+ throw new ArgumentException("起点高度必须大于0", nameof(startHeightMeters));
+
+ var pathPoints = new List();
+
+ // 计算起点:从终点垂直向上
+ var startHeightModelUnits = UnitsConverter.ConvertFromMeters(startHeightMeters);
+ var startPoint = new Point3D(
+ endPoint.X,
+ endPoint.Y,
+ endPoint.Z + startHeightModelUnits);
+
+ // 只生成起点和终点两个点,不进行插值
+ pathPoints.Add(new PathPoint(
+ startPoint,
+ "起点",
+ PathPointType.StartPoint));
+
+ pathPoints.Add(new PathPoint(
+ endPoint,
+ "终点",
+ PathPointType.EndPoint));
+
+ return pathPoints;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/UI/WPF/ViewModels/PathEditingViewModel.cs b/src/UI/WPF/ViewModels/PathEditingViewModel.cs
index b9c4c28..c6fdecc 100644
--- a/src/UI/WPF/ViewModels/PathEditingViewModel.cs
+++ b/src/UI/WPF/ViewModels/PathEditingViewModel.cs
@@ -635,6 +635,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
public ICommand NewPathCommand { get; private set; }
public ICommand NewRailPathCommand { get; private set; }
+ public ICommand NewHoistingPathCommand { get; private set; }
public ICommand DeletePathCommand { get; private set; }
public ICommand RenamePathCommand { get; private set; }
public ICommand StartEditCommand { get; private set; }
@@ -802,6 +803,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
{
NewPathCommand = new RelayCommand(async () => await ExecuteNewPathAsync(), () => CanExecuteNewPath);
NewRailPathCommand = new RelayCommand(async () => await ExecuteNewRailPathAsync(), () => CanExecuteNewRailPath);
+ NewHoistingPathCommand = new RelayCommand(async () => await ExecuteNewHoistingPathAsync());
DeletePathCommand = new RelayCommand(async () => await ExecuteDeletePathAsync());
RenamePathCommand = new RelayCommand(async () => await ExecuteRenamePathAsync());
StartEditCommand = new RelayCommand(async () => await ExecuteAddPathPointAsync(), () => CanExecuteStartEdit);
@@ -1004,6 +1006,147 @@ namespace NavisworksTransport.UI.WPF.ViewModels
}, "新建空轨路径");
}
+ ///
+ /// 执行新建吊装路径命令
+ ///
+ private async Task ExecuteNewHoistingPathAsync()
+ {
+ await SafeExecuteAsync(() =>
+ {
+ UpdateMainStatus("正在创建吊装路径,请选择吊装终点位置...");
+
+ // 激活工具,等待用户选择终点位置
+ if (_pathPlanningManager != null)
+ {
+ // 清除所有现有路径的可视化显示
+ 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);
+ }
+ }
+
+ // 强制重新初始化ToolPlugin以确保获得鼠标焦点
+ if (!ForceReinitializeToolPlugin(subscribeToEvents: true))
+ {
+ UpdateMainStatus("ToolPlugin初始化失败,请重试");
+ LogManager.Error("新建吊装路径:ToolPlugin初始化失败");
+ return;
+ }
+
+ // 启动点击工具,等待用户选择终点位置
+ _pathPlanningManager.StartClickTool(PathPointType.EndPoint, enableRailSnapping: false, enableHoistingMode: true);
+
+ UpdateMainStatus("请在3D视图中点击选择吊装终点位置");
+ LogManager.Info("开始新建吊装路径,等待用户选择终点位置");
+ }
+ else
+ {
+ UpdateMainStatus("路径规划管理器未初始化");
+ LogManager.Error("路径规划管理器未初始化");
+ }
+ }, "新建吊装路径");
+ }
+
+ ///
+ /// 处理吊装路径终点选择
+ ///
+ public void OnHoistingEndPointSelected(object sender, Point3D endPoint)
+ {
+ // 创建并显示对话框
+ var dialog = new Views.HoistingHeightDialog(3.0); // 默认3米
+
+ if (dialog.ShowDialog() == true)
+ {
+ CreateHoistingPath(endPoint, dialog.StartHeightMeters);
+ }
+ else
+ {
+ UpdateMainStatus("已取消创建吊装路径");
+ }
+ }
+
+ ///
+ /// 创建吊装路径
+ ///
+ private async void CreateHoistingPath(Point3D endPoint, double startHeightMeters)
+ {
+ await SafeExecuteAsync(async () =>
+ {
+ try
+ {
+ var parameters = new HoistingPathParameters
+ {
+ EndPoint = endPoint,
+ StartHeightMeters = startHeightMeters
+ };
+
+ var command = new CreateHoistingPathCommand(parameters);
+ var result = await command.ExecuteAsync();
+
+ if (result.IsSuccess)
+ {
+ var genericResult = result as PathPlanningResult;
+ if (genericResult != null && genericResult.Data != null)
+ {
+ var route = genericResult.Data;
+
+ // 创建对应的 WPF ViewModel
+ var newPathViewModel = new PathRouteViewModel
+ {
+ Id = route.Id,
+ Name = route.Name,
+ Description = route.Description,
+ IsActive = true,
+ PathType = PathType.Hoisting
+ };
+
+ // 转换路径点
+ foreach (var corePoint in route.Points)
+ {
+ var wpfPoint = new PathPointViewModel
+ {
+ Id = corePoint.Id,
+ Name = corePoint.Name,
+ X = corePoint.X,
+ Y = corePoint.Y,
+ Z = corePoint.Z,
+ Type = corePoint.Type
+ };
+ newPathViewModel.Points.Add(wpfPoint);
+ }
+
+ // 添加到 UI 列表并选中
+ PathRoutes.Add(newPathViewModel);
+ SelectedPathRoute = newPathViewModel;
+
+ // 绘制路径可视化
+ _pathPlanningManager.DrawRouteVisualization(route, isAutoPath: false);
+
+ // 完成编辑,退出编辑状态
+ _pathPlanningManager.FinishEditing();
+
+ UpdateMainStatus($"吊装路径创建成功: {route.Name}");
+ LogManager.Info($"吊装路径创建成功: {route.Name}");
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ UpdateMainStatus($"创建吊装路径失败: {ex.Message}");
+ LogManager.Error($"创建吊装路径失败: {ex.Message}", ex);
+ MessageBox.Show($"创建吊装路径失败: {ex.Message}", "错误",
+ MessageBoxButton.OK, MessageBoxImage.Error);
+ }
+ }, "创建吊装路径");
+ }
+
///
/// 提取并渲染所有空轨模型的基准路径
///
@@ -2710,6 +2853,9 @@ namespace NavisworksTransport.UI.WPF.ViewModels
// 添加错误事件订阅 - 用于处理自动路径规划失败等错误
_pathPlanningManager.ErrorOccurred += OnPathPlanningError;
+ // 订阅吊装终点选择事件
+ _pathPlanningManager.HoistingEndPointSelected += OnHoistingEndPointSelected;
+
LogManager.Debug("PathEditingViewModel已成功订阅PathPlanningManager事件");
}
catch (Exception ex)
@@ -2732,6 +2878,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
_pathPlanningManager.CurrentRouteChanged -= OnCurrentRouteChanged;
_pathPlanningManager.EditStateChanged -= OnEditStateChanged;
_pathPlanningManager.RouteGenerated -= OnRouteGenerated;
+ _pathPlanningManager.HoistingEndPointSelected -= OnHoistingEndPointSelected;
_pathPlanningManager.RoutesLoaded -= OnRoutesLoaded;
_pathPlanningManager.ErrorOccurred -= OnPathPlanningError;
diff --git a/src/UI/WPF/Views/EditCoordinatesWindow.xaml b/src/UI/WPF/Views/EditCoordinatesWindow.xaml
index f1616c2..7d3afc6 100644
--- a/src/UI/WPF/Views/EditCoordinatesWindow.xaml
+++ b/src/UI/WPF/Views/EditCoordinatesWindow.xaml
@@ -5,6 +5,7 @@
WindowStartupLocation="CenterScreen"
ResizeMode="NoResize"
ShowInTaskbar="False"
+ Topmost="True"
Background="White">
diff --git a/src/UI/WPF/Views/HoistingHeightDialog.xaml b/src/UI/WPF/Views/HoistingHeightDialog.xaml
new file mode 100644
index 0000000..869bfc3
--- /dev/null
+++ b/src/UI/WPF/Views/HoistingHeightDialog.xaml
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/UI/WPF/Views/HoistingHeightDialog.xaml.cs b/src/UI/WPF/Views/HoistingHeightDialog.xaml.cs
new file mode 100644
index 0000000..eb4e59f
--- /dev/null
+++ b/src/UI/WPF/Views/HoistingHeightDialog.xaml.cs
@@ -0,0 +1,58 @@
+using System;
+using System.Windows;
+
+namespace NavisworksTransport.UI.WPF.Views
+{
+ ///
+ /// 吊装起点高度设置对话框
+ ///
+ public partial class HoistingHeightDialog : Window
+ {
+ ///
+ /// 获取或设置起点高度(米)
+ ///
+ public double StartHeightMeters { get; private set; }
+
+ ///
+ /// 构造函数
+ ///
+ public HoistingHeightDialog()
+ {
+ InitializeComponent();
+ }
+
+ ///
+ /// 构造函数(带默认值)
+ ///
+ /// 默认高度(米)
+ public HoistingHeightDialog(double defaultHeight) : this()
+ {
+ HeightTextBox.Text = defaultHeight.ToString("F1");
+ }
+
+ ///
+ /// 确定按钮点击事件
+ ///
+ private void OnOKClick(object sender, RoutedEventArgs e)
+ {
+ if (double.TryParse(HeightTextBox.Text, out double height) && height > 0)
+ {
+ StartHeightMeters = height;
+ DialogResult = true;
+ }
+ else
+ {
+ System.Windows.MessageBox.Show("请输入有效的起点高度(大于0的数字)", "错误",
+ MessageBoxButton.OK, MessageBoxImage.Error);
+ }
+ }
+
+ ///
+ /// 取消按钮点击事件
+ ///
+ private void OnCancelClick(object sender, RoutedEventArgs e)
+ {
+ DialogResult = false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/UI/WPF/Views/PathEditingView.xaml b/src/UI/WPF/Views/PathEditingView.xaml
index 1fac318..18195df 100644
--- a/src/UI/WPF/Views/PathEditingView.xaml
+++ b/src/UI/WPF/Views/PathEditingView.xaml
@@ -201,6 +201,10 @@ NavisworksTransport 路径编辑页签视图 - 采用与动画控制和分层管
+