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