using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
namespace TeamAAS.Motion.Models
{
/// 逻辑轴类型。
public enum MotionAxisKind
{
/// EtherCAT/脉冲实轴,绑定物理通道。
[Description("实轴")]
Physical = 0,
/// 虚轴:仅规划,不绑定输出。
[Description("虚轴")]
Virtual = 1,
/// 组合轴:一条逻辑轴同步驱动一个或多个从轴(主从跟随 / 双驱龙门)。
[Description("组合轴")]
Combined = 2
}
/// 单条逻辑轴配置(持久化在 MotionDevices.json)。
[Serializable]
public class MotionAxisConfig
{
/// 逻辑轴号,流程/调试用,范围 [0,63]。
public int AxisNo { get; set; }
public string Name { get; set; } = "Axis";
public MotionAxisKind Kind { get; set; } = MotionAxisKind.Physical;
/// 实轴的 EtherCAT 输出通道;虚轴/组合轴忽略。
public int OutputChannel { get; set; }
///
/// 从轴列表。虚轴作为主轴时填写从轴号;正数为同向,负数为反向(例如 "1" 或 "1,-2")。
///
public string CombinedMembers { get; set; } = "";
public double Velocity { get; set; } = 100;
public double Acceleration { get; set; } = 500;
public double Deceleration { get; set; } = 500;
public double Jerk { get; set; } = 1000;
/// 是否启用软限位。关闭时不钳制位置、不下发卡限位检查。
public bool SoftLimitEnabled { get; set; }
/// 负向软限位(用户单位)。
public double SoftLimitNeg { get; set; }
/// 正向软限位(用户单位)。
public double SoftLimitPos { get; set; }
/// 最高速度。≤0 表示不限制。
public double MaxVelocity { get; set; }
public MotionAxisConfig Clone()
{
return new MotionAxisConfig
{
AxisNo = AxisNo,
Name = Name,
Kind = Kind,
OutputChannel = OutputChannel,
CombinedMembers = CombinedMembers,
Velocity = Velocity,
Acceleration = Acceleration,
Deceleration = Deceleration,
Jerk = Jerk,
SoftLimitEnabled = SoftLimitEnabled,
SoftLimitNeg = SoftLimitNeg,
SoftLimitPos = SoftLimitPos,
MaxVelocity = MaxVelocity
};
}
public double ClampPosition(double position)
{
if (!SoftLimitEnabled) return position;
double lo = Math.Min(SoftLimitNeg, SoftLimitPos);
double hi = Math.Max(SoftLimitNeg, SoftLimitPos);
if (position < lo) return lo;
if (position > hi) return hi;
return position;
}
public double ClampVelocity(double velocity)
{
if (MaxVelocity <= 0) return velocity;
if (velocity > MaxVelocity) return MaxVelocity;
if (velocity < -MaxVelocity) return -MaxVelocity;
return velocity;
}
public double ClampJog(double velocity, double currentPosition)
{
double v = ClampVelocity(velocity);
if (!SoftLimitEnabled || Math.Abs(v) < 1e-9) return v;
double lo = Math.Min(SoftLimitNeg, SoftLimitPos);
double hi = Math.Max(SoftLimitNeg, SoftLimitPos);
if (v > 0 && currentPosition >= hi) return 0;
if (v < 0 && currentPosition <= lo) return 0;
return v;
}
public List<(int AxisNo, double Scale)> ParseMembers()
{
var list = new List<(int, double)>();
if (string.IsNullOrWhiteSpace(CombinedMembers)) return list;
foreach (var raw in CombinedMembers.Split(new[] { ',', ';', ' ', ',' }, StringSplitOptions.RemoveEmptyEntries))
{
var token = raw.Trim();
if (token.Length == 0 || token == "-" || token == "+") continue;
if (!int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out var signed))
continue;
int no = Math.Abs(signed);
double scale = token.StartsWith("-", StringComparison.Ordinal) ? -1 : 1;
list.Add((no, scale));
}
return list;
}
/// 组合轴,或虚轴填写了至少 1 个从轴时,按主从跟随运行。
[Newtonsoft.Json.JsonIgnore]
public bool IsGrouped
{
get
{
if (ParseMembers().Count < 1) return false;
return Kind == MotionAxisKind.Combined || Kind == MotionAxisKind.Virtual;
}
}
[Newtonsoft.Json.JsonIgnore]
public string KindText
{
get
{
if (Kind == MotionAxisKind.Virtual && ParseMembers().Count >= 1)
return "虚轴(主从)";
switch (Kind)
{
case MotionAxisKind.Virtual: return "虚轴";
case MotionAxisKind.Combined: return "组合轴";
default: return "实轴";
}
}
}
}
public static class MotionAxisCatalog
{
public static readonly string[] DefaultNames = { "X", "Y", "Z", "R" };
public static List CreateDefaults(int count)
{
count = Math.Max(1, count);
var list = new List(count);
for (int i = 0; i < count; i++)
{
list.Add(new MotionAxisConfig
{
AxisNo = i,
Name = i < DefaultNames.Length ? DefaultNames[i] : $"Axis-{i}",
Kind = MotionAxisKind.Physical,
OutputChannel = i
});
}
return list;
}
public static List GetEffective(MotionDeviceConfig cfg)
{
if (cfg?.Axes != null && cfg.Axes.Count > 0)
{
var byNo = new Dictionary();
foreach (var a in cfg.Axes)
{
if (a == null) continue;
byNo[a.AxisNo] = a;
}
if (byNo.Count > 0)
return byNo.Values.OrderBy(x => x.AxisNo).ToList();
}
return CreateDefaults(cfg?.AxisCount ?? 4);
}
}
}