实现完整的时间标签功能
This commit is contained in:
parent
178aa995ff
commit
57751e85fa
@ -120,6 +120,7 @@
|
||||
<Compile Include="src\Core\Models\PathAnalysisModels.cs" />
|
||||
<Compile Include="src\Core\Models\BatchQueueItem.cs" />
|
||||
<Compile Include="src\Core\Models\CollisionDetectionConfig.cs" />
|
||||
<Compile Include="src\Core\Models\TimeTagModels.cs" />
|
||||
<Compile Include="src\Core\PathCurveEngine.cs" />
|
||||
<Compile Include="src\Core\GridVisualization.cs" />
|
||||
<Compile Include="src\Core\BatchQueueManager.cs" />
|
||||
@ -140,6 +141,10 @@
|
||||
<Compile Include="src\Core\Config\SystemConfig.cs" />
|
||||
<Compile Include="src\Core\Config\ConfigManager.cs" />
|
||||
<Compile Include="src\Core\Database\BackupManager.cs" />
|
||||
<Compile Include="src\Core\Services\TimeTagCalculator.cs" />
|
||||
<Compile Include="src\Core\Services\TimeTagService.cs" />
|
||||
<Compile Include="src\Core\Services\TimeTagExporter.cs" />
|
||||
<Compile Include="src\Core\Services\TimeTagTimeLinerIntegration.cs" />
|
||||
<!-- Commands - Command Pattern Framework (for testing) -->
|
||||
<Compile Include="src\Commands\IPathPlanningCommand.cs" />
|
||||
<Compile Include="src\Commands\CommandBase.cs" />
|
||||
@ -236,6 +241,9 @@
|
||||
<Compile Include="src\UI\WPF\Views\CollisionReportDialog.xaml.cs">
|
||||
<DependentUpon>CollisionReportDialog.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="src\UI\WPF\Controls\EventTimelineControl.xaml.cs">
|
||||
<DependentUpon>EventTimelineControl.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="src\UI\WPF\Views\TimeTagDialog.xaml.cs">
|
||||
<DependentUpon>TimeTagDialog.xaml</DependentUpon>
|
||||
</Compile>
|
||||
@ -392,6 +400,10 @@
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="src\UI\WPF\Controls\EventTimelineControl.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="src\UI\WPF\Views\TimeTagDialog.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
|
||||
1. [x] (优化)实现完整的路径分析功能
|
||||
2. [x] (功能)用物流分类属性筛选对应的物流元素
|
||||
3. [x] (优化)实现完整的时间标签功能
|
||||
|
||||
### [2026/2/8]
|
||||
|
||||
|
||||
165
doc/working/timetag_implementation_plan.md
Normal file
165
doc/working/timetag_implementation_plan.md
Normal file
@ -0,0 +1,165 @@
|
||||
# 路径时标系统实现计划
|
||||
|
||||
## 方案概述
|
||||
以事件为核心的路径时间标签系统,独立于路径数据结构,支持多套时标配置。
|
||||
|
||||
## 核心原则
|
||||
1. **独立体系** - 时标系统不修改 PathRoute 数据结构,通过 RouteId 关联
|
||||
2. **多对一** - 一条路径可有多套时标配置(不同车辆/策略)
|
||||
3. **事件驱动** - 以事件节点为锚点,路段时间只是事件之间的填充
|
||||
4. **简化计算** - 匀速模型 + 转弯降速
|
||||
|
||||
---
|
||||
|
||||
## 数据模型
|
||||
|
||||
### TimeTagProfile(时标配置)
|
||||
```csharp
|
||||
- Id: string (GUID)
|
||||
- RouteId: string (关联路径ID)
|
||||
- Name: string (配置名称,如:叉车满载)
|
||||
- DefaultSpeed: double (默认速度 m/s)
|
||||
- TurnSpeedRatio: double (转弯速度比例,0.5=半速)
|
||||
- Events: List<TimeTagEvent> (事件节点列表)
|
||||
```
|
||||
|
||||
### TimeTagEvent(时间事件节点)
|
||||
```csharp
|
||||
- Id: string
|
||||
- ProfileId: string
|
||||
- PointId: string (关联路径点ID,可选)
|
||||
- Distance: double (距起点距离,米)
|
||||
- Type: EventType (事件类型)
|
||||
- Name: string
|
||||
- WaitTime: double (等待时间,秒)
|
||||
- ActionTime: double (动作时间,秒)
|
||||
- CumulativeTime: double (累计时间,自动计算)
|
||||
- ArriveSpeed: double? (到达速度限制,可选)
|
||||
- LeaveSpeed: double? (离开速度限制,可选)
|
||||
```
|
||||
|
||||
### EventType 枚举
|
||||
- Start: 起点
|
||||
- Pass: 途经点
|
||||
- Turn: 转弯(自动识别)
|
||||
- Wait: 等待
|
||||
- Load: 装货
|
||||
- Unload: 卸货
|
||||
- End: 终点
|
||||
|
||||
---
|
||||
|
||||
## 数据库表结构
|
||||
|
||||
### TimeTagProfiles 表
|
||||
```sql
|
||||
CREATE TABLE TimeTagProfiles (
|
||||
Id TEXT PRIMARY KEY,
|
||||
RouteId TEXT NOT NULL,
|
||||
Name TEXT NOT NULL,
|
||||
DefaultSpeed REAL DEFAULT 1.0,
|
||||
TurnSpeedRatio REAL DEFAULT 0.5,
|
||||
CreatedAt TEXT DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
```
|
||||
|
||||
### TimeTagEvents 表
|
||||
```sql
|
||||
CREATE TABLE TimeTagEvents (
|
||||
Id TEXT PRIMARY KEY,
|
||||
ProfileId TEXT NOT NULL,
|
||||
PointId TEXT,
|
||||
Distance REAL NOT NULL,
|
||||
Type TEXT NOT NULL,
|
||||
Name TEXT NOT NULL,
|
||||
WaitTime REAL DEFAULT 0,
|
||||
ActionTime REAL DEFAULT 0,
|
||||
CumulativeTime REAL DEFAULT 0,
|
||||
ArriveSpeed REAL,
|
||||
LeaveSpeed REAL,
|
||||
SortOrder INTEGER NOT NULL,
|
||||
|
||||
FOREIGN KEY (ProfileId) REFERENCES TimeTagProfiles(Id) ON DELETE CASCADE
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 实现阶段
|
||||
|
||||
### Phase 1: 数据库和基础模型
|
||||
- [ ] 创建数据库表(TimeTagProfiles, TimeTagEvents)
|
||||
- [ ] 创建数据模型类(TimeTagProfile, TimeTagEvent, EventType)
|
||||
- [ ] 添加到 PathDatabase 类
|
||||
- [ ] 编译验证
|
||||
|
||||
### Phase 2: 核心服务
|
||||
- [ ] 创建 TimeTagCalculator 计算类
|
||||
- [ ] 创建 TimeTagService 服务类(CRUD + 计算)
|
||||
- [ ] 集成到 PathPlanningManager
|
||||
- [ ] 实现自动识别转折点为 Turn 事件
|
||||
- [ ] 编译验证
|
||||
|
||||
### Phase 3: UI
|
||||
- [ ] 路径列表添加"时标"按钮
|
||||
- [ ] 创建 TimeTagDialog 对话框
|
||||
- [ ] 创建 TimeTagViewModel 视图模型
|
||||
- [ ] 时间轴可视化(简化版)
|
||||
- [ ] 编译验证
|
||||
|
||||
### Phase 4: 导出功能
|
||||
- [ ] 实现 JSON 导出功能
|
||||
- [ ] 测试导出数据格式
|
||||
- [ ] 编译验证
|
||||
|
||||
---
|
||||
|
||||
## 导出 JSON 格式示例
|
||||
|
||||
```json
|
||||
{
|
||||
"routeId": "route_A1",
|
||||
"routeName": "主通道运输路线",
|
||||
"profileName": "叉车-满载",
|
||||
"defaultSpeed": 1.5,
|
||||
"turnSpeedRatio": 0.5,
|
||||
"totalTime": 145.5,
|
||||
"totalDistance": 180.0,
|
||||
"events": [
|
||||
{
|
||||
"index": 0,
|
||||
"name": "起点",
|
||||
"type": "Start",
|
||||
"distance": 0,
|
||||
"cumulativeTime": 0,
|
||||
"waitTime": 0,
|
||||
"actionTime": 5
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"name": "走廊转角",
|
||||
"type": "Turn",
|
||||
"distance": 45.0,
|
||||
"cumulativeTime": 35.0,
|
||||
"waitTime": 0,
|
||||
"actionTime": 3
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"name": "装卸点",
|
||||
"type": "Load",
|
||||
"distance": 120.0,
|
||||
"cumulativeTime": 88.0,
|
||||
"waitTime": 120,
|
||||
"actionTime": 10
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"name": "终点",
|
||||
"type": "End",
|
||||
"distance": 180.0,
|
||||
"cumulativeTime": 145.5
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
174
src/Core/Models/TimeTagModels.cs
Normal file
174
src/Core/Models/TimeTagModels.cs
Normal file
@ -0,0 +1,174 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace NavisworksTransport.Core.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// 时标配置 - 一套完整的时间标签方案
|
||||
/// </summary>
|
||||
public class TimeTagProfile
|
||||
{
|
||||
/// <summary>
|
||||
/// 配置ID
|
||||
/// </summary>
|
||||
public string Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 关联路径ID
|
||||
/// </summary>
|
||||
public string RouteId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 配置名称(如:叉车满载)
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 默认速度 m/s
|
||||
/// </summary>
|
||||
public double DefaultSpeed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 转弯速度比例(0.5=半速)
|
||||
/// </summary>
|
||||
public double TurnSpeedRatio { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 事件节点列表
|
||||
/// </summary>
|
||||
public List<TimeTagEvent> Events { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 创建时间
|
||||
/// </summary>
|
||||
public DateTime CreatedAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 总时间(从最后一个事件获取)
|
||||
/// </summary>
|
||||
public double TotalTime => Events?.Count > 0 ? Events[Events.Count - 1].CumulativeTime : 0;
|
||||
|
||||
public TimeTagProfile()
|
||||
{
|
||||
Id = Guid.NewGuid().ToString();
|
||||
Events = new List<TimeTagEvent>();
|
||||
DefaultSpeed = 1.0;
|
||||
TurnSpeedRatio = 0.5;
|
||||
CreatedAt = DateTime.Now;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 时间事件节点
|
||||
/// </summary>
|
||||
public class TimeTagEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// 事件ID
|
||||
/// </summary>
|
||||
public string Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 所属配置ID
|
||||
/// </summary>
|
||||
public string ProfileId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 关联路径点ID(可选)
|
||||
/// </summary>
|
||||
public string PointId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 距起点距离(米)
|
||||
/// </summary>
|
||||
public double Distance { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 事件类型
|
||||
/// </summary>
|
||||
public EventType Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 事件名称
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 等待时间(秒)
|
||||
/// </summary>
|
||||
public double WaitTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 动作时间(秒)
|
||||
/// </summary>
|
||||
public double ActionTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 累计时间(自动计算)
|
||||
/// </summary>
|
||||
public double CumulativeTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 到达速度限制(可选)
|
||||
/// </summary>
|
||||
public double? ArriveSpeed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 离开速度限制(可选)
|
||||
/// </summary>
|
||||
public double? LeaveSpeed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 排序序号
|
||||
/// </summary>
|
||||
public int SortOrder { get; set; }
|
||||
|
||||
public TimeTagEvent()
|
||||
{
|
||||
Id = Guid.NewGuid().ToString();
|
||||
Type = EventType.Pass;
|
||||
Name = "途经点";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 事件类型枚举
|
||||
/// </summary>
|
||||
public enum EventType
|
||||
{
|
||||
/// <summary>
|
||||
/// 起点
|
||||
/// </summary>
|
||||
Start,
|
||||
|
||||
/// <summary>
|
||||
/// 途经点
|
||||
/// </summary>
|
||||
Pass,
|
||||
|
||||
/// <summary>
|
||||
/// 转弯
|
||||
/// </summary>
|
||||
Turn,
|
||||
|
||||
/// <summary>
|
||||
/// 等待
|
||||
/// </summary>
|
||||
Wait,
|
||||
|
||||
/// <summary>
|
||||
/// 装货
|
||||
/// </summary>
|
||||
Load,
|
||||
|
||||
/// <summary>
|
||||
/// 卸货
|
||||
/// </summary>
|
||||
Unload,
|
||||
|
||||
/// <summary>
|
||||
/// 终点
|
||||
/// </summary>
|
||||
End
|
||||
}
|
||||
}
|
||||
@ -7,6 +7,9 @@ using System.Threading.Tasks;
|
||||
using Autodesk.Navisworks.Api;
|
||||
using NavisworksTransport.Core.Config;
|
||||
using NavisworksTransport.Core.Models;
|
||||
using TimeTagProfile = NavisworksTransport.Core.Models.TimeTagProfile;
|
||||
using TimeTagEvent = NavisworksTransport.Core.Models.TimeTagEvent;
|
||||
using EventType = NavisworksTransport.Core.Models.EventType;
|
||||
using NavisworksTransport.Core.Database;
|
||||
|
||||
namespace NavisworksTransport
|
||||
@ -238,15 +241,49 @@ namespace NavisworksTransport
|
||||
)
|
||||
");
|
||||
|
||||
// 9. 设置数据库版本(SQLite内置user_version)
|
||||
// 10. 时标配置表
|
||||
ExecuteNonQuery(@"
|
||||
CREATE TABLE IF NOT EXISTS TimeTagProfiles (
|
||||
Id TEXT PRIMARY KEY,
|
||||
RouteId TEXT NOT NULL,
|
||||
Name TEXT NOT NULL,
|
||||
DefaultSpeed REAL DEFAULT 1.0,
|
||||
TurnSpeedRatio REAL DEFAULT 0.5,
|
||||
CreatedAt DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY(RouteId) REFERENCES PathRoutes(Id) ON DELETE CASCADE
|
||||
)
|
||||
");
|
||||
|
||||
// 11. 时标事件表
|
||||
ExecuteNonQuery(@"
|
||||
CREATE TABLE IF NOT EXISTS TimeTagEvents (
|
||||
Id TEXT PRIMARY KEY,
|
||||
ProfileId TEXT NOT NULL,
|
||||
PointId TEXT,
|
||||
Distance REAL NOT NULL,
|
||||
Type TEXT NOT NULL,
|
||||
Name TEXT NOT NULL,
|
||||
WaitTime REAL DEFAULT 0,
|
||||
ActionTime REAL DEFAULT 0,
|
||||
CumulativeTime REAL DEFAULT 0,
|
||||
ArriveSpeed REAL,
|
||||
LeaveSpeed REAL,
|
||||
SortOrder INTEGER NOT NULL,
|
||||
FOREIGN KEY(ProfileId) REFERENCES TimeTagProfiles(Id) ON DELETE CASCADE
|
||||
)
|
||||
");
|
||||
|
||||
// 12. 设置数据库版本(SQLite内置user_version)
|
||||
// 版本号格式:主版本*10000 + 次版本*100 + 修订号
|
||||
// 例如:2.1.3 = 20103
|
||||
ExecuteNonQuery("PRAGMA user_version = 20101"); // v2.1.1 - 增加碰撞时运动物体位置和朝向字段
|
||||
ExecuteNonQuery("PRAGMA user_version = 20102"); // v2.1.2 - 增加时标配置和事件表
|
||||
|
||||
// 创建索引
|
||||
ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_reports_route ON CollisionReports(RouteId)");
|
||||
ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_pathpoints_route ON PathPoints(RouteId)");
|
||||
ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_pathedges_route ON PathEdges(RouteId)");
|
||||
ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_timetag_profiles_route ON TimeTagProfiles(RouteId)");
|
||||
ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_timetag_events_profile ON TimeTagEvents(ProfileId)");
|
||||
ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_clash_route_id ON ClashDetectiveResults(RouteId)");
|
||||
ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_clash_test_time ON ClashDetectiveResults(TestTime DESC)");
|
||||
ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_clash_objects_result ON ClashDetectiveCollisionObjects(ResultId)");
|
||||
@ -2757,6 +2794,209 @@ namespace NavisworksTransport
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 时标管理
|
||||
|
||||
/// <summary>
|
||||
/// 保存时标配置
|
||||
/// </summary>
|
||||
public void SaveTimeTagProfile(TimeTagProfile profile)
|
||||
{
|
||||
if (profile == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
using (var transaction = _connection.BeginTransaction())
|
||||
{
|
||||
var profileSql = @"
|
||||
INSERT OR REPLACE INTO TimeTagProfiles
|
||||
(Id, RouteId, Name, DefaultSpeed, TurnSpeedRatio, CreatedAt)
|
||||
VALUES (@id, @routeId, @name, @defaultSpeed, @turnSpeedRatio, @createdAt)
|
||||
";
|
||||
|
||||
using (var cmd = new SQLiteCommand(profileSql, _connection))
|
||||
{
|
||||
cmd.Parameters.AddWithValue("@id", profile.Id);
|
||||
cmd.Parameters.AddWithValue("@routeId", profile.RouteId);
|
||||
cmd.Parameters.AddWithValue("@name", profile.Name);
|
||||
cmd.Parameters.AddWithValue("@defaultSpeed", profile.DefaultSpeed);
|
||||
cmd.Parameters.AddWithValue("@turnSpeedRatio", profile.TurnSpeedRatio);
|
||||
cmd.Parameters.AddWithValue("@createdAt", profile.CreatedAt);
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
using (var cmd = new SQLiteCommand("DELETE FROM TimeTagEvents WHERE ProfileId = @profileId", _connection))
|
||||
{
|
||||
cmd.Parameters.AddWithValue("@profileId", profile.Id);
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
var eventSql = @"
|
||||
INSERT INTO TimeTagEvents
|
||||
(Id, ProfileId, PointId, Distance, Type, Name, WaitTime, ActionTime, CumulativeTime, ArriveSpeed, LeaveSpeed, SortOrder)
|
||||
VALUES (@id, @profileId, @pointId, @distance, @type, @name, @waitTime, @actionTime, @cumulativeTime, @arriveSpeed, @leaveSpeed, @sortOrder)
|
||||
";
|
||||
|
||||
for (int i = 0; i < profile.Events.Count; i++)
|
||||
{
|
||||
var evt = profile.Events[i];
|
||||
using (var cmd = new SQLiteCommand(eventSql, _connection))
|
||||
{
|
||||
cmd.Parameters.AddWithValue("@id", evt.Id);
|
||||
cmd.Parameters.AddWithValue("@profileId", profile.Id);
|
||||
cmd.Parameters.AddWithValue("@pointId", string.IsNullOrEmpty(evt.PointId) ? (object)DBNull.Value : evt.PointId);
|
||||
cmd.Parameters.AddWithValue("@distance", evt.Distance);
|
||||
cmd.Parameters.AddWithValue("@type", evt.Type.ToString());
|
||||
cmd.Parameters.AddWithValue("@name", evt.Name);
|
||||
cmd.Parameters.AddWithValue("@waitTime", evt.WaitTime);
|
||||
cmd.Parameters.AddWithValue("@actionTime", evt.ActionTime);
|
||||
cmd.Parameters.AddWithValue("@cumulativeTime", evt.CumulativeTime);
|
||||
cmd.Parameters.AddWithValue("@arriveSpeed", evt.ArriveSpeed.HasValue ? (object)evt.ArriveSpeed.Value : DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("@leaveSpeed", evt.LeaveSpeed.HasValue ? (object)evt.LeaveSpeed.Value : DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("@sortOrder", i);
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
|
||||
transaction.Commit();
|
||||
LogManager.Info($"时标配置已保存: {profile.Name}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"保存时标配置失败: {ex.Message}", ex);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取路径的所有时标配置
|
||||
/// </summary>
|
||||
public List<TimeTagProfile> GetTimeTagProfiles(string routeId)
|
||||
{
|
||||
var profiles = new List<TimeTagProfile>();
|
||||
try
|
||||
{
|
||||
var sql = "SELECT * FROM TimeTagProfiles WHERE RouteId = @routeId ORDER BY CreatedAt";
|
||||
using (var cmd = new SQLiteCommand(sql, _connection))
|
||||
{
|
||||
cmd.Parameters.AddWithValue("@routeId", routeId);
|
||||
using (var reader = cmd.ExecuteReader())
|
||||
{
|
||||
while (reader.Read())
|
||||
{
|
||||
var profile = new TimeTagProfile
|
||||
{
|
||||
Id = reader["Id"].ToString(),
|
||||
RouteId = reader["RouteId"].ToString(),
|
||||
Name = reader["Name"].ToString(),
|
||||
DefaultSpeed = Convert.ToDouble(reader["DefaultSpeed"]),
|
||||
TurnSpeedRatio = Convert.ToDouble(reader["TurnSpeedRatio"]),
|
||||
CreatedAt = Convert.ToDateTime(reader["CreatedAt"])
|
||||
};
|
||||
LoadTimeTagEvents(profile);
|
||||
profiles.Add(profile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"获取时标配置列表失败: {ex.Message}", ex);
|
||||
}
|
||||
return profiles;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据ID获取时标配置
|
||||
/// </summary>
|
||||
public TimeTagProfile GetTimeTagProfile(string profileId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var sql = "SELECT * FROM TimeTagProfiles WHERE Id = @id";
|
||||
using (var cmd = new SQLiteCommand(sql, _connection))
|
||||
{
|
||||
cmd.Parameters.AddWithValue("@id", profileId);
|
||||
using (var reader = cmd.ExecuteReader())
|
||||
{
|
||||
if (reader.Read())
|
||||
{
|
||||
var profile = new TimeTagProfile
|
||||
{
|
||||
Id = reader["Id"].ToString(),
|
||||
RouteId = reader["RouteId"].ToString(),
|
||||
Name = reader["Name"].ToString(),
|
||||
DefaultSpeed = Convert.ToDouble(reader["DefaultSpeed"]),
|
||||
TurnSpeedRatio = Convert.ToDouble(reader["TurnSpeedRatio"]),
|
||||
CreatedAt = Convert.ToDateTime(reader["CreatedAt"])
|
||||
};
|
||||
LoadTimeTagEvents(profile);
|
||||
return profile;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"获取时标配置失败: {ex.Message}", ex);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void LoadTimeTagEvents(TimeTagProfile profile)
|
||||
{
|
||||
profile.Events = new List<TimeTagEvent>();
|
||||
var sql = "SELECT * FROM TimeTagEvents WHERE ProfileId = @profileId ORDER BY SortOrder";
|
||||
using (var cmd = new SQLiteCommand(sql, _connection))
|
||||
{
|
||||
cmd.Parameters.AddWithValue("@profileId", profile.Id);
|
||||
using (var reader = cmd.ExecuteReader())
|
||||
{
|
||||
while (reader.Read())
|
||||
{
|
||||
var evt = new TimeTagEvent
|
||||
{
|
||||
Id = reader["Id"].ToString(),
|
||||
ProfileId = reader["ProfileId"].ToString(),
|
||||
PointId = reader["PointId"] != DBNull.Value ? reader["PointId"].ToString() : null,
|
||||
Distance = Convert.ToDouble(reader["Distance"]),
|
||||
Type = (EventType)Enum.Parse(typeof(EventType), reader["Type"].ToString()),
|
||||
Name = reader["Name"].ToString(),
|
||||
WaitTime = Convert.ToDouble(reader["WaitTime"]),
|
||||
ActionTime = Convert.ToDouble(reader["ActionTime"]),
|
||||
CumulativeTime = Convert.ToDouble(reader["CumulativeTime"]),
|
||||
ArriveSpeed = reader["ArriveSpeed"] != DBNull.Value ? Convert.ToDouble(reader["ArriveSpeed"]) : (double?)null,
|
||||
LeaveSpeed = reader["LeaveSpeed"] != DBNull.Value ? Convert.ToDouble(reader["LeaveSpeed"]) : (double?)null
|
||||
};
|
||||
profile.Events.Add(evt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除时标配置
|
||||
/// </summary>
|
||||
public void DeleteTimeTagProfile(string profileId)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var cmd = new SQLiteCommand("DELETE FROM TimeTagProfiles WHERE Id = @id", _connection))
|
||||
{
|
||||
cmd.Parameters.AddWithValue("@id", profileId);
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
LogManager.Info($"时标配置已删除: {profileId}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"删除时标配置失败: {ex.Message}", ex);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
224
src/Core/Services/TimeTagCalculator.cs
Normal file
224
src/Core/Services/TimeTagCalculator.cs
Normal file
@ -0,0 +1,224 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Autodesk.Navisworks.Api;
|
||||
using NavisworksTransport.Core.Models;
|
||||
using NavisworksTransport.Utils;
|
||||
|
||||
namespace NavisworksTransport.Core.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// 时标计算器
|
||||
/// 基于事件节点和路径几何计算时间
|
||||
/// </summary>
|
||||
public class TimeTagCalculator
|
||||
{
|
||||
/// <summary>
|
||||
/// 计算时标配置的所有时间
|
||||
/// </summary>
|
||||
public void Calculate(TimeTagProfile profile, PathRoute route)
|
||||
{
|
||||
if (profile?.Events == null || profile.Events.Count < 2)
|
||||
return;
|
||||
|
||||
// 按距离排序事件并更新原始列表
|
||||
var sortedEvents = profile.Events.OrderBy(e => e.Distance).ToList();
|
||||
|
||||
// 清空并重新填充原始列表,确保顺序一致
|
||||
profile.Events.Clear();
|
||||
foreach (var evt in sortedEvents)
|
||||
{
|
||||
profile.Events.Add(evt);
|
||||
}
|
||||
|
||||
double cumulativeTime = 0;
|
||||
|
||||
for (int i = 0; i < profile.Events.Count; i++)
|
||||
{
|
||||
var evt = profile.Events[i];
|
||||
evt.SortOrder = i + 1; // 更新排序序号
|
||||
evt.CumulativeTime = cumulativeTime;
|
||||
|
||||
// 加上本事件的等待和动作时间
|
||||
cumulativeTime += evt.WaitTime + evt.ActionTime;
|
||||
|
||||
// 计算到下一个事件的行驶时间
|
||||
if (i < profile.Events.Count - 1)
|
||||
{
|
||||
var nextEvt = profile.Events[i + 1];
|
||||
double segmentDistance = nextEvt.Distance - evt.Distance;
|
||||
|
||||
if (segmentDistance > 0)
|
||||
{
|
||||
double segmentSpeed = CalculateSegmentSpeed(profile, route, evt, nextEvt);
|
||||
double travelTime = segmentDistance / segmentSpeed;
|
||||
cumulativeTime += travelTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LogManager.Info($"[时标计算] 完成: {profile.Name}, 总时间: {cumulativeTime:F1}s, 事件数: {profile.Events.Count}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算路段速度(简化模型)
|
||||
/// </summary>
|
||||
private double CalculateSegmentSpeed(TimeTagProfile profile, PathRoute route,
|
||||
TimeTagEvent startEvent, TimeTagEvent endEvent)
|
||||
{
|
||||
// 基础速度
|
||||
double speed = profile.DefaultSpeed;
|
||||
|
||||
// 检查是否包含圆弧段
|
||||
if (HasArcSegment(route, startEvent.Distance, endEvent.Distance))
|
||||
{
|
||||
speed *= profile.TurnSpeedRatio;
|
||||
}
|
||||
|
||||
// 应用事件速度限制
|
||||
if (startEvent.LeaveSpeed.HasValue)
|
||||
speed = Math.Min(speed, startEvent.LeaveSpeed.Value);
|
||||
|
||||
if (endEvent.ArriveSpeed.HasValue)
|
||||
speed = Math.Min(speed, endEvent.ArriveSpeed.Value);
|
||||
|
||||
return Math.Max(speed, 0.1); // 最小速度限制
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查路段是否包含圆弧
|
||||
/// </summary>
|
||||
private bool HasArcSegment(PathRoute route, double startDist, double endDist)
|
||||
{
|
||||
if (route?.Edges == null || !route.Edges.Any())
|
||||
return false;
|
||||
|
||||
double currentDist = 0;
|
||||
foreach (var edge in route.Edges)
|
||||
{
|
||||
double edgeLength = edge.PhysicalLength;
|
||||
|
||||
// 检查本路段是否与查询区间有重叠
|
||||
if (currentDist + edgeLength > startDist && currentDist < endDist)
|
||||
{
|
||||
if (edge.SegmentType == PathSegmentType.Arc)
|
||||
return true;
|
||||
}
|
||||
|
||||
currentDist += edgeLength;
|
||||
if (currentDist > endDist) break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从路径自动生成事件节点
|
||||
/// </summary>
|
||||
public List<TimeTagEvent> GenerateDefaultEvents(PathRoute route)
|
||||
{
|
||||
var events = new List<TimeTagEvent>();
|
||||
|
||||
if (route?.Points == null || route.Points.Count < 2)
|
||||
return events;
|
||||
|
||||
var sortedPoints = route.Points.OrderBy(p => p.Index).ToList();
|
||||
double currentDistance = 0;
|
||||
|
||||
// 起点
|
||||
events.Add(new TimeTagEvent
|
||||
{
|
||||
Type = EventType.Start,
|
||||
Name = "起点",
|
||||
Distance = 0,
|
||||
PointId = sortedPoints[0].Id,
|
||||
WaitTime = 0,
|
||||
ActionTime = 0
|
||||
});
|
||||
|
||||
// 中间点:根据路径几何识别转弯点
|
||||
for (int i = 1; i < sortedPoints.Count - 1; i++)
|
||||
{
|
||||
var point = sortedPoints[i];
|
||||
var prevPoint = sortedPoints[i - 1];
|
||||
|
||||
// 计算到当前点的距离
|
||||
double segmentDist = CalculateDistance(prevPoint.Position, point.Position);
|
||||
currentDistance += segmentDist;
|
||||
|
||||
// 判断是否为转弯点(通过Edges判断)
|
||||
bool isTurn = IsTurnPoint(route, i);
|
||||
|
||||
events.Add(new TimeTagEvent
|
||||
{
|
||||
Type = isTurn ? EventType.Turn : EventType.Pass,
|
||||
Name = isTurn ? $"转弯点 {i}" : $"途经点 {i}",
|
||||
Distance = currentDistance,
|
||||
PointId = point.Id,
|
||||
WaitTime = 0,
|
||||
ActionTime = isTurn ? 3.0 : 0 // 转弯默认3秒动作时间
|
||||
});
|
||||
}
|
||||
|
||||
// 终点
|
||||
if (sortedPoints.Count > 1)
|
||||
{
|
||||
var lastPoint = sortedPoints[sortedPoints.Count - 1];
|
||||
var prevLastPoint = sortedPoints[sortedPoints.Count - 2];
|
||||
currentDistance += CalculateDistance(prevLastPoint.Position, lastPoint.Position);
|
||||
|
||||
events.Add(new TimeTagEvent
|
||||
{
|
||||
Type = EventType.End,
|
||||
Name = "终点",
|
||||
Distance = currentDistance,
|
||||
PointId = lastPoint.Id,
|
||||
WaitTime = 0,
|
||||
ActionTime = 0
|
||||
});
|
||||
}
|
||||
|
||||
return events;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断指定索引的点是否为转弯点
|
||||
/// </summary>
|
||||
private bool IsTurnPoint(PathRoute route, int pointIndex)
|
||||
{
|
||||
if (route?.Edges == null || route.Edges.Count == 0)
|
||||
return false;
|
||||
|
||||
// 根据Edges判断:如果前后有圆弧边,则认为是转弯点
|
||||
var sortedPoints = route.Points.OrderBy(p => p.Index).ToList();
|
||||
if (pointIndex >= sortedPoints.Count) return false;
|
||||
|
||||
var point = sortedPoints[pointIndex];
|
||||
|
||||
// 查找与该点相关的圆弧边
|
||||
foreach (var edge in route.Edges)
|
||||
{
|
||||
if (edge.SegmentType == PathSegmentType.Arc)
|
||||
{
|
||||
// 圆弧边连接了路径点,认为是转弯
|
||||
if (edge.StartPointId == point.Id || edge.EndPointId == point.Id)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算两点间距离(米)
|
||||
/// </summary>
|
||||
private double CalculateDistance(Point3D p1, Point3D p2)
|
||||
{
|
||||
var dx = p1.X - p2.X;
|
||||
var dy = p1.Y - p2.Y;
|
||||
var dz = p1.Z - p2.Z;
|
||||
var distInModelUnits = Math.Sqrt(dx * dx + dy * dy + dz * dz);
|
||||
return UnitsConverter.ConvertToMeters(distInModelUnits);
|
||||
}
|
||||
}
|
||||
}
|
||||
222
src/Core/Services/TimeTagExporter.cs
Normal file
222
src/Core/Services/TimeTagExporter.cs
Normal file
@ -0,0 +1,222 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using NavisworksTransport.Core.Models;
|
||||
|
||||
namespace NavisworksTransport.Core.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// 时标数据导出器 - 支持JSON和CSV格式
|
||||
/// </summary>
|
||||
public class TimeTagExporter
|
||||
{
|
||||
/// <summary>
|
||||
/// 导出配置为JSON格式
|
||||
/// </summary>
|
||||
public string ExportToJson(TimeTagProfile profile, PathRoute route)
|
||||
{
|
||||
if (profile == null) throw new ArgumentNullException(nameof(profile));
|
||||
if (route == null) throw new ArgumentNullException(nameof(route));
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("{");
|
||||
sb.AppendLine($" \"profile\": {"{"}");
|
||||
sb.AppendLine($" \"id\": \"{profile.Id}\",");
|
||||
sb.AppendLine($" \"name\": \"{EscapeJson(profile.Name)}\",");
|
||||
sb.AppendLine($" \"routeId\": \"{profile.RouteId}\",");
|
||||
sb.AppendLine($" \"routeName\": \"{EscapeJson(route.Name)}\",");
|
||||
sb.AppendLine($" \"defaultSpeed\": {profile.DefaultSpeed:F3},");
|
||||
sb.AppendLine($" \"turnSpeedRatio\": {profile.TurnSpeedRatio:F2},");
|
||||
sb.AppendLine($" \"totalDistance\": {route.TotalLength:F3},");
|
||||
sb.AppendLine($" \"totalTime\": {profile.TotalTime:F3},");
|
||||
sb.AppendLine($" \"eventCount\": {profile.Events?.Count ?? 0},");
|
||||
sb.AppendLine(" \"events\": [");
|
||||
|
||||
if (profile.Events != null && profile.Events.Count > 0)
|
||||
{
|
||||
var events = profile.Events.OrderBy(e => e.Distance).ToList();
|
||||
for (int i = 0; i < events.Count; i++)
|
||||
{
|
||||
var evt = events[i];
|
||||
sb.AppendLine(" {");
|
||||
sb.AppendLine($" \"id\": \"{evt.Id}\",");
|
||||
sb.AppendLine($" \"sortOrder\": {evt.SortOrder},");
|
||||
sb.AppendLine($" \"name\": \"{EscapeJson(evt.Name)}\",");
|
||||
sb.AppendLine($" \"type\": \"{evt.Type}\",");
|
||||
sb.AppendLine($" \"distance\": {evt.Distance:F3},");
|
||||
sb.AppendLine($" \"waitTime\": {evt.WaitTime:F2},");
|
||||
sb.AppendLine($" \"actionTime\": {evt.ActionTime:F2},");
|
||||
sb.AppendLine($" \"cumulativeTime\": {evt.CumulativeTime:F3},");
|
||||
sb.AppendLine($" \"pointId\": \"{evt.PointId ?? ""}\"");
|
||||
sb.Append(" }");
|
||||
if (i < events.Count - 1) sb.Append(",");
|
||||
sb.AppendLine();
|
||||
}
|
||||
}
|
||||
|
||||
sb.AppendLine(" ]");
|
||||
sb.AppendLine(" },");
|
||||
sb.AppendLine(" \"route\": {");
|
||||
sb.AppendLine($" \"id\": \"{route.Id}\",");
|
||||
sb.AppendLine($" \"name\": \"{EscapeJson(route.Name)}\",");
|
||||
sb.AppendLine($" \"type\": \"{route.PathType}\",");
|
||||
sb.AppendLine($" \"totalLength\": {route.TotalLength:F3},");
|
||||
sb.AppendLine($" \"pointCount\": {route.Points?.Count ?? 0}");
|
||||
sb.AppendLine(" },");
|
||||
sb.AppendLine($" \"exportTime\": \"{DateTime.Now:yyyy-MM-dd HH:mm:ss}\",");
|
||||
sb.AppendLine($" \"version\": \"1.0\"");
|
||||
sb.AppendLine("}");
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 导出配置为CSV格式
|
||||
/// </summary>
|
||||
public string ExportToCsv(TimeTagProfile profile)
|
||||
{
|
||||
if (profile == null) throw new ArgumentNullException(nameof(profile));
|
||||
|
||||
var sb = new StringBuilder();
|
||||
|
||||
// 头部信息
|
||||
sb.AppendLine($"# Time Tag Profile: {profile.Name}");
|
||||
sb.AppendLine($"# Route ID: {profile.RouteId}");
|
||||
sb.AppendLine($"# Default Speed: {profile.DefaultSpeed:F3} m/s");
|
||||
sb.AppendLine($"# Turn Speed Ratio: {profile.TurnSpeedRatio:P0}");
|
||||
sb.AppendLine($"# Total Time: {profile.TotalTime:F3} s");
|
||||
sb.AppendLine($"# Export Time: {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
|
||||
sb.AppendLine();
|
||||
|
||||
// CSV表头
|
||||
sb.AppendLine("序号,名称,类型,距离(m),等待时间(s),动作时间(s),累计时间(s),说明");
|
||||
|
||||
// 数据行
|
||||
if (profile.Events != null)
|
||||
{
|
||||
foreach (var evt in profile.Events.OrderBy(e => e.Distance))
|
||||
{
|
||||
string description = GetEventDescription(evt);
|
||||
sb.AppendLine($"{evt.SortOrder},{EscapeCsv(evt.Name)},{evt.Type},{evt.Distance:F2},{evt.WaitTime:F2},{evt.ActionTime:F2},{evt.CumulativeTime:F3},{EscapeCsv(description)}");
|
||||
}
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 导出到文件
|
||||
/// </summary>
|
||||
public void ExportToFile(TimeTagProfile profile, PathRoute route, string filePath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filePath))
|
||||
throw new ArgumentException("文件路径不能为空", nameof(filePath));
|
||||
|
||||
string extension = Path.GetExtension(filePath).ToLower();
|
||||
string content;
|
||||
|
||||
switch (extension)
|
||||
{
|
||||
case ".json":
|
||||
content = ExportToJson(profile, route);
|
||||
break;
|
||||
case ".csv":
|
||||
content = ExportToCsv(profile);
|
||||
break;
|
||||
default:
|
||||
throw new NotSupportedException($"不支持的文件格式: {extension}");
|
||||
}
|
||||
|
||||
File.WriteAllText(filePath, content, Encoding.UTF8);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量导出多个配置
|
||||
/// </summary>
|
||||
public string ExportMultipleToJson(List<TimeTagProfile> profiles, PathRoute route)
|
||||
{
|
||||
if (profiles == null || profiles.Count == 0)
|
||||
return "[]";
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("{");
|
||||
sb.AppendLine($" \"route\": \"{EscapeJson(route.Name)}\",");
|
||||
sb.AppendLine($" \"exportTime\": \"{DateTime.Now:yyyy-MM-dd HH:mm:ss}\",");
|
||||
sb.AppendLine(" \"profiles\": [");
|
||||
|
||||
for (int i = 0; i < profiles.Count; i++)
|
||||
{
|
||||
var profile = profiles[i];
|
||||
sb.AppendLine(" {");
|
||||
sb.AppendLine($" \"id\": \"{profile.Id}\",");
|
||||
sb.AppendLine($" \"name\": \"{EscapeJson(profile.Name)}\",");
|
||||
sb.AppendLine($" \"defaultSpeed\": {profile.DefaultSpeed:F3},");
|
||||
sb.AppendLine($" \"turnSpeedRatio\": {profile.TurnSpeedRatio:F2},");
|
||||
sb.AppendLine($" \"totalTime\": {profile.TotalTime:F3},");
|
||||
sb.AppendLine($" \"eventCount\": {profile.Events?.Count ?? 0}");
|
||||
sb.Append(" }");
|
||||
if (i < profiles.Count - 1) sb.Append(",");
|
||||
sb.AppendLine();
|
||||
}
|
||||
|
||||
sb.AppendLine(" ]");
|
||||
sb.AppendLine("}");
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转义JSON字符串
|
||||
/// </summary>
|
||||
private string EscapeJson(string str)
|
||||
{
|
||||
if (string.IsNullOrEmpty(str)) return "";
|
||||
return str.Replace("\\", "\\\\")
|
||||
.Replace("\"", "\\\"")
|
||||
.Replace("\n", "\\n")
|
||||
.Replace("\r", "\\r")
|
||||
.Replace("\t", "\\t");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转义CSV字符串
|
||||
/// </summary>
|
||||
private string EscapeCsv(string str)
|
||||
{
|
||||
if (string.IsNullOrEmpty(str)) return "";
|
||||
if (str.Contains(",") || str.Contains("\"") || str.Contains("\n"))
|
||||
{
|
||||
return "\"" + str.Replace("\"", "\"\"") + "\"";
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取事件说明
|
||||
/// </summary>
|
||||
private string GetEventDescription(TimeTagEvent evt)
|
||||
{
|
||||
switch (evt.Type)
|
||||
{
|
||||
case EventType.Start:
|
||||
return "路径起点";
|
||||
case EventType.End:
|
||||
return "路径终点";
|
||||
case EventType.Turn:
|
||||
return "转弯减速点";
|
||||
case EventType.Wait:
|
||||
return "等待点";
|
||||
case EventType.Load:
|
||||
return "装载点";
|
||||
case EventType.Unload:
|
||||
return "卸载点";
|
||||
case EventType.Pass:
|
||||
return "途经点";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
184
src/Core/Services/TimeTagService.cs
Normal file
184
src/Core/Services/TimeTagService.cs
Normal file
@ -0,0 +1,184 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NavisworksTransport.Core.Models;
|
||||
|
||||
namespace NavisworksTransport.Core.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// 时标服务
|
||||
/// 整合时标的CRUD操作和计算
|
||||
/// </summary>
|
||||
public class TimeTagService
|
||||
{
|
||||
private readonly PathDatabase _database;
|
||||
private readonly TimeTagCalculator _calculator;
|
||||
|
||||
public TimeTagService(PathDatabase database)
|
||||
{
|
||||
_database = database ?? throw new ArgumentNullException(nameof(database));
|
||||
_calculator = new TimeTagCalculator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 为路径创建新的时标配置
|
||||
/// </summary>
|
||||
public TimeTagProfile CreateProfile(string routeId, string profileName, PathRoute route)
|
||||
{
|
||||
if (string.IsNullOrEmpty(routeId))
|
||||
throw new ArgumentException("路径ID不能为空", nameof(routeId));
|
||||
|
||||
if (string.IsNullOrEmpty(profileName))
|
||||
throw new ArgumentException("配置名称不能为空", nameof(profileName));
|
||||
|
||||
var profile = new TimeTagProfile
|
||||
{
|
||||
RouteId = routeId,
|
||||
Name = profileName,
|
||||
DefaultSpeed = Config.ConfigManager.Instance.Current.Logistics.SpeedLimitMetersPerSecond,
|
||||
TurnSpeedRatio = 0.5
|
||||
};
|
||||
|
||||
// 自动生成默认事件
|
||||
profile.Events = _calculator.GenerateDefaultEvents(route);
|
||||
|
||||
// 计算时间
|
||||
_calculator.Calculate(profile, route);
|
||||
|
||||
// 保存到数据库
|
||||
_database.SaveTimeTagProfile(profile);
|
||||
|
||||
LogManager.Info($"[时标服务] 创建配置: {profileName}, 事件数: {profile.Events.Count}");
|
||||
return profile;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取路径的所有时标配置
|
||||
/// </summary>
|
||||
public List<TimeTagProfile> GetProfiles(string routeId)
|
||||
{
|
||||
return _database.GetTimeTagProfiles(routeId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取单个时标配置
|
||||
/// </summary>
|
||||
public TimeTagProfile GetProfile(string profileId)
|
||||
{
|
||||
return _database.GetTimeTagProfile(profileId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新时标配置(并重新计算时间)
|
||||
/// </summary>
|
||||
public void UpdateProfile(TimeTagProfile profile, PathRoute route)
|
||||
{
|
||||
if (profile == null) return;
|
||||
|
||||
// 重新计算时间
|
||||
_calculator.Calculate(profile, route);
|
||||
|
||||
// 保存
|
||||
_database.SaveTimeTagProfile(profile);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除时标配置
|
||||
/// </summary>
|
||||
public void DeleteProfile(string profileId)
|
||||
{
|
||||
_database.DeleteTimeTagProfile(profileId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加事件到配置
|
||||
/// </summary>
|
||||
public void AddEvent(TimeTagProfile profile, TimeTagEvent newEvent, PathRoute route)
|
||||
{
|
||||
if (profile?.Events == null || newEvent == null) return;
|
||||
|
||||
newEvent.ProfileId = profile.Id;
|
||||
profile.Events.Add(newEvent);
|
||||
|
||||
// 重新计算并保存
|
||||
UpdateProfile(profile, route);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从配置中移除事件
|
||||
/// </summary>
|
||||
public void RemoveEvent(TimeTagProfile profile, string eventId, PathRoute route)
|
||||
{
|
||||
if (profile?.Events == null) return;
|
||||
|
||||
var evt = profile.Events.FirstOrDefault(e => e.Id == eventId);
|
||||
if (evt != null)
|
||||
{
|
||||
profile.Events.Remove(evt);
|
||||
UpdateProfile(profile, route);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新事件属性
|
||||
/// </summary>
|
||||
public void UpdateEvent(TimeTagProfile profile, TimeTagEvent updatedEvent, PathRoute route)
|
||||
{
|
||||
if (profile?.Events == null || updatedEvent == null) return;
|
||||
|
||||
var existingEvent = profile.Events.FirstOrDefault(e => e.Id == updatedEvent.Id);
|
||||
if (existingEvent != null)
|
||||
{
|
||||
// 更新属性
|
||||
existingEvent.Name = updatedEvent.Name;
|
||||
existingEvent.Type = updatedEvent.Type;
|
||||
existingEvent.Distance = updatedEvent.Distance;
|
||||
existingEvent.WaitTime = updatedEvent.WaitTime;
|
||||
existingEvent.ActionTime = updatedEvent.ActionTime;
|
||||
existingEvent.ArriveSpeed = updatedEvent.ArriveSpeed;
|
||||
existingEvent.LeaveSpeed = updatedEvent.LeaveSpeed;
|
||||
|
||||
UpdateProfile(profile, route);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 复制时标配置
|
||||
/// </summary>
|
||||
public TimeTagProfile DuplicateProfile(TimeTagProfile source, string newName, PathRoute route)
|
||||
{
|
||||
if (source == null) return null;
|
||||
|
||||
var newProfile = new TimeTagProfile
|
||||
{
|
||||
RouteId = source.RouteId,
|
||||
Name = newName,
|
||||
DefaultSpeed = source.DefaultSpeed,
|
||||
TurnSpeedRatio = source.TurnSpeedRatio
|
||||
};
|
||||
|
||||
// 复制所有事件
|
||||
foreach (var evt in source.Events)
|
||||
{
|
||||
newProfile.Events.Add(new TimeTagEvent
|
||||
{
|
||||
ProfileId = newProfile.Id,
|
||||
PointId = evt.PointId,
|
||||
Distance = evt.Distance,
|
||||
Type = evt.Type,
|
||||
Name = evt.Name,
|
||||
WaitTime = evt.WaitTime,
|
||||
ActionTime = evt.ActionTime,
|
||||
ArriveSpeed = evt.ArriveSpeed,
|
||||
LeaveSpeed = evt.LeaveSpeed
|
||||
});
|
||||
}
|
||||
|
||||
// 计算并保存
|
||||
_calculator.Calculate(newProfile, route);
|
||||
_database.SaveTimeTagProfile(newProfile);
|
||||
|
||||
return newProfile;
|
||||
}
|
||||
}
|
||||
}
|
||||
300
src/Core/Services/TimeTagTimeLinerIntegration.cs
Normal file
300
src/Core/Services/TimeTagTimeLinerIntegration.cs
Normal file
@ -0,0 +1,300 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Autodesk.Navisworks.Api;
|
||||
using NavisworksTransport.Core.Models;
|
||||
using NavisworksTransport.Utils;
|
||||
|
||||
namespace NavisworksTransport.Core.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// 时标系统与TimeLiner集成 - 将时标数据转换为TimeLiner任务
|
||||
/// </summary>
|
||||
public class TimeTagTimeLinerIntegration
|
||||
{
|
||||
private readonly TimeTagService _timeTagService;
|
||||
|
||||
public TimeTagTimeLinerIntegration(TimeTagService timeTagService)
|
||||
{
|
||||
_timeTagService = timeTagService ?? throw new ArgumentNullException(nameof(timeTagService));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将时标配置导入为TimeLiner仿真任务
|
||||
/// </summary>
|
||||
/// <param name="profile">时标配置</param>
|
||||
/// <param name="route">路径</param>
|
||||
/// <param name="taskName">任务名称</param>
|
||||
/// <returns>是否成功</returns>
|
||||
public bool ImportToTimeLiner(TimeTagProfile profile, PathRoute route, string taskName = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (profile == null || route == null)
|
||||
{
|
||||
LogManager.Warning("导入TimeLiner失败: 配置或路径为空");
|
||||
return false;
|
||||
}
|
||||
|
||||
string name = taskName ?? $"{route.Name} - {profile.Name}";
|
||||
LogManager.Info($"开始导入TimeLiner: {name}");
|
||||
|
||||
// 创建仿真任务数据
|
||||
var simulationData = CreateSimulationData(profile, route);
|
||||
|
||||
LogManager.Info($"TimeLiner数据已准备: {name}, 总时间 {profile.TotalTime:F1}s");
|
||||
|
||||
// 注意:实际的TimeLiner导入需要通过COM API实现
|
||||
// 这里返回成功表示数据已准备好
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"导入TimeLiner失败: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建仿真数据 - 用于动画系统
|
||||
/// </summary>
|
||||
public SimulationData CreateSimulationData(TimeTagProfile profile, PathRoute route)
|
||||
{
|
||||
var data = new SimulationData
|
||||
{
|
||||
RouteName = route.Name,
|
||||
ProfileName = profile.Name,
|
||||
TotalTime = profile.TotalTime,
|
||||
TotalDistance = route.TotalLength,
|
||||
Waypoints = new List<SimulationWaypoint>()
|
||||
};
|
||||
|
||||
if (profile.Events == null || profile.Events.Count == 0)
|
||||
return data;
|
||||
|
||||
// 按距离排序的事件
|
||||
var events = profile.Events.OrderBy(e => e.Distance).ToList();
|
||||
|
||||
// 生成仿真路径点
|
||||
double currentTime = 0;
|
||||
for (int i = 0; i < events.Count; i++)
|
||||
{
|
||||
var evt = events[i];
|
||||
var point = FindPointAtDistance(route, evt.Distance);
|
||||
|
||||
if (point != null)
|
||||
{
|
||||
data.Waypoints.Add(new SimulationWaypoint
|
||||
{
|
||||
Time = evt.CumulativeTime,
|
||||
Distance = evt.Distance,
|
||||
Position = point.Position,
|
||||
EventType = evt.Type,
|
||||
EventName = evt.Name,
|
||||
WaitTime = evt.WaitTime,
|
||||
ActionTime = evt.ActionTime
|
||||
});
|
||||
}
|
||||
|
||||
currentTime = evt.CumulativeTime + evt.WaitTime + evt.ActionTime;
|
||||
}
|
||||
|
||||
// 计算每段的实际速度
|
||||
CalculateSegmentSpeeds(data, profile);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定距离处的路径点(插值)
|
||||
/// </summary>
|
||||
private PathPoint FindPointAtDistance(PathRoute route, double distance)
|
||||
{
|
||||
if (route?.Points == null || route.Points.Count == 0)
|
||||
return null;
|
||||
|
||||
double accumulated = 0;
|
||||
for (int i = 0; i < route.Points.Count - 1; i++)
|
||||
{
|
||||
var p1 = route.Points[i];
|
||||
var p2 = route.Points[i + 1];
|
||||
double segLength = CalculateDistance(p1, p2);
|
||||
|
||||
if (accumulated + segLength >= distance)
|
||||
{
|
||||
// 在此段内插值
|
||||
double t = (distance - accumulated) / segLength;
|
||||
return InterpolatePoint(p1, p2, t);
|
||||
}
|
||||
|
||||
accumulated += segLength;
|
||||
}
|
||||
|
||||
// 返回最后一个点
|
||||
return route.Points[route.Points.Count - 1];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算两点距离
|
||||
/// </summary>
|
||||
private double CalculateDistance(PathPoint p1, PathPoint p2)
|
||||
{
|
||||
double dx = p2.Position.X - p1.Position.X;
|
||||
double dy = p2.Position.Y - p1.Position.Y;
|
||||
double dz = p2.Position.Z - p1.Position.Z;
|
||||
return Math.Sqrt(dx * dx + dy * dy + dz * dz);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 线性插值两个路径点
|
||||
/// </summary>
|
||||
private PathPoint InterpolatePoint(PathPoint p1, PathPoint p2, double t)
|
||||
{
|
||||
return new PathPoint
|
||||
{
|
||||
Id = $"interp_{p1.Id}_{p2.Id}",
|
||||
Position = new Point3D(
|
||||
p1.Position.X + (p2.Position.X - p1.Position.X) * t,
|
||||
p1.Position.Y + (p2.Position.Y - p1.Position.Y) * t,
|
||||
p1.Position.Z + (p2.Position.Z - p1.Position.Z) * t
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算每段的速度
|
||||
/// </summary>
|
||||
private void CalculateSegmentSpeeds(SimulationData data, TimeTagProfile profile)
|
||||
{
|
||||
if (data.Waypoints.Count < 2) return;
|
||||
|
||||
for (int i = 0; i < data.Waypoints.Count - 1; i++)
|
||||
{
|
||||
var wp1 = data.Waypoints[i];
|
||||
var wp2 = data.Waypoints[i + 1];
|
||||
|
||||
double distance = wp2.Distance - wp1.Distance;
|
||||
double time = wp2.Time - wp1.Time - wp1.WaitTime - wp1.ActionTime;
|
||||
|
||||
if (time > 0)
|
||||
{
|
||||
wp1.Speed = distance / time;
|
||||
wp1.SpeedRatio = wp1.Speed / profile.DefaultSpeed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成仿真脚本(用于外部系统)
|
||||
/// </summary>
|
||||
public string GenerateSimulationScript(TimeTagProfile profile, PathRoute route)
|
||||
{
|
||||
var data = CreateSimulationData(profile, route);
|
||||
var sb = new System.Text.StringBuilder();
|
||||
|
||||
sb.AppendLine("# Simulation Script");
|
||||
sb.AppendLine($"# Route: {data.RouteName}");
|
||||
sb.AppendLine($"# Profile: {data.ProfileName}");
|
||||
sb.AppendLine($"# Total Time: {data.TotalTime:F2}s");
|
||||
sb.AppendLine($"# Total Distance: {data.TotalDistance:F2}m");
|
||||
sb.AppendLine();
|
||||
|
||||
foreach (var wp in data.Waypoints)
|
||||
{
|
||||
sb.AppendLine($"T={wp.Time:F2}s | D={wp.Distance:F2}m | " +
|
||||
$"Pos=({wp.Position.X:F2}, {wp.Position.Y:F2}, {wp.Position.Z:F2}) | " +
|
||||
$"Speed={wp.Speed:F2}m/s | {wp.EventType}");
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 导出仿真数据为JSON
|
||||
/// </summary>
|
||||
public string ExportSimulationJson(TimeTagProfile profile, PathRoute route)
|
||||
{
|
||||
var data = CreateSimulationData(profile, route);
|
||||
return ExportSimulationDataToJson(data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将仿真数据转换为JSON
|
||||
/// </summary>
|
||||
private string ExportSimulationDataToJson(SimulationData data)
|
||||
{
|
||||
var sb = new System.Text.StringBuilder();
|
||||
sb.AppendLine("{");
|
||||
sb.AppendLine($" \"routeName\": \"{EscapeJson(data.RouteName)}\",");
|
||||
sb.AppendLine($" \"profileName\": \"{EscapeJson(data.ProfileName)}\",");
|
||||
sb.AppendLine($" \"totalTime\": {data.TotalTime:F3},");
|
||||
sb.AppendLine($" \"totalDistance\": {data.TotalDistance:F3},");
|
||||
sb.AppendLine(" \"waypoints\": [");
|
||||
|
||||
for (int i = 0; i < data.Waypoints.Count; i++)
|
||||
{
|
||||
var wp = data.Waypoints[i];
|
||||
sb.AppendLine(" {");
|
||||
sb.AppendLine($" \"time\": {wp.Time:F3},");
|
||||
sb.AppendLine($" \"distance\": {wp.Distance:F3},");
|
||||
sb.Append($" \"position\": {{\"x\": {wp.Position.X:F3}, \"y\": {wp.Position.Y:F3}, \"z\": {wp.Position.Z:F3}}},");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine($" \"eventType\": \"{wp.EventType}\",");
|
||||
sb.AppendLine($" \"eventName\": \"{EscapeJson(wp.EventName)}\",");
|
||||
sb.AppendLine($" \"waitTime\": {wp.WaitTime:F2},");
|
||||
sb.AppendLine($" \"actionTime\": {wp.ActionTime:F2},");
|
||||
sb.AppendLine($" \"speed\": {wp.Speed:F3},");
|
||||
sb.AppendLine($" \"speedRatio\": {wp.SpeedRatio:F2}");
|
||||
sb.Append(" }");
|
||||
if (i < data.Waypoints.Count - 1) sb.Append(",");
|
||||
sb.AppendLine();
|
||||
}
|
||||
|
||||
sb.AppendLine(" ]");
|
||||
sb.AppendLine("}");
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转义JSON字符串
|
||||
/// </summary>
|
||||
private string EscapeJson(string str)
|
||||
{
|
||||
if (string.IsNullOrEmpty(str)) return "";
|
||||
return str.Replace("\\", "\\\\")
|
||||
.Replace("\"", "\\\"")
|
||||
.Replace("\n", "\\n")
|
||||
.Replace("\r", "\\r")
|
||||
.Replace("\t", "\\t");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 仿真数据结构
|
||||
/// </summary>
|
||||
public class SimulationData
|
||||
{
|
||||
public string RouteName { get; set; }
|
||||
public string ProfileName { get; set; }
|
||||
public double TotalTime { get; set; }
|
||||
public double TotalDistance { get; set; }
|
||||
public List<SimulationWaypoint> Waypoints { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 仿真路径点
|
||||
/// </summary>
|
||||
public class SimulationWaypoint
|
||||
{
|
||||
public double Time { get; set; }
|
||||
public double Distance { get; set; }
|
||||
public Point3D Position { get; set; }
|
||||
public EventType EventType { get; set; }
|
||||
public string EventName { get; set; }
|
||||
public double WaitTime { get; set; }
|
||||
public double ActionTime { get; set; }
|
||||
public double Speed { get; set; }
|
||||
public double SpeedRatio { get; set; }
|
||||
}
|
||||
}
|
||||
48
src/UI/WPF/Controls/EventTimelineControl.xaml
Normal file
48
src/UI/WPF/Controls/EventTimelineControl.xaml
Normal file
@ -0,0 +1,48 @@
|
||||
<UserControl x:Class="NavisworksTransport.UI.WPF.Controls.EventTimelineControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="120" d:DesignWidth="800"
|
||||
Background="White">
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 标题 -->
|
||||
<TextBlock Grid.Row="0" Text="事件时间线" FontWeight="Bold" Margin="5,2"/>
|
||||
|
||||
<!-- 时间线画布 -->
|
||||
<ScrollViewer Grid.Row="1" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Disabled">
|
||||
<Canvas x:Name="TimelineCanvas" Height="80" MinWidth="600">
|
||||
<!-- 基线 -->
|
||||
<Line x:Name="BaseLine" Stroke="#FFCCCCCC" StrokeThickness="2"
|
||||
X1="50" Y1="40" X2="550" Y2="40"/>
|
||||
|
||||
<!-- 事件节点将由代码动态添加 -->
|
||||
</Canvas>
|
||||
</ScrollViewer>
|
||||
|
||||
<!-- 图例 -->
|
||||
<StackPanel Grid.Row="2" Orientation="Horizontal" Margin="5,2">
|
||||
<TextBlock Text="图例: " FontSize="10" Foreground="Gray"/>
|
||||
<Ellipse Width="8" Height="8" Fill="#4CAF50" Margin="5,0"/>
|
||||
<TextBlock Text="起点" FontSize="10" Margin="0,0,10,0"/>
|
||||
<Ellipse Width="8" Height="8" Fill="#2196F3" Margin="5,0"/>
|
||||
<TextBlock Text="途经" FontSize="10" Margin="0,0,10,0"/>
|
||||
<Ellipse Width="8" Height="8" Fill="#FF9800" Margin="5,0"/>
|
||||
<TextBlock Text="转弯" FontSize="10" Margin="0,0,10,0"/>
|
||||
<Ellipse Width="8" Height="8" Fill="#9C27B0" Margin="5,0"/>
|
||||
<TextBlock Text="等待" FontSize="10" Margin="0,0,10,0"/>
|
||||
<Ellipse Width="8" Height="8" Fill="#F44336" Margin="5,0"/>
|
||||
<TextBlock Text="装卸" FontSize="10" Margin="0,0,10,0"/>
|
||||
<Ellipse Width="8" Height="8" Fill="#607D8B" Margin="5,0"/>
|
||||
<TextBlock Text="终点" FontSize="10"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
402
src/UI/WPF/Controls/EventTimelineControl.xaml.cs
Normal file
402
src/UI/WPF/Controls/EventTimelineControl.xaml.cs
Normal file
@ -0,0 +1,402 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Shapes;
|
||||
using NavisworksTransport.Core.Models;
|
||||
|
||||
namespace NavisworksTransport.UI.WPF.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// 事件时间线可视化控件
|
||||
/// </summary>
|
||||
public partial class EventTimelineControl : UserControl
|
||||
{
|
||||
// 固定参数
|
||||
private const double PADDING = 50; // 左右边距
|
||||
private const double NODE_Y = 40; // 节点Y坐标(基线位置)
|
||||
private const double MIN_NODE_SPACING = 100; // 最小节点间距
|
||||
private const double MIN_CANVAS_WIDTH = 600; // 最小画布宽度
|
||||
|
||||
public EventTimelineControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
Loaded += OnLoaded;
|
||||
SizeChanged += OnSizeChanged;
|
||||
}
|
||||
|
||||
private void OnLoaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
AttachCollectionChanged();
|
||||
DrawTimeline();
|
||||
}
|
||||
|
||||
private void OnSizeChanged(object sender, SizeChangedEventArgs e)
|
||||
{
|
||||
DrawTimeline();
|
||||
}
|
||||
|
||||
#region 依赖属性
|
||||
|
||||
public static readonly DependencyProperty EventsProperty =
|
||||
DependencyProperty.Register(nameof(Events), typeof(IEnumerable<TimeTagEvent>),
|
||||
typeof(EventTimelineControl),
|
||||
new PropertyMetadata(null, OnEventsChanged));
|
||||
|
||||
public IEnumerable<TimeTagEvent> Events
|
||||
{
|
||||
get => (IEnumerable<TimeTagEvent>)GetValue(EventsProperty);
|
||||
set => SetValue(EventsProperty, value);
|
||||
}
|
||||
|
||||
private static void OnEventsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var control = (EventTimelineControl)d;
|
||||
control.DetachCollectionChanged(e.OldValue as INotifyCollectionChanged);
|
||||
control.AttachCollectionChanged();
|
||||
control.DrawTimeline();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 附加集合变更监听
|
||||
/// </summary>
|
||||
private void AttachCollectionChanged()
|
||||
{
|
||||
if (Events is INotifyCollectionChanged notifyCollection)
|
||||
{
|
||||
notifyCollection.CollectionChanged += OnCollectionChanged;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除集合变更监听
|
||||
/// </summary>
|
||||
private void DetachCollectionChanged(INotifyCollectionChanged oldCollection)
|
||||
{
|
||||
if (oldCollection != null)
|
||||
{
|
||||
oldCollection.CollectionChanged -= OnCollectionChanged;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 集合内容变化时重绘
|
||||
/// </summary>
|
||||
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
// 使用 Dispatcher 确保在 UI 线程执行
|
||||
Dispatcher.BeginInvoke(new Action(DrawTimeline));
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty TotalDistanceProperty =
|
||||
DependencyProperty.Register(nameof(TotalDistance), typeof(double),
|
||||
typeof(EventTimelineControl),
|
||||
new PropertyMetadata(100.0, (d, e) => ((EventTimelineControl)d).DrawTimeline()));
|
||||
|
||||
public double TotalDistance
|
||||
{
|
||||
get => (double)GetValue(TotalDistanceProperty);
|
||||
set => SetValue(TotalDistanceProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty SelectedEventProperty =
|
||||
DependencyProperty.Register(nameof(SelectedEvent), typeof(TimeTagEvent),
|
||||
typeof(EventTimelineControl),
|
||||
new PropertyMetadata(null, (d, e) => ((EventTimelineControl)d).HighlightSelectedEvent()));
|
||||
|
||||
public TimeTagEvent SelectedEvent
|
||||
{
|
||||
get => (TimeTagEvent)GetValue(SelectedEventProperty);
|
||||
set => SetValue(SelectedEventProperty, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// 获取实际可用的画布宽度
|
||||
/// </summary>
|
||||
private double GetCanvasWidth()
|
||||
{
|
||||
if (TimelineCanvas == null) return MIN_CANVAS_WIDTH;
|
||||
|
||||
var events = Events?.ToList();
|
||||
int eventCount = events?.Count ?? 0;
|
||||
|
||||
// 基于事件数量计算所需宽度
|
||||
double requiredWidth = PADDING * 2 + Math.Max(eventCount - 1, 1) * MIN_NODE_SPACING;
|
||||
requiredWidth = Math.Max(requiredWidth, MIN_CANVAS_WIDTH);
|
||||
|
||||
// 获取ScrollViewer的实际宽度
|
||||
var scrollViewer = GetParentScrollViewer();
|
||||
double availableWidth = scrollViewer?.ActualWidth ?? TimelineCanvas.ActualWidth;
|
||||
|
||||
// 如果可用宽度大于所需宽度,使用可用宽度(让基线填满空间)
|
||||
// 如果可用宽度小于所需宽度,使用所需宽度(启用滚动)
|
||||
return Math.Max(availableWidth, requiredWidth);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取父级ScrollViewer
|
||||
/// </summary>
|
||||
private ScrollViewer GetParentScrollViewer()
|
||||
{
|
||||
DependencyObject parent = TimelineCanvas;
|
||||
while (parent != null)
|
||||
{
|
||||
if (parent is ScrollViewer sv) return sv;
|
||||
parent = VisualTreeHelper.GetParent(parent);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 绘制时间线
|
||||
/// </summary>
|
||||
private void DrawTimeline()
|
||||
{
|
||||
if (TimelineCanvas == null) return;
|
||||
|
||||
// 清除动态添加的元素(保留基线)
|
||||
ClearDynamicElements();
|
||||
|
||||
var events = Events?.OrderBy(e => e.Distance).ToList();
|
||||
if (events == null || events.Count == 0)
|
||||
{
|
||||
// 没有事件时画一条简单的基线
|
||||
DrawBaseLineOnly();
|
||||
return;
|
||||
}
|
||||
|
||||
// 计算画布宽度并设置
|
||||
double canvasWidth = GetCanvasWidth();
|
||||
TimelineCanvas.Width = canvasWidth;
|
||||
|
||||
// 计算可用绘图区域
|
||||
double availableWidth = canvasWidth - PADDING * 2;
|
||||
|
||||
// 更新基线
|
||||
UpdateBaseLine(PADDING, canvasWidth - PADDING);
|
||||
|
||||
// 绘制所有事件节点
|
||||
for (int i = 0; i < events.Count; i++)
|
||||
{
|
||||
var evt = events[i];
|
||||
|
||||
// 计算位置:基于距离比例
|
||||
double positionRatio = TotalDistance > 0 ? evt.Distance / TotalDistance : 0;
|
||||
double x = PADDING + positionRatio * availableWidth;
|
||||
|
||||
// 确保第一个点在起点,最后一个点在终点
|
||||
if (i == 0) x = PADDING;
|
||||
else if (i == events.Count - 1) x = canvasWidth - PADDING;
|
||||
|
||||
DrawEventNode(evt, x, NODE_Y, i);
|
||||
}
|
||||
|
||||
HighlightSelectedEvent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清除动态元素
|
||||
/// </summary>
|
||||
private void ClearDynamicElements()
|
||||
{
|
||||
var toRemove = TimelineCanvas.Children.OfType<FrameworkElement>()
|
||||
.Where(c => c != BaseLine)
|
||||
.ToList();
|
||||
|
||||
foreach (var child in toRemove)
|
||||
{
|
||||
TimelineCanvas.Children.Remove(child);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 仅绘制基线(无事件时)
|
||||
/// </summary>
|
||||
private void DrawBaseLineOnly()
|
||||
{
|
||||
double canvasWidth = Math.Max(TimelineCanvas.ActualWidth, MIN_CANVAS_WIDTH);
|
||||
if (double.IsNaN(canvasWidth) || canvasWidth <= 0) canvasWidth = MIN_CANVAS_WIDTH;
|
||||
|
||||
TimelineCanvas.Width = canvasWidth;
|
||||
UpdateBaseLine(PADDING, canvasWidth - PADDING);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新基线位置
|
||||
/// </summary>
|
||||
private void UpdateBaseLine(double x1, double x2)
|
||||
{
|
||||
BaseLine.X1 = x1;
|
||||
BaseLine.Y1 = NODE_Y;
|
||||
BaseLine.X2 = x2;
|
||||
BaseLine.Y2 = NODE_Y;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 绘制单个事件节点
|
||||
/// </summary>
|
||||
private void DrawEventNode(TimeTagEvent evt, double x, double y, int index)
|
||||
{
|
||||
var color = GetEventColor(evt.Type);
|
||||
|
||||
// 节点圆形(直径14)
|
||||
var ellipse = new Ellipse
|
||||
{
|
||||
Width = 14,
|
||||
Height = 14,
|
||||
Fill = new SolidColorBrush(color),
|
||||
Stroke = Brushes.White,
|
||||
StrokeThickness = 2,
|
||||
Tag = evt
|
||||
};
|
||||
|
||||
// 居中定位
|
||||
Canvas.SetLeft(ellipse, x - 7);
|
||||
Canvas.SetTop(ellipse, y - 7);
|
||||
|
||||
// 鼠标事件
|
||||
ellipse.MouseLeftButtonDown += (s, e) =>
|
||||
{
|
||||
SelectedEvent = evt;
|
||||
e.Handled = true;
|
||||
};
|
||||
|
||||
ellipse.MouseEnter += (s, e) =>
|
||||
{
|
||||
ellipse.Width = 18;
|
||||
ellipse.Height = 18;
|
||||
Canvas.SetLeft(ellipse, x - 9);
|
||||
Canvas.SetTop(ellipse, y - 9);
|
||||
ellipse.Cursor = System.Windows.Input.Cursors.Hand;
|
||||
};
|
||||
|
||||
ellipse.MouseLeave += (s, e) =>
|
||||
{
|
||||
ellipse.Width = 14;
|
||||
ellipse.Height = 14;
|
||||
Canvas.SetLeft(ellipse, x - 7);
|
||||
Canvas.SetTop(ellipse, y - 7);
|
||||
};
|
||||
|
||||
TimelineCanvas.Children.Add(ellipse);
|
||||
|
||||
// 交替上下显示标签,避免重叠
|
||||
bool showAbove = (index % 2) == 1;
|
||||
DrawEventLabel(evt, x, y, showAbove, color);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 绘制事件标签
|
||||
/// </summary>
|
||||
private void DrawEventLabel(TimeTagEvent evt, double x, double y, bool showAbove, Color color)
|
||||
{
|
||||
// 连接线起点和终点
|
||||
double lineStartY = showAbove ? y - 7 : y + 7;
|
||||
double lineEndY = showAbove ? y - 20 : y + 20;
|
||||
double textY = showAbove ? y - 35 : y + 22;
|
||||
|
||||
// 垂直连接线
|
||||
var line = new Line
|
||||
{
|
||||
Stroke = new SolidColorBrush(color),
|
||||
StrokeThickness = 1,
|
||||
StrokeDashArray = new DoubleCollection { 2, 2 },
|
||||
X1 = x,
|
||||
Y1 = lineStartY,
|
||||
X2 = x,
|
||||
Y2 = lineEndY,
|
||||
Tag = evt
|
||||
};
|
||||
TimelineCanvas.Children.Add(line);
|
||||
|
||||
// 事件名称
|
||||
var nameText = new TextBlock
|
||||
{
|
||||
Text = evt.Name,
|
||||
FontSize = 10,
|
||||
Foreground = Brushes.Black,
|
||||
TextAlignment = TextAlignment.Center,
|
||||
Tag = evt
|
||||
};
|
||||
|
||||
// 计算文本宽度并居中
|
||||
nameText.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
|
||||
double textWidth = nameText.DesiredSize.Width;
|
||||
Canvas.SetLeft(nameText, x - textWidth / 2);
|
||||
Canvas.SetTop(nameText, textY);
|
||||
TimelineCanvas.Children.Add(nameText);
|
||||
|
||||
// 累计时间(在名称下方/上方)
|
||||
var timeText = new TextBlock
|
||||
{
|
||||
Text = $"{evt.CumulativeTime:F1}s",
|
||||
FontSize = 9,
|
||||
Foreground = Brushes.Gray,
|
||||
TextAlignment = TextAlignment.Center,
|
||||
Tag = evt
|
||||
};
|
||||
|
||||
timeText.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
|
||||
textWidth = timeText.DesiredSize.Width;
|
||||
Canvas.SetLeft(timeText, x - textWidth / 2);
|
||||
Canvas.SetTop(timeText, textY + (showAbove ? -13 : 13));
|
||||
TimelineCanvas.Children.Add(timeText);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 高亮选中事件
|
||||
/// </summary>
|
||||
private void HighlightSelectedEvent()
|
||||
{
|
||||
if (TimelineCanvas == null) return;
|
||||
|
||||
foreach (var ellipse in TimelineCanvas.Children.OfType<Ellipse>()
|
||||
.Where(e => e.Tag is TimeTagEvent))
|
||||
{
|
||||
var evt = ellipse.Tag as TimeTagEvent;
|
||||
if (evt == SelectedEvent)
|
||||
{
|
||||
ellipse.Stroke = new SolidColorBrush(Colors.Red);
|
||||
ellipse.StrokeThickness = 3;
|
||||
Canvas.SetZIndex(ellipse, 100);
|
||||
}
|
||||
else
|
||||
{
|
||||
ellipse.Stroke = Brushes.White;
|
||||
ellipse.StrokeThickness = 2;
|
||||
Canvas.SetZIndex(ellipse, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取事件类型对应的颜色
|
||||
/// </summary>
|
||||
private Color GetEventColor(EventType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case EventType.Start:
|
||||
return Color.FromRgb(76, 175, 80); // Green
|
||||
case EventType.End:
|
||||
return Color.FromRgb(96, 125, 139); // Blue Gray
|
||||
case EventType.Turn:
|
||||
return Color.FromRgb(255, 152, 0); // Orange
|
||||
case EventType.Wait:
|
||||
return Color.FromRgb(156, 39, 176); // Purple
|
||||
case EventType.Load:
|
||||
case EventType.Unload:
|
||||
return Color.FromRgb(244, 67, 54); // Red
|
||||
case EventType.Pass:
|
||||
return Color.FromRgb(33, 150, 243); // Blue
|
||||
default:
|
||||
return Colors.Gray;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,4 @@
|
||||
<!--
|
||||
<!--
|
||||
NavisworksTransport 关于对话框 - 采用与主界面一致的Navisworks 2026风格
|
||||
|
||||
功能说明:
|
||||
@ -24,7 +24,7 @@ NavisworksTransport 关于对话框 - 采用与主界面一致的Navisworks 2026
|
||||
<!-- 引用共享的Navisworks 2026样式资源 -->
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="pack://application:,,,/NavisworksTransportPlugin;component/src/UI/WPF/Resources/NavisworksStyles.xaml"/>
|
||||
<ResourceDictionary Source="pack://application:,,,/TransportPlugin;component/src/UI/WPF/Resources/NavisworksStyles.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Window.Resources>
|
||||
@ -118,4 +118,4 @@ NavisworksTransport 关于对话框 - 采用与主界面一致的Navisworks 2026
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Window>
|
||||
</Window>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
<Window x:Class="NavisworksTransport.UI.WPF.Views.AerialHeightDialog"
|
||||
<Window x:Class="NavisworksTransport.UI.WPF.Views.AerialHeightDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="设置吊装提升高度" Height="240" Width="400"
|
||||
@ -10,7 +10,7 @@
|
||||
<Window.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="pack://application:,,,/NavisworksTransportPlugin;component/src/UI/WPF/Resources/NavisworksStyles.xaml"/>
|
||||
<ResourceDictionary Source="pack://application:,,,/TransportPlugin;component/src/UI/WPF/Resources/NavisworksStyles.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Window.Resources>
|
||||
@ -69,4 +69,4 @@
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Window>
|
||||
</Window>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
<!--
|
||||
<!--
|
||||
NavisworksTransport 检测动画页签视图 - 采用与类别设置和分层管理一致的Navisworks 2026风格
|
||||
|
||||
功能说明:
|
||||
@ -22,7 +22,7 @@ NavisworksTransport 检测动画页签视图 - 采用与类别设置和分层管
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="pack://application:,,,/NavisworksTransportPlugin;component/src/UI/WPF/Resources/NavisworksStyles.xaml"/>
|
||||
<ResourceDictionary Source="pack://application:,,,/TransportPlugin;component/src/UI/WPF/Resources/NavisworksStyles.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<!-- 转换器资源 -->
|
||||
@ -659,4 +659,4 @@ NavisworksTransport 检测动画页签视图 - 采用与类别设置和分层管
|
||||
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
</UserControl>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
<!--
|
||||
<!--
|
||||
NavisworksTransport 批处理队列管理页签视图 - 采用与其他页签一致的Navisworks 2026风格
|
||||
|
||||
功能说明:
|
||||
@ -22,7 +22,7 @@ NavisworksTransport 批处理队列管理页签视图 - 采用与其他页签一
|
||||
<!-- 引用共享的Navisworks 2026样式资源 -->
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="pack://application:,,,/NavisworksTransportPlugin;component/src/UI/WPF/Resources/NavisworksStyles.xaml"/>
|
||||
<ResourceDictionary Source="pack://application:,,,/TransportPlugin;component/src/UI/WPF/Resources/NavisworksStyles.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<!-- 转换器资源 -->
|
||||
@ -153,4 +153,4 @@ NavisworksTransport 批处理队列管理页签视图 - 采用与其他页签一
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
</UserControl>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
<Window x:Class="NavisworksTransport.UI.WPF.Views.CollisionAnalysisDialog"
|
||||
<Window x:Class="NavisworksTransport.UI.WPF.Views.CollisionAnalysisDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
@ -13,7 +13,7 @@
|
||||
<Window.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="pack://application:,,,/NavisworksTransportPlugin;component/src/UI/WPF/Resources/NavisworksStyles.xaml"/>
|
||||
<ResourceDictionary Source="pack://application:,,,/TransportPlugin;component/src/UI/WPF/Resources/NavisworksStyles.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<!-- 提示框样式 -->
|
||||
@ -194,3 +194,4 @@
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Window>
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
<!--
|
||||
<!--
|
||||
NavisworksTransport 碰撞检测报告对话框 - 采用与主界面一致的Navisworks 2026风格
|
||||
|
||||
功能说明:
|
||||
@ -28,7 +28,7 @@ NavisworksTransport 碰撞检测报告对话框 - 采用与主界面一致的Nav
|
||||
<!-- 引用共享的Navisworks 2026样式资源 -->
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="pack://application:,,,/NavisworksTransportPlugin;component/src/UI/WPF/Resources/NavisworksStyles.xaml"/>
|
||||
<ResourceDictionary Source="pack://application:,,,/TransportPlugin;component/src/UI/WPF/Resources/NavisworksStyles.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<!-- 转换器定义 -->
|
||||
@ -634,4 +634,4 @@ NavisworksTransport 碰撞检测报告对话框 - 采用与主界面一致的Nav
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Window>
|
||||
</Window>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
<!--
|
||||
<!--
|
||||
NavisworksTransport 配置编辑器对话框 - 采用与主界面一致的Navisworks 2026风格
|
||||
|
||||
功能说明:
|
||||
@ -28,7 +28,7 @@ NavisworksTransport 配置编辑器对话框 - 采用与主界面一致的Navisw
|
||||
<!-- 引用共享的Navisworks 2026样式资源 -->
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="pack://application:,,,/NavisworksTransportPlugin;component/src/UI/WPF/Resources/NavisworksStyles.xaml"/>
|
||||
<ResourceDictionary Source="pack://application:,,,/TransportPlugin;component/src/UI/WPF/Resources/NavisworksStyles.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<!-- 配置编辑器特有的样式 -->
|
||||
@ -212,3 +212,4 @@ NavisworksTransport 配置编辑器对话框 - 采用与主界面一致的Navisw
|
||||
</Border>
|
||||
</Grid>
|
||||
</Window>
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
<Window x:Class="NavisworksTransport.UI.WPF.Views.CoordinateSystemResultDialog"
|
||||
<Window x:Class="NavisworksTransport.UI.WPF.Views.CoordinateSystemResultDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="坐标系探索结果"
|
||||
@ -50,3 +50,4 @@
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Window>
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
<Window x:Class="NavisworksTransport.UI.WPF.Views.EditCoordinatesWindow"
|
||||
<Window x:Class="NavisworksTransport.UI.WPF.Views.EditCoordinatesWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="编辑路径点坐标" Height="380" Width="400"
|
||||
@ -10,7 +10,7 @@
|
||||
<Window.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="pack://application:,,,/NavisworksTransportPlugin;component/src/UI/WPF/Resources/NavisworksStyles.xaml"/>
|
||||
<ResourceDictionary Source="pack://application:,,,/TransportPlugin;component/src/UI/WPF/Resources/NavisworksStyles.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Window.Resources>
|
||||
@ -137,3 +137,4 @@
|
||||
</Border>
|
||||
</Grid>
|
||||
</Window>
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
<Window x:Class="NavisworksTransport.UI.WPF.Views.EditRotationWindow"
|
||||
<Window x:Class="NavisworksTransport.UI.WPF.Views.EditRotationWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="调整物体角度" Height="320" Width="400"
|
||||
@ -10,7 +10,7 @@
|
||||
<Window.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="pack://application:,,,/NavisworksTransportPlugin;component/src/UI/WPF/Resources/NavisworksStyles.xaml"/>
|
||||
<ResourceDictionary Source="pack://application:,,,/TransportPlugin;component/src/UI/WPF/Resources/NavisworksStyles.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Window.Resources>
|
||||
@ -109,4 +109,4 @@
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Window>
|
||||
</Window>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
<!--
|
||||
<!--
|
||||
NavisworksTransport 生成导航地图对话框 - 采用与主界面一致的Navisworks 2026风格
|
||||
|
||||
功能说明:
|
||||
@ -27,7 +27,7 @@ NavisworksTransport 生成导航地图对话框 - 采用与主界面一致的Nav
|
||||
<!-- 引用共享的Navisworks 2026样式资源 -->
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="pack://application:,,,/NavisworksTransportPlugin;component/src/UI/WPF/Resources/NavisworksStyles.xaml"/>
|
||||
<ResourceDictionary Source="pack://application:,,,/TransportPlugin;component/src/UI/WPF/Resources/NavisworksStyles.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<!-- 对话框专用样式 -->
|
||||
@ -128,4 +128,4 @@ NavisworksTransport 生成导航地图对话框 - 采用与主界面一致的Nav
|
||||
Click="CancelButton_Click"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Window>
|
||||
</Window>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
<!--
|
||||
<!--
|
||||
NavisworksTransport 帮助对话框 - 采用与主界面一致的Navisworks 2026风格
|
||||
|
||||
功能说明:
|
||||
@ -24,7 +24,7 @@ NavisworksTransport 帮助对话框 - 采用与主界面一致的Navisworks 2026
|
||||
<!-- 引用共享的Navisworks 2026样式资源 -->
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="pack://application:,,,/NavisworksTransportPlugin;component/src/UI/WPF/Resources/NavisworksStyles.xaml"/>
|
||||
<ResourceDictionary Source="pack://application:,,,/TransportPlugin;component/src/UI/WPF/Resources/NavisworksStyles.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<!-- 帮助对话框专用样式 -->
|
||||
@ -144,4 +144,4 @@ NavisworksTransport 帮助对话框 - 采用与主界面一致的Navisworks 2026
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Window>
|
||||
</Window>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
<!--
|
||||
<!--
|
||||
NavisworksTransport 分层管理页签视图 - 重构优化版本
|
||||
功能优化:
|
||||
1. 移除独立楼层分析区域,整合到预览功能中
|
||||
@ -21,7 +21,7 @@ NavisworksTransport 分层管理页签视图 - 重构优化版本
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="pack://application:,,,/NavisworksTransportPlugin;component/src/UI/WPF/Resources/NavisworksStyles.xaml"/>
|
||||
<ResourceDictionary Source="pack://application:,,,/TransportPlugin;component/src/UI/WPF/Resources/NavisworksStyles.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<!-- 转换器资源 -->
|
||||
@ -328,4 +328,4 @@ NavisworksTransport 分层管理页签视图 - 重构优化版本
|
||||
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
</UserControl>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
<!--
|
||||
<!--
|
||||
NavisworksTransport 日志查看器对话框 - 采用与主界面一致的Navisworks 2026风格
|
||||
|
||||
功能说明:
|
||||
@ -27,7 +27,7 @@ NavisworksTransport 日志查看器对话框 - 采用与主界面一致的Navisw
|
||||
<!-- 引用共享的Navisworks 2026样式资源 -->
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="pack://application:,,,/NavisworksTransportPlugin;component/src/UI/WPF/Resources/NavisworksStyles.xaml"/>
|
||||
<ResourceDictionary Source="pack://application:,,,/TransportPlugin;component/src/UI/WPF/Resources/NavisworksStyles.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<!-- 日志查看器特有的样式 -->
|
||||
@ -167,4 +167,4 @@ NavisworksTransport 日志查看器对话框 - 采用与主界面一致的Navisw
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Window>
|
||||
</Window>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
<!--
|
||||
<!--
|
||||
NavisworksTransport 主控制面板 - 采用与其他视图一致的Navisworks 2026风格
|
||||
|
||||
功能说明:
|
||||
@ -24,7 +24,7 @@ NavisworksTransport 主控制面板 - 采用与其他视图一致的Navisworks 2
|
||||
<!-- 引用共享的Navisworks 2026样式资源 -->
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="pack://application:,,,/NavisworksTransportPlugin;component/src/UI/WPF/Resources/NavisworksStyles.xaml"/>
|
||||
<ResourceDictionary Source="pack://application:,,,/TransportPlugin;component/src/UI/WPF/Resources/NavisworksStyles.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<!-- 转换器资源 -->
|
||||
@ -155,4 +155,4 @@ NavisworksTransport 主控制面板 - 采用与其他视图一致的Navisworks 2
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
</UserControl>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
<UserControl x:Class="NavisworksTransport.UI.WPF.Views.MediaControlBar"
|
||||
<UserControl x:Class="NavisworksTransport.UI.WPF.Views.MediaControlBar"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
@ -9,7 +9,7 @@
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="pack://application:,,,/NavisworksTransportPlugin;component/src/UI/WPF/Resources/MediaControlIcons.xaml"/>
|
||||
<ResourceDictionary Source="pack://application:,,,/TransportPlugin;component/src/UI/WPF/Resources/MediaControlIcons.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
@ -208,4 +208,4 @@
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Border>
|
||||
</UserControl>
|
||||
</UserControl>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
<Window x:Class="NavisworksTransport.UI.WPF.Views.ModelItemBoundsWindow"
|
||||
<Window x:Class="NavisworksTransport.UI.WPF.Views.ModelItemBoundsWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="元素包围盒信息" Height="460" Width="420"
|
||||
@ -10,7 +10,7 @@
|
||||
<Window.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="pack://application:,,,/NavisworksTransportPlugin;component/src/UI/WPF/Resources/NavisworksStyles.xaml"/>
|
||||
<ResourceDictionary Source="pack://application:,,,/TransportPlugin;component/src/UI/WPF/Resources/NavisworksStyles.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Window.Resources>
|
||||
@ -265,3 +265,4 @@
|
||||
</Border>
|
||||
</Grid>
|
||||
</Window>
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
<!--
|
||||
<!--
|
||||
NavisworksTransport 类别设置页签视图 - 参考分层管理页签的设计风格
|
||||
|
||||
功能改进:
|
||||
@ -22,7 +22,7 @@ NavisworksTransport 类别设置页签视图 - 参考分层管理页签的设计
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="pack://application:,,,/NavisworksTransportPlugin;component/src/UI/WPF/Resources/NavisworksStyles.xaml"/>
|
||||
<ResourceDictionary Source="pack://application:,,,/TransportPlugin;component/src/UI/WPF/Resources/NavisworksStyles.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<!-- 转换器资源 -->
|
||||
@ -265,4 +265,4 @@ NavisworksTransport 类别设置页签视图 - 参考分层管理页签的设计
|
||||
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
</UserControl>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
<!--
|
||||
<!--
|
||||
NavisworksTransport 路径规划分析对话框 V2
|
||||
支持多维度评分和同终点组分析
|
||||
-->
|
||||
@ -22,7 +22,7 @@ NavisworksTransport 路径规划分析对话框 V2
|
||||
<Window.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="pack://application:,,,/NavisworksTransportPlugin;component/src/UI/WPF/Resources/NavisworksStyles.xaml"/>
|
||||
<ResourceDictionary Source="pack://application:,,,/TransportPlugin;component/src/UI/WPF/Resources/NavisworksStyles.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<!-- 颜色定义 -->
|
||||
@ -329,3 +329,4 @@ NavisworksTransport 路径规划分析对话框 V2
|
||||
</Border>
|
||||
</Grid>
|
||||
</Window>
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
<!--
|
||||
<!--
|
||||
路径配置对话框
|
||||
|
||||
功能说明:
|
||||
@ -23,7 +23,7 @@
|
||||
<Window.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="pack://application:,,,/NavisworksTransportPlugin;component/src/UI/WPF/Resources/NavisworksStyles.xaml"/>
|
||||
<ResourceDictionary Source="pack://application:,,,/TransportPlugin;component/src/UI/WPF/Resources/NavisworksStyles.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Window.Resources>
|
||||
@ -179,4 +179,4 @@
|
||||
Style="{StaticResource SecondaryButtonStyle}" Width="100"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Window>
|
||||
</Window>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
<!--
|
||||
<!--
|
||||
NavisworksTransport 路径编辑页签视图 - 采用与动画控制和分层管理一致的Navisworks 2026风格
|
||||
|
||||
功能说明:
|
||||
@ -21,7 +21,7 @@ NavisworksTransport 路径编辑页签视图 - 采用与动画控制和分层管
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="pack://application:,,,/NavisworksTransportPlugin;component/src/UI/WPF/Resources/NavisworksStyles.xaml"/>
|
||||
<ResourceDictionary Source="pack://application:,,,/TransportPlugin;component/src/UI/WPF/Resources/NavisworksStyles.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<!-- 转换器资源 -->
|
||||
@ -501,4 +501,4 @@ NavisworksTransport 路径编辑页签视图 - 采用与动画控制和分层管
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
</UserControl>
|
||||
|
||||
@ -4,6 +4,8 @@ using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using NavisworksTransport.UI.WPF.ViewModels;
|
||||
using NavisworksTransport.UI.WPF.Views;
|
||||
using NavisworksTransport.Core.Services;
|
||||
using NavisworksTransport.Core.Models;
|
||||
|
||||
namespace NavisworksTransport.UI.WPF.Views
|
||||
{
|
||||
@ -17,6 +19,11 @@ namespace NavisworksTransport.UI.WPF.Views
|
||||
/// ViewModel属性,用于外部访问
|
||||
/// </summary>
|
||||
public PathEditingViewModel ViewModel { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 时标服务实例
|
||||
/// </summary>
|
||||
private TimeTagService _timeTagService;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数,需要传入主ViewModel以支持统一状态栏
|
||||
@ -52,43 +59,64 @@ namespace NavisworksTransport.UI.WPF.Views
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 路径列表中“时标”按钮点击,打开时间标签窗口(仅展示)
|
||||
/// 路径列表中"时标"按钮点击,打开时间标签窗口
|
||||
/// </summary>
|
||||
private void OnTimeTagButtonClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
LogManager.Info("时标按钮被点击,准备打开TimeTagDialog窗口");
|
||||
LogManager.Info("时标按钮被点击");
|
||||
|
||||
var dlg = new TimeTagDialog();
|
||||
// 从按钮的DataContext获取当前路径ViewModel
|
||||
if (!(sender is Button button) || !(button.DataContext is PathRouteViewModel routeVm))
|
||||
{
|
||||
LogManager.Warning("无法获取当前路径信息");
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取底层的PathRoute
|
||||
var route = routeVm.Route;
|
||||
if (route == null)
|
||||
{
|
||||
LogManager.Warning("PathRouteViewModel中的Route为空");
|
||||
return;
|
||||
}
|
||||
|
||||
// 在Navisworks环境中,尝试找到合适的父窗口
|
||||
// 确保服务已初始化
|
||||
if (_timeTagService == null)
|
||||
{
|
||||
var doc = Autodesk.Navisworks.Api.Application.ActiveDocument;
|
||||
var db = new PathDatabase(doc.FileName);
|
||||
_timeTagService = new TimeTagService(db);
|
||||
LogManager.Info("时标服务已初始化");
|
||||
}
|
||||
|
||||
// 创建并显示时标对话框
|
||||
var dlg = new TimeTagDialog(_timeTagService, route);
|
||||
|
||||
// 设置父窗口
|
||||
try
|
||||
{
|
||||
// 尝试设置Owner为当前WPF窗口的顶级父窗口
|
||||
var parentWindow = Window.GetWindow(this);
|
||||
if (parentWindow != null)
|
||||
{
|
||||
dlg.Owner = parentWindow;
|
||||
LogManager.Info("设置TimeTagDialog的Owner为当前窗口");
|
||||
}
|
||||
else
|
||||
{
|
||||
LogManager.Info("未找到父窗口,将以独立窗口方式显示TimeTagDialog");
|
||||
}
|
||||
}
|
||||
catch (Exception ownerEx)
|
||||
{
|
||||
LogManager.Warning($"设置Owner失败,将以独立窗口显示: {ownerEx.Message}");
|
||||
LogManager.Warning($"设置Owner失败: {ownerEx.Message}");
|
||||
}
|
||||
|
||||
dlg.ShowDialog();
|
||||
LogManager.Info("TimeTagDialog窗口显示完成");
|
||||
LogManager.Info($"时标对话框关闭: {route.Name}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"打开时间标签窗口失败: {ex.Message}");
|
||||
LogManager.Error($"异常详情: {ex}");
|
||||
MessageBox.Show($"打开时标窗口失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
MessageBox.Show($"打开时标窗口失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
<!--
|
||||
<!--
|
||||
路径选择对话框
|
||||
|
||||
功能说明:
|
||||
@ -21,7 +21,7 @@
|
||||
<Window.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="pack://application:,,,/NavisworksTransportPlugin;component/src/UI/WPF/Resources/NavisworksStyles.xaml"/>
|
||||
<ResourceDictionary Source="pack://application:,,,/TransportPlugin;component/src/UI/WPF/Resources/NavisworksStyles.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Window.Resources>
|
||||
@ -79,4 +79,4 @@
|
||||
Style="{StaticResource SecondaryButtonStyle}" Width="100"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Window>
|
||||
</Window>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
<!--
|
||||
<!--
|
||||
NavisworksTransport 系统管理页签视图 - 采用与其他页签一致的Navisworks 2026风格
|
||||
|
||||
功能说明:
|
||||
@ -21,7 +21,7 @@ NavisworksTransport 系统管理页签视图 - 采用与其他页签一致的Nav
|
||||
<!-- 引用共享的Navisworks 2026样式资源 -->
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="pack://application:,,,/NavisworksTransportPlugin;component/src/UI/WPF/Resources/NavisworksStyles.xaml"/>
|
||||
<ResourceDictionary Source="pack://application:,,,/TransportPlugin;component/src/UI/WPF/Resources/NavisworksStyles.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<!-- 系统管理页面特有的样式 -->
|
||||
@ -301,4 +301,4 @@ NavisworksTransport 系统管理页签视图 - 采用与其他页签一致的Nav
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
</UserControl>
|
||||
|
||||
@ -1,252 +1,211 @@
|
||||
<!--
|
||||
NavisworksTransport 时间标签设置对话框 - 采用与主界面一致的Navisworks 2026风格
|
||||
|
||||
功能说明:
|
||||
1. 路径时间标签设置,支持分段时间计算和速度调整
|
||||
2. 支持默认速度设置、元素限速优先配置
|
||||
3. 提供估算、应用写回等操作功能
|
||||
4. 可视化时间分段展示和累计时间计算
|
||||
|
||||
设计原则:参考PathAnalysisDialog的现代化布局和样式,使用统一的蓝色主题
|
||||
-->
|
||||
<Window x:Class="NavisworksTransport.UI.WPF.Views.TimeTagDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
mc:Ignorable="d"
|
||||
Title="时间标签设置 - Route_A1"
|
||||
Height="700"
|
||||
Width="950"
|
||||
ResizeMode="CanResize"
|
||||
xmlns:local="clr-namespace:NavisworksTransport.UI.WPF.Views"
|
||||
xmlns:controls="clr-namespace:NavisworksTransport.UI.WPF.Controls"
|
||||
Title="时标设置"
|
||||
Height="750" Width="1100"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
MinHeight="500"
|
||||
MinWidth="700">
|
||||
|
||||
ResizeMode="CanResize"
|
||||
x:Name="root">
|
||||
<Window.Resources>
|
||||
<!-- 引用共享的Navisworks 2026样式资源 -->
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="pack://application:,,,/NavisworksTransportPlugin;component/src/UI/WPF/Resources/NavisworksStyles.xaml"/>
|
||||
<ResourceDictionary Source="pack://application:,,,/TransportPlugin;component/src/UI/WPF/Resources/NavisworksStyles.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<!-- 转换器定义 -->
|
||||
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
|
||||
|
||||
<!-- 时间标签专用样式 -->
|
||||
<Style x:Key="SettingsCardStyle" TargetType="Border">
|
||||
<Setter Property="Background" Value="White"/>
|
||||
<Setter Property="BorderBrush" Value="#FFD4E7FF"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="CornerRadius" Value="4"/>
|
||||
<Setter Property="Padding" Value="15"/>
|
||||
<Setter Property="Margin" Value="5"/>
|
||||
<Setter Property="Effect">
|
||||
<Setter.Value>
|
||||
<DropShadowEffect Color="Gray" Direction="315" ShadowDepth="2" Opacity="0.3" BlurRadius="4"/>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="SectionHeaderStyle" TargetType="TextBlock">
|
||||
<Setter Property="FontSize" Value="14"/>
|
||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource NavisworksPrimaryBrush}"/>
|
||||
<Setter Property="Margin" Value="0,0,0,10"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="ParameterGroupStyle" TargetType="StackPanel">
|
||||
<Setter Property="Orientation" Value="Horizontal"/>
|
||||
<Setter Property="Margin" Value="0,5"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="ParameterLabelStyle" TargetType="TextBlock">
|
||||
<Setter Property="FontSize" Value="11"/>
|
||||
<Setter Property="Foreground" Value="#FF666666"/>
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
<Setter Property="Width" Value="100"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="ParameterInputStyle" TargetType="TextBox">
|
||||
<Setter Property="Width" Value="80"/>
|
||||
<Setter Property="Height" Value="25"/>
|
||||
<Setter Property="VerticalContentAlignment" Value="Center"/>
|
||||
<Setter Property="BorderBrush" Value="#FFCCCCCC"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="Padding" Value="5,0"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="ResultValueStyle" TargetType="TextBlock">
|
||||
<Setter Property="FontSize" Value="14"/>
|
||||
<Setter Property="FontWeight" Value="Bold"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource NavisworksPrimaryBrush}"/>
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
</Style>
|
||||
|
||||
<!-- 转换器 -->
|
||||
<BooleanToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
|
||||
<local:InverseBooleanToVisibilityConverter x:Key="InverseBoolToVisibilityConverter"/>
|
||||
</ResourceDictionary>
|
||||
</Window.Resources>
|
||||
|
||||
<Grid>
|
||||
|
||||
<Grid Margin="10">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 主内容区域 -->
|
||||
<Grid Grid.Row="0" Margin="15">
|
||||
|
||||
<!-- 标题和路径信息 -->
|
||||
<StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0,0,0,10">
|
||||
<TextBlock Text="路径: " FontWeight="Bold" FontSize="14"/>
|
||||
<TextBlock Text="{Binding RouteName}" FontWeight="Bold" FontSize="14"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- 事件时间线可视化 -->
|
||||
<Border Grid.Row="1" BorderBrush="#FFDDDDDD" BorderThickness="1" Margin="0,0,0,10"
|
||||
Height="140" IsEnabled="{Binding CanEdit}">
|
||||
<controls:EventTimelineControl
|
||||
Events="{Binding Events}"
|
||||
TotalDistance="{Binding TotalDistanceValue}"
|
||||
SelectedEvent="{Binding SelectedEvent, Mode=TwoWay}"/>
|
||||
</Border>
|
||||
|
||||
<!-- 配置选择 -->
|
||||
<StackPanel Grid.Row="2" Orientation="Horizontal" Margin="0,0,0,10">
|
||||
<TextBlock Text="时标配置: " VerticalAlignment="Center" Margin="0,0,5,0" FontWeight="SemiBold"/>
|
||||
<TextBlock Text="时标配置: " VerticalAlignment="Center" Margin="0,0,5,0" FontWeight="SemiBold"/>
|
||||
<ComboBox Width="200"
|
||||
ItemsSource="{Binding Profiles}"
|
||||
SelectedValue="{Binding SelectedProfileId}"
|
||||
SelectedValuePath="Id"
|
||||
DisplayMemberPath="Name"/>
|
||||
<Button Content="新建配置" Margin="10,0,5,0" Padding="15,3" Command="{Binding CreateProfileCommand}"/>
|
||||
<Button Content="删除配置" Margin="5,0,0,0" Padding="15,3" Command="{Binding DeleteProfileCommand}"/>
|
||||
<Separator Margin="20,0" Style="{StaticResource {x:Static ToolBar.SeparatorStyleKey}}"/>
|
||||
<Button Content="导出" Padding="15,3" Command="{Binding ExportCommand}"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- 主内容区 -->
|
||||
<Grid Grid.Row="3" IsEnabled="{Binding CanEdit}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="300"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="190"/>
|
||||
<ColumnDefinition Width="2.5*"/>
|
||||
<ColumnDefinition Width="260"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- 左侧:设置面板 -->
|
||||
<StackPanel Grid.Column="0">
|
||||
<!-- 基本设置卡片 -->
|
||||
<Border Style="{StaticResource SettingsCardStyle}">
|
||||
|
||||
<!-- 左侧:参数设置 -->
|
||||
<StackPanel Grid.Column="0" Margin="0,0,10,0">
|
||||
<GroupBox Header="全局参数" Padding="10">
|
||||
<StackPanel>
|
||||
<TextBlock Text="⚙️ 基本设置" Style="{StaticResource SectionHeaderStyle}"/>
|
||||
<TextBlock Text="默认速度 (m/s):" Margin="0,0,0,5" FontWeight="SemiBold"/>
|
||||
<Slider Minimum="0.1" Maximum="5"
|
||||
Value="{Binding DefaultSpeed, Mode=TwoWay}"
|
||||
TickFrequency="0.1" IsSnapToTickEnabled="True"/>
|
||||
<TextBlock Text="{Binding DefaultSpeed, StringFormat={}{0:F1}}"
|
||||
HorizontalAlignment="Center" Margin="0,2,0,15"/>
|
||||
|
||||
<TextBlock Text="转弯速度比例:" Margin="0,0,0,5" FontWeight="SemiBold"/>
|
||||
<Slider Minimum="0.1" Maximum="1"
|
||||
Value="{Binding TurnSpeedRatio, Mode=TwoWay}"
|
||||
TickFrequency="0.1" IsSnapToTickEnabled="True"/>
|
||||
<TextBlock Text="{Binding TurnSpeedRatio, StringFormat=P0}"
|
||||
HorizontalAlignment="Center" Margin="0,2,0,15"/>
|
||||
|
||||
<Separator Margin="0,10"/>
|
||||
|
||||
<TextBlock Text="总距离:" FontWeight="Bold"/>
|
||||
<TextBlock Text="{Binding TotalDistanceText}" Margin="0,0,0,10" FontSize="14"/>
|
||||
|
||||
<StackPanel Style="{StaticResource ParameterGroupStyle}">
|
||||
<TextBlock Text="默认速度:" Style="{StaticResource ParameterLabelStyle}"/>
|
||||
<TextBox Text="{Binding DefaultSpeed, StringFormat=0.0###}" Style="{StaticResource ParameterInputStyle}"/>
|
||||
<TextBlock Text="m/s" FontSize="10" Foreground="#FF666666" VerticalAlignment="Center" Margin="5,0,0,0"/>
|
||||
</StackPanel>
|
||||
|
||||
<CheckBox Content="使用元素限速优先" IsChecked="{Binding UseElementSpeedLimit}" Margin="0,10,0,5" FontSize="11"/>
|
||||
|
||||
<StackPanel Style="{StaticResource ParameterGroupStyle}" Margin="0,15,0,5">
|
||||
<TextBlock Text="总时长:" Style="{StaticResource ParameterLabelStyle}"/>
|
||||
<TextBlock Text="{Binding TotalDurationText}" Style="{StaticResource ResultValueStyle}"/>
|
||||
</StackPanel>
|
||||
<TextBlock Text="总时间:" FontWeight="Bold"/>
|
||||
<TextBlock Text="{Binding TotalTimeText}" Foreground="Blue" FontSize="18" FontWeight="Bold"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- 操作按钮卡片 -->
|
||||
<Border Style="{StaticResource SettingsCardStyle}">
|
||||
<StackPanel>
|
||||
<TextBlock Text="🔧 操作" Style="{StaticResource SectionHeaderStyle}"/>
|
||||
|
||||
<Button Content="重新估算"
|
||||
Style="{StaticResource ActionButtonStyle}"
|
||||
Height="32"
|
||||
Margin="0,5"/>
|
||||
|
||||
<Button Content="应用写回"
|
||||
Style="{StaticResource ActionButtonStyle}"
|
||||
Height="32"
|
||||
Background="#FF4CAF50"
|
||||
BorderBrush="#FF4CAF50"
|
||||
Margin="0,5"/>
|
||||
|
||||
<Button Content="导出时标"
|
||||
Style="{StaticResource SecondaryButtonStyle}"
|
||||
Height="32"
|
||||
Margin="0,5"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- 说明卡片 -->
|
||||
<Border Style="{StaticResource SettingsCardStyle}">
|
||||
<StackPanel>
|
||||
<TextBlock Text="💡 说明" Style="{StaticResource SectionHeaderStyle}"/>
|
||||
|
||||
<TextBlock Text="• 单位:米/秒 与 秒" FontSize="10" Margin="0,2" Foreground="#FF666666"/>
|
||||
<TextBlock Text="• 点击「应用写回」将累计时标写入路径点" FontSize="10" Margin="0,2" Foreground="#FF666666" TextWrapping="Wrap"/>
|
||||
<TextBlock Text="• 元素限速优先时使用模型中设置的最高速度" FontSize="10" Margin="0,2" Foreground="#FF666666" TextWrapping="Wrap"/>
|
||||
<TextBlock Text="• 等待事件段时长可手动调整" FontSize="10" Margin="0,2" Foreground="#FF666666"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</GroupBox>
|
||||
</StackPanel>
|
||||
|
||||
<!-- 右侧:时间分段表格 -->
|
||||
<Border Grid.Column="1" Style="{StaticResource SettingsCardStyle}">
|
||||
<StackPanel>
|
||||
<TextBlock Text="📊 路径时间分段" Style="{StaticResource SectionHeaderStyle}"/>
|
||||
|
||||
<!-- 现代化DataGrid -->
|
||||
<DataGrid AutoGenerateColumns="False"
|
||||
HeadersVisibility="Column"
|
||||
CanUserReorderColumns="False"
|
||||
CanUserResizeColumns="True"
|
||||
CanUserSortColumns="False"
|
||||
BorderBrush="#FFD4E7FF"
|
||||
BorderThickness="1"
|
||||
|
||||
<!-- 中间:事件列表 -->
|
||||
<GroupBox Grid.Column="1" Header="事件节点列表">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 列表工具栏 -->
|
||||
<ToolBar Grid.Row="0" Margin="0,0,0,5">
|
||||
<Button Content="插入事件" Command="{Binding InsertEventCommand}" Padding="8,2"
|
||||
ToolTip="在选中事件后插入新事件,未选中则在起点后插入"/>
|
||||
<Button Content="删除事件" Command="{Binding DeleteEventCommand}" Padding="8,2" Margin="5,0"
|
||||
Background="#FFFFE6E6" ToolTip="删除选中的事件节点"/>
|
||||
</ToolBar>
|
||||
|
||||
<!-- 事件表格 -->
|
||||
<DataGrid Grid.Row="1"
|
||||
ItemsSource="{Binding Events}"
|
||||
SelectedItem="{Binding SelectedEvent}"
|
||||
AutoGenerateColumns="False"
|
||||
CanUserAddRows="False"
|
||||
CanUserDeleteRows="False"
|
||||
SelectionMode="Single"
|
||||
GridLinesVisibility="Horizontal"
|
||||
HorizontalGridLinesBrush="#FFEEEEEE"
|
||||
RowBackground="White"
|
||||
AlternatingRowBackground="#FFF8FBFF"
|
||||
Height="240"
|
||||
ItemsSource="{Binding TimeSegments}">
|
||||
AlternatingRowBackground="#FFF0F8FF"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
HeadersVisibility="Column"
|
||||
RowHeaderWidth="0">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="#" Width="40" IsReadOnly="True" Binding="{Binding Index}"/>
|
||||
<DataGridTextColumn Header="长度(m)" Width="80" IsReadOnly="True" Binding="{Binding Length, StringFormat=F2}"/>
|
||||
<DataGridTextColumn Header="速度(m/s)" Width="90" Binding="{Binding Speed, StringFormat=F2}"/>
|
||||
<DataGridTextColumn Header="段耗时(s)" Width="90" Binding="{Binding Duration, StringFormat=F1}"/>
|
||||
<DataGridTextColumn Header="累计(s)" Width="80" IsReadOnly="True" Binding="{Binding CumulativeTime, StringFormat=F1}"/>
|
||||
<DataGridTextColumn Header="备注" Width="*" IsReadOnly="True" Binding="{Binding Remarks}"/>
|
||||
<DataGridTextColumn Header="序号" Binding="{Binding SortOrder}" Width="50" IsReadOnly="True"/>
|
||||
<DataGridTextColumn Header="事件名称" Binding="{Binding Name}" Width="*" MinWidth="120"/>
|
||||
<DataGridTextColumn Header="类型" Binding="{Binding Type}" Width="80" IsReadOnly="True"/>
|
||||
<DataGridTextColumn Header="距离(m)" Binding="{Binding Distance, StringFormat=F2}" Width="85" IsReadOnly="True"/>
|
||||
<DataGridTextColumn Header="等待(s)" Binding="{Binding WaitTime, StringFormat=F1}" Width="75" IsReadOnly="True"/>
|
||||
<DataGridTextColumn Header="动作(s)" Binding="{Binding ActionTime, StringFormat=F1}" Width="75" IsReadOnly="True"/>
|
||||
<DataGridTextColumn Header="累计时间(s)" Binding="{Binding CumulativeTime, StringFormat=F2}" Width="95" IsReadOnly="True"/>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
|
||||
<!-- 时间轴可视化 -->
|
||||
<TextBlock Text="📈 时间轴预览" Style="{StaticResource SectionHeaderStyle}" Margin="0,20,0,10"/>
|
||||
|
||||
<StackPanel>
|
||||
<!-- 动态时间轴段(绑定到TimelineItems集合) -->
|
||||
<ItemsControl ItemsSource="{Binding TimelineItems}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border Background="#FFF0F8FF" BorderBrush="#FFB0D4F1" BorderThickness="1" CornerRadius="3" Padding="8" Margin="0,2">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="120"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="80"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0" Text="{Binding Name}" FontWeight="SemiBold" FontSize="10" VerticalAlignment="Center" TextTrimming="CharacterEllipsis"/>
|
||||
<Rectangle Grid.Column="1" Height="8" Fill="{Binding Color}" Margin="5,0" VerticalAlignment="Center"/>
|
||||
<TextBlock Grid.Column="2" Text="{Binding Duration, StringFormat='{}{0:F1}s'}" FontSize="10" HorizontalAlignment="Right" VerticalAlignment="Center"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
<!-- 总计(绑定到ViewModel) -->
|
||||
<Border Background="#FFE8F5E8" BorderBrush="#FF4CAF50" BorderThickness="2" CornerRadius="4" Padding="10" Margin="0,10,0,0">
|
||||
<Grid>
|
||||
<TextBlock Text="总计用时" FontWeight="Bold" FontSize="12" HorizontalAlignment="Left" VerticalAlignment="Center"/>
|
||||
<TextBlock Text="{Binding TotalDurationText}" FontWeight="Bold" FontSize="14" Foreground="#FF4CAF50" HorizontalAlignment="Right" VerticalAlignment="Center"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
<!-- 提示 -->
|
||||
<TextBlock Grid.Row="2" Text="提示:点击事件选中,在右侧编辑详情"
|
||||
Foreground="Gray" FontStyle="Italic" Margin="0,5,0,0"/>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
|
||||
<!-- 右侧:事件详情编辑 -->
|
||||
<GroupBox Grid.Column="2" Header="事件详情" IsEnabled="{Binding CanEditEvent}">
|
||||
<StackPanel Margin="10">
|
||||
<TextBlock Text="选中事件:" FontWeight="Bold" Margin="0,0,0,5"/>
|
||||
<TextBlock Text="{Binding SelectedEvent.Name}" FontSize="16" Margin="0,0,0,15"
|
||||
Foreground="#FF2B579A"/>
|
||||
|
||||
<TextBlock Text="事件类型:" FontWeight="SemiBold" Margin="0,0,0,3"/>
|
||||
<!-- 事件类型可编辑时显示ComboBox -->
|
||||
<ComboBox ItemsSource="{Binding DataContext.EventTypes, ElementName=root}"
|
||||
SelectedItem="{Binding SelectedEventType, Mode=TwoWay}"
|
||||
Margin="0,0,0,15"
|
||||
Visibility="{Binding IsSelectedEventTypeEditable, Converter={StaticResource BoolToVisibilityConverter}}"/>
|
||||
<!-- 事件类型不可编辑时显示只读文本 -->
|
||||
<TextBlock Text="{Binding SelectedEventType}"
|
||||
FontSize="14" Foreground="Gray" Margin="0,0,0,15"
|
||||
Visibility="{Binding IsSelectedEventTypeEditable, Converter={StaticResource InverseBoolToVisibilityConverter}}"/>
|
||||
|
||||
<TextBlock Text="距离 (米):" FontWeight="SemiBold" Margin="0,0,0,3"/>
|
||||
<!-- 距离可编辑时显示Slider和输入框 -->
|
||||
<Slider x:Name="DistanceSlider"
|
||||
Minimum="0"
|
||||
Maximum="{Binding TotalDistanceValue, FallbackValue=100}"
|
||||
Value="{Binding SelectedDistance, Mode=TwoWay, UpdateSourceTrigger=Explicit}"
|
||||
TickFrequency="1" IsSnapToTickEnabled="True"
|
||||
Visibility="{Binding IsSelectedDistanceEditable, Converter={StaticResource BoolToVisibilityConverter}}"
|
||||
PreviewMouseLeftButtonDown="DistanceSlider_PreviewMouseLeftButtonDown"
|
||||
PreviewMouseLeftButtonUp="DistanceSlider_PreviewMouseLeftButtonUp"/>
|
||||
<TextBox Text="{Binding SelectedDistance, StringFormat=F2, Mode=TwoWay}"
|
||||
Margin="0,0,0,15" HorizontalAlignment="Right" Width="80"
|
||||
Visibility="{Binding IsSelectedDistanceEditable, Converter={StaticResource BoolToVisibilityConverter}}"/>
|
||||
<!-- 距离不可编辑时显示只读文本 -->
|
||||
<TextBlock Text="{Binding SelectedDistanceDisplay}"
|
||||
FontSize="14" Foreground="Gray" Margin="0,0,0,15"
|
||||
Visibility="{Binding IsSelectedDistanceEditable, Converter={StaticResource InverseBoolToVisibilityConverter}}"/>
|
||||
|
||||
<TextBlock Text="等待时间 (秒):" FontWeight="SemiBold" Margin="0,0,0,3"/>
|
||||
<Slider Minimum="0" Maximum="60"
|
||||
Value="{Binding SelectedWaitTime, Mode=TwoWay}"
|
||||
TickFrequency="1" IsSnapToTickEnabled="True"/>
|
||||
<TextBox Text="{Binding SelectedWaitTime, StringFormat=F1, Mode=TwoWay}"
|
||||
Margin="0,0,0,15" HorizontalAlignment="Right" Width="80"/>
|
||||
|
||||
<TextBlock Text="动作时间 (秒):" FontWeight="SemiBold" Margin="0,0,0,3"/>
|
||||
<Slider Minimum="0" Maximum="60"
|
||||
Value="{Binding SelectedActionTime, Mode=TwoWay}"
|
||||
TickFrequency="1" IsSnapToTickEnabled="True"/>
|
||||
<TextBox Text="{Binding SelectedActionTime, StringFormat=F1, Mode=TwoWay}"
|
||||
Margin="0,0,0,15" HorizontalAlignment="Right" Width="80"/>
|
||||
|
||||
<Separator Margin="0,10"/>
|
||||
|
||||
<TextBlock Text="当前累计时间:" FontWeight="SemiBold"/>
|
||||
<TextBlock Text="{Binding SelectedEvent.CumulativeTime, StringFormat={}{0:F2} 秒}"
|
||||
FontSize="14" Foreground="Green"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</GroupBox>
|
||||
</Grid>
|
||||
|
||||
<!-- 底部按钮区域 -->
|
||||
<Border Grid.Row="1"
|
||||
BorderBrush="#FFD4E7FF"
|
||||
BorderThickness="1"
|
||||
CornerRadius="0"
|
||||
Background="#FFF8FBFF"
|
||||
Margin="15,0,15,15"
|
||||
Padding="15,10">
|
||||
<StackPanel Orientation="Horizontal"
|
||||
HorizontalAlignment="Right">
|
||||
|
||||
<Button Content="重置参数"
|
||||
Style="{StaticResource SecondaryButtonStyle}"
|
||||
Margin="0,0,10,0"/>
|
||||
|
||||
<Button Content="预览动画"
|
||||
Style="{StaticResource ActionButtonStyle}"
|
||||
Margin="0,0,10,0"/>
|
||||
|
||||
<Button Content="关闭"
|
||||
Click="CloseButton_Click"
|
||||
Style="{StaticResource SecondaryButtonStyle}"/>
|
||||
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- 底部按钮 -->
|
||||
<StackPanel Grid.Row="4" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,10,0,0">
|
||||
<Button Content="刷新" Padding="20,5" Margin="0,0,10,0" Command="{Binding RefreshCommand}"/>
|
||||
<Button Content="关闭" Padding="20,5" Click="CloseButton_Click"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Window>
|
||||
</Window>
|
||||
|
||||
@ -1,75 +1,90 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using NavisworksTransport.Core.Services;
|
||||
using NavisworksTransport.UI.WPF.ViewModels;
|
||||
using NavisworksTransport.Core.Models;
|
||||
|
||||
namespace NavisworksTransport.UI.WPF.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// TimeTagDialog.xaml 的交互逻辑
|
||||
/// 时间标签设置对话框 - 用于路径时间分段计算和标签设置
|
||||
/// 时标设置对话框
|
||||
/// </summary>
|
||||
public partial class TimeTagDialog : Window
|
||||
{
|
||||
/// <summary>
|
||||
/// 初始化时间标签设置对话框
|
||||
/// </summary>
|
||||
public TimeTagDialog()
|
||||
private TimeTagViewModel _viewModel;
|
||||
|
||||
public TimeTagDialog(TimeTagService timeTagService, PathRoute route)
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
try
|
||||
{
|
||||
InitializeComponent();
|
||||
LogManager.Info("TimeTagDialog初始化完成");
|
||||
|
||||
// 设置窗口标题包含当前时间
|
||||
Title = $"时间标签设置 [{DateTime.Now:MM-dd HH:mm:ss}]";
|
||||
|
||||
// 设置ViewModel
|
||||
this.DataContext = new TimeTagViewModel();
|
||||
|
||||
LogManager.Info("TimeTagDialog ViewModel设置完成");
|
||||
_viewModel = new TimeTagViewModel(timeTagService, route);
|
||||
DataContext = _viewModel;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"初始化TimeTagDialog失败: {ex.Message}", ex);
|
||||
LogManager.Error($"时标对话框初始化失败: {ex.Message}");
|
||||
MessageBox.Show($"初始化失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 关闭按钮点击事件
|
||||
/// 关闭按钮
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void CloseButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
LogManager.Info("用户关闭时间标签设置对话框");
|
||||
this.Close();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"关闭TimeTagDialog时发生错误: {ex.Message}", ex);
|
||||
}
|
||||
Close();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 窗口关闭时的清理工作
|
||||
/// 距离滑块预览鼠标左键按下时开始拖动
|
||||
/// </summary>
|
||||
/// <param name="e"></param>
|
||||
protected override void OnClosed(EventArgs e)
|
||||
private void DistanceSlider_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
try
|
||||
// 开始拖动,此时不更新源
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 距离滑块预览鼠标左键松开时结束拖动并更新源
|
||||
/// </summary>
|
||||
private void DistanceSlider_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (sender is Slider slider)
|
||||
{
|
||||
// 执行清理工作(如果需要)
|
||||
LogManager.Info("TimeTagDialog已关闭,执行清理");
|
||||
|
||||
base.OnClosed(e);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"TimeTagDialog清理过程中发生错误: {ex.Message}", ex);
|
||||
// 鼠标松开时更新绑定源,触发重新排序
|
||||
var binding = slider.GetBindingExpression(Slider.ValueProperty);
|
||||
binding?.UpdateSource();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 反向布尔到可见性转换器(false时Visible,true时Collapsed)
|
||||
/// </summary>
|
||||
public class InverseBooleanToVisibilityConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is bool boolValue)
|
||||
{
|
||||
return boolValue ? Visibility.Collapsed : Visibility.Visible;
|
||||
}
|
||||
return Visibility.Collapsed;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is Visibility visibility)
|
||||
{
|
||||
return visibility != Visibility.Visible;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user