init commit

This commit is contained in:
zhaowei.huang
2026-06-14 11:19:15 +08:00
parent e79a3815a5
commit c8e540d272
174 changed files with 60830 additions and 39 deletions
+692
View File
@@ -0,0 +1,692 @@
using LessokajiWeaverUtilities.Utilities;
using Newtonsoft.Json;
using SimpleLite;
using SimpleLite.RCS;
using SimpleLite.RCS.CarTypes;
using SimpleLite.CADTools;
using SimpleLite.Props;
using SimpleLite.UI;
using SimpleCore;
using SimpleCore.Compiler;
using SimpleCore.Extras;
using SimpleCore.Library;
using SimpleCore.PropType;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Net.Http;
using System.Numerics;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using StandardScene.Model;
using StandardScene.Coders;
using StandardScene.Magnetic.Coders;
using Track = SimpleCore.PropType.Track;
namespace StandardScene.CarTypes
{
// MagneticTrackCoder 已物理迁出至 StandardScene.Magnetic.Codersscene.mag 平台)。
// Kiva 系字段袋(KivaCarFields/KivaSiteFields/KivaTrackFields/KivaPlanFields)下沉至
// 基座 StandardScene.Core\CarTypes\KivaFields.csArmCar 等跨平台车型继承它们)。
//标定轮里程计
[TemplateTrackCoderSettings(
priority = 10,
useVerb = "track.CalibrateWheelEncoder && track.ReverseDst != dst.id",
blockVerb = "true",
templateString = "agv.CalibrateWheelEncoder(${src.x},${src.y},${src.id},${dst.x},${dst.y},${dst.id});",
siteFields = typeof(KivaSiteFields),
trackFields = typeof(KivaTrackFields))]
// 通用避障/IO/纠偏 coder 已抽离为 StandardScene.Coders.*(见类前 [ProgramTrackCoderSettings] 引用)
// 避障区尺寸(2参)切换 coder 已抽离为 StandardScene.Coders.AvoidanceParamLWCoder(方案 B;见下方 [ProgramTrackCoderSettings] 引用)
[ProgramTrackCoderSettings(priority = 20, program = typeof(AvoidanceParamLWCoder))]
//plan.SegN 是指 当前路径所有点线的数量
//plan.SegN-2 就是终点倒数第二个站点
//[TemplateTrackCoderSettings(
// priority = 1,
// templateString = "agv.BasicGo(${src.x},${src.y},${src.id},${dst.x},${dst.y},${dst.id}," +
// "${track.id},${track.Speed},${track.Reverse||(track.ReverseDst==dst.id)}," +
// "${track.CarDirectionBias},${track.EnableCarAbsoluteDirection},${track.CarAbsoluteDirection},${track.typeInfo});",
// siteFields = typeof(KivaSiteFields),
// trackFields = typeof(KivaTrackFields),
// planFields = typeof(KivaPlanFields))]
[TemplateTrackCoderSettings(
priority = 30,
useVerb = "dst.tag>0 && src.tag>0",
templateString = "agv.QrGo(${src.x},${src.y},${src.id},${src.tag},${dst.x},${dst.y},${dst.id},${dst.tag},${track.id}," +
"${track.Speed},${track.Reverse || (track.ReverseDst == dst.id)}," +
"${track.CarDirectionBias},${track.EnableCarAbsoluteDirection},${track.CarAbsoluteDirection}," +
"${track.typeInfo});",
blockVerb = "true",
siteFields = typeof(KivaSiteFields),
trackFields = typeof(KivaTrackFields),
planFields = typeof(KivaPlanFields))]
//[TemplateTrackCoderSettings(
// priority = 2,
// useVerb = "track.ReverseDst==dst.id",
// templateString = "agv.BasicGo(${src.x},${src.y},${src.id},${dst.x},${dst.y},${dst.id},${track.id},${track.speed},0,true);",
// blockVerb = "true",
// siteFields = typeof(KivaSiteFields),
// trackFields = typeof(KivaTrackFields))]
[TemplateTrackCoderSettings(
priority = 20,
useVerb = "plan.action=='fetch' && dst.Shelf&&plan.curSeg==plan.segN-2",
templateString = "agv.Wait();" +
"agv.Fetch(${src.x},${src.y},${src.id},${dst.x},${dst.y},${dst.id}," +
"${dst.FetchSpeed},${dst.FetchLidarArea},${dst.FetchIOArea},${dst.FetchReverse}," +
"${dst.FetchBlindMoveDist},${dst.FetchLiftDownTarget},${dst.FetchLiftUpTarget}," +
"${dst.FetchUseQr},${dst.FetchQrMode},${dst.FetchIsUpQr},${dst.FetchUseDetector},${dst.FetchDetector},${dst.FetchDetectWidth},${dst.FetchDetectDepth}," +
"${plan.CarLength},${plan.CarWidth},${dst.FetchLeaveSrcEarly},${dst.FetchShieldObstacleDist});" +
"agv.Wait();",
blockVerb = "true",
siteFields = typeof(KivaSiteFields),
trackFields = typeof(KivaTrackFields),
planFields = typeof(KivaPlanFields))]
[TemplateTrackCoderSettings(
priority = 20,
useVerb = "plan.action=='put' && dst.Shelf&&plan.curSeg==plan.segN-2",
templateString = "agv.Wait();" +
"agv.Put(${src.x},${src.y},${src.id},${dst.x},${dst.y},${dst.id}," +
"${dst.PutSpeed},${dst.PutLidarArea},${dst.PutIOArea},${dst.PutReverse},${dst.PutSyncRotate}," +
"${dst.PutBlindMoveDist},${dst.PutLiftDownTarget},${dst.PutLiftUpTarget}," +
"${dst.PutUseQr},${dst.PutQrMode},${dst.PutIsUpQr},${dst.PutUseDetector},${dst.PutDetector}," +
"${dst.PutDetectWidth},${dst.PutDetectDepth},${dst.PutLeaveSrcEarly},${dst.PutShieldObstacleDist});" +
"agv.Wait();",
blockVerb = "true",
siteFields = typeof(KivaSiteFields),
trackFields = typeof(KivaTrackFields),
planFields = typeof(KivaPlanFields))]
[TemplateTrackCoderSettings(
priority = 22,
useVerb = "src.Shelf&&plan.curSeg==1",
templateString = "agv.LeaveShelf(${src.x},${src.y},${src.id},${dst.x},${dst.y},${dst.id}," +
"${src.LeaveShelfSpeed},${src.LeaveShelfLidarArea},${src.LeaveShelfIOArea},${src.LeaveShelfReverse},${src.LeaveShelfSyncRotate}," +
"${src.LeaveShelfBlindMoveDist},${src.LeaveShelfLiftDownTarget},${src.LeaveShelfRecoveryObstacleDist},);",
blockVerb = "true",
siteFields = typeof(KivaSiteFields),
trackFields = typeof(KivaTrackFields),
planFields = typeof(KivaPlanFields))]
[TemplateSiteCoderSettings(
priority = 25,
useVerb = "plan.action=='fetch'&& plan.segN == 1 &&dst.Shelf",
templateString =
"agv.FetchInPlace(${dst.FetchUseQr},${dst.FetchQrMode},${dst.FetchIsUpQr},${dst.FetchLiftUpTarget},${plan.CarLength},${plan.CarWidth});" +
"agv.Wait();",
blockVerb = "true",
siteFields = typeof(KivaSiteFields),
trackFields = typeof(KivaTrackFields),
planFields = typeof(KivaPlanFields))]
//[TemplateSiteCoderSettings(
// priority = 100,
// useVerb = "plan.curSeg == 0",
// templateString = "agv.StartingMission();agv.Wait();",
// blockVerb = "false",
// siteFields = typeof(KivaSiteFields),
// trackFields = typeof(KivaTrackFields),
// planFields = typeof(KivaPlanFields))]
[ProgramTrackCoderSettings(priority = 5, program = typeof(KivaCarTrackCoder))]
[ProgramTrackCoderSettings(priority = 19, program = typeof(MagneticTrackCoder))]
[ProgramTrackCoderSettings(priority = 17, program = typeof(LidarAreaSwitchCoder))]
[ProgramTrackCoderSettings(priority = 20, program = typeof(AvoidanceDistanceCoder))]
[ProgramTrackCoderSettings(priority = 20, program = typeof(IoAreaSwitchCoder))]
[ProgramTrackCoderSettings(priority = 20, program = typeof(TrackingErrThreshCoder))]
[CarType(Name = "Kiva", editor = typeof(Kiva))]
[I18N.DocumentTranslation(Name = "Kiva", locale = "en")]
[EnvelopConfig(centerX = 0, centerY = 0, lengthX = 950, lengthY = 650)]
public class Kiva : GhostCar, IScriptErrorRecoverable
{
/// <summary>脚本异常自恢复(AbstractLoopMission 经 IScriptErrorRecoverable 调用),转调 newReset。</summary>
public void RecoverReset(int resetSiteId = 0) => newReset(resetSiteId);
[FieldMember] public float CarLength = 2000;
[FieldMember] public float CarWidth = 1400;
public class RotateSiteEnvelope : SiteEnvelopeDefinition<Kiva>
{
public override bool Use() => true;
public override void Prompt()
{
if (curSeg == 0)
{
return;
}
if (plan.codeArr == null)
{
return;
}
var carConfig = usingCar.GetType().GetCustomAttribute<EnvelopConfigAttribute>();
var carLength = carConfig.lengthX;
var carWidth = carConfig.lengthY;
var code = plan.codeArr[curSeg - 1];
if (code.Contains("Fetch"))
{
var lengthX = plan.fields.TryGetValue("MaterialLength", out var materialLength)
? (int.TryParse(materialLength, out var lengthResult) ? lengthResult > 0 ? lengthResult : carLength : carLength)
: carLength;
var lengthY = plan.fields.TryGetValue("MaterialWidth", out var materialWidth)
? (int.TryParse(materialWidth, out var widthResult) ? widthResult > 0 ? widthResult : carWidth : carWidth)
: carWidth;
reshape(lengthX, lengthY, 0, 0);
}
else if (code.Contains("Put"))
{
reshape(carLength, carWidth, 0, 0);
}
}
public override bool Block() => false;
public override int priority => 1;
}
public static async Task<Kiva> Create()
{
var kiva = new Kiva()
{
lstatus = "连接中",
address = "127.0.0.1",
name = $"Kiva",
haveCoordination = true,
speed = 1,
};
return kiva;
}
private const float DrawWidth = 950, DrawLength = 650;
private readonly Pen orientPen = new Pen(Color.White, 3);
private bool twinkle = false;
protected override void draw(Graphics eGraphics)
{
try
{
var DrawWidth = CarLength;
var DrawLength = CarWidth;
var lineCap = new AdjustableArrowCap(5, 5, true);
orientPen.CustomEndCap = lineCap;
orientPen.StartCap = LineCap.RoundAnchor;
var alarmLevel = Commons.GetCarStatus(this, "AlarmLevel");
var soc = Commons.CarValue((Car)this, "Soc");
var electricCurrent = Commons.CarValue((Car)this, "ElectricCurrent");
var thresSpeed = Commons.CarValue(this, "ThresSpeed");
LadderLogic.FlipFlop(ref twinkle, 500, true, false);
Rectangle destRect = new Rectangle(
(int)((int)(-DrawWidth / 2)),
(int)((int)(-DrawLength / 2)),
(int)((int)DrawWidth),
(int)((int)DrawLength)
);
if (int.TryParse(alarmLevel, out var alarm) && alarm > 0)
{
eGraphics.FillRectangle(twinkle ? Brushes.DarkRed : Brushes.Green, destRect);
}
else if (alarmLevel == "-1" || this.siteID == -1)
eGraphics.FillRectangle(Brushes.LightSeaGreen, destRect);
else if (electricCurrent > 1)
eGraphics.FillRectangle(Brushes.YellowGreen, destRect);
else if (thresSpeed <= 0)
{
eGraphics.FillRectangle(twinkle ? Brushes.YellowGreen : Brushes.Green, destRect);
}
else if (soc < 25)
{
eGraphics.FillRectangle(twinkle ? Brushes.DarkRed : Brushes.LightSeaGreen, destRect);
}
else
eGraphics.FillRectangle(Brushes.Green, destRect);
eGraphics.DrawEllipse(Pens.Yellow, -160, -160, 320, 320);
eGraphics.DrawLine(orientPen, 0, 0, 480, 0);
}
catch (Exception e)
{
Diagnosis.Post("绘制小车异常" + ExceptionFormatter.FormatEx(e), "绘制小车异常", true);
}
}
private bool LoopTestRunning = false;
[MethodMember(Name = "循环测试")]
[I18N.DocumentTranslation(Name = "Loop Test", locale = "en")]
public async void LoopTest()
{
LoopTestRunning = true;
var a = SimpleLib.GetAllSites().First(s => s.name == "A");
var b = SimpleLib.GetAllSites().First(s => s.name == "B");
var plan1 = new SegmentPlan() { usingCar = this };
plan1.fields["action"] = "fetch";
plan1.FindRoute(SimpleLib.GetSite(GetLastSite()), a);
await plan1.Compile("fetch").Queue();
var plan2 = new SegmentPlan() { usingCar = this };
plan2.fields["action"] = "go";
plan2.FindRoute(SimpleLib.GetSite(GetLastSite()), b);
await plan2.Compile("go").Queue();
var plan3 = new SegmentPlan() { usingCar = this };
plan3.fields["action"] = "put";
plan3.FindRoute(SimpleLib.GetSite(GetLastSite()), a);
await plan3.Compile("put").Queue();
var plan4 = new SegmentPlan() { usingCar = this };
plan4.fields["action"] = "go";
plan4.FindRoute(SimpleLib.GetSite(GetLastSite()), b);
await plan4.Compile("go").Queue();
}
[MethodMember(Name = "循环停止测试")]
[I18N.DocumentTranslation(Name = "Stop Loop Test", locale = "en")]
public void LoopTestStop()
{
LoopTestRunning = false;
}
public override string SetDisplayInfo()
{
try
{
var str = $"{name}({id})\n";
status.enums.TryGetValue("ChassisMode", out var manual);
var ms = manual == "0" ? "自动" : "手动";
status.enums.TryGetValue("Soc", out var soc);
str = $"{str}{ms}|soc:{soc}";
var alarmStr = Commons.GetCarStatus(this, "AlarmInfo");
if (alarmStr != "")
str = $"{str}|{alarmStr}";
//if (status.enums.TryGetValue("mAlarm", out var mAlarm) && mAlarm != "")
// str = $"{str}|M:{mAlarm}";
//var hasC = status.enums.TryGetValue("cAlarm", out var cAlarm) && cAlarm != "";
//if (mAlarm != "" && hasC)
// str = $"{str}|";
//if (hasC)
// str = $"{str}C:{cAlarm}";
//var L_step = status.enums.TryGetValue("ls", out var ls) && ls != "";
//if (L_step)
//{
// str = $"{str}|ls:{ls}";
//}
if (status.enums.TryGetValue("ElectricCurrent", out var electricCurrent) &&
!string.IsNullOrEmpty(electricCurrent))
{
var charging = float.Parse(electricCurrent) * 0.01f > 0 ? "充电中" : "未充电";
str = $"{str}|charge:{charging}";
}
var thresSpeed = Commons.CarValue(this, "ThresSpeed");
str = $"{str}{((thresSpeed <= 0) ? "|" : "")}";
return str;
}
catch (Exception e)
{
Console.WriteLine(e);
return "bad car";
}
}
[MethodMember(Name = "进入小车远程", Description = "进入小车远程桌面")]
public void Mstsc()
{
var ip = this.address;
// 启动mstsc并传递IP地址
Process.Start(
new ProcessStartInfo
{
FileName = "mstsc",
Arguments = $"/v:{ip}",
UseShellExecute = false,
CreateNoWindow = true
}
);
}
[MethodMember(Name = "显示车辆监控", Description = "打开车辆状态监控窗口")]
public void ShowVehicleMonitor()
{
VehicleMonitor.ShowMonitor();
}
[MethodMember(Name = "模拟电量", Description = "/")]
public void SimarSoc()
{
if (InputBox.ShowDialog("模拟电量", "模拟电量Soc", "0", InputBox.Buttons.OkCancel) == SimpleLite.DialogResult.Cancel) return;
string value = InputBox.ResultValue;
try
{
status.enums["Soc"] = (60 + int.Parse(value)).ToString();
status.enums["Voltage"] = (50 + int.Parse(value)).ToString();
status.enums["ElectricCurrent"] = (40 + int.Parse(value)).ToString();
}
catch (Exception)
{
}
}
[MethodMember(Name = "立即强制结束new", Description = "立刻终止小车运行,重启C,并让小车下线")]
[I18N.DocumentTranslation(Name = "Force Stop new", Description = "Stop car & reset clumsy", locale = "en")]
public new async void ForceStop()
{
try
{
NoSchedule(true);
siteID = -1;
Commons.ClearTags(tags);
new Thread(() =>
{
try
{
AppendDebug("restarting clumsy");
Get("reset");
AppendDebug("wait for any pending task to flush.");
try
{
status.programs.task.Wait();
}
catch
{
}
AppendDebug("Clumsy Restarted");
NoSchedule(true);
siteID = -1;
Commons.DeleteTag(tags, "occupied");
}
catch (Exception ex)
{
MessageBox.Show(
$@"未能立即强制结束小车{name}({id}),原因:{ExceptionFormatter.FormatEx(ex)}"
);
}
})
{
Name = $"ForceStop:{name}({id})"
}.Start();
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
[MethodMember(Name = "返厂检修new", Description = "不再刷新小车状态,当然也不再被调度")]
[I18N.DocumentTranslation(Name = "Blown new", Description = "Car no longer refresh status and scheduling", locale = "en")]
public new void Blown()
{
AppendDebug("ui-blown");
Diagnosis.Post($"Car {name}({id}) blown");
NoSchedule();
siteID = -1;
tags.Clear();
status.usage.AddUsage(
"base",
new CarUsage.CarUsageInfo { scheduling = false, refreshing = false }
);
lstatus = "返厂检修";
}
[MethodMember(Name = "现场检修new ", Description = "让小车不再被调度,但仍然刷新状态,并且当前点不再使用")]
[I18N.DocumentTranslation(Name = "Repair new", Description = "Car no longer scheduling but still refresh status", locale = "en")]
public new void Repair()
{
AppendDebug("ui-repair");
Diagnosis.Post($"Repair Car {name}({id})");
NoSchedule();
siteID = -1;
Commons.DeleteTag(tags, "occupied");
lstatus = "现场检修";
}
public void newReset(int resetId = 0)
{
//if (!status.programs.task.IsCompleted &&
// MessageBox.Show($"小车{name}({id})当前有任务:{status.programs.printStatus()},确认初始化?", "确认",
// MessageBoxButtons.YesNo) == DialogResult.No) return;
try
{
if (GetLastSite() != -1 && Commons.GetVehicleStatus((Car)this)!= VehicleStatus.NeedInit)
{
Diagnosis.Post($"Car{id}初始化失败:已初始化");
return;
}
var debug = "ui invoke reset. ";
// todo: 找点和线段的最近点。。。
// lock
UISite site1 = null;
float dist = float.MaxValue;
List<(float, Site, Site)> trackList = new List<(float, Site, Site)>();
if (resetId == 0)
{
(float bias, Vector2 hPnt, float fd) Project2DLine(
Vector2 pnt,
Vector2 segSt,
Vector2 segEnd
)
{
var dir = Vector2.Normalize(segEnd - segSt);
var fd = Vector2.Dot(pnt - segSt, dir);
var hPnt = segSt + fd * dir;
var bias = dir.X * (pnt.Y - segSt.Y) - (pnt.X - segSt.X) * dir.Y;
return (bias, hPnt, fd);
}
foreach (var allTrack in SimpleLib.GetAllTracks())
{
var s = SimpleLib.GetSite(allTrack.siteA);
var e = SimpleLib.GetSite(allTrack.siteB);
var (b, h, fd) = Project2DLine(
new Vector2(x, y),
new Vector2(s.x, s.y),
new Vector2(e.x, e.y)
);
b = Math.Abs(b);
if (fd < 0 || fd > 1)
continue;
if (dist > b)
{
UISite ss;
if (fd < 0.5)
ss = (UISite)s;
else
ss = (UISite)e;
foreach (var ccar in SimpleLib.GetAllCars())
{
if (ccar == this)
continue;
if (ccar.status.holdingLocks.Contains(s.id))
goto next;
}
dist = b;
site1 = ss;
next:
;
}
}
float radius = 5000;
//根据小车为圆心找到半径radius内的站点
var surroundSites = SimpleLib.GetAllSites().ToList().FindAll(p => LessMath.dist(x, y, p.x, p.y) <= radius);
foreach (var allTrack in SimpleLib.GetAllTracks())
{
var s = SimpleLib.GetSite(allTrack.siteA);
var e = SimpleLib.GetSite(allTrack.siteB);
if (!surroundSites.Contains(s) && !surroundSites.Contains(e) && surroundSites.Count != 0) continue;
var (b, h, fd) = Project2DLine(new Vector2(x, y), new Vector2(s.x, s.y), new Vector2(e.x, e.y));
b = Math.Abs(b);
double pathAngle = Double.NaN;
if (allTrack.direction == 1)
pathAngle = Math.Atan2((e.y - s.y), (e.x - s.x)) / Math.PI * 180;
else if (allTrack.direction == 2)
pathAngle = Math.Atan2((e.y - s.y), (e.x - s.x)) / Math.PI * 180 + 180;
else
trackList.Add((b, s, e));
if (Math.Abs(this.th - pathAngle) < 30)
trackList.Add((b, s, e));
foreach (var ccar in SimpleLib.GetAllCars())
{
if (ccar == this) continue;
if (ccar.status.holdingLocks.Contains(s.id)) goto next;
}
next:;
}
//var sites = trackList.OrderBy(p => p.Item1).ToArray();
List<(double, Site)> distList = new List<(double, Site)>();
foreach (var site in trackList)
{
if (site.Item2.fields.ContainsKey("no_reset") || site.Item3.fields.ContainsKey("no_reset"))
continue;
foreach (var ccar in SimpleLib.GetAllCars())
{
if (ccar == this) continue;
if (ccar.status.holdingLocks.Contains(site.Item2.id) || ccar.status.holdingLocks.Contains(site.Item3.id)) goto next;
}
distList.Add((LessMath.dist(site.Item2.x, site.Item2.y, x, y), site.Item2));
distList.Add((LessMath.dist(site.Item3.x, site.Item3.y, x, y), site.Item3));
next:;
}
site1 = (UISite)distList.OrderBy(p => p.Item1).First().Item2;
//foreach (var site in SimpleLib.GetAllSites())
//{
// if (site.fields.ContainsKey("no_reset"))
// continue;
// foreach (var ccar in SimpleLib.GetAllCars())
// {
// if (ccar == this)
// continue;
// if (ccar.status.holdingLocks.Contains(site.id))
// goto next;
// }
// var d = LessMath.dist(site.x, site.y, x, y);
// if (d < dist)
// {
// dist = (float)d;
// site1 = (UISite)site;
// }
// next:
// ;
//}
TrafficReset(site1, true, strict: false);
}
else
{
TrafficReset(SimpleLib.GetSite(resetId), true, strict: false);
}
siteID = site1.id;
lstatus = "上线";
debug += $"site={site1.id}";
AppendDebug(debug);
G.pushStatus($"Car {name}({id}) reset to Site {site1.id}");
}
catch (Exception e)
{
Diagnosis.Post($"{this.name}---初始化失败--{e.Message}", $"{this.name}", true);
}
}
HttpClient hc = new HttpClient() { Timeout = TimeSpan.FromMilliseconds(300) };
// [MethodMember(name = "小车暂停", desc = "小车暂停")]
public void EmergencyStop(string reason)
{
Diagnosis.Post($"car{name}:EmergencyStop");
hc.GetStringAsync($"http://{address}:8008/setValue?FieldName=ISRelease&Value=1");
}
//[MethodMember(name = "小车暂停恢复", desc = "小车暂停恢复")]
public void EmergencyRelease()
{
Diagnosis.Post($"car{name}:EmergencyStop release");
hc.GetStringAsync($"http://{address}:8008/setValue?FieldName=ISRelease&Value=0");
}
//[MethodMember(name = "小车重启C", desc = "小车重启Clumsy")]
public void ResetClumsy()
{
Diagnosis.Post($"car{name}-{address}:ResetClumsy");
hc.GetStringAsync($"http://{address}:8008/reset");
}
}
/// <summary>
/// 专门用于控制turn的问题
/// </summary>
public class KivaCarTrackCoder : ITrackCoder
{
public bool toBlock()
{
return false;
}
/// <summary>
/// 用于记录上一路径的角度
/// </summary>
public static double lastTh = -1;
public bool Code(SegmentPlan plan, Track track, Site src, Site dst, int i)
{
var trackInfo = StringDictConvert<KivaTrackFields>.Convert(track.fields);
var srcInfo = StringDictConvert<KivaSiteFields>.Convert(src.fields);
Console.WriteLine($@"srcInfo.Turn:{srcInfo.Turn},i={1}");
if (!srcInfo.Turn) //不包含旋转指令,直接跳过
{
return true;
}
if (i == 1)
{
return true;
}
var lastSite = plan.segments[i - 3] as Site;
if (lastSite == null)
{
return true;
}
var lastPathDir = Math.Round(Math.Atan2(src.y - lastSite.y, src.x - lastSite.x) / Math.PI * 2) * 90;
var curPathDir = Math.Round(Math.Atan2(dst.y - src.y, dst.x - src.x) / Math.PI * 2) * 90;
if (Math.Abs(curPathDir - lastPathDir) < 45)
{
return true;
}
var angleTarget = trackInfo.ReverseDst == dst.id || trackInfo.Reverse ? curPathDir + 180 : curPathDir;
plan.codeArr[i] += $"agv.Wait(); agv.SyncRotate({angleTarget}); agv.Wait(); ";
return true;
}
}
}
@@ -0,0 +1,396 @@
using CommonUsage.Mathematics;
using LessokajiWeaverUtilities.Utilities;
using Nancy.Helpers;
using Newtonsoft.Json;
using SimpleLite;
using SimpleLite.RCS;
using SimpleLite.RCS.CarTypes;
using SimpleLite.CADTools;
using SimpleLite.Props;
using SimpleLite.UI;
using SimpleCore;
using SimpleCore.Compiler;
using SimpleCore.Library;
using SimpleCore.PropType;
using StandardScene.Model;
using StandardScene.Coders;
using StandardScene.Magnetic.Coders;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Net.Http;
using System.Numerics;
using System.Runtime;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using Track = SimpleCore.PropType.Track;
namespace StandardScene.CarTypes
{
// 磁循迹器已与 Kiva 版去重合并为唯一 StandardScene.Magnetic.Coders.MagneticTrackCoder。
// 原 MWL 版 NaiveMagGo 缺少 ${track.Speed},合并后统一为含 Speed 的版本(用户确认)。
// MultiWheelLifter 系字段袋下沉至基座 StandardScene.Core\CarTypes\MultiWheelLifterFields.cs
// MultiVehicleCar 等跨平台车型引用它们)。
[TemplateTrackCoderSettings(
priority = 30,
useVerb = "track.SleepTime!=0",
templateString = "agv.Wait();agv.Sleep(${track.SleepTime});agv.Wait();",
blockVerb = "false",
siteFields = typeof(MultiWheelLifterSiteFields),
trackFields = typeof(MultiWheelLifterTrackFields),
planFields = typeof(MultiWheelLifterPlanFields))]
[TemplateTrackCoderSettings(
priority = 30,
useVerb = "track.TrayTarget!=0",
templateString = "agv.Wait();agv.TrayControl(${track.TrayTarget});agv.Wait();",
blockVerb = "false",
siteFields = typeof(MultiWheelLifterSiteFields),
trackFields = typeof(MultiWheelLifterTrackFields),
planFields = typeof(MultiWheelLifterPlanFields))]
//调整方向+钻车
[TemplateTrackCoderSettings(
priority = 19,
useVerb = "dst.name=='FetchSite'&&plan.action=='fetch'",
templateString = "agv.Wait();agv.RotateToTarget(${plan.AngleTarget});agv.Wait();agv.TireFollowing(${plan.TireNum},${plan.FrontLidarDetect},${plan.FirstTire});agv.Wait();",
blockVerb = "false",
siteFields = typeof(MultiWheelLifterSiteFields),
trackFields = typeof(MultiWheelLifterTrackFields),
planFields = typeof(MultiWheelLifterPlanFields))]
//[TemplateTrackCoderSettings(
// priority = 19,
// useVerb = "dst.FetchPlace && plan.action=='fetch'",
// templateString = "agv.Wait();agv.RotateToTarget(${plan.AngleTarget});agv.Wait();",
// blockVerb = "false",
// siteFields = typeof(MultiWheelLifterSiteFields),
// trackFields = typeof(MultiWheelLifterTrackFields),
// planFields = typeof(MultiWheelLifterPlanFields))]
//下使能+夹抱+上使能
[TemplateTrackCoderSettings(
priority = 18,
useVerb = "dst.name=='FetchSite'&&plan.action=='fetch'",
templateString = "agv.DriverDisable();agv.Wait();agv.ClamptoTarget(${dst.ClampClose});agv.Wait();agv.DriverAble();agv.Wait();",
blockVerb = "true",
siteFields = typeof(MultiWheelLifterSiteFields),
trackFields = typeof(MultiWheelLifterTrackFields),
planFields = typeof(MultiWheelLifterPlanFields))]
[TemplateTrackCoderSettings(
priority = 19,
useVerb = "dst.tag>0 && src.tag>0",
templateString = "agv.QrGo(${src.x},${src.y},${src.id},${src.tag},${dst.x},${dst.y},${dst.id},${dst.tag},${track.id}," +
"${track.Speed},${track.Reverse || (track.ReverseDst == dst.id)}," +
"${track.CarDirectionBias},${track.EnableCarAbsoluteDirection},${track.CarAbsoluteDirection}," +
"${track.typeInfo});",
blockVerb = "true",
siteFields = typeof(MultiWheelLifterSiteFields),
trackFields = typeof(MultiWheelLifterTrackFields),
planFields = typeof(MultiWheelLifterPlanFields))]
// 通用避障/IO coder 已抽离为 StandardScene.Coders.*(见类前 [ProgramTrackCoderSettings] 引用)
// 避障区尺寸(4参,含中心点)切换 coder 已抽离为 StandardScene.Coders.AvoidanceParamCoder(见类前 [ProgramTrackCoderSettings] 引用)
// 纠偏阈值 coder 已抽离为 StandardScene.Coders.TrackingErrThreshCoder
[ProgramTrackCoderSettings(priority = 19, program = typeof(MagneticTrackCoder))]
[ProgramTrackCoderSettings(priority = 27, program = typeof(LidarAreaSwitchCoder))]
[ProgramTrackCoderSettings(priority = 20, program = typeof(AvoidanceDistanceCoder))]
[ProgramTrackCoderSettings(priority = 20, program = typeof(IoAreaSwitchCoder))]
[ProgramTrackCoderSettings(priority = 20, program = typeof(TrackingErrThreshCoder))]
[ProgramTrackCoderSettings(priority = 20, program = typeof(AvoidanceParamCoder))]
[EnvelopConfig(lengthX = 3000, lengthY = 1300, centerX = 0, centerY = 0)]
[CarType(Name = "多舵轮顶升车")]
[I18N.DocumentTranslation(Name = "MultiWheel Lifter Car", locale = "en")]
public class MultiWheelLifterCar : GhostCar
{
[FieldMember]public float CarLength = 2000;
[FieldMember] public float CarWidth = 1400;
public static async Task<MultiWheelLifterCar> Create()
{
var car = new MultiWheelLifterCar()
{
lstatus = "连接中",
address = "127.0.0.1",
name = "多舵轮顶升车"
};
return car;
}
[MethodMember(Name = "进入小车远程", Description = "进入小车远程桌面")]
[I18N.DocumentTranslation(Name = "Open remote desktop",Description = "Open the car's remote desktop", locale = "en")]
public void Mstsc()
{
var ip = this.address;
// 启动mstsc并传递IP地址
Process.Start(
new ProcessStartInfo
{
FileName = "mstsc",
Arguments = $"/v:{ip}",
UseShellExecute = false,
CreateNoWindow = true
}
);
}
public void newTrafficReset(int resetId = 0)
{
//if (!status.programs.task.IsCompleted &&
// MessageBox.Show($"小车{name}({id})当前有任务:{status.programs.printStatus()},确认初始化?", "确认",
// MessageBoxButtons.YesNo) == DialogResult.No) return;
try
{
if (GetLastSite() != -1 && Commons.GetVehicleStatus((Car)this) != VehicleStatus.NeedInit)
{
Diagnosis.Post($"Car{id}初始化失败:已初始化");
return;
}
var debug = "ui invoke reset. ";
// todo: 找点和线段的最近点。。。
// lock
UISite site1 = null;
float dist = float.MaxValue;
List<(float, Site, Site)> trackList = new List<(float, Site, Site)>();
if (resetId == 0)
{
(float bias, Vector2 hPnt, float fd) Project2DLine(
Vector2 pnt,
Vector2 segSt,
Vector2 segEnd
)
{
var dir = Vector2.Normalize(segEnd - segSt);
var fd = Vector2.Dot(pnt - segSt, dir);
var hPnt = segSt + fd * dir;
var bias = dir.X * (pnt.Y - segSt.Y) - (pnt.X - segSt.X) * dir.Y;
return (bias, hPnt, fd);
}
foreach (var allTrack in SimpleLib.GetAllTracks())
{
var s = SimpleLib.GetSite(allTrack.siteA);
var e = SimpleLib.GetSite(allTrack.siteB);
var (b, h, fd) = Project2DLine(
new Vector2(x, y),
new Vector2(s.x, s.y),
new Vector2(e.x, e.y)
);
b = Math.Abs(b);
if (fd < 0 || fd > 1)
continue;
if (dist > b)
{
UISite ss;
if (fd < 0.5)
ss = (UISite)s;
else
ss = (UISite)e;
foreach (var ccar in SimpleLib.GetAllCars())
{
if (ccar == this)
continue;
if (ccar.status.holdingLocks.Contains(s.id))
goto next;
}
dist = b;
site1 = ss;
next:
;
}
}
float radius = 5000;
//根据小车为圆心找到半径radius内的站点
var surroundSites = SimpleLib.GetAllSites().ToList().FindAll(p => LessMath.dist(x, y, p.x, p.y) <= radius);
foreach (var allTrack in SimpleLib.GetAllTracks())
{
var s = SimpleLib.GetSite(allTrack.siteA);
var e = SimpleLib.GetSite(allTrack.siteB);
if (!surroundSites.Contains(s) && !surroundSites.Contains(e) && surroundSites.Count != 0) continue;
var (b, h, fd) = Project2DLine(new Vector2(x, y), new Vector2(s.x, s.y), new Vector2(e.x, e.y));
b = Math.Abs(b);
double pathAngle = Double.NaN;
if (allTrack.direction == 1)
pathAngle = Math.Atan2((e.y - s.y), (e.x - s.x)) / Math.PI * 180;
else if (allTrack.direction == 2)
pathAngle = Math.Atan2((e.y - s.y), (e.x - s.x)) / Math.PI * 180 + 180;
else
trackList.Add((b, s, e));
if (Math.Abs(this.th - pathAngle) < 30)
trackList.Add((b, s, e));
foreach (var ccar in SimpleLib.GetAllCars())
{
if (ccar == this) continue;
if (ccar.status.holdingLocks.Contains(s.id)) goto next;
}
next:;
}
//var sites = trackList.OrderBy(p => p.Item1).ToArray();
List<(double, Site)> distList = new List<(double, Site)>();
foreach (var site in trackList)
{
if (site.Item2.fields.ContainsKey("no_reset") || site.Item3.fields.ContainsKey("no_reset"))
continue;
foreach (var ccar in SimpleLib.GetAllCars())
{
if (ccar == this) continue;
if (ccar.status.holdingLocks.Contains(site.Item2.id) || ccar.status.holdingLocks.Contains(site.Item3.id)) goto next;
}
distList.Add((LessMath.dist(site.Item2.x, site.Item2.y, x, y), site.Item2));
distList.Add((LessMath.dist(site.Item3.x, site.Item3.y, x, y), site.Item3));
next:;
}
site1 = (UISite)distList.OrderBy(p => p.Item1).First().Item2;
//foreach (var site in SimpleLib.GetAllSites())
//{
// if (site.fields.ContainsKey("no_reset"))
// continue;
// foreach (var ccar in SimpleLib.GetAllCars())
// {
// if (ccar == this)
// continue;
// if (ccar.status.holdingLocks.Contains(site.id))
// goto next;
// }
// var d = LessMath.dist(site.x, site.y, x, y);
// if (d < dist)
// {
// dist = (float)d;
// site1 = (UISite)site;
// }
// next:
// ;
//}
TrafficReset(site1, true, strict: false);
}
else
{
TrafficReset(SimpleLib.GetSite(resetId), true, strict: false);
}
siteID = site1.id;
lstatus = "上线";
debug += $"site={site1.id}";
AppendDebug(debug);
G.pushStatus($"Car {name}({id}) reset to Site {site1.id}");
}
catch (Exception e)
{
Diagnosis.Post($"{this.name}---初始化失败--{e.Message}", $"{this.name}", true);
}
}
protected override void draw(Graphics eGraphics)
{
var HalfCarLength = CarLength / 2;
var HalfCarWidth = CarWidth / 2;
eGraphics.FillRectangle(Brushes.Gray, -HalfCarLength, -HalfCarWidth, HalfCarLength * 2, HalfCarWidth * 2);
eGraphics.DrawRectangle(Pens.White, -HalfCarLength, -HalfCarWidth, HalfCarLength * 2, HalfCarWidth * 2);
eGraphics.DrawLine(Pens.White, HalfCarLength - HalfCarWidth, -HalfCarWidth, HalfCarLength, 0);
eGraphics.DrawLine(Pens.White, HalfCarLength - HalfCarWidth, HalfCarWidth, HalfCarLength, 0);
}
public override string SetDisplayInfo()
{
try
{
var str = $"{name}({id})\n";
status.enums.TryGetValue("ChassisMode", out var manual);
var ms = manual == "0" ? "自动" : "手动";
status.enums.TryGetValue("Soc", out var soc);
str = $"{str}{ms}|soc:{soc}";
var alarmStr = Commons.GetCarStatus(this, "AlarmInfo");
if (alarmStr != "")
str = $"{str}|{alarmStr}";
if (status.enums.TryGetValue("ElectricCurrent", out var electricCurrent) &&
!string.IsNullOrEmpty(electricCurrent))
{
var charging = float.Parse(electricCurrent) * 0.01f > 0 ? "充电中" : "未充电";
str = $"{str}|charge:{charging}";
}
var thresSpeed = Commons.CarValue(this, "ThresSpeed");
str = $"{str}{((thresSpeed <= 0) ? "|" : "")}";
return str;
}
catch (Exception e)
{
Console.WriteLine(e);
return "bad car";
}
}
private bool _running = false;
[MethodMember(Name = "测试", Description = "循环")]
[I18N.DocumentTranslation(Name = "Test", Description = "Looping", locale = "en")]
public async void Test()
{
var siteA = SimpleLib.GetAllSites().First(s => s.name == "A");
var siteB = SimpleLib.GetAllSites().First(s => s.name == "B");
_running = true;
while (_running)
{
var plan = new SegmentPlan() { usingCar = this };
plan.FindRoute(SimpleLib.GetSite(this.GetLastSite()), siteA);
await plan.Compile("GoA").Queue();
var planB = new SegmentPlan() { usingCar = this };
planB.FindRoute(SimpleLib.GetSite(this.GetLastSite()), siteB);
await planB.Compile("GoB").Queue();
await Task.Delay(1000);
}
}
[MethodMember(Name = "停止测试")]
[I18N.DocumentTranslation(Name = "Stop Test", Description = "Stop Looping", locale = "en")]
public void Stop()
{
_running = false;
}
HttpClient hc = new HttpClient() { Timeout = TimeSpan.FromMilliseconds(300) };
[MethodMember(Name = "小车暂停", Description = "小车暂停")]
[I18N.DocumentTranslation(Name = "Emergency Stop",Description = "Emergency Stop the car", locale = "en")]
public void EmergencyStop(string reason)
{
Diagnosis.Post($"car{name}:EmergencyStop");
hc.GetStringAsync($"http://{address}:8008/setValue?FieldName=ISRelease&Value=1");
}
[MethodMember(Name = "小车暂停恢复", Description = "小车暂停恢复")]
[I18N.DocumentTranslation(Name = "Emergency Release",Description = "Release Emergency Stop of the car", locale = "en")]
public void EmergencyRelease()
{
Diagnosis.Post($"car{name}:EmergencyStop release");
hc.GetStringAsync($"http://{address}:8008/setValue?FieldName=ISRelease&Value=0");
}
[MethodMember(Name = "小车重启C", Description = "小车重启Clumsy")]
[I18N.DocumentTranslation(Name = "Reset Clumsy",Description = "Reset Clumsy of the car", locale = "en")]
public void ResetClumsy()
{
Diagnosis.Post($"car{name}-{address}:ResetClumsy");
hc.GetStringAsync($"http://{address}:8008/reset");
}
}
}
@@ -0,0 +1,168 @@
using CommonUsage.Mathematics;
using LessokajiWeaverUtilities.Utilities;
using SimpleCore;
using SimpleCore.Compiler;
using SimpleCore.Library;
using SimpleCore.PropType;
using SimpleLite.Props;
using StandardScene.CarTypes;
using System;
using System.Linq;
using System.Numerics;
using Track = SimpleCore.PropType.Track;
namespace StandardScene.Magnetic.Coders
{
/// <summary>
/// 唯一磁循迹器(会话1:Kiva 版与 MWL 版去重;NaiveMagGo 采用含 ${track.Speed} 版本,用户确认)。
/// C2 物理迁出:原暂置 Kiva.csStandardScene.CarTypes),现归 scene.mag 平台插件。
/// 触发条件:track.Magnet=true 且非倒车段;分叉/汇流路口按顺时针序计算 magSelect。
/// </summary>
public class MagneticTrackCoder : ITrackCoder
{
public bool Code(SegmentPlan plan, Track track, Site src, Site dst, int i)
{
Diagnosis.Post("enter code", "MagTrackCoder", true);
if (!track.fields.TryGetValue("Magnet", out var mStr) || !bool.TryParse(mStr, out var mBool) ||
!mBool) return false;
// do not use magnet navigation for reversing
if (track.fields.TryGetValue("ReverseDst", out var reverseStr) && int.TryParse(reverseStr, out var rId) &&
rId == dst.id) return false;
Diagnosis.Post($">>>> checking {track.id}", "MagTrackCoder", true);
var magSelect = -1;
// choose forking/converging path in clockwise order.
// i.e. 0 is the left-most forking. -1 means no choosing
float CalculateTangent(Track myTrack, bool checkSrc, int segId)
{
var anchorSite = (Site)(checkSrc ? plan.segments[segId - 1] : plan.segments[segId + 1]);
if (myTrack is UICircularArcTrack arcTrack)
{
var angleStart = arcTrack.Arc.AngleStart;
var direction = 1;
if (Vector2.Distance(anchorSite.v2, arcTrack.Arc.PointEnd) <
Vector2.Distance(anchorSite.v2, arcTrack.Arc.PointStart))
{
angleStart = arcTrack.Arc.AngleEnd;
direction = -1;
}
angleStart = CommonMath.RoundTh(angleStart);
var myAngle = angleStart + direction * 5;
var arcTh = CommonMath.RoundTh(myAngle + 90 * direction);
Console.WriteLine($">>>> {checkSrc} id: {myTrack.id} th: {arcTh:0.0}");
return arcTh;
}
if (myTrack is UIBezierTrack bTrack)
{
var add180 = 0f;
var controlPoints = bTrack.BezierCurve.ControlPoints;
if (Vector2.Distance(anchorSite.v2, controlPoints[0]) >
Vector2.Distance(anchorSite.v2, controlPoints.Last())) add180 = 180;
var TangentPoint = bTrack.BezierCurve.QueryTangentPoint(anchorSite.v2);
Console.WriteLine($">>>>Bezier {checkSrc} id: {myTrack.id} th: {TangentPoint.Angle:0.0} add:{add180} queryid:{anchorSite.id}");
return TangentPoint.Angle + add180;
}
if (myTrack is UINurbsTrack nTrack)
{
}
// line track
var myDst = SimpleLib.GetSite(myTrack.GetOther(anchorSite.id));
var lineTh = (float)(Math.Atan2(myDst.y - anchorSite.y, myDst.x - anchorSite.x) / Math.PI * 180);
Console.WriteLine($">>>> {checkSrc} id: {myTrack.id} th: {lineTh:0.0}");
return lineTh;
}
bool TestMagSelect(bool checkSrc)
{
var anchorSite = checkSrc ? src : dst;
var myTh = CalculateTangent(track, checkSrc, i);
var allTracks = anchorSite.relatedTracks.Select(ii => SimpleLib.GetTrack(ii)).ToList();
if (allTracks.Count <= 2) return false;
var tracksAndThs = allTracks.Select(myTrack => (myTrack, CalculateTangent(myTrack, checkSrc, i))).ToList();
var sameHalfTracks = tracksAndThs.Where(item => Math.Abs(LessMath.thDiff(item.Item2, myTh)) < 90)
.ToList();
if (sameHalfTracks.Count > 1)
{
sameHalfTracks = checkSrc
? sameHalfTracks.OrderByDescending(item => LessMath.thDiff(item.Item2, myTh)).ToList()
: sameHalfTracks.OrderBy(item => LessMath.thDiff(item.Item2, myTh)).ToList();
Console.WriteLine(
$">>>> {checkSrc} {track.id} ({string.Join(", ", sameHalfTracks.Select(tt => tt.myTrack.id))})");
magSelect = sameHalfTracks.FindIndex(tt => tt.myTrack.id == track.id);
return true;
}
// check if adjacent to forking/converging path
Track adjacentTrack;
int adjacentSegId;
if (checkSrc)
{
if (i <= 1) return false;
adjacentTrack = (Track)plan.segments[i - 2];
adjacentSegId = i - 2;
}
else
{
if (i >= plan.segments.Count - 2) return false;
adjacentTrack = (Track)plan.segments[i + 2];
adjacentSegId = i + 2;
}
var otherHalfTracks = tracksAndThs.Where(item => Math.Abs(LessMath.thDiff(item.Item2, myTh)) >= 90)
.ToList();
var adjacentTh = CalculateTangent(adjacentTrack, !checkSrc, adjacentSegId);
otherHalfTracks = checkSrc
? otherHalfTracks.OrderBy(item => LessMath.thDiff(item.Item2, adjacentTh)).ToList()
: otherHalfTracks.OrderByDescending(item => LessMath.thDiff(item.Item2, adjacentTh)).ToList();
Console.WriteLine($">>>> use adjacentTrack {checkSrc} {adjacentTrack.id} ({string.Join(", ", otherHalfTracks.Select(tt => tt.myTrack.id))})");
magSelect = otherHalfTracks.FindIndex(tt => tt.myTrack.id == adjacentTrack.id);
return true;
}
if (!TestMagSelect(true)) TestMagSelect(false);
Console.WriteLine($">>>> {track.id} select {magSelect}");
var engine = ProgramCoderHelper.PrepareTrackEngine(plan, track, src, dst, i,
carFields: typeof(BasicCarFields),
siteFields: typeof(BasicSiteFields),
trackFields: typeof(BasicTrackFields),
planFields: typeof(BasicPlanFields));
var tStr = "agv.MagGo(${src.x},${src.y},${src.id},${dst.x},${dst.y},${dst.id},${track.id}," +
$"{magSelect}," +
"${track.Speed},${track.Reverse || (track.ReverseDst == dst.id)}," +
"${track.CarDirectionBias},${track.EnableCarAbsoluteDirection},${track.CarAbsoluteDirection});";
if (track.fields.TryGetValue("NaiveMagnet", out var naiveStr) && bool.TryParse(naiveStr, out var naive) &&
naive)
{
var slowDown = i == plan.segments.Count - 2;
if (slowDown && (!dst.fields.TryGetValue("TagValue", out var tagValStr) ||
!int.TryParse(tagValStr, out _)))
throw new Exception($"Destination site {dst.id} must have valid TagValue field!");
Console.WriteLine($">>>> Naive magnet!");
tStr = "agv.NaiveMagGo(${src.id},${dst.id},${track.id}," +
$"{magSelect}," + "${track.Speed}," +
$"{slowDown.ToString().ToLower()}," +
"${dst.TagValue}," +
"${src.x},${src.y},${dst.x},${dst.y},${track.typeInfo});";
}
plan.codeArr[i] += (string)engine.ExecuteExpression($"`{tStr}`", default);
return true;
}
public bool toBlock()
{
return true;
}
}
}
@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using SimpleCore.Navigation;
using StandardScene.CarTypes;
namespace StandardScene.Magnetic
{
/// <summary>
/// scene.mag 平台画像:磁导航场景插件(磁条循迹为主,兼容二维码地标段)。
/// 宿主(SimpleLite)加载本 dll 后反射实例化并 OnActivate / 注册。
/// </summary>
public sealed class MagneticSceneProfile : NavigationProfileBase
{
public override NavKind Kind => NavKind.Magnetic;
public override string SceneId => "scene.mag";
public override string DisplayName => "磁导航平台";
public override IReadOnlyList<Type> CarTypes => new[]
{
typeof(Kiva),
typeof(MultiWheelLifterCar),
};
public override void OnActivate(ISceneContext context)
{
context.Log($"{DisplayName} 已激活(车型:Kiva / 多舵轮顶升车)");
}
}
}
@@ -0,0 +1,54 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
<OutputType>Library</OutputType>
<UseWindowsForms>true</UseWindowsForms>
<RootNamespace>StandardScene.Magnetic</RootNamespace>
<AssemblyName>StandardScene.Magnetic</AssemblyName>
<LangVersion>latest</LangVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Platforms>AnyCPU;x64</Platforms>
<PlatformTarget>x64</PlatformTarget>
<Deterministic>true</Deterministic>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>disable</Nullable>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<NoWarn>$(NoWarn);NU1701;CS0618;CS0612;MSB3277;CA1416</NoWarn>
<AssemblySearchPaths>{HintPathFromItem};{TargetFrameworkDirectory};{RawFileName};{GAC}</AssemblySearchPaths>
</PropertyGroup>
<!-- 插件清单:随 dll 输出,供 SimpleLite plugins 选择性加载(约定 <dll>.scene.json -->
<ItemGroup>
<None Update="StandardScene.Magnetic.scene.json" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<!-- 基座:车型基类字段袋 / 通用 coder / 任务调度等留 Core;本 dll 为 scene.mag 平台(磁导航车型 + 磁循迹 coder) -->
<ItemGroup>
<ProjectReference Include="..\StandardScene.Core\StandardScene.Core.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="SimpleLite">
<HintPath>E:\Work\Core\Simple-FR\Simple\SimpleLite\bin\Debug\SimpleLite.dll</HintPath>
</Reference>
<Reference Include="SimpleCore">
<HintPath>E:\Work\Core\Simple-FR\Simple\SimpleCore\bin\Debug\netstandard2.0\SimpleCore.dll</HintPath>
</Reference>
<Reference Include="CommonUsage">
<HintPath>D:\MDCS\Dependencies\Commons\CommonUsage.dll</HintPath>
</Reference>
<Reference Include="Topaz">
<HintPath>E:\Work\Core\Simple-FR\Simple\tools\Topaz.dll</HintPath>
</Reference>
<Reference Include="LessokajiWeaverUtilities">
<HintPath>E:\Work\Core\Simple-FR\Simple\SimpleLite\bin\Debug\LessokajiWeaverUtilities.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
</ItemGroup>
</Project>
@@ -0,0 +1,12 @@
{
"id": "scene.mag",
"displayName": "磁导航平台",
"navKind": "magnetic",
"assembly": "StandardScene.Magnetic.dll",
"coreVersion": ">=1.0.0",
"requiresCore": "StandardScene.dll",
"provides": {
"carTypes": [ "Kiva", "MultiWheelLifterCar" ],
"missionTypes": []
}
}