using Flyshot.Core.Domain;
using Microsoft.Extensions.Logging;
namespace Flyshot.ControllerClientCompat;
///
/// MovePose 直角坐标轨迹生成器。
/// 将起始 TCP 位姿到目标 TCP 位姿的单段运动,按保守直角速度、加速度和 jerk 限制生成 8ms 稠密点。
///
internal static class MovePoseTrajectoryGenerator
{
///
/// 直角位置轴最大速度,单位为 mm/s。
///
private const double LinearVelocityLimit = 50.0;
///
/// 直角位置轴最大加速度,单位为 mm/s^2。
///
private const double LinearAccelerationLimit = 250.0;
///
/// 直角位置轴最大 jerk,单位为 mm/s^3。
///
private const double LinearJerkLimit = 1250.0;
///
/// 姿态轴最大速度,单位为 deg/s。
///
private const double AngularVelocityLimit = 10.0;
///
/// 姿态轴最大加速度,单位为 deg/s^2。
///
private const double AngularAccelerationLimit = 50.0;
///
/// 姿态轴最大 jerk,单位为 deg/s^3。
///
private const double AngularJerkLimit = 250.0;
///
/// 7 阶平滑点到点时间律的一阶导数最大值。
///
private const double SmoothPtpVelocityShapeCoefficient = 2.1875;
///
/// 7 阶平滑点到点时间律的二阶导数最大值。
///
private const double SmoothPtpAccelerationShapeCoefficient = 7.513188404399293;
///
/// 7 阶平滑点到点时间律的三阶导数最大值。
///
private const double SmoothPtpJerkShapeCoefficient = 52.5;
///
/// 单次 MovePose 最大采样点数上限,避免极低速度倍率生成过大的队列。
///
private const int MaxMovePoseSampleCount = 1_000_000;
///
/// 离散限位校验允许的浮点容差。
///
private const double DiscreteLimitTolerance = 1.000001;
///
/// 离散限位校验失败时最多拉长的采样周期次数。
///
private const int MaxDiscreteLimitStretchIterations = 10_000;
///
/// 计算 MovePose 轨迹的完整结果。
///
/// 起始位姿 [x,y,z,w,p,r],单位为 mm/deg。
/// 目标位姿 [x,y,z,w,p,r],单位为 mm/deg。
/// J519 伺服发送周期。
/// 速度倍率,必须大于 0。
/// 可选诊断日志。
/// 包含稠密直角轨迹的规划结果。
public static TrajectoryResult CreateResult(
IReadOnlyList startPose,
IReadOnlyList targetPose,
TimeSpan servoPeriod,
double speedRatio,
ILogger? logger = null)
{
ArgumentNullException.ThrowIfNull(startPose);
ArgumentNullException.ThrowIfNull(targetPose);
EnsurePoseVector(startPose, nameof(startPose));
EnsurePoseVector(targetPose, nameof(targetPose));
if (speedRatio <= 0.0 || double.IsNaN(speedRatio) || double.IsInfinity(speedRatio))
{
throw new InvalidOperationException("Speed ratio must be greater than zero for MovePose execution.");
}
var samplePeriodSeconds = servoPeriod.TotalSeconds;
if (samplePeriodSeconds <= 0.0 || double.IsNaN(samplePeriodSeconds) || double.IsInfinity(samplePeriodSeconds))
{
throw new InvalidOperationException("MovePose servo period must be a finite positive duration.");
}
var requestedDurationSeconds = ResolveDurationSeconds(startPose, targetPose, speedRatio);
var durationSeconds = AlignDurationToServoStep(requestedDurationSeconds, samplePeriodSeconds);
var denseCartesianTrajectory = GenerateDenseTrajectory(startPose, targetPose, durationSeconds, samplePeriodSeconds);
var stretchCount = 0;
while (!SatisfiesDefaultCartesianLimits(denseCartesianTrajectory, speedRatio))
{
stretchCount++;
if (stretchCount > MaxDiscreteLimitStretchIterations)
{
throw new InvalidOperationException("MovePose duration cannot be stretched enough to satisfy Cartesian limits.");
}
// 离散差分以真实下发点为准;不满足时逐周期拉长后重采样。
durationSeconds = AlignDurationToServoStep(durationSeconds + samplePeriodSeconds, samplePeriodSeconds);
denseCartesianTrajectory = GenerateDenseTrajectory(startPose, targetPose, durationSeconds, samplePeriodSeconds);
}
logger?.LogDebug(
"MovePoseTrajectoryGenerator: requestedDuration={RequestedDuration:F4}s, duration={Duration:F4}s, speedRatio={SpeedRatio}, samplePeriod={SamplePeriod:F6}s, sampleCount={SampleCount}, stretchCount={StretchCount}",
requestedDurationSeconds,
durationSeconds,
speedRatio,
samplePeriodSeconds,
denseCartesianTrajectory.Count,
stretchCount);
return new TrajectoryResult(
programName: "move-pose",
method: PlanningMethod.Doubles,
isValid: true,
duration: TimeSpan.FromSeconds(durationSeconds),
shotEvents: Array.Empty(),
triggerTimeline: Array.Empty(),
artifacts: Array.Empty(),
failureReason: null,
usedCache: false,
originalWaypointCount: 2,
plannedWaypointCount: denseCartesianTrajectory.Count,
denseCartesianTrajectory: denseCartesianTrajectory);
}
///
/// 检查稠密直角轨迹是否满足默认六维速度、加速度和 jerk 限制。
///
/// 稠密轨迹行,每行为 [time,x,y,z,w,p,r]。
/// 生成该轨迹时使用的速度倍率。
/// 满足限制时返回 true,否则返回 false。
public static bool SatisfiesDefaultCartesianLimits(IReadOnlyList> rows, double speedRatio)
{
if (speedRatio <= 0.0 || double.IsNaN(speedRatio) || double.IsInfinity(speedRatio))
{
throw new InvalidOperationException("Speed ratio must be greater than zero for MovePose limit validation.");
}
double? previousTime = null;
double[]? previousPositions = null;
double[]? previousVelocities = null;
double[]? previousAccelerations = null;
foreach (var row in rows)
{
if (row.Count != 7)
{
throw new InvalidOperationException("MovePose dense trajectory rows must contain time plus six pose values.");
}
var currentTime = row[0];
var currentPositions = row.Skip(1).Take(6).ToArray();
if (previousTime is not null && previousPositions is not null)
{
var dt = currentTime - previousTime.Value;
if (dt <= 0.0)
{
throw new InvalidOperationException("MovePose dense trajectory timestamps must be strictly increasing.");
}
var velocities = new double[6];
var accelerations = new double[6];
for (var index = 0; index < 6; index++)
{
var limits = GetAxisLimits(index, speedRatio);
velocities[index] = (currentPositions[index] - previousPositions[index]) / dt;
if (Math.Abs(velocities[index]) > limits.Velocity * DiscreteLimitTolerance)
{
return false;
}
accelerations[index] = previousVelocities is null
? 0.0
: (velocities[index] - previousVelocities[index]) / dt;
if (Math.Abs(accelerations[index]) > limits.Acceleration * DiscreteLimitTolerance)
{
return false;
}
if (previousAccelerations is not null)
{
var jerk = (accelerations[index] - previousAccelerations[index]) / dt;
if (Math.Abs(jerk) > limits.Jerk * DiscreteLimitTolerance)
{
return false;
}
}
}
previousVelocities = velocities;
previousAccelerations = accelerations;
}
previousTime = currentTime;
previousPositions = currentPositions;
}
return true;
}
///
/// 根据 7 阶平滑点到点时间律和直角六维限制计算理论最短时长。
///
private static double ResolveDurationSeconds(IReadOnlyList startPose, IReadOnlyList targetPose, double speedRatio)
{
var duration = 0.0;
for (var index = 0; index < 6; index++)
{
var distance = Math.Abs(targetPose[index] - startPose[index]);
if (distance <= 0.0)
{
continue;
}
var limits = GetAxisLimits(index, speedRatio);
var velocityDuration = distance * SmoothPtpVelocityShapeCoefficient / limits.Velocity;
var accelerationDuration = Math.Sqrt(distance * SmoothPtpAccelerationShapeCoefficient / limits.Acceleration);
var jerkDuration = Math.Cbrt(distance * SmoothPtpJerkShapeCoefficient / limits.Jerk);
duration = Math.Max(duration, Math.Max(velocityDuration, Math.Max(accelerationDuration, jerkDuration)));
}
return duration;
}
///
/// 生成从起始位姿到目标位姿的稠密等时间隔直角轨迹点序列。
///
private static IReadOnlyList> GenerateDenseTrajectory(
IReadOnlyList startPose,
IReadOnlyList targetPose,
double durationSeconds,
double samplePeriodSeconds)
{
var sampleCount = ResolveSampleIntervalCount(durationSeconds, samplePeriodSeconds) + 1;
var rows = new List>(checked((int)sampleCount));
for (var index = 0L; index < sampleCount; index++)
{
var time = Math.Min(index * samplePeriodSeconds, durationSeconds);
rows.Add(CreateRow(time, durationSeconds, startPose, targetPose));
}
return rows;
}
///
/// 将请求时长向上对齐到整数个伺服周期。
///
private static double AlignDurationToServoStep(double durationSeconds, double samplePeriodSeconds)
{
return ResolveSampleIntervalCount(durationSeconds, samplePeriodSeconds) * samplePeriodSeconds;
}
///
/// 计算时长对应的采样间隔数,轨迹至少包含起点和终点两帧。
///
private static long ResolveSampleIntervalCount(double durationSeconds, double samplePeriodSeconds)
{
var rawIntervals = durationSeconds / samplePeriodSeconds;
if (double.IsNaN(rawIntervals) || double.IsInfinity(rawIntervals))
{
throw new InvalidOperationException("MovePose sample count is not representable.");
}
var intervals = (long)Math.Ceiling(Math.Max(0.0, rawIntervals) - 1e-9);
intervals = Math.Max(1, intervals);
if (intervals + 1 > MaxMovePoseSampleCount)
{
throw new InvalidOperationException($"MovePose sample count must be between 2 and {MaxMovePoseSampleCount}.");
}
return intervals;
}
///
/// 构造单个轨迹行:[time_seconds,x,y,z,w,p,r]。
///
private static IReadOnlyList CreateRow(
double timeSeconds,
double durationSeconds,
IReadOnlyList startPose,
IReadOnlyList targetPose)
{
var u = durationSeconds <= 0.0 ? 1.0 : Math.Clamp(timeSeconds / durationSeconds, 0.0, 1.0);
var scale = MoveJointTrajectoryGenerator.EvaluateSmoothPtpPositionScale(u);
var row = new double[7];
row[0] = Math.Round(timeSeconds, 9);
for (var index = 0; index < 6; index++)
{
row[index + 1] = startPose[index] + ((targetPose[index] - startPose[index]) * scale);
}
return row;
}
///
/// 获取指定直角轴在当前速度倍率下的有效限制。
///
private static CartesianAxisLimit GetAxisLimits(int index, double speedRatio)
{
var linearAxis = index < 3;
var velocity = linearAxis ? LinearVelocityLimit : AngularVelocityLimit;
var acceleration = linearAxis ? LinearAccelerationLimit : AngularAccelerationLimit;
var jerk = linearAxis ? LinearJerkLimit : AngularJerkLimit;
return new CartesianAxisLimit(
velocity * speedRatio,
acceleration * speedRatio * speedRatio,
jerk * speedRatio * speedRatio * speedRatio);
}
///
/// 校验位姿向量必须为六维有限数。
///
private static void EnsurePoseVector(IReadOnlyList pose, string parameterName)
{
if (pose.Count != 6)
{
throw new ArgumentException("MovePose expects pose [x,y,z,w,p,r].", parameterName);
}
if (pose.Any(static value => double.IsNaN(value) || double.IsInfinity(value)))
{
throw new ArgumentException("MovePose pose values must be finite.", parameterName);
}
}
///
/// 表示单个直角轴的有效速度、加速度和 jerk 限制。
///
private readonly record struct CartesianAxisLimit(double Velocity, double Acceleration, double Jerk);
}