* 新增飞拍轨迹文件存储,支持上传、加载与删除 * 接通 ControllerClientCompat 到运行时的轨迹编排 * 完善 FANUC 命令与 J519 客户端发送链路 * 补充密集轨迹执行、运行时编排和协议客户端测试 * 更新 README 与 AGENTS 中的当前实现状态
177 lines
6.8 KiB
C#
177 lines
6.8 KiB
C#
using Flyshot.Core.Config;
|
|
using Flyshot.Core.Domain;
|
|
|
|
namespace Flyshot.Core.Tests;
|
|
|
|
/// <summary>
|
|
/// 锁定 Task 3 的兼容输入行为,确保旧配置、.robot 元数据和路径策略都能被稳定加载。
|
|
/// </summary>
|
|
public sealed class ConfigCompatibilityTests
|
|
{
|
|
/// <summary>
|
|
/// 验证现有 RobotConfig.json 能被加载,并保持关键机器人参数与飞拍程序内容不变。
|
|
/// </summary>
|
|
[Fact]
|
|
public void RobotConfigLoader_LoadsLegacyRobotConfig_AndPreservesPrograms()
|
|
{
|
|
var workspaceRoot = GetWorkspaceRoot();
|
|
var configPath = Path.Combine(workspaceRoot, "Rvbust", "EOL10_EAU_0", "RobotConfig.json");
|
|
|
|
var loaded = new RobotConfigLoader().Load(configPath);
|
|
|
|
Assert.True(loaded.Robot.UseDo);
|
|
Assert.Equal([7, 8], loaded.Robot.IoAddresses);
|
|
Assert.Equal(2, loaded.Robot.IoKeepCycles);
|
|
Assert.Equal(1.0, loaded.Robot.AccLimitScale);
|
|
Assert.Equal(1.0, loaded.Robot.JerkLimitScale);
|
|
Assert.Equal(5, loaded.Robot.AdaptIcspTryNum);
|
|
|
|
var program = Assert.Contains("EOL10_EAU_0", loaded.Programs);
|
|
Assert.Equal("EOL10_EAU_0", program.Name);
|
|
Assert.Equal(45, program.Waypoints.Count);
|
|
Assert.Equal(42, program.ShotWaypointCount);
|
|
Assert.Empty(program.AddressGroups[0].Addresses);
|
|
Assert.Equal([4, 3], program.AddressGroups[1].Addresses);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 验证旧配置缺少 offset_values 和 addr 字段时,会自动回填与旧系统一致的默认值。
|
|
/// </summary>
|
|
[Fact]
|
|
public void RobotConfigLoader_FillsLegacyDefaults_WhenOptionalFieldsAreMissing()
|
|
{
|
|
var tempRoot = CreateTempDirectory();
|
|
try
|
|
{
|
|
var configPath = Path.Combine(tempRoot, "legacy.json");
|
|
File.WriteAllText(
|
|
configPath,
|
|
"""
|
|
{
|
|
"robot": {
|
|
"use_do": false,
|
|
"io_keep_cycles": 3,
|
|
"acc_limit": 0.5,
|
|
"jerk_limit": 0.25
|
|
},
|
|
"flying_shots": {
|
|
"demo": {
|
|
"traj_waypoints": [[0, 1], [2, 3], [4, 5]],
|
|
"shot_flags": [0, 1, 0]
|
|
}
|
|
}
|
|
}
|
|
""");
|
|
|
|
var loaded = new RobotConfigLoader().Load(configPath);
|
|
var program = Assert.Contains("demo", loaded.Programs);
|
|
|
|
Assert.False(loaded.Robot.UseDo);
|
|
Assert.Empty(loaded.Robot.IoAddresses);
|
|
Assert.Equal(3, loaded.Robot.IoKeepCycles);
|
|
Assert.Equal(0.5, loaded.Robot.AccLimitScale);
|
|
Assert.Equal(0.25, loaded.Robot.JerkLimitScale);
|
|
Assert.Equal([0, 0, 0], program.OffsetValues);
|
|
Assert.All(program.AddressGroups, group => Assert.Empty(group.Addresses));
|
|
}
|
|
finally
|
|
{
|
|
Directory.Delete(tempRoot, recursive: true);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 验证 .robot 解析会保留 Joint3 对 Joint2 的 couple 元数据,并构造规划侧可直接消费的 RobotProfile。
|
|
/// </summary>
|
|
[Fact]
|
|
public void RobotModelLoader_LoadsRobotProfile_WithJointLimitsAndCoupling()
|
|
{
|
|
var workspaceRoot = GetWorkspaceRoot();
|
|
var modelPath = Path.Combine(workspaceRoot, "FlyingShot", "FlyingShot", "Models", "LR_Mate_200iD_7L.robot");
|
|
|
|
var profile = new RobotModelLoader().LoadProfile(modelPath);
|
|
|
|
Assert.Equal("FANUC_LR_Mate_200iD_7L", profile.Name);
|
|
Assert.Equal(modelPath, profile.ModelPath);
|
|
Assert.Equal(6, profile.DegreesOfFreedom);
|
|
Assert.Equal(6.45, profile.JointLimits[0].VelocityLimit, precision: 2);
|
|
Assert.Equal(29.81, profile.JointLimits[2].AccelerationLimit, precision: 2);
|
|
|
|
var coupling = Assert.Single(profile.JointCouplings);
|
|
Assert.Equal("Joint3", coupling.SlaveJointName);
|
|
Assert.Equal("Joint2", coupling.MasterJointName);
|
|
Assert.Equal(1.0, coupling.Multiplier);
|
|
Assert.Equal(0.0, coupling.Offset);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 验证 RobotConfig 中的 acc_limit 和 jerk_limit 乘子会正确叠加到模型关节限制上。
|
|
/// </summary>
|
|
[Fact]
|
|
public void RobotModelLoader_AppliesAccelerationAndJerkScales()
|
|
{
|
|
var workspaceRoot = GetWorkspaceRoot();
|
|
var modelPath = Path.Combine(workspaceRoot, "FlyingShot", "FlyingShot", "Models", "LR_Mate_200iD_7L.robot");
|
|
|
|
var profile = new RobotModelLoader().LoadProfile(modelPath, accLimitScale: 0.5, jerkLimitScale: 0.25);
|
|
|
|
Assert.Equal(14.905, profile.JointLimits[2].AccelerationLimit, precision: 3);
|
|
Assert.Equal(62.115, profile.JointLimits[2].JerkLimit, precision: 3);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 验证路径兼容层既能补旧目录候选,也能按平台策略生成默认用户数据目录。
|
|
/// </summary>
|
|
[Fact]
|
|
public void PathCompatibility_ResolvesLegacyCandidates_AndBuildsUserDataRoots()
|
|
{
|
|
var tempRoot = CreateTempDirectory();
|
|
try
|
|
{
|
|
var legacyConfigPath = Path.Combine(tempRoot, "Rvbust", "Install", "FlyingShot", "Config", "sample.json");
|
|
Directory.CreateDirectory(Path.GetDirectoryName(legacyConfigPath)!);
|
|
File.WriteAllText(legacyConfigPath, "{}");
|
|
|
|
var resolved = PathCompatibility.ResolveConfigPath("sample.json", tempRoot);
|
|
|
|
Assert.Equal(legacyConfigPath, resolved);
|
|
Assert.Equal("/home/tester/.Rvbust/Data", PathCompatibility.BuildUserDataRoot("/home/tester", CompatibilityPathStyle.Posix));
|
|
Assert.Equal(@"C:\Users\tester\.Rvbust\Data", PathCompatibility.BuildUserDataRoot(@"C:\Users\tester", CompatibilityPathStyle.Windows));
|
|
}
|
|
finally
|
|
{
|
|
Directory.Delete(tempRoot, recursive: true);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 定位当前工作区根目录,便于复用父仓库中的真实样本。
|
|
/// </summary>
|
|
private static string GetWorkspaceRoot()
|
|
{
|
|
var current = new DirectoryInfo(AppContext.BaseDirectory);
|
|
while (current is not null)
|
|
{
|
|
var slnPath = Path.Combine(current.FullName, "FlyshotReplacement.sln");
|
|
if (File.Exists(slnPath))
|
|
{
|
|
return Path.GetFullPath(Path.Combine(current.FullName, ".."));
|
|
}
|
|
|
|
current = current.Parent;
|
|
}
|
|
|
|
throw new DirectoryNotFoundException("Unable to locate the flyshot workspace root.");
|
|
}
|
|
|
|
/// <summary>
|
|
/// 创建当前测试专用的临时目录,避免不同测试之间相互污染。
|
|
/// </summary>
|
|
private static string CreateTempDirectory()
|
|
{
|
|
var tempPath = Path.Combine(Path.GetTempPath(), "flyshot-config-tests", Guid.NewGuid().ToString("N"));
|
|
Directory.CreateDirectory(tempPath);
|
|
return tempPath;
|
|
}
|
|
}
|