556 lines
13 KiB
Markdown
556 lines
13 KiB
Markdown
# CycleGUI 实用示例
|
||||
|
|
|
|||
|
|
## 示例 1:最小应用
|
|||
|
|
|
|||
|
|
最简可运行 CycleGUI 应用:
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
using CycleGUI;
|
|||
|
|
using CycleGUI.Terminals;
|
|||
|
|
|
|||
|
|
LocalTerminal.SetTitle("Hello CycleGUI");
|
|||
|
|
LocalTerminal.AddMenuItem("Exit", LocalTerminal.Terminate);
|
|||
|
|
LocalTerminal.Start();
|
|||
|
|
|
|||
|
|
GUI.PromptPanel(pb =>
|
|||
|
|
{
|
|||
|
|
pb.Panel.ShowTitle("Hello");
|
|||
|
|
pb.Label("Hello from CycleGUI!");
|
|||
|
|
if (pb.Button("Exit"))
|
|||
|
|
pb.Panel.Exit();
|
|||
|
|
});
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 示例 2:多面板导航(LearnCycleGUI 模式)
|
|||
|
|
|
|||
|
|
主面板按钮打开子面板,子面板停靠在左侧:
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
Panel mainPanel = null;
|
|||
|
|
Panel settingsPanel = null;
|
|||
|
|
List<Panel> activePanels = new();
|
|||
|
|
|
|||
|
|
mainPanel = GUI.PromptPanel(pb =>
|
|||
|
|
{
|
|||
|
|
pb.Panel.ShowTitle("Main");
|
|||
|
|
|
|||
|
|
if (pb.Button("Open Settings"))
|
|||
|
|
{
|
|||
|
|
if (!activePanels.Contains(settingsPanel))
|
|||
|
|
{
|
|||
|
|
settingsPanel = GUI.DeclarePanel()
|
|||
|
|
.ShowTitle("Settings")
|
|||
|
|
.InitPosRelative(mainPanel, 0, 16, 0, 1)
|
|||
|
|
.SetDefaultDocking(Panel.Docking.Left);
|
|||
|
|
settingsPanel.Define(CreateSettingsHandler());
|
|||
|
|
activePanels.Add(settingsPanel);
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
settingsPanel.BringToFront();
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
PanelBuilder.CycleGUIHandler CreateSettingsHandler()
|
|||
|
|
{
|
|||
|
|
float volume = 0.5f;
|
|||
|
|
bool darkMode = true;
|
|||
|
|
int quality = 1;
|
|||
|
|
|
|||
|
|
return pb =>
|
|||
|
|
{
|
|||
|
|
pb.SliderFloat("Volume", ref volume, 0, 1);
|
|||
|
|
pb.Toggle("Dark Mode", ref darkMode);
|
|||
|
|
pb.RadioButtons("Quality", new[] { "Low", "Medium", "High" }, ref quality);
|
|||
|
|
|
|||
|
|
pb.Separator();
|
|||
|
|
if (pb.Closing())
|
|||
|
|
{
|
|||
|
|
activePanels.Remove(settingsPanel);
|
|||
|
|
pb.Panel.Exit();
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 示例 3:模态对话框
|
|||
|
|
|
|||
|
|
阻塞式确认对话框,返回用户选择:
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
bool confirmed = false;
|
|||
|
|
|
|||
|
|
GUI.PromptAndWaitPanel(pb =>
|
|||
|
|
{
|
|||
|
|
pb.Panel.ShowTitle("Confirm").Modal(true).AutoSize(true);
|
|||
|
|
pb.Label("Are you sure you want to delete?");
|
|||
|
|
pb.SameLine();
|
|||
|
|
if (pb.Button("Yes", distinct: "yes"))
|
|||
|
|
{
|
|||
|
|
confirmed = true;
|
|||
|
|
pb.Panel.Exit();
|
|||
|
|
}
|
|||
|
|
pb.SameLine();
|
|||
|
|
if (pb.Button("No", distinct: "no"))
|
|||
|
|
{
|
|||
|
|
pb.Panel.Exit();
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if (confirmed) { /* proceed */ }
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 示例 4:实时数据面板
|
|||
|
|
|
|||
|
|
后台线程更新数据,面板实时刷新:
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
float temperature = 20f;
|
|||
|
|
float humidity = 50f;
|
|||
|
|
bool running = true;
|
|||
|
|
|
|||
|
|
var panel = GUI.DeclarePanel()
|
|||
|
|
.ShowTitle("Sensor Monitor")
|
|||
|
|
.InitSize(350, 200)
|
|||
|
|
.SetDefaultDocking(Panel.Docking.Right);
|
|||
|
|
|
|||
|
|
panel.Define(pb =>
|
|||
|
|
{
|
|||
|
|
pb.MiniPlot("Temperature", temperature);
|
|||
|
|
pb.MiniPlot("Humidity", humidity);
|
|||
|
|
pb.RealtimePlot("Temp Chart", temperature);
|
|||
|
|
|
|||
|
|
pb.Separator();
|
|||
|
|
if (pb.Button("Stop"))
|
|||
|
|
running = false;
|
|||
|
|
|
|||
|
|
pb.Panel.Repaint(); // 实时数据:handler 内持续 Repaint
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
Task.Run(() =>
|
|||
|
|
{
|
|||
|
|
var rng = new Random();
|
|||
|
|
while (running)
|
|||
|
|
{
|
|||
|
|
temperature = 20f + (float)rng.NextDouble() * 5;
|
|||
|
|
humidity = 50f + (float)rng.NextDouble() * 10;
|
|||
|
|
Thread.Sleep(100);
|
|||
|
|
// 也可在此 panel.Repaint() 替代 handler 内 Repaint,二选一
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 示例 5:表格展示与交互
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
string[] names = { "Alice", "Bob", "Charlie", "Diana" };
|
|||
|
|
float[] scores = { 95, 87, 73, 91 };
|
|||
|
|
bool[] enabled = { true, true, false, true };
|
|||
|
|
|
|||
|
|
panel.Define(pb =>
|
|||
|
|
{
|
|||
|
|
pb.Table("students", new[] { "Name", "Score", "Enabled", "Action" },
|
|||
|
|
names.Length, (row, i) =>
|
|||
|
|
{
|
|||
|
|
row.Label(names[i]);
|
|||
|
|
row.Label(scores[i].ToString("F1"));
|
|||
|
|
row.Checkbox(ref enabled[i]);
|
|||
|
|
if (row.ButtonGroup("", new[] { "Edit", "Delete" }, out int act))
|
|||
|
|
{
|
|||
|
|
if (act == 0) Console.WriteLine($"Edit {names[i]}");
|
|||
|
|
if (act == 1) Console.WriteLine($"Delete {names[i]}");
|
|||
|
|
}
|
|||
|
|
}, height: 200, enableSearch: true, title: "Student Records");
|
|||
|
|
});
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 示例 6:3D 场景 - 加载模型和点云
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
using CycleGUI;
|
|||
|
|
using CycleGUI.API;
|
|||
|
|
using System.Numerics;
|
|||
|
|
|
|||
|
|
// 加载模型类
|
|||
|
|
Workspace.AddProp(new LoadModel
|
|||
|
|
{
|
|||
|
|
name = "car_model",
|
|||
|
|
detail = new Workspace.ModelDetail(File.ReadAllBytes("car.glb"))
|
|||
|
|
{
|
|||
|
|
Scale = 0.001f,
|
|||
|
|
Center = new Vector3(0, 0, 0),
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 放置两个实例
|
|||
|
|
Workspace.AddProp(new PutModelObject
|
|||
|
|
{
|
|||
|
|
name = "car_a",
|
|||
|
|
clsName = "car_model",
|
|||
|
|
newPosition = new Vector3(0, 0, 0),
|
|||
|
|
newQuaternion = Quaternion.Identity,
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
Workspace.AddProp(new PutModelObject
|
|||
|
|
{
|
|||
|
|
name = "car_b",
|
|||
|
|
clsName = "car_model",
|
|||
|
|
newPosition = new Vector3(5, 0, 0),
|
|||
|
|
newQuaternion = Quaternion.CreateFromAxisAngle(Vector3.UnitZ, MathF.PI / 2),
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 添加地面点云
|
|||
|
|
var gridPoints = new List<Vector4>();
|
|||
|
|
var gridColors = new List<uint>();
|
|||
|
|
for (float x = -10; x <= 10; x += 0.5f)
|
|||
|
|
for (float y = -10; y <= 10; y += 0.5f)
|
|||
|
|
{
|
|||
|
|
gridPoints.Add(new Vector4(x, y, 0, 2));
|
|||
|
|
gridColors.Add(0xFF808080);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Workspace.AddProp(new PutPointCloud
|
|||
|
|
{
|
|||
|
|
name = "ground_grid",
|
|||
|
|
xyzSzs = gridPoints.ToArray(),
|
|||
|
|
colors = gridColors.ToArray(),
|
|||
|
|
newPosition = Vector3.Zero,
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 设置相机
|
|||
|
|
new SetCamera
|
|||
|
|
{
|
|||
|
|
lookAt = new Vector3(2.5f, 0, 0),
|
|||
|
|
altitude = MathF.PI / 4,
|
|||
|
|
azimuth = -MathF.PI / 3,
|
|||
|
|
distance = 15f,
|
|||
|
|
}.IssueToAllTerminals();
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 示例 7:Painter 实时调试绘制
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
var painter = Painter.GetPainter("debug_overlay");
|
|||
|
|
|
|||
|
|
Task.Run(() =>
|
|||
|
|
{
|
|||
|
|
float t = 0;
|
|||
|
|
while (true)
|
|||
|
|
{
|
|||
|
|
painter.Clear();
|
|||
|
|
|
|||
|
|
// 绘制坐标轴
|
|||
|
|
painter.DrawLine(Color.Red, Vector3.Zero, Vector3.UnitX * 2, 2f,
|
|||
|
|
Painter.ArrowType.End);
|
|||
|
|
painter.DrawLine(Color.Green, Vector3.Zero, Vector3.UnitY * 2, 2f,
|
|||
|
|
Painter.ArrowType.End);
|
|||
|
|
painter.DrawLine(Color.Blue, Vector3.Zero, Vector3.UnitZ * 2, 2f,
|
|||
|
|
Painter.ArrowType.End);
|
|||
|
|
|
|||
|
|
// 绘制旋转点
|
|||
|
|
float x = MathF.Cos(t) * 3;
|
|||
|
|
float y = MathF.Sin(t) * 3;
|
|||
|
|
painter.DrawDot(Color.Yellow, new Vector3(x, y, 0), 5f);
|
|||
|
|
painter.DrawText(Color.White, new Vector3(x, y, 0.3f),
|
|||
|
|
$"({x:F1}, {y:F1})");
|
|||
|
|
|
|||
|
|
// 绘制轨迹线
|
|||
|
|
for (int i = 0; i < 20; i++)
|
|||
|
|
{
|
|||
|
|
float t1 = t - i * 0.1f;
|
|||
|
|
float t2 = t - (i + 1) * 0.1f;
|
|||
|
|
painter.DrawLine(Color.FromArgb(255 - i * 12, 255, 255, 0),
|
|||
|
|
new Vector3(MathF.Cos(t1) * 3, MathF.Sin(t1) * 3, 0),
|
|||
|
|
new Vector3(MathF.Cos(t2) * 3, MathF.Sin(t2) * 3, 0));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
t += 0.05f;
|
|||
|
|
Thread.Sleep(33);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 示例 8:对象选择与 Guizmo 操作
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
SelectObject selectOp = null;
|
|||
|
|
GuizmoAction guizmoOp = null;
|
|||
|
|
string selectedObject = null;
|
|||
|
|
|
|||
|
|
void StartSelect()
|
|||
|
|
{
|
|||
|
|
selectOp = new SelectObject();
|
|||
|
|
selectOp.feedback = (results, op) =>
|
|||
|
|
{
|
|||
|
|
if (results.Length > 0)
|
|||
|
|
{
|
|||
|
|
selectedObject = results[0].name;
|
|||
|
|
Console.WriteLine($"Selected: {selectedObject}");
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
selectOp.Start();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void StartGuizmo()
|
|||
|
|
{
|
|||
|
|
if (selectedObject == null) return;
|
|||
|
|
selectOp?.End();
|
|||
|
|
|
|||
|
|
guizmoOp = new GuizmoAction
|
|||
|
|
{
|
|||
|
|
dof = GuizmoAction.GuizmoDof.PlanarXYYaw,
|
|||
|
|
realtimeResult = true,
|
|||
|
|
};
|
|||
|
|
guizmoOp.feedback = (results, op) =>
|
|||
|
|
{
|
|||
|
|
foreach (var (name, pos, rot) in results)
|
|||
|
|
Console.WriteLine($"Moved {name} to {pos}");
|
|||
|
|
};
|
|||
|
|
guizmoOp.finished = () => StartSelect();
|
|||
|
|
guizmoOp.Start();
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 示例 9:拾取坐标绘制线段
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
Vector3? lineStart = null;
|
|||
|
|
|
|||
|
|
var getPos = new GetPosition
|
|||
|
|
{
|
|||
|
|
Name = "Draw Line",
|
|||
|
|
method = GetPosition.PickMode.GridPlane,
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
getPos.feedback = (wp, op) =>
|
|||
|
|
{
|
|||
|
|
if (lineStart == null)
|
|||
|
|
{
|
|||
|
|
lineStart = wp.mouse_pos;
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
Workspace.AddProp(new PutStraightLine
|
|||
|
|
{
|
|||
|
|
name = $"line_{DateTime.Now.Ticks}",
|
|||
|
|
start = lineStart.Value,
|
|||
|
|
end = wp.mouse_pos,
|
|||
|
|
color = Color.Cyan,
|
|||
|
|
width = 2,
|
|||
|
|
arrowType = Painter.ArrowType.End,
|
|||
|
|
});
|
|||
|
|
lineStart = null;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
getPos.Start();
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 示例 10:Medulla2 风格的 IOObject 面板
|
|||
|
|
|
|||
|
|
Medulla2 中每个设备对象(IOObject)通过 `OpenUI` 打开独立的控制面板:
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
public class Camera3DIOObject : IOObject
|
|||
|
|
{
|
|||
|
|
private Panel uiPanel;
|
|||
|
|
private bool displaying = false;
|
|||
|
|
|
|||
|
|
public void OpenUI()
|
|||
|
|
{
|
|||
|
|
displaying = true;
|
|||
|
|
uiPanel = GUI.DeclarePanel()
|
|||
|
|
.ShowTitle($"Camera: {Name}")
|
|||
|
|
.SetDefaultDocking(Panel.Docking.Right);
|
|||
|
|
uiPanel.Define(pb =>
|
|||
|
|
{
|
|||
|
|
if (pb.Button("Capture")) TakeSnapshot();
|
|||
|
|
pb.Toggle("Live View", ref displaying);
|
|||
|
|
pb.MiniPlot("FPS", currentFps);
|
|||
|
|
|
|||
|
|
if (pb.Closing())
|
|||
|
|
{
|
|||
|
|
displaying = false;
|
|||
|
|
Painter.GetPainter(Name).Clear();
|
|||
|
|
uiPanel.Exit();
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void CloseUI()
|
|||
|
|
{
|
|||
|
|
displaying = false;
|
|||
|
|
Painter.GetPainter(Name).Clear();
|
|||
|
|
uiPanel?.Exit();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void draw()
|
|||
|
|
{
|
|||
|
|
if (!displaying) return;
|
|||
|
|
var pp = Painter.GetPainter(Name);
|
|||
|
|
pp.Clear();
|
|||
|
|
foreach (var pt in cachedPoints)
|
|||
|
|
{
|
|||
|
|
var cc = Color.FromArgb(pt.r, pt.g, pt.b);
|
|||
|
|
pp.DrawDot(cc, new Vector3(pt.X, pt.Y, pt.Z) / 1000f, 1);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 示例 11:工具栏面板
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
var toolbar = GUI.DeclarePanel()
|
|||
|
|
.ShowTitle(null)
|
|||
|
|
.AsToolbarPanel(38, Panel.ToolbarAnchor.RightTop);
|
|||
|
|
|
|||
|
|
toolbar.Define(pb =>
|
|||
|
|
{
|
|||
|
|
var tpb = new ToolbarPanelBuilder(pb);
|
|||
|
|
tpb.Label($"{IconFonts.ForkAwesome.Cube} Scene");
|
|||
|
|
tpb.Separator();
|
|||
|
|
|
|||
|
|
if (tpb.Button($"{IconFonts.ForkAwesome.Play} Run", distinct: "tb-run"))
|
|||
|
|
StartSimulation();
|
|||
|
|
|
|||
|
|
if (tpb.Button($"{IconFonts.ForkAwesome.Stop} Stop", distinct: "tb-stop"))
|
|||
|
|
StopSimulation();
|
|||
|
|
|
|||
|
|
tpb.PopMenuButton($"{IconFonts.ForkAwesome.Camera} View", new[]
|
|||
|
|
{
|
|||
|
|
new MenuItem("Front", () => new SetCamera { azimuth = 0, altitude = MathF.PI/2 }.IssueToDefault()),
|
|||
|
|
new MenuItem("Top", () => new SetCamera { azimuth = 0, altitude = 0 }.IssueToDefault()),
|
|||
|
|
new MenuItem("Reset", () => new SetCamera().IssueToDefault()),
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 示例 12:同时启用 Local + Web 终端
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
static void Main(string[] args)
|
|||
|
|
{
|
|||
|
|
LocalTerminal.SetTitle("Dual Terminal App");
|
|||
|
|
LocalTerminal.SetIcon(LoadIcon(), "DualApp");
|
|||
|
|
LocalTerminal.AddMenuItem("Exit", LocalTerminal.Terminate);
|
|||
|
|
LocalTerminal.Start();
|
|||
|
|
|
|||
|
|
// Web 终端在后台启动
|
|||
|
|
Task.Run(() =>
|
|||
|
|
{
|
|||
|
|
var htdocs = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "htdocs");
|
|||
|
|
if (Directory.Exists(htdocs))
|
|||
|
|
LeastServer.AddServingFiles("/", htdocs);
|
|||
|
|
|
|||
|
|
// 添加自定义 API
|
|||
|
|
LeastServer.AddGetHandler("/api/status", () =>
|
|||
|
|
JsonConvert.SerializeObject(new { status = "running", time = DateTime.Now }));
|
|||
|
|
|
|||
|
|
WebTerminal.Use(port: 8081, ico: LoadIcon());
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 远程连接时显示的面板
|
|||
|
|
Terminal.RegisterRemotePanel(terminal =>
|
|||
|
|
CreateMainPanel(terminal));
|
|||
|
|
|
|||
|
|
// 本地面板
|
|||
|
|
GUI.PromptPanel(CreateMainPanel(GUI.defaultTerminal));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static PanelBuilder.CycleGUIHandler CreateMainPanel(Terminal t)
|
|||
|
|
{
|
|||
|
|
return pb =>
|
|||
|
|
{
|
|||
|
|
pb.Panel.ShowTitle("Control Panel");
|
|||
|
|
pb.Label($"Terminal: {t.GetType().Name}");
|
|||
|
|
// 同样的 UI 在两个终端上都可见...
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 图标统一用 .ico 字节(也可 File.ReadAllBytes 从磁盘读)
|
|||
|
|
// 需 csproj: <EmbeddedResource Include="app_icon.ico" />;exe 图标另用 <ApplicationIcon>
|
|||
|
|
static byte[] LoadIcon()
|
|||
|
|
{
|
|||
|
|
var asm = Assembly.GetExecutingAssembly();
|
|||
|
|
using var s = asm.GetManifestResourceStream(
|
|||
|
|
asm.GetManifestResourceNames().First(p => p.Contains(".ico")));
|
|||
|
|
return new BinaryReader(s).ReadBytes((int)s.Length);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 示例 13:Painter 填充多边形
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
var painter = Painter.GetPainter("polygon_demo");
|
|||
|
|
painter.Clear();
|
|||
|
|
|
|||
|
|
// 绘制地面上的三角形
|
|||
|
|
var triangle = new[]
|
|||
|
|
{
|
|||
|
|
new Vector2(0, 0),
|
|||
|
|
new Vector2(1, 0),
|
|||
|
|
new Vector2(0.5f, 0.866f),
|
|||
|
|
};
|
|||
|
|
painter.DrawPolygonFilled(triangle,
|
|||
|
|
trans: new Vector3(0, 0, 0),
|
|||
|
|
quat: Quaternion.Identity,
|
|||
|
|
borderColor: Color.White,
|
|||
|
|
fillColor: Color.FromArgb(128, Color.Green));
|
|||
|
|
|
|||
|
|
// 拉伸为柱体
|
|||
|
|
painter.DrawPolygonFilled(triangle,
|
|||
|
|
trans: new Vector3(3, 0, 0),
|
|||
|
|
quat: Quaternion.Identity,
|
|||
|
|
borderColor: Color.Yellow,
|
|||
|
|
fillColor: Color.FromArgb(100, Color.Blue),
|
|||
|
|
thickness: 1.5f);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 示例 14:Medulla2 插件结构
|
|||
|
|
|
|||
|
|
Medulla2 插件 DLL 必须暴露 `MainIOObject` 类:
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
// MyPlugin/MainIOObject.cs
|
|||
|
|
public class MainIOObject : IOObject
|
|||
|
|
{
|
|||
|
|
public object init(string typeName, dynamic[] args)
|
|||
|
|
{
|
|||
|
|
// 根据 typeName 创建设备实例
|
|||
|
|
if (typeName == "sensor_a")
|
|||
|
|
return new SensorA((string)args[0], (int)args[1]);
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// MyPlugin/SensorA.cs
|
|||
|
|
public class SensorA : IOObject
|
|||
|
|
{
|
|||
|
|
public SensorA(string port, int baudRate) { /* init */ }
|
|||
|
|
|
|||
|
|
[IOObjectUtility]
|
|||
|
|
public void Calibrate() { /* 显示为 UI 按钮 */ }
|
|||
|
|
|
|||
|
|
[IOObjectMonitor]
|
|||
|
|
public float Temperature => ReadTemp();
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
csproj 引用 refasmer 生成的参考程序集:
|
|||
|
|
```xml
|
|||
|
|
<Reference Include="RefMedullaCore">
|
|||
|
|
<HintPath>$(ReleaseDir)\RefMedullaCore.dll</HintPath>
|
|||
|
|
</Reference>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
输出到插件目录:
|
|||
|
|
```xml
|
|||
|
|
<OutputPath>$(DebugDir)\plugins</OutputPath>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
加载方式(在 .iocmd 脚本中):
|
|||
|
|
```
|
|||
|
|
sensor = io load plugins/MyPlugin.dll
|
|||
|
|
mySensor = sensor init sensor_a COM3 115200
|
|||
|
|
```
|