新增空轨数据结构、自动提取基准路径并可视化、路径点吸附空轨基准路径功能

This commit is contained in:
tian 2026-01-11 16:52:07 +08:00
parent 0a2e29cee9
commit 8424503576
13 changed files with 2289 additions and 81 deletions

View File

@ -181,6 +181,7 @@
<Compile Include="src\PathPlanning\VoxelPathFinder.cs" />
<Compile Include="src\PathPlanning\MeshSDFTester.cs" />
<Compile Include="src\PathPlanning\VoxelGridVisualizer.cs" />
<Compile Include="src\PathPlanning\RailGeometryHelper.cs" />
<!-- UI - WPF -->
<Compile Include="src\UI\WPF\Views\LogisticsControlPanel.xaml.cs">
<DependentUpon>LogisticsControlPanel.xaml</DependentUpon>
@ -254,6 +255,7 @@
<Compile Include="src\UI\WPF\Converters\BoolToVisibilityConverter.cs" />
<Compile Include="src\UI\WPF\Converters\IndexConverter.cs" />
<Compile Include="src\UI\WPF\Converters\CountToVisibilityConverter.cs" />
<Compile Include="src\UI\WPF\Converters\PathTypeConverter.cs" />
<Compile Include="src\UI\WPF\Models\LogisticsModel.cs" />
<Compile Include="src\UI\WPF\Models\PathRouteViewModel.cs" />
<Compile Include="src\UI\WPF\Models\SplitPreviewItem.cs" />

View File

@ -0,0 +1,522 @@
# 空轨支持方案
## 一、需求分析
### 1.1 核心需求
1. **新增空轨属性**:在物流属性系统中增加"空轨"类型
2. **空轨路径生成**
- 空轨走向固定,只需设置起点和终点
- 自动提取空轨几何体的下表面中心线作为路径
3. **路径类型区分**:路径需要标记为"地面路径"或"空轨路径"
4. **动画运行模式**
- 地面路径:车辆中心点在路径点上
- 空轨路径:车辆悬挂在空轨下方运行(车辆中心 = 路径点 - 车辆高度/2
5. **碰撞检测**
- 统一使用3D碰撞检测
- 空轨路径需要排除空轨本身
6. **限制条件**:空轨暂时只支持人工路径,不支持自动路径规划
### 1.2 使用场景
- **地面路径**:转运车在楼面、走廊、通道上运行
- **空轨路径**:悬挂式输送设备在空轨上运行
### 1.3 已确认信息
1. ✅ **空轨模型**:已存在,不需要建模规范
2. ✅ **空轨类型**:不需要分类型,只有一个"空轨"类型
3. ✅ **车辆悬挂**:车辆悬挂在空轨下方
4. ✅ **碰撞检测**:需要排除空轨本身
5. ✅ **数据库迁移**:不需要,新字段使用默认值
6. ✅ **向后兼容**:不需要向后兼容性
7. ✅ **碰撞检测统一**地面路径和空轨路径都使用3D碰撞检测
8. ✅ **轨道几何**:轨道不是水平的,需要精确计算几何体数据
---
## 二、架构影响分析
### 2.1 涉及的核心模块
| 模块 | 影响程度 | 改动内容 |
|------|---------|---------|
| **物流属性系统** | 🟢 低 | 新增"空轨"枚举值 |
| **路径数据模型** | 🟡 中 | 新增路径类型字段 |
| **路径规划系统** | 🟡 中 | 新增空轨路径生成逻辑 |
| **动画系统** | 🟡 中 | 根据路径类型调整车辆位置 |
| **碰撞检测系统** | 🟢 低 | 空轨路径排除空轨本身 |
| **UI界面** | 🟡 中 | 路径类型选择、空轨路径生成 |
---
## 三、详细实施方案
### 3.1 第一阶段基础数据结构1天
#### 3.1.1 新增空轨属性
**文件**`CategoryAttributeManager.cs`
```csharp
public enum LogisticsElementType
{
// ... 现有类型 ...
/// <summary>
/// 空轨 - 空中运输路径车辆悬挂运行权重0.7
/// </summary>
空轨 = 9,
}
```
#### 3.1.2 路径类型枚举
**文件**`PathPlanningModels.cs`
```csharp
/// <summary>
/// 路径类型
/// </summary>
public enum PathType
{
/// <summary>
/// 地面路径 - 车辆在地面运行
/// </summary>
Ground = 0,
/// <summary>
/// 空轨路径 - 车辆悬挂在空轨下方运行
/// </summary>
Rail = 1,
}
```
#### 3.1.3 路径数据模型扩展
**文件**`PathRouteViewModel.cs`
```csharp
private PathType _pathType = PathType.Ground;
public PathType PathType
{
get => _pathType;
set => SetProperty(ref _pathType, value);
}
```
#### 3.1.4 路径数据库适配
**文件**`PathDatabase.cs`
```csharp
// 保存路径时,保存路径类型
public void SavePath(PathRouteViewModel path)
{
// ... 现有逻辑 ...
// 新增:保存路径类型
}
// 加载路径时,读取路径类型
public PathRouteViewModel LoadPath(string pathId)
{
// ... 现有逻辑 ...
// 新增:读取路径类型
}
```
---
### 3.2 第二阶段空轨路径生成2天
#### 3.2.1 复用现有几何计算
**现有代码复用**
- `ChannelHeightDetector.AnalyzeChannelGeometry()` - 分析通道几何
- `ChannelHeightDetector.CalculatePreciseHeightAtPosition()` - 计算精确高度
- `GeometryHelper.ExtractTriangles()` - 提取三角形
- `GeometryHelper.RayTriangleIntersect()` - 射线-三角形交点检测
#### 3.2.2 新增底部高度检测方法
**文件**`ChannelHeightDetector.cs`
新增方法:
- `GetChannelBottomHeight(Point3D position, IEnumerable<ModelItem> channelItems)` - 获取通道底面高度
- `CalculateBottomPreciseHeightAtPosition(Point3D position, ChannelHeightInfo heightInfo, ModelItem channel)` - 计算底面精确高度
- `TryRaycastFromBelow(Point3D position, ModelItem channel, double floorHeight, out double height)` - 从下方射线投射
- `PerformRayTriangleIntersectionFromBelow(Point3D position, List<Triangle3D> triangles)` - 从下往上射线-三角形交点检测
- `TryGetGeometricBottomHeight(Point3D position, ModelItem channel, out double height)` - View API获取底面高度
- `AnalyzeBottomSurfaceHeightAtPosition(Point3D position, ModelItem channel, double floorHeight, double ceilingHeight)` - 分析底面表面高度
- `TryMultiPointBottomSampling(Point3D position, ModelItem channel)` - 多点采样获取底面高度
#### 3.2.3 空轨中心线提取工具
**文件**`RailGeometryHelper.cs`(新建)
```csharp
/// <summary>
/// 空轨几何体辅助工具
/// 负责提取空轨的下表面中心线
/// </summary>
public static class RailGeometryHelper
{
/// <summary>
/// 从空轨几何体提取下表面中心线
/// </summary>
/// <param name="railModel">空轨模型项</param>
/// <param name="startPoint">起点2D坐标</param>
/// <param name="endPoint">终点2D坐标</param>
/// <returns>路径点列表包含精确的Z坐标</returns>
public static List<Point3D> ExtractRailCenterLine(
ModelItem railModel,
Point3D startPoint,
Point3D endPoint)
{
var pathPoints = new List<Point3D>();
try
{
LogManager.Info($"[空轨] 开始提取空轨中心线: {railModel.DisplayName}");
// 1. 创建高度检测器实例
var heightDetector = new ChannelHeightDetector();
var railItems = new List<ModelItem> { railModel };
// 2. 计算起点和终点在X-Y平面上的投影距离
double distance2D = Math.Sqrt(
Math.Pow(endPoint.X - startPoint.X, 2) +
Math.Pow(endPoint.Y - startPoint.Y, 2)
);
if (distance2D < 0.01)
{
LogManager.Warning("[空轨] 起点和终点重合");
return pathPoints;
}
// 3. 在起点和终点之间采样计算每个采样点的精确Z坐标
int sampleCount = Math.Max(2, (int)Math.Ceiling(distance2D / 0.5)); // 每0.5米一个采样点
for (int i = 0; i < sampleCount; i++)
{
double t = (double)i / (sampleCount - 1);
// 线性插值计算X、Y坐标
double x = startPoint.X + t * (endPoint.X - startPoint.X);
double y = startPoint.Y + t * (endPoint.Y - startPoint.Y);
double z = startPoint.Z; // 初始Z坐标
// 调用现有的GetChannelBottomHeight方法获取精确底面高度
var samplePoint = new Point3D(x, y, z); // Z坐标会被方法更新
var bottomHeight = heightDetector.GetChannelBottomHeight(samplePoint, railItems);
pathPoints.Add(new Point3D(x, y, bottomHeight));
}
LogManager.Info($"[空轨] 生成 {pathPoints.Count} 个路径点,总长度: {distance2D:F2}m");
}
catch (Exception ex)
{
LogManager.Error($"[空轨] 提取空轨中心线失败: {ex.Message}");
}
return pathPoints;
}
}
```
#### 3.2.4 路径编辑器扩展
**文件**`PathEditingViewModel.cs`
新增方法:
- `GeneratePathFromRail(ModelItem railModel, Point3D startPoint, Point3D endPoint)` - 从空轨生成路径
- `SelectRailModelCommand` - 选择空轨命令
- `SetPathStartPointCommand` - 设置起点命令
- `SetPathEndPointCommand` - 设置终点命令
- `GeneratePathFromRailCommand` - 生成路径命令
新增属性:
- `SelectedPathType` - 选中的路径类型
- `PathTypes` - 路径类型数组
- `IsRailPathType` - 是否为空轨路径类型
- `SelectedRailModel` - 选中的空轨模型
- `SelectedRailModelName` - 选中的空轨模型名称
- `PathStartPoint` - 路径起点
- `PathStartPointDisplay` - 路径起点显示
- `PathEndPoint` - 路径终点
- `PathEndPointDisplay` - 路径终点显示
---
### 3.3 第三阶段动画系统适配1天
#### 3.3.1 车辆位置计算
**文件**`PathAnimationManager.cs`
```csharp
/// <summary>
/// 计算车辆在路径点上的位置
/// </summary>
private Point3D CalculateVehiclePosition(Point3D pathPoint, PathType pathType)
{
if (pathType == PathType.Rail)
{
// 空轨路径:车辆悬挂在空轨下方
// 车辆中心点 = 空轨点 - 车辆高度的一半
double vehicleCenterZ = pathPoint.Z - _virtualVehicleHeight / 2;
return new Point3D(pathPoint.X, pathPoint.Y, vehicleCenterZ);
}
else
{
// 地面路径:车辆中心点在路径点上
return pathPoint;
}
}
```
#### 3.3.2 传递路径类型
**文件**`PathAnimationManager.cs`
修改方法:
- `GenerateAnimationFrames(List<Point3D> pathPoints, PathType pathType)` - 传递路径类型参数
- `PrecomputeCollisions(List<Point3D> pathPoints, PathType pathType, List<ModelItem> railModels)` - 传递路径类型和空轨模型列表
- `DetectFrameCollisions(AnimationFrame frame, PathType pathType, List<ModelItem> collisionExclusions)` - 传递路径类型和排除列表
---
### 3.4 第四阶段碰撞检测适配1天
#### 3.4.1 空轨排除逻辑
**文件**`PathAnimationManager.cs`
修改方法:
- `PrecomputeCollisions(List<Point3D> pathPoints, PathType pathType, List<ModelItem> railModels)` - 新增空轨排除列表
- `DetectFrameCollisions(AnimationFrame frame, PathType pathType, List<ModelItem> collisionExclusions)` - 使用排除列表过滤
**核心逻辑**
```csharp
// 创建碰撞排除列表
var collisionExclusions = new List<ModelItem>();
// 如果是空轨路径,将空轨模型加入排除列表
if (pathType == PathType.Rail && railModels != null)
{
collisionExclusions.AddRange(railModels);
LogManager.Info($"[碰撞检测] 已排除 {railModels.Count} 个空轨模型");
}
// 在碰撞检测循环中跳过排除列表中的项目
foreach (var modelItem in allModelItems)
{
// 跳过排除列表中的项目(包括空轨本身)
if (collisionExclusions.Contains(modelItem))
{
continue;
}
// 统一使用3D碰撞检测
double distance = BoundingBoxGeometryUtils.CalculateDistance(vehicleBoundingBox, modelBoundingBox);
if (distance <= 0) // 相交或接触
{
collisions.Add(new CollisionResult
{
Item1 = _animatedObject,
Item2 = modelItem,
Distance = distance
});
}
}
```
---
### 3.5 第五阶段UI界面适配1天
#### 3.5.1 路径类型选择
**文件**`PathEditingViewModel.cs`
新增属性:
- `PathTypes` - 路径类型数组
- `SelectedPathType` - 选中的路径类型
- `IsRailPathType` - 是否为空轨路径类型
#### 3.5.2 命令
**文件**`PathEditingViewModel.cs`
新增命令:
- `GeneratePathFromRailCommand` - 从空轨生成路径命令
- `SelectRailModelCommand` - 选择空轨命令
- `SetPathStartPointCommand` - 设置起点命令
- `SetPathEndPointCommand` - 设置终点命令
#### 3.5.3 UI布局
**文件**`LogisticsControlPanel.xaml`
新增控件:
- 路径类型选择 ComboBox
- 空轨选择 StackPanel仅在选择空轨类型时显示
- 选择空轨按钮
- 设置起点按钮
- 设置终点按钮
- 生成路径按钮
---
## 四、实施步骤
### 第一阶段基础数据结构1天
1. ✅ 新增"空轨"物流属性类型
2. ✅ 新增路径类型枚举
3. ✅ 扩展路径数据模型
4. ✅ 适配数据库读写
### 第二阶段空轨路径生成2天
1. ✅ 在 `ChannelHeightDetector` 中新增底部高度检测方法
2. ✅ 复用现有的几何计算代码
3. ✅ 实现 `RailGeometryHelper`(调用现有方法)
4. ✅ 扩展路径编辑器
5. ✅ UI界面适配
6. ✅ 集成测试
### 第三阶段动画系统适配1天
1. ✅ 车辆位置计算适配
2. ✅ 传递路径类型参数
3. ✅ 动画播放测试
### 第四阶段碰撞检测适配1天
1. ✅ 实现空轨排除逻辑
2. ✅ 统一使用3D碰撞检测
3. ✅ 碰撞报告适配
4. ✅ 集成测试
### 第五阶段测试和优化1天
1. ✅ 功能测试
2. ✅ 性能测试
3. ✅ 用户验收测试
**总计**6天
---
## 五、风险评估
| 风险项 | 风险等级 | 缓解措施 |
|--------|---------|---------|
| 空轨几何体提取失败 | 🟢 低 | 空轨模型已存在,直接使用 |
| 车辆悬挂位置计算错误 | 🟡 中 | 充分测试,提供可视化验证 |
| 碰撞检测误报 | 🟡 中 | 排除空轨本身,调整检测参数 |
| 性能下降 | 🟢 低 | 空轨路径数量少,影响有限 |
| 几何计算性能 | 🟡 中 | 复用现有代码,优化采样策略 |
---
## 六、总结
### 6.1 方案优势
- ✅ **代码复用最大化**:充分利用现有的几何计算代码
- ✅ **架构统一**:空轨和地面路径使用相同的高度检测框架
- ✅ **改动最小化**:只需新增底部检测方法,其他全部复用
- ✅ **易于维护**:几何计算逻辑集中管理
- ✅ **向后兼容**:不需要迁移,新字段使用默认值
- ✅ **碰撞检测统一**统一使用3D检测简化逻辑
### 6.2 关键技术点
1. **复用现有代码**`ChannelHeightDetector` 的几何计算方法
2. **新增底部检测**`GetChannelBottomHeight()` 方法
3. **车辆悬挂计算**:车辆中心 = 空轨点 - 车辆高度/2
4. **碰撞检测统一**统一使用3D检测
5. **排除空轨本身**:通过排除列表过滤空轨模型
6. **精确几何计算**:支持倾斜、弯曲的轨道
### 6.3 技术难点
- 需要处理大量三角形面片(性能优化)
- 需要处理重叠三角形(选择最高点)
- 需要处理采样点不在任何三角形内的情况(使用默认值)
### 6.4 预期成果
- ✅ 支持空轨路径生成
- ✅ 支持车辆悬挂运行
- ✅ 支持空轨碰撞检测(排除空轨本身)
- ✅ 保持与现有地面路径功能的兼容性
---
## 七、附录
### 7.1 文件清单
#### 新增文件
- `RailGeometryHelper.cs` - 空轨几何体辅助工具
#### 修改文件
- `CategoryAttributeManager.cs` - 新增空轨枚举
- `PathPlanningModels.cs` - 新增路径类型枚举
- `PathRouteViewModel.cs` - 新增路径类型字段
- `PathDatabase.cs` - 适配路径类型读写
- `ChannelHeightDetector.cs` - 新增底部高度检测方法
- `PathEditingViewModel.cs` - 扩展路径编辑功能
- `PathAnimationManager.cs` - 适配车辆位置和碰撞检测
- `LogisticsControlPanel.xaml` - UI界面适配
### 7.2 方法清单
#### 新增方法ChannelHeightDetector.cs
- `GetChannelBottomHeight(Point3D position, IEnumerable<ModelItem> channelItems)` - 获取通道底面高度
- `CalculateBottomPreciseHeightAtPosition(Point3D position, ChannelHeightInfo heightInfo, ModelItem channel)` - 计算底面精确高度
- `TryRaycastFromBelow(Point3D position, ModelItem channel, double floorHeight, out double height)` - 从下方射线投射
- `PerformRayTriangleIntersectionFromBelow(Point3D position, List<Triangle3D> triangles)` - 从下往上射线-三角形交点检测
- `TryGetGeometricBottomHeight(Point3D position, ModelItem channel, out double height)` - View API获取底面高度
- `AnalyzeBottomSurfaceHeightAtPosition(Point3D position, ModelItem channel, double floorHeight, double ceilingHeight)` - 分析底面表面高度
- `TryMultiPointBottomSampling(Point3D position, ModelItem channel)` - 多点采样获取底面高度
#### 新增方法RailGeometryHelper.cs
- `ExtractRailCenterLine(ModelItem railModel, Point3D startPoint, Point3D endPoint)` - 提取空轨中心线
#### 新增方法PathEditingViewModel.cs
- `GeneratePathFromRail(ModelItem railModel, Point3D startPoint, Point3D endPoint)` - 从空轨生成路径
#### 新增方法PathAnimationManager.cs
- `CalculateVehiclePosition(Point3D pathPoint, PathType pathType)` - 计算车辆位置
#### 修改方法PathAnimationManager.cs
- `GenerateAnimationFrames(List<Point3D> pathPoints, PathType pathType)` - 传递路径类型参数
- `PrecomputeCollisions(List<Point3D> pathPoints, PathType pathType, List<ModelItem> railModels)` - 传递路径类型和空轨模型列表
- `DetectFrameCollisions(AnimationFrame frame, PathType pathType, List<ModelItem> collisionExclusions)` - 传递路径类型和排除列表
---

View File

@ -82,6 +82,7 @@ namespace NavisworksTransport
MaxVehicleHeight REAL,
SafetyMargin REAL,
GridSize REAL,
PathType INTEGER,
CreatedTime DATETIME,
LastModified DATETIME
)
@ -221,8 +222,8 @@ namespace NavisworksTransport
// 保存路径基本信息
var sql = @"
INSERT OR REPLACE INTO PathRoutes
(Id, Name, TotalLength, EstimatedTime, TurnRadius, IsCurved, MaxVehicleLength, MaxVehicleWidth, MaxVehicleHeight, SafetyMargin, GridSize, CreatedTime, LastModified)
VALUES (@id, @name, @length, @time, @turnRadius, @isCurved, @maxLength, @maxWidth, @maxHeight, @safetyMargin, @gridSize, @created, @modified)
(Id, Name, TotalLength, EstimatedTime, TurnRadius, IsCurved, MaxVehicleLength, MaxVehicleWidth, MaxVehicleHeight, SafetyMargin, GridSize, PathType, CreatedTime, LastModified)
VALUES (@id, @name, @length, @time, @turnRadius, @isCurved, @maxLength, @maxWidth, @maxHeight, @safetyMargin, @gridSize, @pathType, @created, @modified)
";
using (var cmd = new SQLiteCommand(sql, _connection))
@ -238,6 +239,7 @@ namespace NavisworksTransport
cmd.Parameters.AddWithValue("@maxHeight", route.MaxVehicleHeight);
cmd.Parameters.AddWithValue("@safetyMargin", route.SafetyMargin);
cmd.Parameters.AddWithValue("@gridSize", route.GridSize);
cmd.Parameters.AddWithValue("@pathType", (int)route.PathType);
cmd.Parameters.AddWithValue("@created", route.CreatedTime);
cmd.Parameters.AddWithValue("@modified", DateTime.Now);
cmd.ExecuteNonQuery();
@ -822,6 +824,7 @@ namespace NavisworksTransport
MaxVehicleHeight = reader.IsDBNull(reader.GetOrdinal("MaxVehicleHeight")) ? 2.0 : Convert.ToDouble(reader["MaxVehicleHeight"]),
SafetyMargin = reader.IsDBNull(reader.GetOrdinal("SafetyMargin")) ? 0.5 : Convert.ToDouble(reader["SafetyMargin"]),
GridSize = reader.IsDBNull(reader.GetOrdinal("GridSize")) ? 0.5 : Convert.ToDouble(reader["GridSize"]),
PathType = reader.IsDBNull(reader.GetOrdinal("PathType")) ? PathType.Ground : (PathType)Convert.ToInt32(reader["PathType"]),
CreatedTime = Convert.ToDateTime(reader["CreatedTime"]),
LastModified = Convert.ToDateTime(reader["LastModified"])
};

View File

@ -47,6 +47,9 @@ namespace NavisworksTransport
// 自动路径规划模式标志
private bool _isInAutoPathMode = false;
// 空轨吸附模式标志
private bool _enableRailSnapping = false;
// 路径点3D标记管理
private List<PathPointMarker> _pathPointMarkers;
@ -848,18 +851,29 @@ namespace NavisworksTransport
/// </summary>
/// <param name="pointType">点击类型</param>
public void StartClickTool(PathPointType pointType)
{
StartClickTool(pointType, enableRailSnapping: false);
}
/// <summary>
/// 启动点击工具(支持空轨吸附)
/// </summary>
/// <param name="pointType">点击类型</param>
/// <param name="enableRailSnapping">是否启用空轨吸附</param>
public void StartClickTool(PathPointType pointType, bool enableRailSnapping)
{
try
{
CurrentPointType = pointType;
_enableRailSnapping = enableRailSnapping;
// 检查是否在自动路径模式 - 如果是则不订阅PathPlanningManager的事件
bool shouldSubscribeToEvents = !IsInAutoPathMode;
LogManager.Info($"StartClickTool - 自动路径模式: {IsInAutoPathMode}, 订阅事件: {shouldSubscribeToEvents}");
LogManager.Info($"StartClickTool - 自动路径模式: {IsInAutoPathMode}, 订阅事件: {shouldSubscribeToEvents}, 空轨吸附: {enableRailSnapping}");
ActivateToolPlugin(shouldSubscribeToEvents);
PathEditState = PathEditState.AddingPoints;
LogManager.Info($"点击工具已启动,类型: {pointType},事件订阅: {shouldSubscribeToEvents}");
LogManager.Info($"点击工具已启动,类型: {pointType},事件订阅: {shouldSubscribeToEvents},空轨吸附: {enableRailSnapping}");
}
catch (Exception ex)
{
@ -1466,20 +1480,25 @@ namespace NavisworksTransport
/// 开始新建路径
/// </summary>
/// <param name="routeName">路径名称</param>
/// <param name="isRailPath">是否为空轨路径</param>
/// <returns>创建的新路径</returns>
public PathRoute StartCreatingNewRoute(string routeName = null)
public PathRoute StartCreatingNewRoute(string routeName = null, bool isRailPath = false)
{
try
{
// 自动选择所有可通行的物流模型(先检查是否有可通行的模型)
AutoSelectLogisticsChannels();
// 检查是否有可通行的物流模型
if (_walkableAreas == null || _walkableAreas.Count == 0)
// 空轨路径不需要自动选择可通行物流模型
if (!isRailPath)
{
RaiseErrorOccurred("没有找到任何可通行的物流模型,请先为模型设置可通行的物流属性");
// 不需要重置状态,因为还没有进入创建状态
return null;
// 自动选择所有可通行的物流模型(先检查是否有可通行的模型)
AutoSelectLogisticsChannels();
// 检查是否有可通行的物流模型
if (_walkableAreas == null || _walkableAreas.Count == 0)
{
RaiseErrorOccurred("没有找到任何可通行的物流模型,请先为模型设置可通行的物流属性");
// 不需要重置状态,因为还没有进入创建状态
return null;
}
}
// 设置为创建状态
@ -1493,7 +1512,11 @@ namespace NavisworksTransport
// 智能管理ToolPlugin状态
ManageToolPluginForEditState();
RaiseStatusChanged($"正在新建路径: {newRoute.Name} - 请在3D视图中可通行的物流模型上点击设置路径点", PathPlanningStatusType.Info);
string statusMessage = isRailPath
? $"正在新建空轨路径: {newRoute.Name} - 请在3D视图中点击空轨模型设置路径点将自动吸附到基准路径"
: $"正在新建路径: {newRoute.Name} - 请在3D视图中可通行的物流模型上点击设置路径点";
RaiseStatusChanged(statusMessage, PathPlanningStatusType.Info);
return newRoute;
}
@ -1537,8 +1560,8 @@ namespace NavisworksTransport
}
}
// 如果是创建模式,将当前路径添加到路径集合
if (_pathEditState == PathEditState.Creating && CurrentRoute != null)
// 如果是创建模式或添加点模式,将当前路径添加到路径集合
if ((PathEditState == PathEditState.Creating || PathEditState == PathEditState.AddingPoints) && CurrentRoute != null)
{
if (!_routes.Contains(CurrentRoute))
{
@ -2669,78 +2692,169 @@ namespace NavisworksTransport
/// </summary>
private void ProcessManualPathEditing(PickItemResult pickResult)
{
// 检查当前选中的通道状态
LogManager.Debug($"[手动编辑] 当前_selectedChannels状态: {(_walkableAreas == null ? "NULL" : $"{_walkableAreas.Count}")}");
// 如果没有选中的通道,尝试实时搜索
if (_walkableAreas == null || _walkableAreas.Count == 0)
try
{
LogManager.Debug("[手动编辑] 没有预选通道,开始实时搜索可通行的物流模型");
SearchAndSetTraversableChannels();
}
// 检查是否在可通行的物流模型内并处理点击
if (_walkableAreas != null && _walkableAreas.Any())
{
bool isInTraversableLogisticsModel = IsItemInSelectedChannels(pickResult.ModelItem) ||
IsItemChildOfSelectedChannels(pickResult.ModelItem);
LogManager.Debug($"[手动编辑] 在可通行的物流模型内: {isInTraversableLogisticsModel}");
if (isInTraversableLogisticsModel)
// 如果启用了空轨吸附,先吸附到基准路径
Point3D clickedPoint = pickResult.Point;
if (_enableRailSnapping)
{
// 手动路径编辑 - 根据当前模式处理点击
if (PathEditState == PathEditState.AddingPoints)
{
// 添加路径点模式 - 使用预览点
LogManager.Debug("[手动编辑] 设置预览点位置");
var previewPoint = SetPreviewPoint(pickResult.Point);
clickedPoint = SnapToRailBaseline(clickedPoint);
}
if (previewPoint != null)
{
LogManager.Debug($"[手动编辑] ✓ 预览点已设置: {previewPoint.Name}");
}
else
{
LogManager.Warning("[手动编辑] ✗ 预览点设置失败");
}
}
else if (PathEditState == PathEditState.EditingPoint)
// 检查当前选中的通道状态
LogManager.Debug($"[手动编辑] 当前_selectedChannels状态: {(_walkableAreas == null ? "NULL" : $"{_walkableAreas.Count}")}");
// 如果没有选中的通道,尝试实时搜索
if (_walkableAreas == null || _walkableAreas.Count == 0)
{
LogManager.Debug("[手动编辑] 没有预选通道,开始实时搜索可通行的物流模型");
SearchAndSetTraversableChannels();
}
// 空轨路径直接添加路径点,不检查可通行物流模型
if (_enableRailSnapping)
{
LogManager.Debug("[手动编辑] 空轨路径模式,直接添加路径点");
var pathPoint = AddPathPointIn3D(clickedPoint);
if (pathPoint != null)
{
// 修改路径点模式 - 设置预览位置
LogManager.Debug("[手动编辑] 设置修改路径点预览位置");
SetEditingPreviewPoint(pickResult.Point);
LogManager.Debug($"[手动编辑] ✓ 修改路径点预览位置已设置: ({pickResult.Point.X:F3}, {pickResult.Point.Y:F3}, {pickResult.Point.Z:F3})");
LogManager.Debug($"[手动编辑] ✓ 空轨路径点添加成功: {pathPoint.Name}");
}
else
{
// 其他编辑模式 - 保持原有逻辑
LogManager.Debug("[手动编辑] 调用AddPathPointIn3D添加路径点");
var pathPoint = AddPathPointIn3D(pickResult.Point);
LogManager.Warning("[手动编辑] ✗ 空轨路径点添加失败");
}
}
// 地面路径检查是否在可通行的物流模型内并处理点击
else if (_walkableAreas != null && _walkableAreas.Any())
{
bool isInTraversableLogisticsModel = IsItemInSelectedChannels(pickResult.ModelItem) ||
IsItemChildOfSelectedChannels(pickResult.ModelItem);
if (pathPoint != null)
LogManager.Debug($"[手动编辑] 在可通行的物流模型内: {isInTraversableLogisticsModel}");
if (isInTraversableLogisticsModel)
{
// 手动路径编辑 - 根据当前模式处理点击
if (PathEditState == PathEditState.AddingPoints)
{
LogManager.Debug($"[手动编辑] ✓ 路径点添加成功: {pathPoint.Name}");
// 添加路径点模式 - 使用预览点
LogManager.Debug("[手动编辑] 设置预览点位置");
var previewPoint = SetPreviewPoint(clickedPoint);
if (previewPoint != null)
{
LogManager.Debug($"[手动编辑] ✓ 预览点已设置: {previewPoint.Name}");
}
else
{
LogManager.Warning("[手动编辑] ✗ 预览点设置失败");
}
}
else if (PathEditState == PathEditState.EditingPoint)
{
// 修改路径点模式 - 设置预览位置
LogManager.Debug("[手动编辑] 设置修改路径点预览位置");
SetEditingPreviewPoint(clickedPoint);
LogManager.Debug($"[手动编辑] ✓ 修改路径点预览位置已设置: ({clickedPoint.X:F3}, {clickedPoint.Y:F3}, {clickedPoint.Z:F3})");
}
else
{
LogManager.Warning("[手动编辑] ✗ 路径点添加失败");
// 其他编辑模式 - 保持原有逻辑
LogManager.Debug("[手动编辑] 调用AddPathPointIn3D添加路径点");
var pathPoint = AddPathPointIn3D(clickedPoint);
if (pathPoint != null)
{
LogManager.Debug($"[手动编辑] ✓ 路径点添加成功: {pathPoint.Name}");
}
else
{
LogManager.Warning("[手动编辑] ✗ 路径点添加失败");
}
}
}
else
{
LogManager.Debug("[手动编辑] ✗ 点击位置不在可通行的物流模型内");
RaiseErrorOccurred("点击位置不在物流通道内,请选择有效的物流路径位置");
}
}
else
{
LogManager.Debug("[手动编辑] ✗ 点击位置不在可通行的物流模型内");
RaiseErrorOccurred("点击位置不在物流通道内,请选择有效的物流路径位置");
LogManager.Warning("[手动编辑] ✗ 未找到可通行的物流模型");
RaiseErrorOccurred("未找到可通行的物流通道,请先选择或配置物流通道");
}
}
else
catch (Exception ex)
{
LogManager.Warning("[手动编辑] ✗ 未找到可通行的物流模型");
RaiseErrorOccurred("未找到可通行的物流通道,请先选择或配置物流通道");
LogManager.Error($"[手动编辑] 处理异常: {ex.Message}");
LogManager.Error($"[手动编辑] 堆栈: {ex.StackTrace}");
}
}
/// <summary>
/// 将点击点吸附到空轨基准路径
/// </summary>
private Point3D SnapToRailBaseline(Point3D clickedPoint)
{
try
{
// 获取所有空轨基准路径
var baselinePaths = PathPointRenderPlugin.Instance?.GetAllRailBaselinePaths();
if (baselinePaths == null || baselinePaths.Count == 0)
{
LogManager.Warning("[空轨吸附] 没有找到空轨基准路径,不进行吸附");
return clickedPoint;
}
// 找到最近的基准路径点
Point3D nearestPoint = null;
double minDistance = double.MaxValue;
double maxSnapDistance = 2.0; // 最大吸附距离(模型单位)
foreach (var pathPoints in baselinePaths.Values)
{
foreach (var point in pathPoints)
{
double distance = CalculateDistance3D(clickedPoint, point);
if (distance < minDistance)
{
minDistance = distance;
nearestPoint = point;
}
}
}
if (nearestPoint != null && minDistance <= maxSnapDistance)
{
LogManager.Info($"[空轨吸附] 点击点 ({clickedPoint.X:F2}, {clickedPoint.Y:F2}, {clickedPoint.Z:F2}) 吸附到基准路径点 ({nearestPoint.X:F2}, {nearestPoint.Y:F2}, {nearestPoint.Z:F2}),距离 {minDistance:F2}");
return nearestPoint;
}
else
{
LogManager.Warning($"[空轨吸附] 未找到合适的基准路径点(最近距离: {minDistance:F2},最大吸附距离: {maxSnapDistance}");
return clickedPoint;
}
}
catch (Exception ex)
{
LogManager.Error($"[空轨吸附] 吸附失败: {ex.Message}");
return clickedPoint;
}
}
/// <summary>
/// 计算3D距离
/// </summary>
private double CalculateDistance3D(Point3D a, Point3D b)
{
double dx = a.X - b.X;
double dy = a.Y - b.Y;
double dz = a.Z - b.Z;
return Math.Sqrt(dx * dx + dy * dy + dz * dz);
}
/// <summary>
/// 搜索并设置可通行的通道
/// </summary>
@ -2963,6 +3077,16 @@ namespace NavisworksTransport
try
{
// 空轨路径不进行曲线化,直接保存
if (route.PathType == PathType.Rail)
{
LogManager.Info($"空轨路径跳过曲线化,直接保存: {route.Name}");
SavePathToDatabase(route);
LogManager.Info($"空轨路径已保存到数据库: {route.Name}");
return;
}
// 地面路径进行曲线化
double samplingStep = ConfigManager.Instance.Current.PathEditing.ArcSamplingStep;
route.TurnRadius = ConfigManager.Instance.Current.PathEditing.DefaultPathTurnRadius;
List<string> warnings;

View File

@ -44,7 +44,23 @@ namespace NavisworksTransport
/// </summary>
EditingPoint
}
/// <summary>
/// 路径类型枚举
/// </summary>
public enum PathType
{
/// <summary>
/// 地面路径 - 车辆在地面运行
/// </summary>
Ground = 0,
/// <summary>
/// 空轨路径 - 车辆悬挂在空轨下方运行
/// </summary>
Rail = 1
}
/// <summary>
/// 通道检测结果
/// </summary>
@ -546,6 +562,11 @@ namespace NavisworksTransport
/// </summary>
public bool IsCurved { get; set; } = false;
/// <summary>
/// 路径类型
/// </summary>
public PathType PathType { get; set; } = PathType.Ground;
// 数据库分析相关属性
/// <summary>
/// 碰撞数量(从数据库加载)

View File

@ -118,7 +118,12 @@ namespace NavisworksTransport
/// <summary>
/// 安全警告样式(红色)
/// </summary>
SafetyWarning
SafetyWarning,
/// <summary>
/// 空轨基准路径样式(浅红色)
/// </summary>
RailBaseline
}
/// <summary>
@ -367,6 +372,9 @@ namespace NavisworksTransport
// 预览连线标记
private List<LineMarker> _previewLines = new List<LineMarker>();
// 空轨基准路径可视化
private Dictionary<string, PathVisualization> _railBaselineVisualizations = new Dictionary<string, PathVisualization>();
// 当前网格大小(米),用于自适应点大小计算
private double _currentGridSizeInMeters;
@ -495,14 +503,16 @@ namespace NavisworksTransport
// 检查是否有路径或预览点需要渲染
int pathCount;
int railBaselineCount;
bool hasPreviewPoint;
lock (_lockObject)
{
pathCount = _pathVisualizations.Count;
railBaselineCount = _railBaselineVisualizations.Count;
hasPreviewPoint = _previewMarker != null;
}
if (pathCount == 0 && !hasPreviewPoint)
if (pathCount == 0 && railBaselineCount == 0 && !hasPreviewPoint)
{
return;
}
@ -591,6 +601,19 @@ namespace NavisworksTransport
graphics.Cylinder(previewLine.StartPoint, previewLine.EndPoint, previewLine.Radius);
}
}
// 渲染空轨基准路径(浅红色)
if (_railBaselineVisualizations.Count > 0)
{
foreach (var visualization in _railBaselineVisualizations.Values)
{
foreach (var pathLineMarker in visualization.PathLineMarkers)
{
graphics.Color(pathLineMarker.Color, 0.7); // 使用70%透明度
graphics.Cylinder(pathLineMarker.StartPoint, pathLineMarker.EndPoint, pathLineMarker.Radius);
}
}
}
}
graphics.EndModelContext();
@ -854,6 +877,129 @@ namespace NavisworksTransport
}
}
/// <summary>
/// 渲染空轨基准路径
/// </summary>
/// <param name="railModelId">空轨模型ID</param>
/// <param name="baselinePoints">基准路径点列表</param>
public void RenderRailBaseline(string railModelId, List<Point3D> baselinePoints)
{
if (string.IsNullOrEmpty(railModelId) || baselinePoints == null || baselinePoints.Count < 2)
{
LogManager.Warning($"[路径渲染] RenderRailBaseline参数无效: railModelId={railModelId}, points={(baselinePoints?.Count ?? 0)}");
return;
}
try
{
LogManager.Info($"[路径渲染] 开始渲染空轨基准路径: {railModelId}, {baselinePoints.Count} 个点");
// 先清除该模型的旧基准路径可视化
ClearRailBaseline(railModelId);
// 创建PathVisualization对象
var visualization = new PathVisualization
{
PathId = railModelId,
ShowControlVisualization = false, // 不显示控制点
LastUpdated = DateTime.Now
};
var renderStyle = GetRenderStyle(RenderStyleName.RailBaseline);
LogManager.Info($"[路径渲染] RailBaseline样式: R={renderStyle.Color.R}, G={renderStyle.Color.G}, B={renderStyle.Color.B}, Alpha={renderStyle.Alpha}");
// 创建连线标记
for (int i = 0; i < baselinePoints.Count - 1; i++)
{
var start = baselinePoints[i];
var end = baselinePoints[i + 1];
var lineMarker = new LineMarker
{
StartPoint = start,
EndPoint = end,
Color = renderStyle.Color,
Radius = GetLineRadius() * 0.2,
SegmentType = PathSegmentType.Straight,
FromIndex = i,
ToIndex = i + 1
};
visualization.PathLineMarkers.Add(lineMarker);
}
LogManager.Info($"[路径渲染] 创建了 {visualization.PathLineMarkers.Count} 个连线标记");
lock (_lockObject)
{
_railBaselineVisualizations[railModelId] = visualization;
LogManager.Info($"[路径渲染] 已添加到字典,当前共有 {_railBaselineVisualizations.Count} 个基准路径");
}
RequestViewRefresh();
LogManager.Info($"[路径渲染] 已请求视图刷新");
}
catch (Exception ex)
{
LogManager.Error($"[路径渲染] 渲染空轨基准路径失败: {ex.Message}", ex);
}
}
/// <summary>
/// 清除指定空轨的基准路径可视化
/// </summary>
/// <param name="railModelId">空轨模型ID</param>
public void ClearRailBaseline(string railModelId)
{
if (string.IsNullOrEmpty(railModelId))
{
return;
}
lock (_lockObject)
{
if (_railBaselineVisualizations.Remove(railModelId))
{
RequestViewRefresh();
LogManager.Info($"[路径渲染] 已清除空轨基准路径: {railModelId}");
}
}
}
/// <summary>
/// 获取所有空轨基准路径
/// </summary>
/// <returns>所有空轨基准路径的字典</returns>
public Dictionary<string, List<Point3D>> GetAllRailBaselinePaths()
{
lock (_lockObject)
{
var result = new Dictionary<string, List<Point3D>>();
foreach (var kvp in _railBaselineVisualizations)
{
result[kvp.Key] = kvp.Value.PathLineMarkers.Select(m => m.StartPoint).ToList();
}
return result;
}
}
/// <summary>
/// 清除所有空轨基准路径可视化
/// </summary>
public void ClearAllRailBaselines()
{
lock (_lockObject)
{
int count = _railBaselineVisualizations.Count;
_railBaselineVisualizations.Clear();
if (count > 0)
{
RequestViewRefresh();
LogManager.Info($"[路径渲染] 已清除所有空轨基准路径, 共 {count} 个");
}
}
}
/// <summary>
/// 清空所有路径
/// </summary>
@ -942,9 +1088,30 @@ namespace NavisworksTransport
// 构建控制点连线(用户意图,半透明)
BuildControlLines(visualization, sortedPoints);
// 所有模式都使用曲线化后的路径Edges
if (visualization.PathRoute.Edges != null && visualization.PathRoute.Edges.Count > 0)
// 地面路径使用曲线化后的路径Edges
// 空轨路径直接使用控制点连线作为路径连线
if (visualization.PathRoute.PathType == NavisworksTransport.PathType.Rail)
{
// 空轨路径:将控制点连线复制到路径连线,使用不透明样式
foreach (var controlLine in visualization.ControlLineMarkers)
{
var pathLineMarker = new LineMarker
{
StartPoint = controlLine.StartPoint,
EndPoint = controlLine.EndPoint,
Color = GetRenderStyle(RenderStyleName.Line).Color,
Radius = GetLineRadius(),
SegmentType = PathSegmentType.Straight,
FromIndex = controlLine.FromIndex,
ToIndex = controlLine.ToIndex
};
visualization.PathLineMarkers.Add(pathLineMarker);
}
LogManager.Debug($"[路径渲染] 空轨路径使用控制点连线,共 {visualization.PathLineMarkers.Count} 条");
}
else if (visualization.PathRoute.Edges != null && visualization.PathRoute.Edges.Count > 0)
{
// 地面路径:使用曲线化后的路径
BuildPathLines(visualization, sortedPoints);
}
@ -1371,6 +1538,9 @@ namespace NavisworksTransport
case RenderStyleName.SafetyWarning:
return new RenderStyle(Color.FromByteRGB(244, 67, 54), 0.85); // Material Red安全警告15%透明
case RenderStyleName.RailBaseline:
return new RenderStyle(Color.FromByteRGB(255, 138, 128), 0.7); // 浅红色30%透明
default:
return new RenderStyle(Color.White, 1.0); // 默认白色,完全不透明
}

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using Autodesk.Navisworks.Api;
using NavisworksTransport.Utils;
using NavisworksTransport.PathPlanning;
namespace NavisworksTransport
{
@ -88,10 +89,15 @@ namespace NavisworksTransport
/// </summary>
= 9,
/// <summary>
/// 空轨 - 空中运输路径车辆悬挂运行权重0.7
/// </summary>
= 10,
/// <summary>
/// 无关项 - 不参与网格生成的大型构件(如地基、结构基础等)
/// </summary>
= 10
= 11
}
/// <summary>
@ -170,6 +176,13 @@ namespace NavisworksTransport
propertyInternalNames);
LogManager.Info($"[属性添加] 添加操作完成,成功添加 {successCount} 个模型的属性");
// 如果是空轨类型,预计算基准路径
if (elementType == LogisticsElementType. && successCount > 0)
{
PreCalculateRailBaselinePaths(items);
}
return successCount;
}
@ -524,6 +537,52 @@ namespace NavisworksTransport
return LogisticsElementType.;
}
/// <summary>
/// 预计算空轨基准路径
/// </summary>
/// <param name="items">已设置为空轨的模型项集合</param>
private static void PreCalculateRailBaselinePaths(ModelItemCollection items)
{
if (items == null || items.Count == 0)
return;
LogManager.Info($"[空轨] 开始预计算 {items.Count} 个空轨模型的基准路径");
int successCount = 0;
int failCount = 0;
foreach (ModelItem item in items)
{
try
{
// 调用RailGeometryHelper提取基准路径
var baselinePath = PathPlanning.RailGeometryHelper.ExtractRailBaselinePath(item);
if (baselinePath != null && baselinePath.PathPoints != null && baselinePath.PathPoints.Count > 0)
{
successCount++;
LogManager.Info($"[空轨] 成功提取模型 {item.DisplayName} 的基准路径,共 {baselinePath.PathPoints.Count} 个点,总长度 {baselinePath.TotalLength:F2} 模型单位");
// 自动可视化基准路径
var railModelId = $"{item.DisplayName}_{item.GetHashCode()}";
PathPointRenderPlugin.Instance.RenderRailBaseline(railModelId, baselinePath.PathPoints);
}
else
{
failCount++;
LogManager.Warning($"[空轨] 模型 {item.DisplayName} 的基准路径提取失败或路径为空");
}
}
catch (Exception ex)
{
failCount++;
LogManager.Error($"[空轨] 预计算模型 {item.DisplayName} 的基准路径时发生错误: {ex.Message}");
}
}
LogManager.Info($"[空轨] 基准路径预计算完成,成功 {successCount} 个,失败 {failCount} 个");
}
/// <summary>
/// 删除选定模型项的物流属性
/// </summary>

View File

@ -2362,7 +2362,7 @@ namespace NavisworksTransport.PathPlanning
};
// 检查路径类型
if (astarPath.Type == PathType.ClosestApproach)
if (astarPath.Type == Roy_T.AStar.Paths.PathType.ClosestApproach)
{
LogManager.Info($"[A*执行] 找到部分路径(最近接近),包含 {astarPath.Edges.Count + 1} 个网格点");
LogManager.Warning($"[A*执行] 无法完全到达目标点,已找到最接近的可达点");

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,34 @@
using System;
using System.Globalization;
using System.Windows.Data;
namespace NavisworksTransport.UI.WPF.Converters
{
/// <summary>
/// 路径类型转换器将PathType枚举转换为中文显示
/// </summary>
public class PathTypeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is NavisworksTransport.PathType pathType)
{
switch (pathType)
{
case NavisworksTransport.PathType.Ground:
return "地面";
case NavisworksTransport.PathType.Rail:
return "空轨";
default:
return "未知";
}
}
return "地面";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

View File

@ -182,6 +182,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
private double _totalLength;
private bool _isValidated;
private string _validationStatus;
private NavisworksTransport.PathType _pathType;
// UI状态管理
private readonly UIStateManager _uiStateManager;
@ -272,6 +273,15 @@ namespace NavisworksTransport.UI.WPF.ViewModels
set => SetProperty(ref _isOptimal, value);
}
/// <summary>
/// 路径类型
/// </summary>
public NavisworksTransport.PathType PathType
{
get => _pathType;
set => SetProperty(ref _pathType, value);
}
/// <summary>
/// 创建时间
/// </summary>

View File

@ -246,10 +246,6 @@ namespace NavisworksTransport.UI.WPF.ViewModels
LogManager.Warning($"[路径可视化] 未找到对应的Core路径: {_selectedPathRoute.Name}");
}
}
else
{
LogManager.Debug("[路径可视化] 没有选中的路径,仅清理显示");
}
// 3. 恢复网格可视化如果网格可视化已启用且当前路径有关联的GridMap
if (_pathPlanningManager?.IsAnyGridVisualizationEnabled == true)
@ -638,6 +634,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
#region
public ICommand NewPathCommand { get; private set; }
public ICommand NewRailPathCommand { get; private set; }
public ICommand DeletePathCommand { get; private set; }
public ICommand RenamePathCommand { get; private set; }
public ICommand StartEditCommand { get; private set; }
@ -665,6 +662,8 @@ namespace NavisworksTransport.UI.WPF.ViewModels
public bool CanExecuteNewPath => !IsSelectingStartPoint && !IsSelectingEndPoint;
public bool CanExecuteNewRailPath => !IsSelectingStartPoint && !IsSelectingEndPoint;
public bool CanExecuteStartEdit => SelectedPathRoute != null &&
(_pathPlanningManager?.PathEditState == PathEditState.Viewing);
@ -802,6 +801,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
private void InitializeCommands()
{
NewPathCommand = new RelayCommand(async () => await ExecuteNewPathAsync(), () => CanExecuteNewPath);
NewRailPathCommand = new RelayCommand(async () => await ExecuteNewRailPathAsync(), () => CanExecuteNewRailPath);
DeletePathCommand = new RelayCommand(async () => await ExecuteDeletePathAsync());
RenamePathCommand = new RelayCommand(async () => await ExecuteRenamePathAsync());
StartEditCommand = new RelayCommand(async () => await ExecuteAddPathPointAsync(), () => CanExecuteStartEdit);
@ -910,6 +910,97 @@ namespace NavisworksTransport.UI.WPF.ViewModels
}, "新建路径");
}
private async Task ExecuteNewRailPathAsync()
{
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(isRailPath: true);
if (newRoute != null)
{
// 设置路径类型为空轨
newRoute.PathType = PathType.Rail;
// 创建对应的 WPF ViewModel
var newPathViewModel = new PathRouteViewModel
{
Id = newRoute.Id,
Name = newRoute.Name,
Description = newRoute.Description,
IsActive = true,
PathType = PathType.Rail
};
// 转换路径点
foreach (var corePoint in newRoute.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;
// 强制重新初始化ToolPlugin以确保获得鼠标焦点
if (!ForceReinitializeToolPlugin(subscribeToEvents: true))
{
UpdateMainStatus("ToolPlugin初始化失败请重试");
LogManager.Error("新建空轨路径ToolPlugin初始化失败");
return;
}
// 启动点击工具,设置空轨吸附模式
_pathPlanningManager.StartClickTool(PathPointType.WayPoint, enableRailSnapping: true);
LogManager.Info($"已启动空轨路径点击工具(吸附模式): {newRoute.Name}");
UpdateMainStatus($"已进入新建空轨路径模式: {newRoute.Name} - 请在3D视图中点击空轨模型设置路径点将自动吸附到基准路径");
LogManager.Info($"开始新建空轨路径: {newRoute.Name}已强制重新初始化ToolPlugin获取鼠标焦点");
}
else
{
UpdateMainStatus("创建新空轨路径失败:没有可通行的物流模型");
LogManager.Error("创建新空轨路径失败:没有可通行的物流模型");
MessageBox.Show("创建新空轨路径失败:没有找到任何可通行的物流模型。\n请先为模型设置可通行的物流属性然后再尝试创建路径。", "错误",
MessageBoxButton.OK, MessageBoxImage.Warning);
}
}
else
{
UpdateMainStatus("路径规划管理器未初始化");
LogManager.Error("路径规划管理器未初始化");
}
}, "新建空轨路径");
}
private async Task ExecuteDeletePathAsync()
{
if (SelectedPathRoute == null) return;
@ -1366,13 +1457,24 @@ namespace NavisworksTransport.UI.WPF.ViewModels
SelectedPathRoute.IsActive = true;
UpdateMainStatus($"正在激活3D路径编辑模式: {SelectedPathRoute.Name}...");
// 检查是否是空轨路径
bool isRailPath = SelectedPathRoute.PathType == PathType.Rail;
// 启动PathPlanningManager的点击工具这会设置正确的编辑状态
if (_pathPlanningManager != null)
{
try
{
_pathPlanningManager.StartClickTool(PathPointType.WayPoint);
LogManager.Info($"已启动PathPlanningManager点击工具: {SelectedPathRoute.Name}");
if (isRailPath)
{
_pathPlanningManager.StartClickTool(PathPointType.WayPoint, enableRailSnapping: true);
LogManager.Info($"已启动PathPlanningManager点击工具空轨吸附模式: {SelectedPathRoute.Name}");
}
else
{
_pathPlanningManager.StartClickTool(PathPointType.WayPoint);
LogManager.Info($"已启动PathPlanningManager点击工具: {SelectedPathRoute.Name}");
}
}
catch (Exception ex)
{
@ -1390,7 +1492,8 @@ namespace NavisworksTransport.UI.WPF.ViewModels
return;
}
UpdateMainStatus($"已进入3D路径编辑模式: {SelectedPathRoute.Name} - 在3D视图中点击设置路径点");
string modeText = isRailPath ? "(空轨吸附模式)" : "";
UpdateMainStatus($"已进入3D路径编辑模式: {SelectedPathRoute.Name} {modeText} - 在3D视图中点击设置路径点");
LogManager.Info($"开始添加路径点: {SelectedPathRoute.Name},已启动点击工具");
// 手动触发按钮状态更新
@ -3023,7 +3126,8 @@ namespace NavisworksTransport.UI.WPF.ViewModels
Id = coreRoute.Id,
Name = coreRoute.Name,
Description = coreRoute.Description,
IsActive = false // 历史路径默认不激活
IsActive = false, // 历史路径默认不激活
PathType = coreRoute.PathType
};
// 设置时间信息

View File

@ -26,6 +26,7 @@ NavisworksTransport 路径编辑页签视图 - 采用与动画控制和分层管
<!-- 转换器资源 -->
<converters:BoolToVisibilityConverter x:Key="BoolToVisConverter"/>
<converters:PathTypeConverter x:Key="PathTypeConverter"/>
<!-- 滑块样式 -->
<Style x:Key="SliderStyle" TargetType="Slider">
@ -211,6 +212,10 @@ NavisworksTransport 路径编辑页签视图 - 采用与动画控制和分层管
<Button Content="手动创建"
Command="{Binding NewPathCommand}"
Style="{StaticResource ActionButtonStyle}"/>
<Button Content="空轨路径"
Command="{Binding NewRailPathCommand}"
Style="{StaticResource ActionButtonStyle}"
Margin="5,0,0,0"/>
<Button Content="删除"
Command="{Binding DeletePathCommand}"
Style="{StaticResource SecondaryButtonStyle}"
@ -236,7 +241,8 @@ NavisworksTransport 路径编辑页签视图 - 采用与动画控制和分层管
BorderThickness="1">
<ListView.View>
<GridView>
<GridViewColumn Header="路径名称" DisplayMemberBinding="{Binding Name}" Width="160"/>
<GridViewColumn Header="路径名称" DisplayMemberBinding="{Binding Name}" Width="120"/>
<GridViewColumn Header="类型" DisplayMemberBinding="{Binding PathType, Converter={StaticResource PathTypeConverter}}" Width="50"/>
<GridViewColumn Header="点数" DisplayMemberBinding="{Binding PointCount}" Width="50"/>
<GridViewColumn Header="状态" DisplayMemberBinding="{Binding StatusString}" Width="60"/>
<GridViewColumn Header="创建时间" DisplayMemberBinding="{Binding CreatedTime, StringFormat='MM-dd HH:mm:ss'}" Width="100"/>