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
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);
}
///