diff --git a/FlyshotReplacement.sln b/FlyshotReplacement.sln index 3e115ad..21dddc0 100644 --- a/FlyshotReplacement.sln +++ b/FlyshotReplacement.sln @@ -1,4 +1,3 @@ - Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.0.31903.59 @@ -27,8 +26,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Flyshot.ControllerClientCom EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Flyshot.Runtime.Fanuc", "src\Flyshot.Runtime.Fanuc\Flyshot.Runtime.Fanuc.csproj", "{B705FA6C-19CA-44A8-882C-6CE26A5379C9}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Flyshot.Native.Planning", "src\Flyshot.Native.Planning\Flyshot.Native.Planning.csproj", "{928CF396-C0E1-488E-BCC1-CF0279CF2DD0}" -EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Flyshot.Native.Planning", "src\Flyshot.Native.Planning\Flyshot.Native.Planning.csproj", "{928CF396-C0E1-488E-BCC1-CF0279CF2DD0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Flyshot.CSharp.Planning", "src\Flyshot.CSharp.Planning\Flyshot.CSharp.Planning.csproj", "{4717558C-0A09-4D4D-BB1A-C0FD9241070E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -159,18 +160,30 @@ Global {B705FA6C-19CA-44A8-882C-6CE26A5379C9}.Release|x64.Build.0 = Release|Any CPU {B705FA6C-19CA-44A8-882C-6CE26A5379C9}.Release|x86.ActiveCfg = Release|Any CPU {B705FA6C-19CA-44A8-882C-6CE26A5379C9}.Release|x86.Build.0 = Release|Any CPU - {928CF396-C0E1-488E-BCC1-CF0279CF2DD0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {928CF396-C0E1-488E-BCC1-CF0279CF2DD0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {928CF396-C0E1-488E-BCC1-CF0279CF2DD0}.Debug|x64.ActiveCfg = Debug|Any CPU - {928CF396-C0E1-488E-BCC1-CF0279CF2DD0}.Debug|x64.Build.0 = Debug|Any CPU - {928CF396-C0E1-488E-BCC1-CF0279CF2DD0}.Debug|x86.ActiveCfg = Debug|Any CPU - {928CF396-C0E1-488E-BCC1-CF0279CF2DD0}.Debug|x86.Build.0 = Debug|Any CPU - {928CF396-C0E1-488E-BCC1-CF0279CF2DD0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {928CF396-C0E1-488E-BCC1-CF0279CF2DD0}.Release|Any CPU.Build.0 = Release|Any CPU - {928CF396-C0E1-488E-BCC1-CF0279CF2DD0}.Release|x64.ActiveCfg = Release|Any CPU - {928CF396-C0E1-488E-BCC1-CF0279CF2DD0}.Release|x64.Build.0 = Release|Any CPU - {928CF396-C0E1-488E-BCC1-CF0279CF2DD0}.Release|x86.ActiveCfg = Release|Any CPU - {928CF396-C0E1-488E-BCC1-CF0279CF2DD0}.Release|x86.Build.0 = Release|Any CPU + {928CF396-C0E1-488E-BCC1-CF0279CF2DD0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {928CF396-C0E1-488E-BCC1-CF0279CF2DD0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {928CF396-C0E1-488E-BCC1-CF0279CF2DD0}.Debug|x64.ActiveCfg = Debug|Any CPU + {928CF396-C0E1-488E-BCC1-CF0279CF2DD0}.Debug|x64.Build.0 = Debug|Any CPU + {928CF396-C0E1-488E-BCC1-CF0279CF2DD0}.Debug|x86.ActiveCfg = Debug|Any CPU + {928CF396-C0E1-488E-BCC1-CF0279CF2DD0}.Debug|x86.Build.0 = Debug|Any CPU + {928CF396-C0E1-488E-BCC1-CF0279CF2DD0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {928CF396-C0E1-488E-BCC1-CF0279CF2DD0}.Release|Any CPU.Build.0 = Release|Any CPU + {928CF396-C0E1-488E-BCC1-CF0279CF2DD0}.Release|x64.ActiveCfg = Release|Any CPU + {928CF396-C0E1-488E-BCC1-CF0279CF2DD0}.Release|x64.Build.0 = Release|Any CPU + {928CF396-C0E1-488E-BCC1-CF0279CF2DD0}.Release|x86.ActiveCfg = Release|Any CPU + {928CF396-C0E1-488E-BCC1-CF0279CF2DD0}.Release|x86.Build.0 = Release|Any CPU + {4717558C-0A09-4D4D-BB1A-C0FD9241070E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4717558C-0A09-4D4D-BB1A-C0FD9241070E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4717558C-0A09-4D4D-BB1A-C0FD9241070E}.Debug|x64.ActiveCfg = Debug|Any CPU + {4717558C-0A09-4D4D-BB1A-C0FD9241070E}.Debug|x64.Build.0 = Debug|Any CPU + {4717558C-0A09-4D4D-BB1A-C0FD9241070E}.Debug|x86.ActiveCfg = Debug|Any CPU + {4717558C-0A09-4D4D-BB1A-C0FD9241070E}.Debug|x86.Build.0 = Debug|Any CPU + {4717558C-0A09-4D4D-BB1A-C0FD9241070E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4717558C-0A09-4D4D-BB1A-C0FD9241070E}.Release|Any CPU.Build.0 = Release|Any CPU + {4717558C-0A09-4D4D-BB1A-C0FD9241070E}.Release|x64.ActiveCfg = Release|Any CPU + {4717558C-0A09-4D4D-BB1A-C0FD9241070E}.Release|x64.Build.0 = Release|Any CPU + {4717558C-0A09-4D4D-BB1A-C0FD9241070E}.Release|x86.ActiveCfg = Release|Any CPU + {4717558C-0A09-4D4D-BB1A-C0FD9241070E}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -186,6 +199,7 @@ Global {E4DDC34C-9AB6-4050-A927-3DF69804708A} = {64EABE09-B1E0-4476-A213-32C93E46E7C3} {5B45CC23-3551-4D0F-B3CC-22659C2A8BA3} = {64EABE09-B1E0-4476-A213-32C93E46E7C3} {B705FA6C-19CA-44A8-882C-6CE26A5379C9} = {64EABE09-B1E0-4476-A213-32C93E46E7C3} - {928CF396-C0E1-488E-BCC1-CF0279CF2DD0} = {64EABE09-B1E0-4476-A213-32C93E46E7C3} + {928CF396-C0E1-488E-BCC1-CF0279CF2DD0} = {64EABE09-B1E0-4476-A213-32C93E46E7C3} + {4717558C-0A09-4D4D-BB1A-C0FD9241070E} = {64EABE09-B1E0-4476-A213-32C93E46E7C3} EndGlobalSection EndGlobal diff --git a/README.md b/README.md index ffac5d3..d808492 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,8 @@ - 当前仓库不再恢复旧 `50001/TCP+JSON` 监听入口;旧 `ControllerClient` 逆向资料只作为接口语义参考,不作为运行时目标。 - 宿主只保留 ASP.NET Core HTTP 控制器层,以及其后端 `Flyshot.ControllerClientCompat` 兼容服务。 - `ExecuteTrajectory` 与 `ExecuteFlyShotTraj` 已经接入 `Planning + Triggering + Runtime` 链路;Web 状态页已通过 `/status` 和 `/api/status/snapshot` 暴露当前兼容层与运行时状态。 -- `Flyshot.Core.Planning` 的 ICSP / self-adapt-icsp 轨迹已经完成旧系统导出轨迹对齐;`doubles` 仍未实现。 -- `Flyshot.Native.Planning` 已提供显式 Rust ICSP 规划入口,通过 C ABI 调用 `flyshot_planning`;普通轨迹和飞拍轨迹可使用准确参数 `method="icsp_rust"` 选择该后端,默认 `icsp` 仍使用 C# `ICspPlanner`。 +- `Flyshot.Core.Planning` 保留规划公共模型、样条、采样、导出和运动学能力;C# ICSP / self-adapt-icsp 已拆分到可选项目 `Flyshot.CSharp.Planning`。 +- `Flyshot.Native.Planning` 已提供默认 Rust ICSP 规划入口,通过 C ABI 调用 `flyshot_planning`;普通轨迹和飞拍轨迹的默认 `method="icsp"` 使用 Rust 后端,`method="icsp-csharp"` 和 `method="self-adapt-icsp"` 只有显式注册 C# 后端时可用。 - `Flyshot.Runtime.Fanuc` 已固化 `10010 / 10012 / 60015` 基础协议帧编解码。`10010` 状态通道以 `j519 协议.pcap` 和 `Rvbust/uttc-20260428/20260428.pcap` 真机抓包确认为 90B 固定帧。 - 2026-04-28 UTTC 抓包确认:UDP 60015 命令 `target[0..5]` 为关节角度制 `deg`,`JointDetialTraj.txt` 为弧度制 `rad`,`speed_ratio=0.7` 体现为 UDP 下发时间轴约 `1.427730x` 拉伸;2026-04-30 实体机确认 `speed_ratio` 不影响生成的 `JointTraj.txt` 规划时长,当前实际生成约 `7.4s` 轨迹。 - 2026-04-30 本机 `50001/TCP+JSON` 抓包确认:`ExecuteFlyShotTraj(save_traj=true,use_cache=false)` 请求只显式携带规划方法、保存、缓存和等待参数,不携带 `JointLimits / acc_limit / jerk_limit / velocity / acceleration / jerk`。因此旧系统不可见的有效规划限制不再继续假设来自公开链路,新系统按 replacement-only 内部参数限制规划加速度。 @@ -65,7 +65,7 @@ - [x] 打通最小宿主与 `/healthz` - [x] 建立领域模型与模块边界 - [x] 落地配置兼容与机器人模型解析 -- [x] 落地 ICSP / self-adapt-icsp 轨迹规划与飞拍触发时间轴 +- [x] 落地 Rust ICSP、可选 C# ICSP / self-adapt-icsp 轨迹规划与飞拍触发时间轴 - [x] 完成 ICSP 轨迹导出结果与旧系统对齐 - [x] 增加 Rust `cdylib` ICSP 核心、C# P/Invoke 适配和 C#/Rust 自动对拍 - [x] 将 `ExecuteTrajectory` / `ExecuteFlyShotTraj` 接入 FANUC 运行时链路 @@ -81,9 +81,11 @@ - [x] 完成真机优先与离线飞拍规划改造:`RealReady / OfflineReady / ModelLoadFailed` 状态、固定轨迹资产、完整离线规划响应和 Debug 页面 - [x] 删除生产仿真切换 HTTP 入口及旧 `/init_mpc_robt`,统一使用 `/init_mpc_robot` -### Rust ICSP 原生库构建 +### 规划后端与 Rust ICSP 原生库构建 -Rust 规划器通过普通轨迹或飞拍轨迹的准确参数 `method="icsp_rust"` 显式选择,不替换默认 `icsp` 使用的 C# `ICspPlanner`,也不会在原生库缺失时静默回退。 +普通轨迹或飞拍轨迹的默认参数 `method="icsp"` 会调用 Rust ICSP 后端。旧 `method="icsp_rust"` 已不再作为正式入口,调用时会提示改用 `method="icsp"`;Rust 原生库缺失时会明确报错,不会静默回退 C#。 + +C# `ICspPlanner` 与 `SelfAdaptIcspPlanner` 已拆分到 `Flyshot.CSharp.Planning.dll`。默认服务项目不引用该 DLL;只有在部署方显式引用并调用 `AddCSharpPlanningBackends()` 后,`method="icsp-csharp"` 与 `method="self-adapt-icsp"` 才可用。 Windows x64 需要安装: diff --git a/docs/controller-client-api-compatibility-requirements.md b/docs/controller-client-api-compatibility-requirements.md index 9078c23..792849b 100644 --- a/docs/controller-client-api-compatibility-requirements.md +++ b/docs/controller-client-api-compatibility-requirements.md @@ -130,11 +130,11 @@ 以下内容是当前新实现的状态,不反推为旧系统事实: - `Flyshot.ControllerClientCompat` 继续作为 HTTP 控制器后端兼容服务,不启动 `50001/TCP+JSON` 监听。 -- `ExecuteTrajectory` 会先通过 `ICspPlanner` 规划普通轨迹,再把 `TrajectoryResult` 和最终关节位置交给 `IControllerRuntime`。 -- `ExecuteFlyShotTraj` 会从上传轨迹目录取出轨迹,通过 `SelfAdaptIcspPlanner` 规划并用 `ShotTimelineBuilder` 生成 `ShotEvent` / `TrajectoryDoEvent`。 +- `ExecuteTrajectory` 会先通过默认 Rust `method="icsp"` 后端规划普通轨迹,再把 `TrajectoryResult` 和最终关节位置交给 `IControllerRuntime`。 +- `ExecuteFlyShotTraj` 会从上传轨迹目录取出轨迹,通过已注册的规划后端生成轨迹,并用 `ShotTimelineBuilder` 生成 `ShotEvent` / `TrajectoryDoEvent`。 - HTTP 控制器已经按公开文档和抓包补齐 `ExecuteTrajectory(method, save_traj)` 与 `ExecuteFlyShotTraj(move_to_start, method, save_traj, use_cache, wait)` 参数,并继续兼容旧的裸 waypoint 数组和只传 `name` 的请求体。 - 规划阶段会继续消费旧配置中的 `acc_limit / jerk_limit`。如果现场需要复现旧服务端不可见的保守约束,replacement 设计上使用内部 `planning_acceleration_scale` 限制规划加速度;该字段不属于旧 `ControllerClient` 公开 API,也不会通过 `50001` JSON 下发。 -- `method="icsp"` 与 `method="self-adapt-icsp"` 已接入当前规划器;`method="doubles"` 会被识别但返回显式未实现,不会静默降级成 ICSP。 +- `method="icsp"` 已接入默认 Rust 后端;`method="icsp-csharp"` 与 `method="self-adapt-icsp"` 只有显式注册 `Flyshot.CSharp.Planning` 后端时可用;`method="doubles"` 会被识别但返回显式未实现,不会静默降级成 ICSP。 - `Flyshot.Runtime.Fanuc.Protocol` 已经固化 `10010` 状态帧、`10012` 命令帧和 `60015` J519 数据包的基础编解码,并使用逆向抓包样本覆盖最小测试;`10010` 当前现场确认固定 90B。 - `Flyshot.Runtime.Fanuc` 已具备基础 Socket 客户端、程序启停、速度倍率/TCP/IO 参数命令和 J519 状态包驱动发送链路;稠密轨迹下发已按 `speed_ratio` 推进轨迹时间,并在收到机器人 UDP status 后按该 status sequence 回发命令。真实 R30iB 全流程现场联调仍需执行。 - 2026-04-28 `UTTC_MS11` 抓包确认 J519 命令目标为 `deg`、导出 `JointDetialTraj.txt` 为 `rad`,`speed_ratio=0.5/0.7/1.0` 分别形成 `1851/1322/926` 个主运行 J519 包;实际执行不发送 464 行导出点,而是在 8ms 实发周期上按 `t_traj = t_send * speed_ratio` 采样,包数为 `floor(duration / (0.008 * speed_ratio)) + 1`。 diff --git a/scripts/Publish-Obfuscated.ps1 b/scripts/Publish-Obfuscated.ps1 index 9d4f9b2..bd63735 100644 --- a/scripts/Publish-Obfuscated.ps1 +++ b/scripts/Publish-Obfuscated.ps1 @@ -428,7 +428,7 @@ function Install-ObfuscatedAssemblies } | Remove-Item -Force - # Rust ICSP 原生库必须随框架依赖包一起交付,否则 method=icsp_rust 会在运行时加载失败。 + # Rust ICSP 原生库必须随框架依赖包一起交付,否则默认 method=icsp 会在运行时加载失败。 $nativePlanningLibraryPath = Join-Path $PublishedDirectory 'flyshot_planning.dll' if (-not (Test-Path -LiteralPath $nativePlanningLibraryPath -PathType Leaf)) { diff --git a/src/Flyshot.CSharp.Planning/CSharpIcspPlanningBackend.cs b/src/Flyshot.CSharp.Planning/CSharpIcspPlanningBackend.cs new file mode 100644 index 0000000..086c2e7 --- /dev/null +++ b/src/Flyshot.CSharp.Planning/CSharpIcspPlanningBackend.cs @@ -0,0 +1,44 @@ +using Flyshot.Core.Domain; +using Flyshot.Core.Planning; +using Microsoft.Extensions.Logging; + +namespace Flyshot.CSharp.Planning; + +/// +/// 将拆分后的 C# ICSP 规划器暴露为可选规划后端。 +/// +public sealed class CSharpIcspPlanningBackend : IPlanningBackend +{ + private readonly ICspPlanner _planner; + + /// + /// 初始化 C# ICSP 后端。 + /// + /// C# ICSP 规划器日志记录器;允许为空。 + public CSharpIcspPlanningBackend(ILogger? logger = null) + { + _planner = new ICspPlanner(logger: logger); + } + + /// + /// 获取外部调用 C# ICSP 后端时使用的准确方法名。 + /// + public string MethodName => "icsp-csharp"; + + /// + /// 获取 C# ICSP 后端写入结果对象的规划方法。 + /// + public PlanningMethod ResultMethod => PlanningMethod.IcspCSharp; + + /// + /// 执行 C# ICSP 规划,并将结果标记为 C# 后端。 + /// + /// 包含机器人约束和示教路点的规划请求。 + /// 规划上下文;普通 C# ICSP 当前不使用该参数。 + /// 由 C# ICSP 规划器生成的轨迹结果。 + public PlannedTrajectory Plan(TrajectoryRequest request, PlanningBackendContext context) + { + ArgumentNullException.ThrowIfNull(context); + return _planner.Plan(request); + } +} diff --git a/src/Flyshot.CSharp.Planning/CSharpPlanningServiceCollectionExtensions.cs b/src/Flyshot.CSharp.Planning/CSharpPlanningServiceCollectionExtensions.cs new file mode 100644 index 0000000..3b9c10b --- /dev/null +++ b/src/Flyshot.CSharp.Planning/CSharpPlanningServiceCollectionExtensions.cs @@ -0,0 +1,24 @@ +using Flyshot.Core.Planning; +using Microsoft.Extensions.DependencyInjection; + +namespace Flyshot.CSharp.Planning; + +/// +/// 提供可选 C# 规划算法后端的依赖注入注册入口。 +/// +public static class CSharpPlanningServiceCollectionExtensions +{ + /// + /// 显式注册 C# ICSP 与 self-adapt-icsp 后端。 + /// + /// 当前宿主服务集合。 + /// 同一服务集合,便于链式调用。 + public static IServiceCollection AddCSharpPlanningBackends(this IServiceCollection services) + { + ArgumentNullException.ThrowIfNull(services); + + services.AddSingleton(); + services.AddSingleton(); + return services; + } +} diff --git a/src/Flyshot.CSharp.Planning/Flyshot.CSharp.Planning.csproj b/src/Flyshot.CSharp.Planning/Flyshot.CSharp.Planning.csproj new file mode 100644 index 0000000..bcad611 --- /dev/null +++ b/src/Flyshot.CSharp.Planning/Flyshot.CSharp.Planning.csproj @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/Flyshot.Core.Planning/ICSP-算法说明.md b/src/Flyshot.CSharp.Planning/ICSP-算法说明.md similarity index 94% rename from src/Flyshot.Core.Planning/ICSP-算法说明.md rename to src/Flyshot.CSharp.Planning/ICSP-算法说明.md index 4424737..3c5cdf8 100644 --- a/src/Flyshot.Core.Planning/ICSP-算法说明.md +++ b/src/Flyshot.CSharp.Planning/ICSP-算法说明.md @@ -1,6 +1,6 @@ # ICSP 算法说明(`ICspPlanner`) -本文档用于解释 `Flyshot.Core.Planning.ICspPlanner` 当前实现的 **ICSP 规划算法**在本仓库中的真实含义与计算逻辑,便于与逆向结论对照、以及指导后续改造(例如“按约束生成中间点位”)。 +本文档用于解释 `Flyshot.CSharp.Planning.ICspPlanner` 当前实现的 **C# ICSP 规划算法**在本仓库中的真实含义与计算逻辑,便于与逆向结论对照、以及指导后续改造(例如“按约束生成中间点位”)。 > 适用范围:本文描述的是当前 C# 实现的 **“CubicSpline + 逐段时间缩放迭代(retiming)”** 版本。 > 重要澄清:`ICspPlanner` 的主要输出是 **时间轴**(每个示教点的时间戳),而不是直接输出固定周期的稠密点序列;稠密点在后续采样层生成。 @@ -186,9 +186,8 @@ globalScale > 1 + \text{finalScaleTolerance} ## 7. 关联实现位置(便于跳转) -- 算法入口:`src/Flyshot.Core.Planning/ICspPlanner.cs` -- 自适应补点:`src/Flyshot.Core.Planning/SelfAdaptIcspPlanner.cs` +- 算法入口:`src/Flyshot.CSharp.Planning/ICspPlanner.cs` +- 自适应补点:`src/Flyshot.CSharp.Planning/SelfAdaptIcspPlanner.cs` - 三次样条实现(clamped-zero + 解析导峰值):`src/Flyshot.Core.Planning/CubicSplineInterpolator.cs` - 规划层稠密采样:`src/Flyshot.Core.Planning/Sampling/TrajectorySampler.cs` - J519 实发重采样:`src/Flyshot.Core.Planning/Sampling/J519SendTrajectorySampler.cs` - diff --git a/src/Flyshot.Core.Planning/ICspPlanner.cs b/src/Flyshot.CSharp.Planning/ICspPlanner.cs similarity index 98% rename from src/Flyshot.Core.Planning/ICspPlanner.cs rename to src/Flyshot.CSharp.Planning/ICspPlanner.cs index 65f571a..527c6ad 100644 --- a/src/Flyshot.Core.Planning/ICspPlanner.cs +++ b/src/Flyshot.CSharp.Planning/ICspPlanner.cs @@ -1,7 +1,8 @@ using Flyshot.Core.Domain; +using Flyshot.Core.Planning; using Microsoft.Extensions.Logging; -namespace Flyshot.Core.Planning; +namespace Flyshot.CSharp.Planning; /// /// 基于 CubicSpline + 逐段时间缩放迭代的 ICSP 规划器。 @@ -196,7 +197,7 @@ public sealed class ICspPlanner waypointTimes: bestWaypointTimes, segmentDurations: bestDurations, segmentScales: bestScales, - method: PlanningMethod.Icsp, + method: PlanningMethod.IcspCSharp, iterations: bestIterations, threshold: bestThreshold); } diff --git a/src/Flyshot.Core.Planning/SelfAdaptIcspPlanner.cs b/src/Flyshot.CSharp.Planning/SelfAdaptIcspPlanner.cs similarity index 97% rename from src/Flyshot.Core.Planning/SelfAdaptIcspPlanner.cs rename to src/Flyshot.CSharp.Planning/SelfAdaptIcspPlanner.cs index aef327a..f2a673e 100644 --- a/src/Flyshot.Core.Planning/SelfAdaptIcspPlanner.cs +++ b/src/Flyshot.CSharp.Planning/SelfAdaptIcspPlanner.cs @@ -1,7 +1,8 @@ -using Flyshot.Core.Domain; -using Microsoft.Extensions.Logging; - -namespace Flyshot.Core.Planning; +using Flyshot.Core.Domain; +using Flyshot.Core.Planning; +using Microsoft.Extensions.Logging; + +namespace Flyshot.CSharp.Planning; /// /// 在 ICSP 外层包裹补中点策略,实现 self-adapt-icsp 行为。 diff --git a/src/Flyshot.CSharp.Planning/SelfAdaptIcspPlanningBackend.cs b/src/Flyshot.CSharp.Planning/SelfAdaptIcspPlanningBackend.cs new file mode 100644 index 0000000..0f54112 --- /dev/null +++ b/src/Flyshot.CSharp.Planning/SelfAdaptIcspPlanningBackend.cs @@ -0,0 +1,44 @@ +using Flyshot.Core.Domain; +using Flyshot.Core.Planning; +using Microsoft.Extensions.Logging; + +namespace Flyshot.CSharp.Planning; + +/// +/// 将 C# self-adapt-icsp 规划器暴露为可选规划后端。 +/// +public sealed class SelfAdaptIcspPlanningBackend : IPlanningBackend +{ + private readonly SelfAdaptIcspPlanner _planner; + + /// + /// 初始化 C# self-adapt-icsp 后端。 + /// + /// self-adapt-icsp 规划器日志记录器;允许为空。 + public SelfAdaptIcspPlanningBackend(ILogger? logger = null) + { + _planner = new SelfAdaptIcspPlanner(logger); + } + + /// + /// 获取外部调用 self-adapt-icsp 后端时使用的准确方法名。 + /// + public string MethodName => "self-adapt-icsp"; + + /// + /// 获取 self-adapt-icsp 后端写入结果对象的规划方法。 + /// + public PlanningMethod ResultMethod => PlanningMethod.SelfAdaptIcsp; + + /// + /// 执行 C# 自适应 ICSP 规划,按上下文使用配置中的补点次数。 + /// + /// 包含机器人约束、示教路点和触发元数据的规划请求。 + /// 包含 self-adapt-icsp 最大补点次数的规划上下文。 + /// 由 self-adapt-icsp 规划器生成的轨迹结果。 + public PlannedTrajectory Plan(TrajectoryRequest request, PlanningBackendContext context) + { + ArgumentNullException.ThrowIfNull(context); + return _planner.Plan(request, context.AdaptIcspTryNum); + } +} diff --git a/src/Flyshot.ControllerClientCompat/ControllerClientCompatServiceCollectionExtensions.cs b/src/Flyshot.ControllerClientCompat/ControllerClientCompatServiceCollectionExtensions.cs index fc09530..4491c2f 100644 --- a/src/Flyshot.ControllerClientCompat/ControllerClientCompatServiceCollectionExtensions.cs +++ b/src/Flyshot.ControllerClientCompat/ControllerClientCompatServiceCollectionExtensions.cs @@ -1,4 +1,6 @@ using Flyshot.Core.Config; +using Flyshot.Core.Planning; +using Flyshot.Native.Planning; using Flyshot.Runtime.Common; using Flyshot.Runtime.Fanuc; using Microsoft.Extensions.Configuration; @@ -31,6 +33,7 @@ public static class ControllerClientCompatServiceCollectionExtensions services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/Flyshot.ControllerClientCompat/ControllerClientTrajectoryOrchestrator.cs b/src/Flyshot.ControllerClientCompat/ControllerClientTrajectoryOrchestrator.cs index 3837eef..eb6abcc 100644 --- a/src/Flyshot.ControllerClientCompat/ControllerClientTrajectoryOrchestrator.cs +++ b/src/Flyshot.ControllerClientCompat/ControllerClientTrajectoryOrchestrator.cs @@ -29,9 +29,7 @@ public sealed class ControllerClientTrajectoryOrchestrator /// private const double SmoothStartStopBlend = 0.60; - private readonly ICspPlanner _icspPlanner; - private readonly RustIcspPlanner _rustIcspPlanner; - private readonly SelfAdaptIcspPlanner _selfAdaptIcspPlanner; + private readonly IReadOnlyDictionary _planningBackends; private readonly ShotTimelineBuilder _shotTimelineBuilder = new(new WaypointTimestampResolver()); private readonly Dictionary _flyshotCache = new(StringComparer.Ordinal); private readonly ILogger? _logger; @@ -39,16 +37,16 @@ public sealed class ControllerClientTrajectoryOrchestrator /// /// 初始化轨迹编排器。 /// + /// 显式注册的规划后端;为空时仅启用默认 Rust icsp 后端。 /// 日志记录器;允许 null。 /// 日志工厂;允许 null。 public ControllerClientTrajectoryOrchestrator( + IEnumerable? planningBackends = null, ILogger? logger = null, ILoggerFactory? loggerFactory = null) { _logger = logger; - _icspPlanner = new(logger: loggerFactory?.CreateLogger()); - _rustIcspPlanner = new(); - _selfAdaptIcspPlanner = new(logger: loggerFactory?.CreateLogger()); + _planningBackends = BuildPlanningBackendMap(planningBackends); } /// @@ -84,14 +82,15 @@ public sealed class ControllerClientTrajectoryOrchestrator offsetValues: Enumerable.Repeat(0, waypoints.Count), addressGroups: Enumerable.Range(0, waypoints.Count).Select(static _ => Array.Empty())); - var method = ParseOrdinaryMethod(options.Method); + var methodName = ParseOrdinaryMethod(options.Method); + var backend = ResolvePlanningBackend(methodName); var request = new TrajectoryRequest( robot: planningRobot, program: program, - method: method, + method: backend.ResultMethod, saveTrajectoryArtifacts: options.SaveTrajectory); - var plannedTrajectory = PlanByMethod(request, method); + var plannedTrajectory = PlanByBackend(request, backend); var executionTrajectory = plannedTrajectory; var denseJointTrajectory = CreateLimitCompliantDenseTrajectory(ref executionTrajectory, shapeTrajectoryEdges: false); var shotTimeline = new ShotTimeline(Array.Empty(), Array.Empty()); @@ -155,7 +154,8 @@ public sealed class ControllerClientTrajectoryOrchestrator offsetValues: uploaded.OffsetValues, addressGroups: uploaded.AddressGroups); - var method = ParseFlyshotMethod(options.Method); + var methodName = ParseFlyshotMethod(options.Method); + var backend = ResolvePlanningBackend(methodName); var cacheKey = CreateFlyshotCacheKey(planningRobot, uploaded, options, settings, effectivePlanningSpeedScale); if (options.UseCache && _flyshotCache.TryGetValue(cacheKey, out var cachedBundle)) { @@ -168,12 +168,12 @@ public sealed class ControllerClientTrajectoryOrchestrator var request = new TrajectoryRequest( robot: planningRobot, program: program, - method: method, + method: backend.ResultMethod, moveToStart: options.MoveToStart, saveTrajectoryArtifacts: options.SaveTrajectory, useCache: options.UseCache); - var plannedTrajectory = PlanByMethod(request, method, settings); + var plannedTrajectory = PlanByBackend(request, backend, settings); var smoothedExecutionTrajectory = ApplyExecutionTiming(plannedTrajectory, settings); var denseJointTrajectory = CreateLimitCompliantDenseTrajectory(ref smoothedExecutionTrajectory, shapeTrajectoryEdges: false); var shotTimeline = _shotTimelineBuilder.Build( @@ -206,14 +206,15 @@ public sealed class ControllerClientTrajectoryOrchestrator /// /// 旧 SDK 传入的方法名。 /// 领域层规划方法。 - private static PlanningMethod ParseOrdinaryMethod(string method) + private static string ParseOrdinaryMethod(string method) { var normalized = NormalizeMethod(method); return normalized switch { - "icsp" => PlanningMethod.Icsp, - "icsp_rust" => PlanningMethod.IcspRust, - "doubles" => PlanningMethod.Doubles, + "icsp" => normalized, + "icsp-csharp" => normalized, + "icsp_rust" => throw new ArgumentException("method=\"icsp_rust\" 已改名为 method=\"icsp\"。", nameof(method)), + "doubles" => normalized, _ => throw new ArgumentException($"Unsupported ExecuteTrajectory method: {method}", nameof(method)) }; } @@ -223,35 +224,80 @@ public sealed class ControllerClientTrajectoryOrchestrator /// /// 旧 SDK 传入的方法名。 /// 领域层规划方法。 - private static PlanningMethod ParseFlyshotMethod(string method) + private static string ParseFlyshotMethod(string method) { var normalized = NormalizeMethod(method); return normalized switch { - "icsp" => PlanningMethod.Icsp, - "icsp_rust" => PlanningMethod.IcspRust, - "self-adapt-icsp" => PlanningMethod.SelfAdaptIcsp, - "doubles" => PlanningMethod.Doubles, + "icsp" => normalized, + "icsp-csharp" => normalized, + "icsp_rust" => throw new ArgumentException("method=\"icsp_rust\" 已改名为 method=\"icsp\"。", nameof(method)), + "self-adapt-icsp" => normalized, + "doubles" => normalized, _ => throw new ArgumentException($"Unsupported ExecuteFlyShotTraj method: {method}", nameof(method)) }; } /// - /// 按领域枚举分派到当前已经落地的规划器。 + /// 使用显式注册的规划后端执行规划。 /// /// 规划请求。 - /// 规划方法。 + /// 已解析的规划后端。 + /// 当前机器人兼容配置;为空时使用默认补点次数。 /// 规划轨迹。 - private PlannedTrajectory PlanByMethod(TrajectoryRequest request, PlanningMethod method, CompatibilityRobotSettings? settings = null) + private static PlannedTrajectory PlanByBackend( + TrajectoryRequest request, + IPlanningBackend backend, + CompatibilityRobotSettings? settings = null) { - return method switch + var context = new PlanningBackendContext(settings?.AdaptIcspTryNum ?? 5); + return backend.Plan(request, context); + } + + /// + /// 从已注册后端中解析 method 对应的规划实现。 + /// + /// 已经归一化的外部 method 名称。 + /// 可执行该 method 的规划后端。 + private IPlanningBackend ResolvePlanningBackend(string methodName) + { + if (methodName == "doubles") { - PlanningMethod.Icsp => _icspPlanner.Plan(request), - PlanningMethod.IcspRust => _rustIcspPlanner.Plan(request), - PlanningMethod.SelfAdaptIcsp => _selfAdaptIcspPlanner.Plan(request, settings?.AdaptIcspTryNum ?? 5), - PlanningMethod.Doubles => throw new NotSupportedException("doubles 轨迹规划尚未落地。"), - _ => throw new ArgumentOutOfRangeException(nameof(method), method, "未知轨迹规划方法。") - }; + throw new NotSupportedException("doubles 轨迹规划尚未落地。"); + } + + if (_planningBackends.TryGetValue(methodName, out var backend)) + { + return backend; + } + + throw new NotSupportedException($"轨迹规划方法 {methodName} 未注册对应后端。"); + } + + /// + /// 构造规划后端查找表,未显式注册时只启用默认 Rust icsp 后端。 + /// + /// 调用方显式注册的规划后端集合。 + /// 按 method 名称索引的后端表。 + private static IReadOnlyDictionary BuildPlanningBackendMap(IEnumerable? planningBackends) + { + var backends = planningBackends?.ToArray() ?? []; + if (backends.Length == 0) + { + backends = [new RustIcspPlanningBackend()]; + } + + var map = new Dictionary(StringComparer.Ordinal); + foreach (var backend in backends) + { + var methodName = NormalizeMethod(backend.MethodName); + if (!map.TryAdd(methodName, backend)) + { + throw new InvalidOperationException($"规划后端 method={methodName} 被重复注册。"); + } + } + + return map; } /// diff --git a/src/Flyshot.ControllerClientCompat/FlyshotExecutionOptions.cs b/src/Flyshot.ControllerClientCompat/FlyshotExecutionOptions.cs index 292eda8..62f30f7 100644 --- a/src/Flyshot.ControllerClientCompat/FlyshotExecutionOptions.cs +++ b/src/Flyshot.ControllerClientCompat/FlyshotExecutionOptions.cs @@ -9,7 +9,7 @@ public sealed class FlyshotExecutionOptions /// 初始化飞拍轨迹执行参数。 /// /// 执行前是否自动移动到轨迹起点。 - /// 轨迹生成方法,支持 `icsp`、`icsp_rust`、`doubles` 或 `self-adapt-icsp`。 + /// 轨迹生成方法,支持默认 Rust `icsp`、可选 C# `icsp-csharp`、`self-adapt-icsp` 或 `doubles`。 /// 是否保存轨迹信息。 /// 是否优先复用已规划轨迹缓存。 /// 是否等待机器人执行完整条飞拍轨迹后再返回。 diff --git a/src/Flyshot.ControllerClientCompat/TrajectoryExecutionOptions.cs b/src/Flyshot.ControllerClientCompat/TrajectoryExecutionOptions.cs index f666043..01711f0 100644 --- a/src/Flyshot.ControllerClientCompat/TrajectoryExecutionOptions.cs +++ b/src/Flyshot.ControllerClientCompat/TrajectoryExecutionOptions.cs @@ -8,7 +8,7 @@ public sealed class TrajectoryExecutionOptions /// /// 初始化普通轨迹执行参数。 /// - /// 轨迹生成方法,支持 `icsp`、`icsp_rust` 或 `doubles`。 + /// 轨迹生成方法,支持默认 Rust `icsp`、可选 C# `icsp-csharp` 或 `doubles`。 /// 是否保存轨迹信息。 public TrajectoryExecutionOptions(string method = "icsp", bool saveTrajectory = false) { diff --git a/src/Flyshot.Core.Domain/TrajectoryRequest.cs b/src/Flyshot.Core.Domain/TrajectoryRequest.cs index b91bae8..3988bb5 100644 --- a/src/Flyshot.Core.Domain/TrajectoryRequest.cs +++ b/src/Flyshot.Core.Domain/TrajectoryRequest.cs @@ -70,14 +70,14 @@ public sealed class TrajectoryRequest public enum PlanningMethod { /// - /// Uses the cubic-spline ICSP planner. + /// 使用正式 icsp 后端;当前默认实现为 Rust 原生 ICSP。 /// Icsp, /// - /// 使用 Rust 原生库实现的 ICSP 规划器。 + /// 使用拆分到可选程序集中的 C# ICSP 规划器。 /// - IcspRust, + IcspCSharp, /// /// Uses the adaptive ICSP planner that may insert additional points. diff --git a/src/Flyshot.Core.Planning/IPlanningBackend.cs b/src/Flyshot.Core.Planning/IPlanningBackend.cs new file mode 100644 index 0000000..2a8c5b5 --- /dev/null +++ b/src/Flyshot.Core.Planning/IPlanningBackend.cs @@ -0,0 +1,27 @@ +using Flyshot.Core.Domain; + +namespace Flyshot.Core.Planning; + +/// +/// 定义可由兼容层按 method 字符串分派的轨迹规划后端。 +/// +public interface IPlanningBackend +{ + /// + /// 获取外部 HTTP/ControllerClient method 参数使用的准确方法名。 + /// + string MethodName { get; } + + /// + /// 获取该后端成功规划后写入结果对象的领域规划方法。 + /// + PlanningMethod ResultMethod { get; } + + /// + /// 执行轨迹规划。 + /// + /// 包含机器人约束、轨迹程序和调用标志的规划请求。 + /// 兼容层传入的规划上下文参数。 + /// 规划后的轨迹时间轴、路点和收敛信息。 + PlannedTrajectory Plan(TrajectoryRequest request, PlanningBackendContext context); +} diff --git a/src/Flyshot.Core.Planning/PlanningBackendContext.cs b/src/Flyshot.Core.Planning/PlanningBackendContext.cs new file mode 100644 index 0000000..0f22c77 --- /dev/null +++ b/src/Flyshot.Core.Planning/PlanningBackendContext.cs @@ -0,0 +1,21 @@ +namespace Flyshot.Core.Planning; + +/// +/// 表示规划后端执行时由兼容层补充的运行参数。 +/// +public sealed class PlanningBackendContext +{ + /// + /// 初始化规划后端上下文。 + /// + /// self-adapt-icsp 允许的最大补点重试次数。 + public PlanningBackendContext(int adaptIcspTryNum = 5) + { + AdaptIcspTryNum = adaptIcspTryNum; + } + + /// + /// 获取 self-adapt-icsp 允许的最大补点重试次数。 + /// + public int AdaptIcspTryNum { get; } +} diff --git a/src/Flyshot.Native.Planning/RustIcspPlanner.cs b/src/Flyshot.Native.Planning/RustIcspPlanner.cs index c30b1fa..625a218 100644 --- a/src/Flyshot.Native.Planning/RustIcspPlanner.cs +++ b/src/Flyshot.Native.Planning/RustIcspPlanner.cs @@ -109,7 +109,7 @@ public sealed class RustIcspPlanner waypointTimes: waypointTimes, segmentDurations: segmentDurations, segmentScales: segmentScales, - method: PlanningMethod.IcspRust, + method: PlanningMethod.Icsp, iterations: nativeResult.Iterations, threshold: nativeResult.Threshold); } diff --git a/src/Flyshot.Native.Planning/RustIcspPlanningBackend.cs b/src/Flyshot.Native.Planning/RustIcspPlanningBackend.cs new file mode 100644 index 0000000..aeb1bc9 --- /dev/null +++ b/src/Flyshot.Native.Planning/RustIcspPlanningBackend.cs @@ -0,0 +1,43 @@ +using Flyshot.Core.Domain; +using Flyshot.Core.Planning; + +namespace Flyshot.Native.Planning; + +/// +/// 将 Rust ICSP 规划器暴露为默认正式 icsp 后端。 +/// +public sealed class RustIcspPlanningBackend : IPlanningBackend +{ + private readonly RustIcspPlanner _planner; + + /// + /// 初始化 Rust ICSP 后端。 + /// + /// 可选的 Rust ICSP 规划器实例;为空时使用默认原生库解析规则。 + public RustIcspPlanningBackend(RustIcspPlanner? planner = null) + { + _planner = planner ?? new RustIcspPlanner(); + } + + /// + /// 获取外部调用 Rust ICSP 后端时使用的正式方法名。 + /// + public string MethodName => "icsp"; + + /// + /// 获取 Rust ICSP 后端写入结果对象的规划方法。 + /// + public PlanningMethod ResultMethod => PlanningMethod.Icsp; + + /// + /// 执行 Rust ICSP 规划。 + /// + /// 包含机器人约束和示教路点的规划请求。 + /// 规划上下文;Rust ICSP 当前不使用该参数。 + /// 由 Rust 原生库生成的轨迹结果。 + public PlannedTrajectory Plan(TrajectoryRequest request, PlanningBackendContext context) + { + ArgumentNullException.ThrowIfNull(context); + return _planner.Plan(request); + } +} diff --git a/src/Flyshot.Server.Host/Flyshot.Server.Host.csproj b/src/Flyshot.Server.Host/Flyshot.Server.Host.csproj index 2867803..7cc2499 100644 --- a/src/Flyshot.Server.Host/Flyshot.Server.Host.csproj +++ b/src/Flyshot.Server.Host/Flyshot.Server.Host.csproj @@ -16,7 +16,7 @@ - + diff --git a/src/Flyshot.Server.Host/wwwroot/debug.html b/src/Flyshot.Server.Host/wwwroot/debug.html index 2ef4c4f..86421ca 100644 --- a/src/Flyshot.Server.Host/wwwroot/debug.html +++ b/src/Flyshot.Server.Host/wwwroot/debug.html @@ -46,6 +46,7 @@ 规划方法 @@ -214,7 +215,7 @@ "description": "执行普通关节轨迹。该接口兼容两种历史请求体形态:一是对象形态 `{ waypoints, method, save_traj }`,二是直接传纯数组路点;查询参数里的 `method` 和 `save_traj` 仍可覆盖请求体值。", "requestBodyFields": [ { "name": "waypoints", "type": "array> | array", "required": true, "description": "历史兼容对象形态中的轨迹路点集合;每个路点既可直接是关节数组,也可写成 `{ joints: [...] }`。" }, - { "name": "method", "type": "string", "required": false, "description": "对象形态中的轨迹规划方法,默认 `icsp`。" }, + { "name": "method", "type": "string", "required": false, "description": "对象形态中的轨迹规划方法,默认 `icsp`;`icsp-csharp` 需要显式注册 C# 后端。" }, { "name": "save_traj", "type": "boolean", "required": false, "description": "对象形态中的是否保存轨迹标记,默认 `false`。" }, { "name": "根数组", "type": "array> | array", "required": true, "description": "纯数组形态时,请直接提交路点数组本身,而不是对象包裹层。" }, { "name": "根数组[].joints", "type": "array", "required": false, "description": "当路点元素是对象形态时,兼容层会读取其中的 `joints` 数组。" } @@ -237,7 +238,7 @@ "requestBodyFields": [ { "name": "name", "type": "string", "required": true, "description": "要执行的飞拍轨迹名称。" }, { "name": "move_to_start", "type": "boolean", "required": false, "description": "是否先用临时 PTP 轨迹移动到飞拍起点,默认 `true`。" }, - { "name": "method", "type": "string", "required": false, "description": "飞拍轨迹生成方法,默认 `icsp`。" }, + { "name": "method", "type": "string", "required": false, "description": "飞拍轨迹生成方法,默认 `icsp`;`icsp-csharp` 和 `self-adapt-icsp` 需要显式注册 C# 后端。" }, { "name": "save_traj", "type": "boolean", "required": false, "description": "是否保存轨迹信息,默认 `true`。" }, { "name": "use_cache", "type": "boolean", "required": false, "description": "是否优先复用已缓存的轨迹结果,默认 `true`。" }, { "name": "wait", "type": "boolean", "required": false, "description": "是否等待整条飞拍轨迹执行完成后再返回,默认 `true`。" } @@ -248,7 +249,7 @@ "description": "仅要求机器人模型已加载;完成规划、触发绑定、最终 8ms 序列生成和文件导出,并返回完整规划结果。", "requestBodyFields": [ { "name": "name", "type": "string", "required": true, "description": "目标飞拍轨迹名称。" }, - { "name": "method", "type": "string", "required": false, "description": "轨迹规划方法,默认 `icsp`。" } + { "name": "method", "type": "string", "required": false, "description": "轨迹规划方法,默认 `icsp`;`icsp-csharp` 和 `self-adapt-icsp` 需要显式注册 C# 后端。" } ] }, "POST /is_flyShotTrajValid/": { @@ -256,7 +257,7 @@ "description": "检查指定飞拍轨迹是否可生成有效执行结果,并返回轨迹时长;底层未实现时会返回 501。", "requestBodyFields": [ { "name": "name", "type": "string", "required": true, "description": "要校验的飞拍轨迹名称。" }, - { "name": "method", "type": "string", "required": false, "description": "轨迹规划方法,默认 `icsp`。" }, + { "name": "method", "type": "string", "required": false, "description": "轨迹规划方法,默认 `icsp`;`icsp-csharp` 和 `self-adapt-icsp` 需要显式注册 C# 后端。" }, { "name": "save_traj", "type": "boolean", "required": false, "description": "是否同时保存轨迹信息,默认 `true`。" } ] }, diff --git a/tests/Flyshot.Core.Tests/Flyshot.Core.Tests.csproj b/tests/Flyshot.Core.Tests/Flyshot.Core.Tests.csproj index c0c4b40..86ba80d 100644 --- a/tests/Flyshot.Core.Tests/Flyshot.Core.Tests.csproj +++ b/tests/Flyshot.Core.Tests/Flyshot.Core.Tests.csproj @@ -27,6 +27,7 @@ + diff --git a/tests/Flyshot.Core.Tests/OfflinePlanTests.cs b/tests/Flyshot.Core.Tests/OfflinePlanTests.cs index 97a1138..9edb631 100644 --- a/tests/Flyshot.Core.Tests/OfflinePlanTests.cs +++ b/tests/Flyshot.Core.Tests/OfflinePlanTests.cs @@ -1,4 +1,5 @@ using Flyshot.Core.Config; +using Flyshot.CSharp.Planning; using Flyshot.Core.Domain; using Flyshot.Core.Planning; using Flyshot.Core.Planning.Export; @@ -55,7 +56,7 @@ public sealed class OfflinePlanTests var request = new TrajectoryRequest( robot: scaledProfile, program: program, - method: useSelfAdapt ? PlanningMethod.SelfAdaptIcsp : PlanningMethod.Icsp); + method: useSelfAdapt ? PlanningMethod.SelfAdaptIcsp : PlanningMethod.IcspCSharp); PlannedTrajectory trajectory; if (useSelfAdapt) diff --git a/tests/Flyshot.Core.Tests/PlanningCompatibilityTests.cs b/tests/Flyshot.Core.Tests/PlanningCompatibilityTests.cs index 43df597..8b9348d 100644 --- a/tests/Flyshot.Core.Tests/PlanningCompatibilityTests.cs +++ b/tests/Flyshot.Core.Tests/PlanningCompatibilityTests.cs @@ -1,4 +1,5 @@ using Flyshot.Core.Config; +using Flyshot.CSharp.Planning; using Flyshot.Core.Domain; using Flyshot.Core.Planning; using Flyshot.Core.Planning.Sampling; @@ -27,7 +28,7 @@ public sealed class PlanningCompatibilityTests new[] { 0.8, 0.3, 0.0, 0.0, 0.0, 0.0 }, new[] { 1.0, 0.2, 0.0, 0.0, 0.0, 0.0 } }), - method: PlanningMethod.Icsp); + method: PlanningMethod.IcspCSharp); var trajectory = new ICspPlanner().Plan(request); @@ -51,7 +52,7 @@ public sealed class PlanningCompatibilityTests new[] { 20.0 }, new[] { 30.0 } }), - method: PlanningMethod.Icsp); + method: PlanningMethod.IcspCSharp); var planner = new ICspPlanner(maxIterations: 0); diff --git a/tests/Flyshot.Core.Tests/RuntimeOrchestrationTests.cs b/tests/Flyshot.Core.Tests/RuntimeOrchestrationTests.cs index aaba0b0..67bf60e 100644 --- a/tests/Flyshot.Core.Tests/RuntimeOrchestrationTests.cs +++ b/tests/Flyshot.Core.Tests/RuntimeOrchestrationTests.cs @@ -1,4 +1,5 @@ using Flyshot.ControllerClientCompat; +using Flyshot.CSharp.Planning; using Flyshot.Core.Config; using Flyshot.Core.Domain; using Flyshot.Core.Planning; @@ -16,6 +17,19 @@ namespace Flyshot.Core.Tests; /// public sealed class RuntimeOrchestrationTests { + /// + /// 创建显式注册 C# ICSP 后端的编排器,用于验证可选算法 DLL 的行为边界。 + /// + /// 包含 C# 普通 ICSP 与 self-adapt-icsp 后端的轨迹编排器。 + private static ControllerClientTrajectoryOrchestrator CreateOrchestratorWithCSharpBackends() + { + return new ControllerClientTrajectoryOrchestrator( + [ + new CSharpIcspPlanningBackend(), + new SelfAdaptIcspPlanningBackend() + ]); + } + /// /// 验证真机运行时会把 TCP 10010 状态通道健康度映射为可诊断连接状态。 /// @@ -107,54 +121,10 @@ public sealed class RuntimeOrchestrationTests } /// - /// 验证普通轨迹使用准确字串 icsp_rust 时会真实调用 Rust DLL,并在结果中标记原生后端。 + /// 验证普通轨迹默认 icsp 会真实调用 Rust DLL,并在结果中保留正式 icsp 方法标记。 /// [Fact] - public void ControllerClientTrajectoryOrchestrator_PlanOrdinaryTrajectory_UsesRustPlanner() - { - var orchestrator = new ControllerClientTrajectoryOrchestrator(); - var robot = TestRobotFactory.CreateRobotProfile(); - - var bundle = orchestrator.PlanOrdinaryTrajectory( - robot, - [ - [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], - [0.001, 0.0, 0.0, 0.0, 0.0, 0.0], - [0.002, 0.0, 0.0, 0.0, 0.0, 0.0], - [0.003, 0.0, 0.0, 0.0, 0.0, 0.0] - ], - new TrajectoryExecutionOptions(method: "icsp_rust")); - - Assert.Equal(PlanningMethod.IcspRust, bundle.PlannedTrajectory.Method); - Assert.Equal(PlanningMethod.IcspRust, bundle.Result.Method); - } - - /// - /// 验证飞拍轨迹使用准确字串 icsp_rust 时会真实调用 Rust DLL,同时保留触发时间轴。 - /// - [Fact] - public void ControllerClientTrajectoryOrchestrator_PlanUploadedFlyshot_UsesRustPlanner() - { - var orchestrator = new ControllerClientTrajectoryOrchestrator(); - var robot = TestRobotFactory.CreateRobotProfile(); - var uploaded = TestRobotFactory.CreateUploadedTrajectoryWithSingleShot(); - - var bundle = orchestrator.PlanUploadedFlyshot( - robot, - uploaded, - new FlyshotExecutionOptions(method: "icsp_rust", useCache: false)); - - Assert.Equal(PlanningMethod.IcspRust, bundle.PlannedTrajectory.Method); - Assert.Equal(PlanningMethod.IcspRust, bundle.Result.Method); - Assert.Single(bundle.Result.ShotEvents); - Assert.Single(bundle.Result.TriggerTimeline); - } - - /// - /// 验证普通轨迹默认 icsp 仍使用现有 C# 规划器。 - /// - [Fact] - public void ControllerClientTrajectoryOrchestrator_PlanOrdinaryTrajectory_KeepsManagedIcspPlanner() + public void ControllerClientTrajectoryOrchestrator_PlanOrdinaryTrajectory_DefaultIcspUsesRustPlanner() { var orchestrator = new ControllerClientTrajectoryOrchestrator(); var robot = TestRobotFactory.CreateRobotProfile(); @@ -174,12 +144,98 @@ public sealed class RuntimeOrchestrationTests } /// - /// 验证 self-adapt-icsp 仍使用 C# 自适应规划器,不切换到 Rust。 + /// 验证旧的 icsp_rust 字串不再作为正式入口,并提示调用方改用 icsp。 /// [Fact] - public void ControllerClientTrajectoryOrchestrator_PlanUploadedFlyshot_KeepsManagedSelfAdaptPlanner() + public void ControllerClientTrajectoryOrchestrator_PlanOrdinaryTrajectory_RejectsLegacyIcspRustName() { var orchestrator = new ControllerClientTrajectoryOrchestrator(); + var robot = TestRobotFactory.CreateRobotProfile(); + + var exception = Assert.Throws(() => orchestrator.PlanOrdinaryTrajectory( + robot, + [ + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.001, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.002, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.003, 0.0, 0.0, 0.0, 0.0, 0.0] + ], + new TrajectoryExecutionOptions(method: "icsp_rust"))); + + Assert.Contains("icsp", exception.Message); + Assert.Contains("icsp_rust", exception.Message); + } + + /// + /// 验证未注册 C# 后端时,icsp-csharp 会明确报错而不是静默回退。 + /// + [Fact] + public void ControllerClientTrajectoryOrchestrator_PlanOrdinaryTrajectory_RejectsCSharpIcspWhenBackendIsMissing() + { + var orchestrator = new ControllerClientTrajectoryOrchestrator(); + var robot = TestRobotFactory.CreateRobotProfile(); + + var exception = Assert.Throws(() => orchestrator.PlanOrdinaryTrajectory( + robot, + [ + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.001, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.002, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.003, 0.0, 0.0, 0.0, 0.0, 0.0] + ], + new TrajectoryExecutionOptions(method: "icsp-csharp"))); + + Assert.Contains("icsp-csharp", exception.Message); + } + + /// + /// 验证显式注册 C# 后端后,icsp-csharp 使用拆分后的托管规划器。 + /// + [Fact] + public void ControllerClientTrajectoryOrchestrator_PlanOrdinaryTrajectory_UsesRegisteredCSharpIcspPlanner() + { + var orchestrator = CreateOrchestratorWithCSharpBackends(); + var robot = TestRobotFactory.CreateRobotProfile(); + + var bundle = orchestrator.PlanOrdinaryTrajectory( + robot, + [ + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.001, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.002, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.003, 0.0, 0.0, 0.0, 0.0, 0.0] + ], + new TrajectoryExecutionOptions(method: "icsp-csharp")); + + Assert.Equal(PlanningMethod.IcspCSharp, bundle.PlannedTrajectory.Method); + Assert.Equal(PlanningMethod.IcspCSharp, bundle.Result.Method); + } + + /// + /// 验证未注册 C# 后端时,self-adapt-icsp 会明确报错而不是把行为藏在主服务里。 + /// + [Fact] + public void ControllerClientTrajectoryOrchestrator_PlanUploadedFlyshot_RejectsSelfAdaptWhenBackendIsMissing() + { + var orchestrator = new ControllerClientTrajectoryOrchestrator(); + var robot = TestRobotFactory.CreateRobotProfile(); + var uploaded = TestRobotFactory.CreateUploadedTrajectoryWithSingleShot(); + + var exception = Assert.Throws(() => orchestrator.PlanUploadedFlyshot( + robot, + uploaded, + new FlyshotExecutionOptions(method: "self-adapt-icsp", useCache: false))); + + Assert.Contains("self-adapt-icsp", exception.Message); + } + + /// + /// 验证显式注册 C# 后端后,self-adapt-icsp 仍使用 C# 自适应规划器。 + /// + [Fact] + public void ControllerClientTrajectoryOrchestrator_PlanUploadedFlyshot_UsesRegisteredManagedSelfAdaptPlanner() + { + var orchestrator = CreateOrchestratorWithCSharpBackends(); var robot = new RobotProfile( name: "ManagedSelfAdaptTestRobot", modelPath: "Models/ManagedSelfAdaptTest.robot", diff --git a/tests/Flyshot.Core.Tests/RustIcspPlannerInteropTests.cs b/tests/Flyshot.Core.Tests/RustIcspPlannerInteropTests.cs index 0fdd17f..1d681c4 100644 --- a/tests/Flyshot.Core.Tests/RustIcspPlannerInteropTests.cs +++ b/tests/Flyshot.Core.Tests/RustIcspPlannerInteropTests.cs @@ -1,4 +1,5 @@ using Flyshot.Core.Domain; +using Flyshot.CSharp.Planning; using Flyshot.Core.Planning; using Flyshot.Native.Planning; @@ -35,7 +36,7 @@ public sealed class RustIcspPlannerInteropTests var result = planner.Plan(CreateSimpleRequest()); - Assert.Equal(PlanningMethod.IcspRust, result.Method); + Assert.Equal(PlanningMethod.Icsp, result.Method); Assert.Equal(result.PlannedWaypointCount, result.WaypointTimes.Count); Assert.Equal(result.PlannedWaypointCount - 1, result.SegmentDurations.Count); Assert.Equal(result.SegmentDurations.Count, result.SegmentScales.Count); diff --git a/tests/Flyshot.Core.Tests/RustIcspPlannerParityTests.cs b/tests/Flyshot.Core.Tests/RustIcspPlannerParityTests.cs index 13c42e4..1397ec4 100644 --- a/tests/Flyshot.Core.Tests/RustIcspPlannerParityTests.cs +++ b/tests/Flyshot.Core.Tests/RustIcspPlannerParityTests.cs @@ -1,4 +1,5 @@ using Flyshot.Core.Domain; +using Flyshot.CSharp.Planning; using Flyshot.Core.Planning; using Flyshot.Native.Planning; @@ -19,8 +20,8 @@ public sealed class RustIcspPlannerParityTests PlannedTrajectory managed = new ICspPlanner().Plan(request); PlannedTrajectory native = new RustIcspPlanner().Plan(request); - Assert.Equal(PlanningMethod.Icsp, managed.Method); - Assert.Equal(PlanningMethod.IcspRust, native.Method); + Assert.Equal(PlanningMethod.IcspCSharp, managed.Method); + Assert.Equal(PlanningMethod.Icsp, native.Method); AssertTrajectoryMatches(managed, native, tolerance: 1e-9); } @@ -35,8 +36,8 @@ public sealed class RustIcspPlannerParityTests PlannedTrajectory managed = new ICspPlanner(enforceFinalScale: false).Plan(request); PlannedTrajectory native = new RustIcspPlanner(options).Plan(request); - Assert.Equal(PlanningMethod.Icsp, managed.Method); - Assert.Equal(PlanningMethod.IcspRust, native.Method); + Assert.Equal(PlanningMethod.IcspCSharp, managed.Method); + Assert.Equal(PlanningMethod.Icsp, native.Method); AssertTrajectoryMatches(managed, native, tolerance: 1e-9); } @@ -116,7 +117,7 @@ public sealed class RustIcspPlannerParityTests .Select(_ => new IoAddressGroup([])) .ToArray()); - return new TrajectoryRequest(robot, program, PlanningMethod.Icsp); + return new TrajectoryRequest(robot, program, PlanningMethod.IcspCSharp); } ///