增加了吊装路径
This commit is contained in:
parent
1a219b7b0e
commit
ddf44b887f
@ -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>
|
||||
|
||||
@ -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注册完成");
|
||||
}
|
||||
|
||||
|
||||
110
src/Commands/CreateHoistingPathCommand.cs
Normal file
110
src/Commands/CreateHoistingPathCommand.cs
Normal 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("参数验证通过");
|
||||
}
|
||||
}
|
||||
}
|
||||
21
src/Commands/HoistingPathParameters.cs
Normal file
21
src/Commands/HoistingPathParameters.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
|
||||
// 使用包围盒距离检测方法
|
||||
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
|
||||
@ -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>
|
||||
/// 碰撞数量(从数据库加载)
|
||||
|
||||
@ -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); // 默认白色,完全不透明
|
||||
}
|
||||
|
||||
54
src/PathPlanning/HoistingPathGenerator.cs
Normal file
54
src/PathPlanning/HoistingPathGenerator.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
WindowStartupLocation="CenterScreen"
|
||||
ResizeMode="NoResize"
|
||||
ShowInTaskbar="False"
|
||||
Topmost="True"
|
||||
Background="White">
|
||||
<Window.Resources>
|
||||
<ResourceDictionary>
|
||||
|
||||
72
src/UI/WPF/Views/HoistingHeightDialog.xaml
Normal file
72
src/UI/WPF/Views/HoistingHeightDialog.xaml
Normal 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>
|
||||
58
src/UI/WPF/Views/HoistingHeightDialog.xaml.cs
Normal file
58
src/UI/WPF/Views/HoistingHeightDialog.xaml.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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}"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user