✨ feat(*): 添加轨迹产物导出与规划速度倍率隔离
* 新增 FlyshotTrajectoryArtifactWriter,支持 saveTrajectory 将规划结果导出到 Config/Data/name(JointTraj、CartTraj、 ShotEvents 等) * RobotConfig 新增 PlanningSpeedScale,区分规划阶段限速倍率 与运行时 J519 下发倍率 * 轨迹缓存键纳入 planningSpeedScale,避免降速规划误用缓存 * 完善 FanucCommandClient 命令参数日志与状态通道重连 * 补充 RuntimeOrchestrationTests 覆盖产物导出与倍率隔离 * 更新 README 进度文档
This commit is contained in:
@@ -136,6 +136,42 @@ public sealed class RuntimeOrchestrationTests
|
||||
Assert.Single(bundle.Result.TriggerTimeline);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证飞拍规划会把规划限速倍率纳入速度/加速度/Jerk 限制,而不是复用运行时下发倍率。
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ControllerClientTrajectoryOrchestrator_PlanUploadedFlyshot_AppliesPlanningSpeedScaleToLimits()
|
||||
{
|
||||
var orchestrator = new ControllerClientTrajectoryOrchestrator();
|
||||
var robot = TestRobotFactory.CreateRobotProfile();
|
||||
var uploaded = TestRobotFactory.CreateUploadedTrajectoryWithSingleShot();
|
||||
|
||||
var fullSpeed = orchestrator.PlanUploadedFlyshot(robot, uploaded, planningSpeedScale: 1.0);
|
||||
var halfSpeed = orchestrator.PlanUploadedFlyshot(robot, uploaded, planningSpeedScale: 0.5);
|
||||
|
||||
Assert.True(
|
||||
halfSpeed.Result.Duration.TotalSeconds > fullSpeed.Result.Duration.TotalSeconds * 1.9,
|
||||
$"半速规划时长应接近全速的 2 倍,实际 full={fullSpeed.Result.Duration.TotalSeconds}, half={halfSpeed.Result.Duration.TotalSeconds}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证飞拍缓存键包含规划限速倍率,避免降速验证时误用 100% 速度下的规划结果。
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ControllerClientTrajectoryOrchestrator_PlanUploadedFlyshot_CacheKeyIncludesPlanningSpeedScale()
|
||||
{
|
||||
var orchestrator = new ControllerClientTrajectoryOrchestrator();
|
||||
var robot = TestRobotFactory.CreateRobotProfile();
|
||||
var uploaded = TestRobotFactory.CreateUploadedTrajectoryWithSingleShot();
|
||||
var options = new FlyshotExecutionOptions(useCache: true);
|
||||
|
||||
var fullSpeed = orchestrator.PlanUploadedFlyshot(robot, uploaded, options, planningSpeedScale: 1.0);
|
||||
var halfSpeed = orchestrator.PlanUploadedFlyshot(robot, uploaded, options, planningSpeedScale: 0.5);
|
||||
|
||||
Assert.False(halfSpeed.Result.UsedCache);
|
||||
Assert.True(halfSpeed.Result.Duration > fullSpeed.Result.Duration);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证飞拍编排会使用 RobotConfig.json 中的 IO 保持周期。
|
||||
/// </summary>
|
||||
@@ -289,8 +325,7 @@ public sealed class RuntimeOrchestrationTests
|
||||
new ControllerClientCompatRobotCatalog(options, new RobotModelLoader()),
|
||||
runtime,
|
||||
new ControllerClientTrajectoryOrchestrator(),
|
||||
new RobotConfigLoader(),
|
||||
new InMemoryFlyshotTrajectoryStore());
|
||||
new RobotConfigLoader());
|
||||
|
||||
service.SetUpRobot("FANUC_LR_Mate_200iD");
|
||||
|
||||
@@ -304,6 +339,49 @@ public sealed class RuntimeOrchestrationTests
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证 IsFlyshotTrajectoryValid(saveTrajectory=true) 会把规划后的结果点位导出到 Config/Data/name。
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ControllerClientCompatService_IsFlyshotTrajectoryValid_SaveTrajectoryExportsPlannedData()
|
||||
{
|
||||
var configRoot = CreateTempConfigRoot();
|
||||
try
|
||||
{
|
||||
WriteRobotConfigWithDemoTrajectory(configRoot);
|
||||
var options = new ControllerClientCompatOptions { ConfigRoot = configRoot };
|
||||
var service = new ControllerClientCompatService(
|
||||
options,
|
||||
new ControllerClientCompatRobotCatalog(options, new RobotModelLoader()),
|
||||
new RecordingControllerRuntime(),
|
||||
new ControllerClientTrajectoryOrchestrator(),
|
||||
new RobotConfigLoader());
|
||||
|
||||
service.SetUpRobot("FANUC_LR_Mate_200iD");
|
||||
|
||||
var valid = service.IsFlyshotTrajectoryValid(
|
||||
out var duration,
|
||||
"demo-flyshot",
|
||||
method: "icsp",
|
||||
saveTrajectory: true);
|
||||
|
||||
var outputDir = Path.Combine(configRoot, "Data", "demo-flyshot");
|
||||
Assert.True(valid);
|
||||
Assert.True(duration > TimeSpan.Zero);
|
||||
Assert.True(File.Exists(Path.Combine(outputDir, "JointTraj.txt")));
|
||||
Assert.True(File.Exists(Path.Combine(outputDir, "JointDetialTraj.txt")));
|
||||
Assert.True(File.Exists(Path.Combine(outputDir, "CartTraj.txt")));
|
||||
Assert.True(File.Exists(Path.Combine(outputDir, "CartDetialTraj.txt")));
|
||||
Assert.True(File.Exists(Path.Combine(outputDir, "ShotEvents.json")));
|
||||
Assert.NotEmpty(File.ReadAllLines(Path.Combine(outputDir, "JointDetialTraj.txt")));
|
||||
Assert.NotEmpty(File.ReadAllLines(Path.Combine(outputDir, "CartDetialTraj.txt")));
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(configRoot, recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建只包含当前支持机器人模型和 RobotConfig.json 的临时运行配置根。
|
||||
/// </summary>
|
||||
@@ -322,6 +400,41 @@ public sealed class RuntimeOrchestrationTests
|
||||
|
||||
return configRoot;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 写入包含一条飞拍轨迹的最小 RobotConfig.json,供兼容服务从统一配置恢复轨迹。
|
||||
/// </summary>
|
||||
/// <param name="configRoot">测试运行配置根。</param>
|
||||
private static void WriteRobotConfigWithDemoTrajectory(string configRoot)
|
||||
{
|
||||
File.WriteAllText(
|
||||
Path.Combine(configRoot, "RobotConfig.json"),
|
||||
"""
|
||||
{
|
||||
"robot": {
|
||||
"use_do": true,
|
||||
"io_addr": [7, 8],
|
||||
"io_keep_cycles": 2,
|
||||
"acc_limit": 1.0,
|
||||
"jerk_limit": 1.0,
|
||||
"adapt_icsp_try_num": 5
|
||||
},
|
||||
"flying_shots": {
|
||||
"demo-flyshot": {
|
||||
"traj_waypoints": [
|
||||
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
|
||||
[0.1, 0.0, 0.0, 0.0, 0.0, 0.0],
|
||||
[0.2, 0.0, 0.0, 0.0, 0.0, 0.0],
|
||||
[0.3, 0.0, 0.0, 0.0, 0.0, 0.0]
|
||||
],
|
||||
"shot_flags": [false, true, false, false],
|
||||
"offset_values": [0, 1, 0, 0],
|
||||
"addr": [[], [7, 8], [], []]
|
||||
}
|
||||
}
|
||||
}
|
||||
""");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -389,8 +502,7 @@ internal static class TestRobotFactory
|
||||
new ControllerClientCompatRobotCatalog(options, new RobotModelLoader()),
|
||||
new FanucControllerRuntime(),
|
||||
new ControllerClientTrajectoryOrchestrator(),
|
||||
new RobotConfigLoader(),
|
||||
new InMemoryFlyshotTrajectoryStore());
|
||||
new RobotConfigLoader());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -443,33 +555,6 @@ internal static class TestRobotFactory
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 内存中的轨迹存储实现,用于避免单元测试污染真实文件系统。
|
||||
/// </summary>
|
||||
internal sealed class InMemoryFlyshotTrajectoryStore : IFlyshotTrajectoryStore
|
||||
{
|
||||
private readonly Dictionary<string, ControllerClientCompatUploadedTrajectory> _store = new(StringComparer.Ordinal);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Save(string robotName, CompatibilityRobotSettings settings, ControllerClientCompatUploadedTrajectory trajectory)
|
||||
{
|
||||
_store[trajectory.Name] = trajectory;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Delete(string robotName, string trajectoryName)
|
||||
{
|
||||
_store.Remove(trajectoryName);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyDictionary<string, ControllerClientCompatUploadedTrajectory> LoadAll(string robotName, out CompatibilityRobotSettings? settings)
|
||||
{
|
||||
settings = null;
|
||||
return _store;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 记录 ResetRobot 入参的测试运行时,用于验证兼容服务传递的机器人配置。
|
||||
/// </summary>
|
||||
|
||||
Reference in New Issue
Block a user