增加了吊装路径

This commit is contained in:
tian 2026-01-18 12:59:30 +08:00
parent 1a219b7b0e
commit ddf44b887f
15 changed files with 674 additions and 26 deletions

View File

@ -145,6 +145,8 @@
<Compile Include="src\Commands\GenerateCollisionReportCommand.cs" />
<Compile Include="src\Commands\VoxelGridSDFTestCommand.cs" />
<Compile Include="src\Commands\VoxelPathFindingTestCommand.cs" />
<Compile Include="src\Commands\CreateHoistingPathCommand.cs" />
<Compile Include="src\Commands\HoistingPathParameters.cs" />
<!-- Core - Animation System -->
<Compile Include="src\Core\Animation\PathAnimationManager.cs" />
<Compile Include="src\Core\Animation\TimeLinerIntegrationManager.cs" />
@ -183,6 +185,7 @@
<Compile Include="src\PathPlanning\MeshSDFTester.cs" />
<Compile Include="src\PathPlanning\VoxelGridVisualizer.cs" />
<Compile Include="src\PathPlanning\RailGeometryHelper.cs" />
<Compile Include="src\PathPlanning\HoistingPathGenerator.cs" />
<!-- UI - WPF -->
<Compile Include="src\UI\WPF\Views\LogisticsControlPanel.xaml.cs">
<DependentUpon>LogisticsControlPanel.xaml</DependentUpon>
@ -232,6 +235,9 @@
<Compile Include="src\UI\WPF\Views\EditCoordinatesWindow.xaml.cs">
<DependentUpon>EditCoordinatesWindow.xaml</DependentUpon>
</Compile>
<Compile Include="src\UI\WPF\Views\HoistingHeightDialog.xaml.cs">
<DependentUpon>HoistingHeightDialog.xaml</DependentUpon>
</Compile>
<!-- UI - WPF ViewModels -->
<Compile Include="src\UI\WPF\ViewModels\ViewModelBase.cs" />
<Compile Include="src\UI\WPF\ViewModels\LogisticsControlViewModel.cs" />
@ -345,6 +351,10 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="src\UI\WPF\Views\HoistingHeightDialog.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<!-- Shared Resource Dictionary -->
<Page Include="src\UI\WPF\Resources\NavisworksStyles.xaml">
<SubType>Designer</SubType>

View File

@ -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注册完成");
}

View File

@ -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
{
/// <summary>
/// 创建吊装路径命令
/// </summary>
public class CreateHoistingPathCommand : CommandBase
{
private readonly HoistingPathParameters _parameters;
private readonly PathPlanningManager _pathPlanningManager;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="parameters">吊装路径参数</param>
public CreateHoistingPathCommand(HoistingPathParameters parameters)
{
_parameters = parameters ?? throw new ArgumentNullException(nameof(parameters));
_pathPlanningManager = PathPlanningManager.Instance;
}
/// <summary>
/// 执行命令
/// </summary>
protected override Task<PathPlanningResult> ExecuteInternalAsync(CancellationToken cancellationToken)
{
try
{
// 验证参数
var validationResult = ValidateParameters();
if (!validationResult.IsSuccess)
{
return Task.FromResult<PathPlanningResult>(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<PathRoute>.Success(route, "吊装路径创建成功");
return Task.FromResult<PathPlanningResult>(genericResult);
}
catch (Exception ex)
{
LogManager.Error($"创建吊装路径失败: {ex.Message}", ex);
var genericResult = PathPlanningResult<PathRoute>.Failure($"创建吊装路径失败: {ex.Message}", ex);
return Task.FromResult<PathPlanningResult>(genericResult);
}
}
/// <summary>
/// 验证参数
/// </summary>
protected override PathPlanningResult ValidateParameters()
{
if (_parameters.EndPoint == null)
return PathPlanningResult.ValidationFailure("终点不能为空");
if (_parameters.StartHeightMeters <= 0)
return PathPlanningResult.ValidationFailure("起点高度必须大于0");
return PathPlanningResult.Success("参数验证通过");
}
}
}

View File

@ -0,0 +1,21 @@
using Autodesk.Navisworks.Api;
namespace NavisworksTransport.Commands
{
/// <summary>
/// 吊装路径参数
/// </summary>
public class HoistingPathParameters
{
/// <summary>
/// 终点(下层目标位置)
/// </summary>
public Point3D EndPoint { get; set; }
/// <summary>
/// 起点高度(米,相对于终点向上)
/// 如果为0则使用默认高度3米
/// </summary>
public double StartHeightMeters { get; set; } = 3.0;
}
}

View File

@ -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<Point3D> allSampledPoints = new List<Point3D>();
List<double> segmentLengths = new List<double>();
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<AnimationFrame>();
@ -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();
// 使用包围盒距离检测方法

View File

@ -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));
}
}
}

View File

@ -50,6 +50,9 @@ namespace NavisworksTransport
// 空轨吸附模式标志
private bool _enableRailSnapping = false;
// 吊装模式标志
private bool _enableHoistingMode = false;
// 路径点3D标记管理
private List<PathPointMarker> _pathPointMarkers;
@ -339,6 +342,11 @@ namespace NavisworksTransport
[Obsolete("请使用新的强类型事件接口")]
public event EventHandler<PathRoute> PathPointsListUpdated_Legacy;
/// <summary>
/// 吊装终点选择事件
/// </summary>
public event EventHandler<Point3D> HoistingEndPointSelected;
#endregion
#region
@ -852,7 +860,7 @@ namespace NavisworksTransport
/// <param name="pointType">点击类型</param>
public void StartClickTool(PathPointType pointType)
{
StartClickTool(pointType, enableRailSnapping: false);
StartClickTool(pointType, enableRailSnapping: false, enableHoistingMode: false);
}
/// <summary>
@ -861,19 +869,31 @@ namespace NavisworksTransport
/// <param name="pointType">点击类型</param>
/// <param name="enableRailSnapping">是否启用空轨吸附</param>
public void StartClickTool(PathPointType pointType, bool enableRailSnapping)
{
StartClickTool(pointType, enableRailSnapping, enableHoistingMode: false);
}
/// <summary>
/// 启动点击工具(支持空轨吸附和吊装模式)
/// </summary>
/// <param name="pointType">点击类型</param>
/// <param name="enableRailSnapping">是否启用空轨吸附</param>
/// <param name="enableHoistingMode">是否启用吊装模式</param>
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
/// 完成当前编辑并保存
/// </summary>
/// <returns>是否成功完成编辑</returns>
/// <summary>
/// 完成路径编辑
/// </summary>
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);

View File

@ -58,7 +58,12 @@ namespace NavisworksTransport
/// <summary>
/// 空轨路径 - 车辆悬挂在空轨下方运行
/// </summary>
Rail = 1
Rail = 1,
/// <summary>
/// 吊装路径 - 设备从孔洞垂直下降
/// </summary>
Hoisting = 2
}
/// <summary>
@ -567,6 +572,18 @@ namespace NavisworksTransport
/// </summary>
public PathType PathType { get; set; } = PathType.Ground;
/// <summary>
/// 吊装孔洞模型(仅吊装路径使用)
/// </summary>
[XmlIgnore]
public ModelItem HoleModel { get; set; }
/// <summary>
/// 下层楼面模型(仅吊装路径使用,用于碰撞排除)
/// </summary>
[XmlIgnore]
public ModelItem LowerFloorModel { get; set; }
// 数据库分析相关属性
/// <summary>
/// 碰撞数量(从数据库加载)

View File

@ -123,7 +123,12 @@ namespace NavisworksTransport
/// <summary>
/// 空轨基准路径样式(浅红色)
/// </summary>
RailBaseline
RailBaseline,
/// <summary>
/// 吊装路径样式(紫色)
/// </summary>
HoistingLine
}
/// <summary>
@ -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
/// <param name="sortedPoints">排序后的路径点列表</param>
private void BuildControlLines(PathVisualization visualization, List<PathPoint> 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); // 默认白色,完全不透明
}

View File

@ -0,0 +1,54 @@
using System;
using System.Collections.Generic;
using Autodesk.Navisworks.Api;
using NavisworksTransport.Utils;
using NavisworksTransport.Core.Config;
namespace NavisworksTransport.PathPlanning
{
/// <summary>
/// 吊装路径生成器
/// 用于生成垂直吊装路径,只包含起点和终点两个点
/// </summary>
public class HoistingPathGenerator
{
/// <summary>
/// 生成吊装路径点(只包含起点和终点)
/// </summary>
/// <param name="endPoint">终点(下层目标位置)</param>
/// <param name="startHeightMeters">起点高度(米,相对于终点向上)</param>
/// <returns>路径点列表(仅包含起点和终点)</returns>
public List<PathPoint> 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<PathPoint>();
// 计算起点:从终点垂直向上
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;
}
}
}

View File

@ -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
}, "新建空轨路径");
}
/// <summary>
/// 执行新建吊装路径命令
/// </summary>
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("路径规划管理器未初始化");
}
}, "新建吊装路径");
}
/// <summary>
/// 处理吊装路径终点选择
/// </summary>
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("已取消创建吊装路径");
}
}
/// <summary>
/// 创建吊装路径
/// </summary>
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<PathRoute>;
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);
}
}, "创建吊装路径");
}
/// <summary>
/// 提取并渲染所有空轨模型的基准路径
/// </summary>
@ -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;

View File

@ -5,6 +5,7 @@
WindowStartupLocation="CenterScreen"
ResizeMode="NoResize"
ShowInTaskbar="False"
Topmost="True"
Background="White">
<Window.Resources>
<ResourceDictionary>

View File

@ -0,0 +1,72 @@
<Window x:Class="NavisworksTransport.UI.WPF.Views.HoistingHeightDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="设置吊装起点高度" Height="240" Width="400"
WindowStartupLocation="CenterScreen"
ResizeMode="NoResize"
ShowInTaskbar="False"
Topmost="True"
Background="White">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/NavisworksTransportPlugin;component/src/UI/WPF/Resources/NavisworksStyles.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- 标题栏 -->
<Border Grid.Row="0" Background="#FFF8FBFF" BorderBrush="#FFD4E7FF" BorderThickness="0,0,0,1" Padding="20,15">
<StackPanel>
<TextBlock Text="设置吊装起点高度" FontWeight="SemiBold" FontSize="14" Foreground="#FF2B579A"/>
<TextBlock Text="输入起点相对于终点的高度(向上为正)" FontSize="10" Foreground="#FF666666" Margin="0,3,0,0"/>
</StackPanel>
</Border>
<!-- 输入区域 -->
<Border Grid.Row="1" Padding="20,15">
<StackPanel>
<Grid Margin="0,0,0,12">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="起点高度(米):" VerticalAlignment="Center" FontSize="11" Foreground="#FF333333"/>
<TextBox Grid.Column="1"
Name="HeightTextBox"
Text="3.0"
Height="28"
VerticalContentAlignment="Center"
Padding="8,0"
FontSize="11"
BorderBrush="#FFCCCCCC"
BorderThickness="1"/>
</Grid>
</StackPanel>
</Border>
<!-- 按钮栏 -->
<Border Grid.Row="2" Background="#FFF8FBFF" BorderBrush="#FFD4E7FF" BorderThickness="0,1,0,0" Padding="20,12">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<Button Content="确定"
Click="OnOKClick"
Style="{StaticResource ActionButtonStyle}"
Width="90"
Height="32"
Margin="0,0,10,0"/>
<Button Content="取消"
Click="OnCancelClick"
Style="{StaticResource SecondaryButtonStyle}"
Width="90"
Height="32"/>
</StackPanel>
</Border>
</Grid>
</Window>

View File

@ -0,0 +1,58 @@
using System;
using System.Windows;
namespace NavisworksTransport.UI.WPF.Views
{
/// <summary>
/// 吊装起点高度设置对话框
/// </summary>
public partial class HoistingHeightDialog : Window
{
/// <summary>
/// 获取或设置起点高度(米)
/// </summary>
public double StartHeightMeters { get; private set; }
/// <summary>
/// 构造函数
/// </summary>
public HoistingHeightDialog()
{
InitializeComponent();
}
/// <summary>
/// 构造函数(带默认值)
/// </summary>
/// <param name="defaultHeight">默认高度(米)</param>
public HoistingHeightDialog(double defaultHeight) : this()
{
HeightTextBox.Text = defaultHeight.ToString("F1");
}
/// <summary>
/// 确定按钮点击事件
/// </summary>
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);
}
}
/// <summary>
/// 取消按钮点击事件
/// </summary>
private void OnCancelClick(object sender, RoutedEventArgs e)
{
DialogResult = false;
}
}
}

View File

@ -201,6 +201,10 @@ NavisworksTransport 路径编辑页签视图 - 采用与动画控制和分层管
<Button Content="空轨路径"
Command="{Binding NewRailPathCommand}"
Style="{StaticResource ActionButtonStyle}"/>
<Button Content="吊装路径"
Command="{Binding NewHoistingPathCommand}"
Style="{StaticResource ActionButtonStyle}"
ToolTip="创建垂直下降的吊装路径"/>
<Button Content="删除"
Command="{Binding DeletePathCommand}"
Style="{StaticResource SecondaryButtonStyle}"