Add path duplication workflow
This commit is contained in:
parent
a5d1db6416
commit
7db5a7a201
@ -53,7 +53,9 @@
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="UnitTests\Core\PathHelperTests.cs" />
|
||||
<Compile Include="UnitTests\Core\PathPersistenceTests.cs" />
|
||||
<Compile Include="UnitTests\Core\PathRouteCloneTests.cs" />
|
||||
<Compile Include="UnitTests\CoordinateSystem\HostCoordinateAdapterTests.cs" />
|
||||
<Compile Include="UnitTests\CoordinateSystem\CanonicalRailPoseBuilderTests.cs" />
|
||||
<Compile Include="UnitTests\CoordinateSystem\CanonicalPlanarPoseBuilderTests.cs" />
|
||||
|
||||
28
UnitTests/Core/PathHelperTests.cs
Normal file
28
UnitTests/Core/PathHelperTests.cs
Normal file
@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using NavisworksTransport.Utils;
|
||||
|
||||
namespace NavisworksTransport.UnitTests.Core
|
||||
{
|
||||
[TestClass]
|
||||
public class PathHelperTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void BuildDuplicatedPathName_ShouldRebuildTrailingTimestamp()
|
||||
{
|
||||
var now = new DateTime(2026, 3, 28, 9, 45, 12);
|
||||
|
||||
var duplicatedName = PathHelper.BuildDuplicatedPathName("人工_0328_081530", now);
|
||||
|
||||
Assert.AreEqual("副本_人工_0328_094512", duplicatedName);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void BuildDuplicatedPathName_ShouldKeepPlainNameWithoutTimestamp()
|
||||
{
|
||||
var duplicatedName = PathHelper.BuildDuplicatedPathName("我的路径");
|
||||
|
||||
Assert.AreEqual("副本_我的路径", duplicatedName);
|
||||
}
|
||||
}
|
||||
}
|
||||
71
UnitTests/Core/PathRouteCloneTests.cs
Normal file
71
UnitTests/Core/PathRouteCloneTests.cs
Normal file
@ -0,0 +1,71 @@
|
||||
using Autodesk.Navisworks.Api;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace NavisworksTransport.UnitTests.Core
|
||||
{
|
||||
[TestClass]
|
||||
public class PathRouteCloneTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void Clone_ShouldPreserveRailSpecificPropertiesAndGenerateNewIds()
|
||||
{
|
||||
var route = new PathRoute("原路径")
|
||||
{
|
||||
PathType = PathType.Rail,
|
||||
RailMountMode = RailMountMode.OverRail,
|
||||
RailPathDefinitionMode = RailPathDefinitionMode.RailCenterLine,
|
||||
RailNormalOffset = 12.5,
|
||||
RailPreferredNormal = new Point3D(0, 1, 0)
|
||||
};
|
||||
|
||||
route.Points.Add(new PathPoint(new Point3D(1, 2, 3), "起点", PathPointType.StartPoint));
|
||||
route.Points.Add(new PathPoint(new Point3D(4, 5, 6), "终点", PathPointType.EndPoint));
|
||||
|
||||
var clonedRoute = route.Clone();
|
||||
|
||||
Assert.AreEqual(PathType.Rail, clonedRoute.PathType);
|
||||
Assert.AreEqual(RailMountMode.OverRail, clonedRoute.RailMountMode);
|
||||
Assert.AreEqual(RailPathDefinitionMode.RailCenterLine, clonedRoute.RailPathDefinitionMode);
|
||||
Assert.AreEqual(12.5, clonedRoute.RailNormalOffset, 1e-6);
|
||||
Assert.IsNotNull(clonedRoute.RailPreferredNormal);
|
||||
Assert.AreNotEqual(route.Id, clonedRoute.Id);
|
||||
Assert.AreEqual(2, clonedRoute.Points.Count);
|
||||
Assert.AreNotEqual(route.Points[0].Id, clonedRoute.Points[0].Id);
|
||||
Assert.AreEqual(route.Points[0].Name, clonedRoute.Points[0].Name);
|
||||
Assert.AreEqual(route.Points[0].Type, clonedRoute.Points[0].Type);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Clone_ShouldCopyGroundEdgesAndRemapPointReferences()
|
||||
{
|
||||
var route = new PathRoute("地面路径")
|
||||
{
|
||||
PathType = PathType.Ground,
|
||||
TurnRadius = 3.0,
|
||||
IsCurved = true
|
||||
};
|
||||
|
||||
var startPoint = new PathPoint(new Point3D(0, 0, 0), "起点", PathPointType.StartPoint);
|
||||
var endPoint = new PathPoint(new Point3D(10, 0, 0), "终点", PathPointType.EndPoint);
|
||||
route.Points.Add(startPoint);
|
||||
route.Points.Add(endPoint);
|
||||
route.Edges.Add(new PathEdge
|
||||
{
|
||||
StartPointId = startPoint.Id,
|
||||
EndPointId = endPoint.Id,
|
||||
SegmentType = PathSegmentType.Straight,
|
||||
PhysicalLength = 10.0
|
||||
});
|
||||
|
||||
var clonedRoute = route.Clone();
|
||||
|
||||
Assert.AreEqual(PathType.Ground, clonedRoute.PathType);
|
||||
Assert.AreEqual(1, clonedRoute.Edges.Count);
|
||||
Assert.AreEqual(3.0, clonedRoute.TurnRadius, 1e-6);
|
||||
Assert.IsTrue(clonedRoute.IsCurved);
|
||||
Assert.AreEqual(clonedRoute.Points[0].Id, clonedRoute.Edges[0].StartPointId);
|
||||
Assert.AreEqual(clonedRoute.Points[1].Id, clonedRoute.Edges[0].EndPointId);
|
||||
Assert.AreNotEqual(route.Edges[0].Id, clonedRoute.Edges[0].Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1058,7 +1058,7 @@ namespace NavisworksTransport
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
EstimatedTime = EstimatedTime,
|
||||
// TotalLength:计算属性,自动从几何数据计算,克隆时会重新计算
|
||||
CreatedTime = CreatedTime,
|
||||
CreatedTime = DateTime.Now,
|
||||
LastModified = DateTime.Now,
|
||||
Description = Description,
|
||||
AssociatedChannelIds = new List<string>(AssociatedChannelIds),
|
||||
@ -1072,14 +1072,18 @@ namespace NavisworksTransport
|
||||
MaxObjectWidth = MaxObjectWidth,
|
||||
MaxObjectHeight = MaxObjectHeight,
|
||||
SafetyMargin = SafetyMargin,
|
||||
TurnRadius = TurnRadius,
|
||||
IsCurved = IsCurved,
|
||||
PathType = PathType,
|
||||
LiftHeight = LiftHeight,
|
||||
RailMountMode = RailMountMode,
|
||||
RailPathDefinitionMode = RailPathDefinitionMode,
|
||||
RailNormalOffset = RailNormalOffset,
|
||||
RailPreferredNormal = RailPreferredNormal != null
|
||||
? new Point3D(RailPreferredNormal.X, RailPreferredNormal.Y, RailPreferredNormal.Z)
|
||||
: null
|
||||
RailPreferredNormal = RailPreferredNormal
|
||||
};
|
||||
|
||||
var pointIdMap = new Dictionary<string, string>();
|
||||
|
||||
// 克隆所有路径点
|
||||
foreach (var point in Points)
|
||||
{
|
||||
@ -1088,9 +1092,43 @@ namespace NavisworksTransport
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
Index = point.Index,
|
||||
CreatedTime = point.CreatedTime,
|
||||
Notes = point.Notes
|
||||
Notes = point.Notes,
|
||||
SpeedLimit = point.SpeedLimit,
|
||||
CustomTurnRadius = point.CustomTurnRadius,
|
||||
VisualizationDiameter = point.VisualizationDiameter,
|
||||
Direction = point.Direction
|
||||
};
|
||||
clonedRoute.Points.Add(clonedPoint);
|
||||
pointIdMap[point.Id] = clonedPoint.Id;
|
||||
}
|
||||
|
||||
foreach (var edge in Edges)
|
||||
{
|
||||
var clonedEdge = new PathEdge
|
||||
{
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
StartPointId = pointIdMap.ContainsKey(edge.StartPointId) ? pointIdMap[edge.StartPointId] : edge.StartPointId,
|
||||
EndPointId = pointIdMap.ContainsKey(edge.EndPointId) ? pointIdMap[edge.EndPointId] : edge.EndPointId,
|
||||
SegmentType = edge.SegmentType,
|
||||
PhysicalLength = edge.PhysicalLength,
|
||||
Trajectory = edge.Trajectory != null
|
||||
? new ArcTrajectory
|
||||
{
|
||||
Ts = edge.Trajectory.Ts,
|
||||
Te = edge.Trajectory.Te,
|
||||
ArcCenter = edge.Trajectory.ArcCenter,
|
||||
RequestedRadius = edge.Trajectory.RequestedRadius,
|
||||
ActualRadius = edge.Trajectory.ActualRadius,
|
||||
DeflectionAngle = edge.Trajectory.DeflectionAngle,
|
||||
ArcLength = edge.Trajectory.ArcLength
|
||||
}
|
||||
: null,
|
||||
SampledPoints = edge.SampledPoints != null
|
||||
? new List<Point3D>(edge.SampledPoints)
|
||||
: new List<Point3D>()
|
||||
};
|
||||
|
||||
clonedRoute.Edges.Add(clonedEdge);
|
||||
}
|
||||
|
||||
return clonedRoute;
|
||||
|
||||
@ -1005,6 +1005,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
public ICommand NewRailPathCommand { get; private set; }
|
||||
public ICommand NewHoistingPathCommand { get; private set; }
|
||||
public ICommand NewMultiLevelHoistingPathCommand { get; private set; }
|
||||
public ICommand DuplicatePathCommand { get; private set; }
|
||||
public ICommand DeletePathCommand { get; private set; }
|
||||
public ICommand RenamePathCommand { get; private set; }
|
||||
public ICommand StartEditCommand { get; private set; }
|
||||
@ -1364,6 +1365,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
PickMultiLevelTargetCommand = new RelayCommand<HoistingLevelItem>((item) => ExecutePickMultiLevelTarget(item));
|
||||
CreateMultiLevelPathCommand = new RelayCommand(async () => await ExecuteCreateMultiLevelPathAsync(), () => CanCreateMultiLevelPath);
|
||||
CancelMultiLevelHoistingCommand = new RelayCommand(() => ExecuteCancelMultiLevelHoisting());
|
||||
DuplicatePathCommand = new RelayCommand(async () => await ExecuteDuplicatePathAsync());
|
||||
DeletePathCommand = new RelayCommand(async () => await ExecuteDeletePathAsync());
|
||||
RenamePathCommand = new RelayCommand(async () => await ExecuteRenamePathAsync());
|
||||
StartEditCommand = new RelayCommand(async () => await ExecuteAddPathPointAsync(), () => CanExecuteStartEdit);
|
||||
@ -3595,6 +3597,51 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
}, "删除路径");
|
||||
}
|
||||
|
||||
private async Task ExecuteDuplicatePathAsync()
|
||||
{
|
||||
if (SelectedPathRoute == null || _pathPlanningManager == null) return;
|
||||
|
||||
await SafeExecuteAsync(async () =>
|
||||
{
|
||||
var sourceRoute = SelectedPathRoute.Route ??
|
||||
_pathPlanningManager.Routes.FirstOrDefault(r => r.Id == SelectedPathRoute.Id);
|
||||
if (sourceRoute == null)
|
||||
{
|
||||
throw new InvalidOperationException("未找到要复制的路径数据");
|
||||
}
|
||||
|
||||
UpdateMainStatus($"正在复制路径: {sourceRoute.Name}...");
|
||||
|
||||
var duplicatedRoute = sourceRoute.Clone();
|
||||
duplicatedRoute.Name = PathHelper.BuildDuplicatedPathName(sourceRoute.Name);
|
||||
duplicatedRoute.CreatedTime = DateTime.Now;
|
||||
duplicatedRoute.LastModified = DateTime.Now;
|
||||
|
||||
if (!_pathPlanningManager.AddRoute(duplicatedRoute))
|
||||
{
|
||||
throw new InvalidOperationException($"复制路径失败: {sourceRoute.Name}");
|
||||
}
|
||||
|
||||
var duplicatedPathViewModel = new PathRouteViewModel(isFromDatabase: true)
|
||||
{
|
||||
Route = duplicatedRoute,
|
||||
IsActive = false
|
||||
};
|
||||
duplicatedPathViewModel.SetTimeInfo(duplicatedRoute.CreatedTime, duplicatedRoute.LastModified);
|
||||
|
||||
foreach (var point in CreatePathPointViewModelsFromCoreRoute(duplicatedRoute))
|
||||
{
|
||||
duplicatedPathViewModel.Points.Add(point);
|
||||
}
|
||||
|
||||
PathRoutes.Add(duplicatedPathViewModel);
|
||||
SelectedPathRoute = duplicatedPathViewModel;
|
||||
UpdateMainStatus($"已复制路径: {sourceRoute.Name} -> {duplicatedPathViewModel.Name}");
|
||||
|
||||
await Task.CompletedTask;
|
||||
}, "复制路径");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 坐标编辑命令
|
||||
|
||||
@ -214,6 +214,10 @@ NavisworksTransport 路径编辑页签视图 - 采用与动画控制和分层管
|
||||
Command="{Binding NewHoistingPathCommand}"
|
||||
Style="{StaticResource ActionButtonStyle}"
|
||||
ToolTip="创建吊装路径(支持多层)"/>
|
||||
<Button Content="复制"
|
||||
Command="{Binding DuplicatePathCommand}"
|
||||
Style="{StaticResource SecondaryButtonStyle}"
|
||||
IsEnabled="{Binding SelectedPathRoute, Converter={x:Static converters:BoolToVisibilityConverter.IsNotNullToBoolConverter}}"/>
|
||||
<Button Content="删除"
|
||||
Command="{Binding DeletePathCommand}"
|
||||
Style="{StaticResource SecondaryButtonStyle}"
|
||||
|
||||
@ -2,6 +2,7 @@ using System;
|
||||
using System.Drawing.Imaging;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Autodesk.Navisworks.Api;
|
||||
|
||||
namespace NavisworksTransport.Utils
|
||||
@ -83,6 +84,26 @@ namespace NavisworksTransport.Utils
|
||||
return $"{prefix}_{timestamp}.{extension}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成复制路径名称。
|
||||
/// 如果原名称以 MMdd_HHmmss 结尾,则重建时间戳;否则仅添加“副本_”前缀。
|
||||
/// </summary>
|
||||
public static string BuildDuplicatedPathName(string originalName, DateTime? now = null)
|
||||
{
|
||||
var normalizedName = string.IsNullOrWhiteSpace(originalName) ? "路径" : originalName.Trim();
|
||||
var timestampPattern = new Regex(@"^(.*)_\d{4}_\d{6}$");
|
||||
var match = timestampPattern.Match(normalizedName);
|
||||
|
||||
if (!match.Success)
|
||||
{
|
||||
return $"副本_{normalizedName}";
|
||||
}
|
||||
|
||||
var baseName = match.Groups[1].Value;
|
||||
var timestamp = (now ?? DateTime.Now).ToString("MMdd_HHmmss");
|
||||
return $"副本_{baseName}_{timestamp}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算从HTML文件到目标文件的相对路径
|
||||
/// </summary>
|
||||
@ -203,4 +224,4 @@ namespace NavisworksTransport.Utils
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user