将Feature/XP.Common和Feature/XP.Hardware分支合并至Develop/XP.forHardwareAndCommon,完善XPapp注册和相关硬件类库通用类库功能。

This commit is contained in:
QI Mingxuan
2026-04-16 17:31:13 +08:00
parent 6ec4c3ddaa
commit 2bd6e566c3
581 changed files with 74600 additions and 222 deletions
@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<!-- ==================== 运动控制配置 | Motion Control Configuration ==================== -->
<!-- 直线轴配置(单位:mm| Linear axis config (unit: mm) -->
<add key="MotionControl:SourceZ:Min" value="0" />
<add key="MotionControl:SourceZ:Max" value="500" />
<add key="MotionControl:SourceZ:Origin" value="0" />
<add key="MotionControl:DetectorZ:Min" value="0" />
<add key="MotionControl:DetectorZ:Max" value="600" />
<add key="MotionControl:DetectorZ:Origin" value="0" />
<add key="MotionControl:StageX:Min" value="-150" />
<add key="MotionControl:StageX:Max" value="150" />
<add key="MotionControl:StageX:Origin" value="0" />
<add key="MotionControl:StageY:Min" value="-150" />
<add key="MotionControl:StageY:Max" value="150" />
<add key="MotionControl:StageY:Origin" value="0" />
<!-- 旋转轴配置(单位:度)| Rotary axis config (unit: degrees) -->
<add key="MotionControl:DetectorSwing:Min" value="-45" />
<add key="MotionControl:DetectorSwing:Max" value="45" />
<add key="MotionControl:DetectorSwing:Origin" value="0" />
<add key="MotionControl:DetectorSwing:Enabled" value="true" />
<add key="MotionControl:StageRotation:Min" value="-360" />
<add key="MotionControl:StageRotation:Max" value="360" />
<add key="MotionControl:StageRotation:Origin" value="0" />
<add key="MotionControl:StageRotation:Enabled" value="true" />
<add key="MotionControl:FixtureRotation:Min" value="-90" />
<add key="MotionControl:FixtureRotation:Max" value="90" />
<add key="MotionControl:FixtureRotation:Origin" value="0" />
<add key="MotionControl:FixtureRotation:Enabled" value="true" />
<!-- 几何原点(mm| Geometry origins (mm) -->
<add key="MotionControl:Geometry:SourceZOrigin" value="0" />
<add key="MotionControl:Geometry:DetectorZOrigin" value="600" />
<add key="MotionControl:Geometry:StageRotationCenterZ" value="300" />
<!-- 探测器摆动几何参数(mm| Detector swing geometry parameters (mm) -->
<!-- SwingPivotOffset: Pivot 相对于 DetectorZ 绝对坐标的 Z 方向偏移,正值表示 Pivot 在 DetectorZ_abs 上方 -->
<!-- SwingPivotOffset: Pivot Z offset relative to DetectorZ absolute, positive = above -->
<add key="MotionControl:Geometry:SwingPivotOffset" value="0" />
<!-- SwingRadius: Pivot 到探测器感光面中心的距离,为 0 时退化为无摆动模型 -->
<!-- SwingRadius: Distance from Pivot to detector active area center, 0 = no swing model -->
<add key="MotionControl:Geometry:SwingRadius" value="0" />
<!-- 运行参数 | Runtime parameters -->
<add key="MotionControl:PollingInterval" value="100" />
<add key="MotionControl:DefaultVelocity" value="500" />
</appSettings>
@@ -0,0 +1,322 @@
# 运动控制模块集成指南 | Motion Control Module Integration Guide
## 1. 模块注册 | Module Registration
`App.xaml.cs` 中注册模块:
```csharp
using XP.Hardware.MotionControl.Module;
protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
{
moduleCatalog.AddModule<MotionControlModule>();
}
```
模块启动时自动完成:
- 从 App.config 加载配置(轴范围、几何原点、轮询周期)
- 注册 `IMotionSystem``IMotionControlService``GeometryCalculator` 为单例
- 注册多语言资源到 Fallback Chain
- 启动 PLC 状态轮询(100ms 周期)
---
## 2. 获取轴状态和位置 | Reading Axis Status and Position
通过 DI 注入 `IMotionSystem`
```csharp
using XP.Hardware.MotionControl.Abstractions;
using XP.Hardware.MotionControl.Abstractions.Enums;
public class YourService
{
private readonly IMotionSystem _motionSystem;
public YourService(IMotionSystem motionSystem)
{
_motionSystem = motionSystem;
}
public void ReadStatus()
{
// 获取直线轴 | Get linear axis
ILinearAxis sourceZ = _motionSystem.GetLinearAxis(AxisId.SourceZ);
double position = sourceZ.ActualPosition; // 实际位置(mm
AxisStatus status = sourceZ.Status; // Idle/Moving/Homing/Error/Alarm
bool posLimit = sourceZ.PositiveLimitHit; // 正限位
bool negLimit = sourceZ.NegativeLimitHit; // 负限位
// 获取旋转轴 | Get rotary axis
IRotaryAxis stageRot = _motionSystem.GetRotaryAxis(RotaryAxisId.StageRotation);
double angle = stageRot.ActualAngle; // 实际角度(度)
bool enabled = stageRot.Enabled; // 是否启用
// 获取安全门 | Get safety door
ISafetyDoor door = _motionSystem.SafetyDoor;
DoorStatus doorStatus = door.Status; // Unknown/Opening/Open/Closing/Closed/Locked/Error
bool interlocked = door.IsInterlocked; // 联锁信号
// 遍历所有轴 | Iterate all axes
foreach (var kvp in _motionSystem.LinearAxes)
{
AxisId id = kvp.Key;
ILinearAxis axis = kvp.Value;
// ...
}
foreach (var kvp in _motionSystem.RotaryAxes)
{
RotaryAxisId id = kvp.Key;
IRotaryAxis axis = kvp.Value;
// ...
}
}
}
```
---
## 3. 下发运动控制命令 | Sending Motion Commands
通过 DI 注入 `IMotionControlService`
```csharp
using XP.Hardware.MotionControl.Abstractions;
using XP.Hardware.MotionControl.Abstractions.Enums;
using XP.Hardware.MotionControl.Services;
public class YourController
{
private readonly IMotionControlService _mc;
public YourController(IMotionControlService motionControlService)
{
_mc = motionControlService;
}
// 单轴移动 | Single axis move
public void MoveSingleAxis()
{
MotionResult result = _mc.MoveToTarget(AxisId.SourceZ, 100.0);
if (!result.Success)
Console.WriteLine($"移动失败: {result.ErrorMessage}");
// 带速度参数 | With speed parameter
_mc.MoveToTarget(AxisId.SourceZ, 100.0, speed: 50.0);
// 旋转轴 | Rotary axis
_mc.MoveRotaryToTarget(RotaryAxisId.DetectorSwing, 15.0);
_mc.MoveRotaryToTarget(RotaryAxisId.DetectorSwing, 15.0, speed: 30.0);
}
// 多轴联动 | Multi-axis coordinated move
public void MoveMultipleAxes()
{
var targets = new Dictionary<AxisId, double>
{
{ AxisId.SourceZ, 100.0 },
{ AxisId.DetectorZ, 400.0 },
{ AxisId.StageX, 0.0 },
{ AxisId.StageY, 0.0 }
};
// 原子性边界检查:任意轴越界则全部拒绝
MotionResult result = _mc.MoveAllToTarget(targets);
}
// 停止和回零 | Stop and Home
public void StopAndHome()
{
_mc.StopAll(); // 停止所有已启用轴
_mc.HomeAll(); // 所有已启用轴回零
}
// Jog 点动 | Jog
public void JogAxis()
{
_mc.JogStart(AxisId.SourceZ, positive: true); // 正向 Jog
// ... 用户松开按钮时 ...
_mc.JogStop(AxisId.SourceZ); // 停止 Jog
// 旋转轴 Jog
_mc.JogRotaryStart(RotaryAxisId.DetectorSwing, positive: false);
_mc.JogRotaryStop(RotaryAxisId.DetectorSwing);
}
// 安全门控制 | Safety door control
public void DoorControl()
{
MotionResult openResult = _mc.OpenDoor(); // 含联锁检查
_mc.CloseDoor();
_mc.StopDoor();
}
}
```
---
## 4. 几何计算 | Geometry Calculation
```csharp
public class YourGeometryUser
{
private readonly IMotionControlService _mc;
private readonly GeometryCalculator _calc;
public YourGeometryUser(
IMotionControlService mc,
GeometryCalculator calc)
{
_mc = mc;
_calc = calc;
}
// 正算:获取当前 FOD/FDD/放大倍率 | Forward: get current geometry
public void GetCurrentGeometry()
{
var (fod, fdd, magnification) = _mc.GetCurrentGeometry();
Console.WriteLine($"FOD={fod:F2}mm, FDD={fdd:F2}mm, M={magnification:F3}");
}
// 反算:由目标 FOD+FDD 移动轴 | Inverse: move axes by target FOD+FDD
public void ApplyGeometry()
{
MotionResult result = _mc.ApplyGeometry(targetFOD: 200.0, targetFDD: 500.0);
}
// 反算:由目标 FOD+放大倍率 移动轴 | Inverse: move axes by target FOD+magnification
public void ApplyByMagnification()
{
MotionResult result = _mc.ApplyGeometryByMagnification(targetFOD: 200.0, magnification: 2.5);
}
// 直接使用 GeometryCalculator | Use GeometryCalculator directly
public void DirectCalculation()
{
double fod = _calc.CalcFOD(sourceZAbsolute: 100.0, stageRotationCenterZ: 300.0);
double fdd = _calc.CalcFDD(sourceZAbsolute: 100.0, detectorZAbsolute: 600.0);
double mag = _calc.CalcMagnification(fdd, fod); // FOD < 0.001 时返回 NaN
// 反算轴目标位置
var (szTarget, dzTarget) = _calc.CalcAxisTargets(
targetFOD: 200.0, targetFDD: 500.0,
stageRotationCenterZ: 300.0,
sourceZOrigin: 0.0, detectorZOrigin: 600.0);
}
}
```
---
## 5. 事件订阅 | Event Subscription
通过 Prism `IEventAggregator` 被动接收状态变化:
```csharp
using Prism.Events;
using XP.Hardware.MotionControl.Abstractions.Enums;
using XP.Hardware.MotionControl.Abstractions.Events;
public class YourMonitor
{
public YourMonitor(IEventAggregator eventAggregator)
{
// 轴状态变化 | Axis status changed
eventAggregator.GetEvent<AxisStatusChangedEvent>()
.Subscribe(data =>
{
Console.WriteLine($"轴 {data.AxisId} 状态变为 {data.Status}");
}, ThreadOption.UIThread);
// 门状态变化 | Door status changed
eventAggregator.GetEvent<DoorStatusChangedEvent>()
.Subscribe(status =>
{
Console.WriteLine($"门状态: {status}");
}, ThreadOption.UIThread);
// 门联锁状态变化 | Door interlock status changed
eventAggregator.GetEvent<DoorInterlockChangedEvent>()
.Subscribe(isInterlocked =>
{
Console.WriteLine($"联锁状态: {(isInterlocked ? "" : "")}");
}, ThreadOption.UIThread);
// 几何参数更新(每轮询周期触发)| Geometry updated (every polling cycle)
eventAggregator.GetEvent<GeometryUpdatedEvent>()
.Subscribe(data =>
{
Console.WriteLine($"FOD={data.FOD:F2}, FDD={data.FDD:F2}, M={data.Magnification:F3}");
}, ThreadOption.UIThread);
// 运动错误(轴进入 Error/Alarm| Motion error
eventAggregator.GetEvent<MotionErrorEvent>()
.Subscribe(data =>
{
Console.WriteLine($"运动错误: 轴 {data.AxisId} - {data.ErrorMessage}");
}, ThreadOption.UIThread);
}
}
```
---
## 6. PLC 信号定义 | PLC Signal Definitions
信号名称硬编码在 `MotionSignalNames.cs` 中,信号地址定义在 `PlcAddrDfn.xml`
每个轴包含以下信号(以 SourceZ 为例):
| 信号名 | 方向 | 类型 | 说明 |
|--------|------|------|------|
| `MC_SourceZ_Pos` | Read | single | 实际位置 |
| `MC_SourceZ_Target` | Write | single | 目标位置 |
| `MC_SourceZ_Speed` | Write | single | 运动速度 |
| `MC_SourceZ_JogPos` | Write | byte | 正向 Jog |
| `MC_SourceZ_JogNeg` | Write | byte | 反向 Jog |
| `MC_SourceZ_Home` | Write | byte | 回零 |
| `MC_SourceZ_Stop` | Write | byte | 停止 |
安全门信号:
| 信号名 | 方向 | 说明 |
|--------|------|------|
| `MC_Door_Open` | Write | 开门 |
| `MC_Door_Close` | Write | 关门 |
| `MC_Door_Stop` | Write | 停门 |
| `MC_Door_Status` | Read | 门状态(int: 0-6 |
| `MC_Door_Interlock` | Read | 联锁信号 |
---
## 7. 安全机制 | Safety Mechanisms
| 机制 | 说明 |
|------|------|
| 边界检查 | 目标位置超出 Min/Max 范围时拒绝移动 |
| 运动中防重入 | 轴处于 Moving 状态时拒绝新的移动命令 |
| 联锁检查 | 联锁信号有效时禁止开门 |
| 禁用轴检查 | Enabled=false 的旋转轴拒绝所有命令 |
| Homing 中拒绝 Jog | 回零过程中不允许 Jog 操作 |
| 多轴原子性检查 | MoveAllToTarget 任意轴越界则全部拒绝 |
| 轮询异常不中断 | 轮询中 PLC 异常被捕获,不影响下一轮 |
| UI 异常保护 | ViewModel 命令通过 SafeRun 包裹,PLC 异常弹窗而非崩溃 |
---
## 8. 轮询机制 | Polling Mechanism
`MotionControlService` 使用 `System.Threading.Timer` 以配置的 `PollingInterval`(默认 100ms)周期执行:
1. 检查 PLC 连接状态(通过 `IPlcService.IsConnected`),未连接时跳过
2. 缓存所有轴、门状态和联锁状态的旧值
3. 调用 `IMotionSystem.UpdateAllStatus()` 从 PLC 读取最新状态
4. 重新计算几何参数,发布 `GeometryUpdatedEvent`
5. 检测状态变化,发布 `AxisStatusChangedEvent``DoorStatusChangedEvent``DoorInterlockChangedEvent`
6. 轴进入 Error/Alarm 时额外发布 `MotionErrorEvent`
7. 异常捕获并记录 Error 日志,不中断轮询
通过 `StartPolling()` / `StopPolling()` 控制轮询生命周期。
---
**最后更新 | Last Updated**: 2026-04-14
@@ -0,0 +1,520 @@
# 探测器摆动几何计算模型 | Geometry Calculation with Detector Swing
> 文档版本:v1.0
> 创建日期:2026-04-13
> 适用模块:XP.Hardware.MotionControl
---
## 1. 问题背景 | Background
原始几何计算假设射线源(Source)、载物台旋转中心(Stage)、探测器(Detector)三者共线于 Z 轴,使用简单的绝对坐标差值计算 FOD 和 FDD。
但实际系统中,探测器具有摆动轴(DetectorSwing),可在 XZ 平面内绕固定旋转中心(Pivot)旋转。当摆动角度 θ ≠ 0 时,探测器感光面中心偏离 Z 轴,射线束从射线源到探测器的路径不再沿 Z 轴方向,FOD 和 FDD 的定义需要重新考虑。
---
## 2. 物理模型 | Physical Model
### 2.1 坐标系定义 | Coordinate System
- Z 轴:垂直方向(射线源和探测器的主运动方向)
- X 轴:水平方向(探测器摆动平面内的横向)
- 原点:系统参考零点
### 2.2 关键几何元素 | Key Geometry Elements
```
探测器中心 D ● (Dx, Dz)
\
\ R ← 摆动半径(Pivot 到探测器感光面中心的距离)
\
\ θ ← 摆动角度
/
摆动旋转中心 P ● (Px=0, Pz=SwingPivotZ)
|
|
载物台旋转中心 O ● (Ox=0, Oz=StageRotationCenterZ)
|
| ← 射线束中心线(从 S 到 D)
|
射线源 S (Sx=0, Sz=SourceZ_abs)
```
### 2.3 各点坐标 | Point Coordinates
| 点 | X 坐标 | Z 坐标 | 说明 |
|----|--------|--------|------|
| S(射线源)| 0 | `SourceZ_pos + SourceZOrigin` | 射线源始终在 Z 轴上 |
| O(载物台旋转中心)| 0 | `StageRotationCenterZ`(配置固定值)| 载物台平面与 Z 轴的交点 |
| P(摆动旋转中心)| 0 | `SwingPivotZ`(配置固定值)| 探测器摆动的旋转中心,在 Z 轴上 |
| D(探测器感光面中心)| `Dx` | `Dz` | 由摆动角度和半径计算得出 |
### 2.4 探测器中心坐标计算 | Detector Center Coordinate Calculation
摆动角度 θ 定义:θ = 0 时探测器正对射线源(D 在 Z 轴上,P 正上方),正角度为顺时针(向 X 正方向偏转)。
```
Dx = Px + R × sin(θ) = R × sin(θ) (因为 Px = 0
Dz = Pz - R × cos(θ) (探测器感光面在 Pivot 下方,朝向射线源)
```
注意:这里假设 θ=0 时探测器在 Pivot 正下方(朝向射线源方向),Z 轴向上为正。射线源在底部(Z 值小),探测器在顶部(Z 值大),探测器感光面朝下。公式为:
```
Dx = R × sin(θ)
Dz = Pz - R × cos(θ)
```
---
## 3. FOD 和 FDD 的正确定义 | Correct FOD and FDD Definitions
### 3.1 FOD(焦点到物体距离)| Focus-to-Object Distance
FOD 的定义是:射线源焦点到载物台旋转中心的距离,沿射线束中心线方向测量。
但在平面 CT 系统中,更常用的定义是:射线源到载物台旋转中心沿 Z 轴的投影距离(因为载物台是一个平面,射线束穿过载物台平面的位置才是关键)。
**方案 A:沿射线束中心线的 FOD(几何真实距离)**
射线束中心线从 S 到 D,载物台平面在 Z = StageRotationCenterZ 处。FOD 是射线源 S 到射线束与载物台平面交点的距离。
射线束参数方程(从 S 到 D):
```
x(t) = Sx + t × (Dx - Sx) = t × Dx
z(t) = Sz + t × (Dz - Sz)
```
载物台平面在 Z = Oz,求交点时 t:
```
t_stage = (Oz - Sz) / (Dz - Sz)
```
交点坐标:
```
x_intersect = t_stage × Dx
z_intersect = Oz
```
FOD = S 到交点的欧几里得距离:
```
FOD = sqrt((x_intersect - Sx)² + (z_intersect - Sz)²)
= sqrt((t_stage × Dx)² + (Oz - Sz)²)
```
**方案 B:沿 Z 轴的 FOD(简化定义,与原始实现一致)**
```
FOD_z = |Sz - Oz|
```
这是原始实现的方式,不受探测器摆动影响。
### 3.2 FDD(焦点到探测器距离)| Focus-to-Detector Distance
FDD 是射线源焦点到探测器感光面中心的欧几里得距离:
```
FDD = sqrt((Dx - Sx)² + (Dz - Sz)²)
= sqrt((R × sin(θ))² + (Pz - R × cos(θ) - Sz)²)
```
当 θ = 0 时:
```
FDD = |Pz - R - Sz|
```
此时 `Pz - R` 就是探测器感光面中心在 Z 轴上的绝对坐标(Pivot 下方 R 处),与原始公式 `|DetectorZ_abs - SourceZ_abs|` 一致。
### 3.3 放大倍率 | Magnification
```
M = FDD / FOD
```
---
## 4. 推荐方案 | Recommended Approach
### 4.1 采用方案 A(射线束中心线投影)
对于平面 CT 系统,FOD 应该是沿射线束中心线测量的,因为放大倍率 M = FDD / FOD 直接决定了成像的几何放大关系。
### 4.2 正算公式汇总 | Forward Calculation Summary
已知输入:
- `Sz`:射线源 Z 绝对坐标 = SourceZ_pos + SourceZOrigin
- `Pz`:摆动旋转中心 Z 绝对坐标 = SwingPivotZ(配置)
- `R`:摆动半径 = SwingRadius(配置)
- `θ`:探测器摆动角度 = DetectorSwing 实际角度(度)
- `Oz`:载物台旋转中心 Z 绝对坐标 = StageRotationCenterZ(配置)
计算步骤:
```
1. 探测器中心坐标:
Dx = R × sin(θ)
Dz = Pz - R × cos(θ)
2. FDD = sqrt(Dx² + (Dz - Sz)²)
3. 射线束与载物台平面交点参数:
t_stage = (Oz - Sz) / (Dz - Sz)
4. 交点 X 坐标:
x_intersect = t_stage × Dx
5. FOD = sqrt(x_intersect² + (Oz - Sz)²)
6. M = FDD / FOD
```
### 4.3 反算公式汇总 | Inverse Calculation Summary
反算场景:给定目标 FOD 和 FDD(以及当前探测器摆动角度 θ),计算 SourceZ 和 DetectorZ 的目标位置。
**重要约束**:反算时,探测器摆动角度 θ 是已知的当前值(或目标值),不由 FOD/FDD 反算决定。
已知输入:
- `targetFOD`:目标 FOD
- `targetFDD`:目标 FDD
- `θ`:探测器摆动角度(当前值或目标值)
- `Pz``R``Oz`:配置参数
当 θ = 0 时(简化情况,退化为原始公式):
```
Dx = 0, Dz = Pz - R
FOD = |Sz - Oz| → Sz = Oz - targetFOD(射线源在载物台下方)
FDD = |Dz - Sz| → Dz = Sz + targetFDD
DetectorZ_pos = Dz - DetectorZOrigin
```
当 θ ≠ 0 时:
由 FOD 求 SourceZ
```
FOD 的定义涉及射线束方向,而射线束方向取决于 S 和 D 的位置。
D 的位置取决于 DetectorZ(通过 Pz 和 R 和 θ 计算)。
但在本系统中,探测器的 Z 位置由 DetectorZ 轴控制,
而摆动角度 θ 只改变探测器在 XZ 平面内的朝向。
实际上,Pz(摆动旋转中心)的 Z 坐标 = DetectorZ_pos + DetectorZOrigin + PivotOffset
其中 PivotOffset 是从 DetectorZ 轴位置到 Pivot 点的固定偏移。
简化模型:Pivot 的 Z 坐标随 DetectorZ 轴移动。
```
**关键认识**:摆动旋转中心 P 是固定在探测器机械臂上的,它随 DetectorZ 轴一起上下移动。因此:
```
Pz = DetectorZ_pos + DetectorZOrigin + PivotOffsetFromDetectorOrigin
```
其中 `PivotOffsetFromDetectorOrigin` 是一个固定的机械偏移量(配置参数)。
但为了简化,我们可以将 Pivot 的 Z 坐标直接表示为:
```
Pz = DetectorZ_abs + SwingPivotOffset
```
其中 `SwingPivotOffset` 是 Pivot 相对于 DetectorZ 绝对坐标的固定偏移。
探测器感光面中心:
```
Dx = R × sin(θ)
Dz = Pz - R × cos(θ) = DetectorZ_abs + SwingPivotOffset - R × cos(θ)
```
反算步骤:
1. 由 targetFOD 确定 SourceZ
- 当 θ 较小时,FOD ≈ |Sz - Oz|,所以 Sz ≈ Oz - targetFOD
- 精确求解需要迭代(因为 FOD 的定义涉及射线束方向,而射线束方向又取决于 D 的位置)
2. 由 targetFDD 和已知 Sz 确定 DetectorZ
```
FDD² = Dx² + (Dz - Sz)²
= (R × sin(θ))² + (DetectorZ_abs + SwingPivotOffset - R × cos(θ) - Sz)²
```
设 `A = SwingPivotOffset - R × cos(θ)``B = R × sin(θ)`,则:
```
FDD² = B² + (DetectorZ_abs + A - Sz)²
DetectorZ_abs + A - Sz = ±sqrt(FDD² - B²)
DetectorZ_abs = Sz - A + sqrt(FDD² - B²) (取正值,探测器在射线源上方)
```
3. 转换为轴相对坐标:
```
SourceZ_pos = Sz - SourceZOrigin
DetectorZ_pos = DetectorZ_abs - DetectorZOrigin
```
---
## 5. 配置参数完整说明 | Complete Configuration Parameters
所有参数在 `App.config` 的 `<appSettings>` 中配置,键名前缀为 `MotionControl:`。
### 5.1 几何计算相关参数 | Geometry Calculation Parameters
以下参数直接参与 FOD/FDD/放大倍率的计算:
| 参数 | 键名 | 默认值 | 单位 | 说明 |
|------|------|--------|------|------|
| 射线源Z原点偏移 | `Geometry:SourceZOrigin` | 0 | mm | 射线源Z轴的原点偏移。绝对坐标 = 轴位置 + Origin。用于将轴的相对坐标转换为系统绝对坐标 |
| 探测器Z原点偏移 | `Geometry:DetectorZOrigin` | 600 | mm | 探测器Z轴的原点偏移。含义同上。通常设为探测器回零时感光面中心的绝对Z坐标 |
| 载物台旋转中心Z | `Geometry:StageRotationCenterZ` | 300 | mm | 载物台旋转中心的绝对Z坐标(固定值)。载物台平面与Z轴的交点,FOD 的参考基准 |
| 摆动旋转中心偏移 | `Geometry:SwingPivotOffset` | 0 | mm | 探测器摆动旋转中心(Pivot)相对于 DetectorZ 绝对坐标的Z方向偏移。Pivot 在探测器上方(Z 值更大),正值。Pivot 的绝对Z坐标 = DetectorZ_abs + SwingPivotOffset |
| 摆动半径 | `Geometry:SwingRadius` | 0 | mm | Pivot 到探测器感光面中心的距离。当值为 0 时,系统退化为无摆动的简化模型(完全向后兼容) |
参数间的关系:
```
射线源绝对Z坐标 Sz = SourceZ_pos + SourceZOrigin
探测器绝对Z坐标 DetZ_abs = DetectorZ_pos + DetectorZOrigin
摆动旋转中心Z坐标 Pz = DetZ_abs + SwingPivotOffset
探测器感光面中心 Dx = SwingRadius × sin(θ)
Dz = Pz - SwingRadius × cos(θ)
载物台旋转中心 Oz = StageRotationCenterZ(固定值)
```
### 5.2 直线轴配置 | Linear Axis Configuration
每个直线轴包含 3 个参数,键名格式为 `MotionControl:<AxisName>:<Param>`
| 轴名 | Min | Max | Origin | 说明 |
|------|-----|-----|--------|------|
| SourceZ | 0 | 500 | 0 | 射线源Z轴,控制射线源上下运动(mm) |
| DetectorZ | 0 | 600 | 0 | 探测器Z轴,控制探测器上下运动(mm) |
| StageX | -150 | 150 | 0 | 载物台X轴,控制载物台前后运动(mm) |
| StageY | -150 | 150 | 0 | 载物台Y轴,控制载物台左右运动(mm) |
- `Min` / `Max`:轴的软限位范围(相对坐标),超出范围的移动命令会被拒绝
- `Origin`:轴的原点偏移,用于将相对坐标转换为绝对坐标
### 5.3 旋转轴配置 | Rotary Axis Configuration
每个旋转轴包含 4 个参数,键名格式为 `MotionControl:<AxisName>:<Param>`
| 轴名 | Min | Max | Origin | Enabled | 说明 |
|------|-----|-----|--------|---------|------|
| DetectorSwing | -45 | 45 | 0 | true | 探测器摆动轴,在XZ平面内旋转(度) |
| StageRotation | -360 | 360 | 0 | true | 载物台旋转轴,绕Z轴旋转(度) |
| FixtureRotation | -90 | 90 | 0 | true | 夹具旋转轴,可选配置(度) |
- `Enabled`:是否启用该旋转轴,`false` 时 UI 隐藏且拒绝所有命令
### 5.4 运行参数 | Runtime Parameters
| 参数 | 键名 | 默认值 | 单位 | 说明 |
|------|------|--------|------|------|
| 轮询周期 | `MotionControl:PollingInterval` | 100 | ms | PLC 状态轮询周期,每个周期读取所有轴位置并重新计算几何参数 |
| 默认速度 | `MotionControl:DefaultVelocity` | 500 | - | 未指定速度时使用的默认运动速度 |
---
## 5.5 实际设备配置示例 | Real Device Configuration Example
以下是一个典型平面CT设备的配置示例和测量方法。
### 设备物理布局
假设设备的实际机械尺寸如下:
```
Z=1000mm (顶部)
┃ DetectorZ 轴行程 0~800mm
┃ 探测器回零时 DetectorZ 绝对坐标在 Z=500mm 处
┃ |
┃ D | 探测器感光面中心(朝下,面向射线源)
┃ ● |
┃ \θ |
┃ \ | R=200mm
┃ \ |
┃ \|
┃ ● P 摆动旋转中心 (Z=580mm,随 DetectorZ 移动)
┃ |
┃ |
┃ ● O 载物台旋转中心 (Z=350mm,固定)
┃ |
┃ |
┃ |
┃ ● S 射线源焦点 (Z=50mm,即 SourceZ_pos=0 时)
┃ SourceZ 轴行程 0~500mm
┃ 射线源回零时焦点在 Z=50mm 处
Z=0mm (底部)
```
### 测量方法
1. **SourceZOrigin(射线源原点偏移)**
- 将射线源移动到回零位置(SourceZ_pos = 0
- 测量此时射线源焦点的绝对Z坐标 → 即为 SourceZOrigin
- 本例:焦点在 Z=50mm → `SourceZOrigin = 50`
2. **DetectorZOrigin(探测器原点偏移)**
- 将探测器移动到回零位置(DetectorZ_pos = 0
- 测量此时探测器Z轴参考点的绝对Z坐标 → 即为 DetectorZOrigin
- 本例:参考点在 Z=500mm → `DetectorZOrigin = 500`
3. **StageRotationCenterZ(载物台旋转中心Z坐标)**:
- 测量载物台旋转中心的绝对Z坐标(固定值,由机械结构决定)
- 本例:Z=350mm → `StageRotationCenterZ = 350`
4. **SwingPivotOffset(摆动旋转中心偏移)**:
- 将探测器移动到回零位置,此时 DetectorZ_abs = 0 + 500 = 500mm
- 测量摆动旋转中心(Pivot)的绝对Z坐标 → 本例 Z=580mm
- SwingPivotOffset = Pz - DetectorZ_abs = 580 - 500 = 80mm
- `SwingPivotOffset = 80`
- 注意:Pivot 随 DetectorZ 轴一起移动,此偏移是固定的机械常数
5. **SwingRadius(摆动半径)**
- 测量 Pivot 到探测器感光面中心的距离(固定值,由机械臂长度决定)
- 本例:200mm → `SwingRadius = 200`
### 对应的 App.config 配置
```xml
<!-- 直线轴配置 -->
<add key="MotionControl:SourceZ:Min" value="0" />
<add key="MotionControl:SourceZ:Max" value="500" />
<add key="MotionControl:SourceZ:Origin" value="0" />
<add key="MotionControl:DetectorZ:Min" value="0" />
<add key="MotionControl:DetectorZ:Max" value="800" />
<add key="MotionControl:DetectorZ:Origin" value="0" />
<add key="MotionControl:StageX:Min" value="-150" />
<add key="MotionControl:StageX:Max" value="150" />
<add key="MotionControl:StageX:Origin" value="0" />
<add key="MotionControl:StageY:Min" value="-150" />
<add key="MotionControl:StageY:Max" value="150" />
<add key="MotionControl:StageY:Origin" value="0" />
<!-- 旋转轴配置 -->
<add key="MotionControl:DetectorSwing:Min" value="-45" />
<add key="MotionControl:DetectorSwing:Max" value="45" />
<add key="MotionControl:DetectorSwing:Origin" value="0" />
<add key="MotionControl:DetectorSwing:Enabled" value="true" />
<add key="MotionControl:StageRotation:Min" value="-360" />
<add key="MotionControl:StageRotation:Max" value="360" />
<add key="MotionControl:StageRotation:Origin" value="0" />
<add key="MotionControl:StageRotation:Enabled" value="true" />
<add key="MotionControl:FixtureRotation:Min" value="-90" />
<add key="MotionControl:FixtureRotation:Max" value="90" />
<add key="MotionControl:FixtureRotation:Origin" value="0" />
<add key="MotionControl:FixtureRotation:Enabled" value="false" />
<!-- 几何参数 -->
<add key="MotionControl:Geometry:SourceZOrigin" value="50" />
<add key="MotionControl:Geometry:DetectorZOrigin" value="500" />
<add key="MotionControl:Geometry:StageRotationCenterZ" value="350" />
<add key="MotionControl:Geometry:SwingPivotOffset" value="80" />
<add key="MotionControl:Geometry:SwingRadius" value="200" />
<!-- 运行参数 -->
<add key="MotionControl:PollingInterval" value="100" />
<add key="MotionControl:DefaultVelocity" value="500" />
```
### 验证计算
以上述配置为例,当 SourceZ_pos=100, DetectorZ_pos=200, θ=15° 时:
```
Sz = 100 + 50 = 150mm (射线源绝对Z,底部)
DetZ_abs = 200 + 500 = 700mm (探测器绝对Z,顶部)
Pz = 700 + 80 = 780mm (Pivot 绝对Z,探测器上方)
Dx = 200 × sin(15°) = 200 × 0.2588 = 51.76mm (探测器中心X偏移)
Dz = 780 - 200 × cos(15°) = 780 - 193.19 = 586.81mm (探测器感光面中心ZPivot 下方)
FDD = sqrt(51.76² + (586.81 - 150)²)
= sqrt(2679.1 + 190,729.5)
= sqrt(193,408.6)
≈ 439.78mm
射线束与载物台平面交点参数:
t_stage = (350 - 150) / (586.81 - 150) = 200 / 436.81 = 0.4579
x_intersect = 0.4579 × 51.76 = 23.70mm
FOD = sqrt(23.70² + 200²)
= sqrt(561.7 + 40000)
= sqrt(40561.7)
≈ 201.40mm
M = FDD / FOD = 439.78 / 201.40 ≈ 2.183
对比 θ=0 时(同样轴位置):
Dx = 0, Dz = 780 - 200 = 580mm
FDD_0 = |150 - 580| = 430mm
FOD_0 = |150 - 350| = 200mm
M_0 = 430 / 200 = 2.15
可以看到 θ=15° 时 FDD 略大(439.78 vs 430),FOD 略大(201.40 vs 200),
放大倍率从 2.15 升为 2.183。这是因为摆动使探测器感光面中心在 X 方向偏移,
增加了射线源到探测器的欧几里得距离。
```
---
## 6. 几何示意图 | Geometry Diagram
### 6.1 θ = 0(无摆动,退化为原始模型)
```
D ●─────────────────── 探测器感光面中心 (Z = Pz - R)
|
| R(摆动半径)
|
P ●─────────────────── 摆动旋转中心 (Z = Pz = DetZ_abs + Offset)
|
|
O ●─────────────────── 载物台旋转中心 (Z = Oz)
|
| FOD = |Sz - Oz|
|
S ●─────────────────── 射线源 (Z = Sz)
FDD = |Sz - (Pz - R)| = |Sz - Dz|
```
### 6.2 θ ≠ 0(有摆动)
```
D ●────/──────────── 探测器感光面中心 (Dx, Dz)
/ /
/θ /
/ /
P ●────/──────────────── 摆动旋转中心 (0, Pz)
| /
| / 射线束与载物台平面的交点 I (x_i, Oz)
O ●──/────────────────── 载物台旋转中心 (0, Oz)
| /
| / ← 射线束中心线
| /
|/
S ●─────────────────── 射线源 (0, Sz)
Dx = R × sin(θ)
Dz = Pz - R × cos(θ)
FDD = sqrt(Dx² + (Dz - Sz)²)
FOD = sqrt(x_i² + (Oz - Sz)²)
其中 x_i = Dx × (Oz - Sz) / (Dz - Sz)
```
---
## 7. 特殊情况处理 | Edge Cases
| 情况 | 处理方式 |
|------|----------|
| θ = 0 | 退化为原始公式,Dx = 0FDD = \|Dz - Sz\| |
| SwingRadius = 0 | 完全退化为原始模型(无摆动影响) |
| FOD ≈ 0 | 放大倍率返回 NaN(与原始行为一致) |
| Dz = Sz | FDD 方向水平,t_stage 无穷大,FOD 计算需要特殊处理 |
| FDD < \|R × sin(θ)\| | 反算时 sqrt 内为负数,目标不可达,返回错误 |
---
**最后更新 | Last Updated**: 2026-04-14
@@ -0,0 +1,494 @@
# XP.Hardware.MotionControl 运动控制模块设计规划
> 文档版本:v1.0
> 创建日期:2026-04-10
> 适用项目:XplorePlane 平面CT工业检测系统
---
## 1. 背景与硬件描述
本模块用于控制平面CT系统中的所有运动轴、旋转轴及安全防护门。
### 1.1 直线轴(4轴)
| 轴标识 | 说明 | 运动方向 |
|--------|------|----------|
| 轴1 SourceZ | 射线源Z方向运动 | 上下 |
| 轴2 DetectorZ | 探测器Z方向运动 | 上下 |
| 轴3 StageX | 载物台X方向运动 | 前后 |
| 轴4 StageY | 载物台Y方向运动 | 左右 |
### 1.2 旋转轴(3轴)
| 轴标识 | 说明 | 特点 |
|--------|------|------|
| 旋转轴1 DetectorSwing | 探测器在XZ平面的旋转摆动 | 有限角度,旋转中心在射线源Z方向延长线上 |
| 旋转轴2 StageRotation | 载物台围绕XY中心的Z方向旋转 | 可选配置 |
| 旋转轴3 FixtureRotation | 旋转夹具在载物台XY平面上围绕X方向的旋转 | 可选配置 |
### 1.3 安全门
防护门,具备状态机控制与互锁信号联动。
### 1.4 几何关系
```
FOD(焦点到旋转中心距离)= |SourceZ绝对坐标 - 旋转中心绝对Z坐标|
FDD(焦点到探测器距离) = |SourceZ绝对坐标 - DetectorZ绝对坐标|
放大倍率 M = FDD / FOD
```
只有确定了射线源、探测器、载物台旋转中心的绝对坐标,才能准确计算 FOD 和 FDD,进而控制放大倍率。
---
## 2. 目录结构
```
XP.Hardware.MotionControl/
├── Abstractions/
│ ├── ILinearAxis.cs # 直线轴策略接口
│ ├── IRotaryAxis.cs # 旋转轴策略接口
│ ├── ISafetyDoor.cs # 安全门策略接口
│ ├── IMotionSystem.cs # 顶层系统管理接口
│ ├── LinearAxisBase.cs # 直线轴抽象基类
│ ├── RotaryAxisBase.cs # 旋转轴抽象基类
│ ├── SafetyDoorBase.cs # 安全门抽象基类
│ ├── MotionResult.cs # 统一操作结果类型
│ └── Enums/
│ ├── AxisStatus.cs # 轴状态枚举
│ ├── DoorStatus.cs # 门状态枚举
│ └── AxisId.cs # 轴标识枚举
├── Implementations/
│ ├── PlcLinearAxis.cs # 基于PLC的直线轴实现
│ ├── PlcRotaryAxis.cs # 基于PLC的旋转轴实现
│ └── PlcSafetyDoor.cs # 基于PLC的安全门实现
├── Services/
│ ├── IMotionControlService.cs # 运动控制业务服务接口
│ ├── MotionControlService.cs # 服务实现(含轮询线程、几何计算)
│ └── GeometryCalculator.cs # FOD/FDD/放大倍率计算器
├── Config/
│ ├── MotionControlConfig.cs # 配置实体(轴范围、原点、PLC信号名)
│ └── ConfigLoader.cs # 配置加载器
├── ViewModels/
│ └── MotionControlViewModel.cs # 操作面板 ViewModel
├── Views/
│ └── MotionControlView.xaml # 操作面板 UserControl(宽350
├── Module/
│ └── MotionControlModule.cs # Prism 模块注册
├── Documents/
│ └── MotionControl_Design.md # 本文档
└── Resources/
├── Resources.resx
├── Resources.zh-CN.resx
└── Resources.en-US.resx
```
---
## 3. 核心抽象层设计
### 3.1 三类硬件抽象继承关系
```
ILinearAxis IRotaryAxis ISafetyDoor
│ │ │
LinearAxisBase RotaryAxisBase SafetyDoorBase
│ │ │
PlcLinearAxis PlcRotaryAxis PlcSafetyDoor
```
### 3.2 ILinearAxis 接口职责
- 位置读取(实际值)
- 目标位置写入
- Jog 正向 / Jog 反向(按住运动,松开停止)
- 回零
- 停止
- 限位状态读取(正限位 / 负限位)
- 轴状态(Idle / Moving / Homing / Error / Alarm
### 3.3 IRotaryAxis 接口职责
- 角度读取(实际值)
- 目标角度写入
- Jog 正向 / Jog 反向
- 回零
- 停止
- 角度软限位(MinAngle / MaxAngle,来自配置)
- 轴状态
### 3.4 ISafetyDoor 接口职责
- 状态机读取(Unknown / Opening / Open / Closing / Closed / Locked / Error
- 开门指令
- 关门指令
- 互锁信号读取(是否允许开门)
### 3.5 IMotionSystem 顶层接口职责
-`AxisId` 枚举索引各直线轴(SourceZ、DetectorZ、StageX、StageY
-`RotaryAxisId` 枚举索引各旋转轴(DetectorSwing、StageRotation、FixtureRotation
- 持有安全门实例
- 协调运动:开门前联锁检查
- 几何计算委托给 `GeometryCalculator`
---
## 4. 服务层设计
### 4.1 IMotionControlService
封装业务规则,供 ViewModel 调用:
- `StartPolling()` / `StopPolling()` — 启停 PLC 数据轮询
- `MoveToTarget(AxisId, double)` — 移动指定轴到目标位置
- `MoveAllToTarget(targets)` — 多轴联动移动
- `StopAll()` — 停止所有轴
- `HomeAll()` — 全部回零
- `JogStart(AxisId, direction)` / `JogStop(AxisId)` — Jog 控制
- `OpenDoor()` / `CloseDoor()` / `StopDoor()` — 门控制(含联锁前置检查)
- `ApplyGeometry(fod, fdd)` — 根据 FOD/FDD 反算并移动
- `GetCurrentGeometry()` — 获取当前 FOD/FDD/M
### 4.2 GeometryCalculator
```csharp
// 正算:由轴位置计算几何参数
double CalcFOD(double sourceZAbs, double stageRotCenterZAbs)
double CalcFDD(double sourceZAbs, double detectorZAbs)
double CalcMagnification(double fdd, double fod)
// 反算:由目标几何参数计算轴目标位置
(double sourceZTarget, double detectorZTarget) CalcAxisTargets(double targetFOD, double targetFDD)
// 边界检查:目标值是否在轴 Min/Max 范围内
bool ValidateTargets(double sourceZTarget, double detectorZTarget)
```
绝对坐标计算方式:
```
SourceZ绝对坐标 = SourceZ当前位置 + SourceZOrigin(配置)
DetectorZ绝对坐标 = DetectorZ当前位置 + DetectorZOrigin(配置)
旋转中心绝对Z坐标 = StageRotationCenterZ(配置,固定值)
```
### 4.3 轮询线程
`MotionControlService` 内部使用 `System.Threading.Timer`,周期约 100ms,仅在 PLC 已连接时激活。每次轮询通过 `ISignalDataService.GetValueByName()` 读取所有轴的实际位置和状态,更新到 ViewModel 的可绑定属性。
---
## 5. 与 PLC 模块的依赖关系
MotionControl 通过依赖注入获取 `ISignalDataService`(来自 XP.Hardware.PLC),所有读写操作通过信号逻辑名称进行,不直接操作 PLC 地址。信号名称在配置文件中定义。
```
XP.Hardware.MotionControl
└── 依赖注入 → ISignalDataServiceXP.Hardware.PLC
└── 读:GetValueByName<float>("MC_SourceZ_Pos")
└── 写:EnqueueWrite("MC_SourceZ_Target", value)
```
---
## 6. 配置文件设计(App.config
```xml
<MotionControl>
<!-- 直线轴配置(单位:mm -->
<SourceZ Min="0" Max="500" Origin="0"
PlcSignalRead="MC_SourceZ_Pos" PlcSignalWrite="MC_SourceZ_Target"
PlcSignalJogPos="MC_SourceZ_JogPos" PlcSignalJogNeg="MC_SourceZ_JogNeg"
PlcSignalHome="MC_SourceZ_Home" PlcSignalStop="MC_SourceZ_Stop" />
<DetectorZ Min="0" Max="600" Origin="0"
PlcSignalRead="MC_DetZ_Pos" PlcSignalWrite="MC_DetZ_Target"
PlcSignalJogPos="MC_DetZ_JogPos" PlcSignalJogNeg="MC_DetZ_JogNeg"
PlcSignalHome="MC_DetZ_Home" PlcSignalStop="MC_DetZ_Stop" />
<StageX Min="-150" Max="150" Origin="0"
PlcSignalRead="MC_StageX_Pos" PlcSignalWrite="MC_StageX_Target"
PlcSignalJogPos="MC_StageX_JogPos" PlcSignalJogNeg="MC_StageX_JogNeg"
PlcSignalHome="MC_StageX_Home" PlcSignalStop="MC_StageX_Stop" />
<StageY Min="-150" Max="150" Origin="0"
PlcSignalRead="MC_StageY_Pos" PlcSignalWrite="MC_StageY_Target"
PlcSignalJogPos="MC_StageY_JogPos" PlcSignalJogNeg="MC_StageY_JogNeg"
PlcSignalHome="MC_StageY_Home" PlcSignalStop="MC_StageY_Stop" />
<!-- 旋转轴配置(单位:度) -->
<DetectorSwing Min="-45" Max="45" Origin="0" Enabled="true"
PlcSignalRead="MC_DetSwing_Angle" PlcSignalWrite="MC_DetSwing_Target"
PlcSignalJogPos="MC_DetSwing_JogPos" PlcSignalJogNeg="MC_DetSwing_JogNeg"
PlcSignalHome="MC_DetSwing_Home" PlcSignalStop="MC_DetSwing_Stop" />
<StageRotation Min="-360" Max="360" Origin="0" Enabled="true"
PlcSignalRead="MC_StageRot_Angle" PlcSignalWrite="MC_StageRot_Target"
PlcSignalJogPos="MC_StageRot_JogPos" PlcSignalJogNeg="MC_StageRot_JogNeg"
PlcSignalHome="MC_StageRot_Home" PlcSignalStop="MC_StageRot_Stop" />
<FixtureRotation Min="-90" Max="90" Origin="0" Enabled="false"
PlcSignalRead="MC_FixRot_Angle" PlcSignalWrite="MC_FixRot_Target"
PlcSignalJogPos="MC_FixRot_JogPos" PlcSignalJogNeg="MC_FixRot_JogNeg"
PlcSignalHome="MC_FixRot_Home" PlcSignalStop="MC_FixRot_Stop" />
<!-- 安全门配置 -->
<SafetyDoor PlcSignalOpen="MC_Door_Open"
PlcSignalClose="MC_Door_Close"
PlcSignalStop="MC_Door_Stop"
PlcSignalStatus="MC_Door_Status"
PlcSignalInterlock="MC_Door_Interlock" />
<!-- 几何原点(绝对坐标 mm,用于 FOD/FDD 计算) -->
<Geometry SourceZOrigin="0"
DetectorZOrigin="600"
StageRotationCenterZ="300" />
<!-- 轮询周期(ms -->
<PollingInterval>100</PollingInterval>
<!-- 运动速度(%0-100 -->
<DefaultVelocity>30</DefaultVelocity>
<JogVelocity>10</JogVelocity>
</MotionControl>
```
---
## 7. ViewModel 设计
### 7.1 属性分组
```csharp
// 直线轴(每轴一组实际值 + 目标值)
double SourceZActual / SourceZTarget
double DetectorZActual / DetectorZTarget
double StageXActual / StageXTarget
double StageYActual / StageYTarget
// 旋转轴(每轴一组,可选轴按配置控制 Visibility)
double DetSwingActual / DetSwingTarget // 附带角度限制提示文本
double StageRotActual / StageRotTarget
double FixtureRotActual / FixtureRotTarget
// 几何信息(实时计算,只读显示)
double FOD / FDD / Magnification
// 几何反算输入
double TargetFOD / TargetFDD / TargetMagnification
// 安全门
DoorStatus DoorStatus
bool IsInterlocked
// 状态
bool IsMoving
bool IsPlcConnected
// 调试模式(默认 false,折叠隐藏调试区域)
bool IsDebugMode
```
### 7.2 命令列表
```csharp
// 主操作命令
ICommand MoveCommand // 执行运动到各轴目标位置
ICommand StopCommand // 停止所有轴
ICommand HomeCommand // 全部回零
ICommand OpenDoorCommand
ICommand CloseDoorCommand
ICommand StopDoorCommand
ICommand ApplyGeometryCommand // 根据 FOD/FDD/M 反算并移动
// 调试命令(Jog,按住触发,松开停止)
ICommand JogPlusCommand // 参数:AxisId
ICommand JogMinusCommand // 参数:AxisId
ICommand JogStopCommand // 参数:AxisId
```
---
## 8. View 布局设计(宽 350pxUserControl
```
┌─────────────────────────────────────┐ 350px
│ [安全门状态] ● 已关闭 [互锁: 正常] │
├─────────────────────────────────────┤
│ 直线轴 │
│ 源Z [实际: 123.4mm] [▲目标▼] │
│ 探Z [实际: 456.7mm] [▲目标▼] │
│ 台X [实际: 12.3mm] [▲目标▼] │
│ 台Y [实际: -5.6mm] [▲目标▼] │
├─────────────────────────────────────┤
│ 旋转轴 │
│ 探摆 [实际: 15.0°] [▲目标▼] │
│ 限制: -45° ~ +45° │
│ 台转 [实际: 180.0°] [▲目标▼] │
│ 夹具 [实际: 0.0°] [▲目标▼] │
├─────────────────────────────────────┤
│ 几何信息 │
│ FOD: 150.0mm FDD: 450.0mm M: 3.0│
│ [目标FOD▲▼] [目标FDD▲▼] [应用] │
├─────────────────────────────────────┤
│ [开始运动] [停止运动] [回零] │
│ [开门] [关门] │
├─────────────────────────────────────┤
│ ▼ 调试模式(默认折叠) │
│ 源Z [+Jog][-Jog] 探Z [+][-] │
│ 台X [+Jog][-Jog] 台Y [+][-] │
│ 探摆 [+Jog][-Jog] 台转[+][-] │
│ 夹具 [+Jog][-Jog] │
└─────────────────────────────────────┘
```
---
## 9. FOD/FDD 反算逻辑
用户输入目标 FOD 和 FDD(或放大倍率 M),系统自动计算各轴目标位置:
```
// 已知:StageRotationCenterZ(旋转中心绝对Z坐标,来自配置,固定值)
// 输入 FOD + FDD
SourceZ_target = StageRotationCenterZ - FOD
DetectorZ_target = SourceZ_target + FDD
// 输入 FOD + MM = FDD/FOD):
FDD = M * FOD
DetectorZ_target = SourceZ_target + FDD
// 边界检查:
if SourceZ_target < SourceZ.Min || SourceZ_target > SourceZ.Max → 拒绝并提示
if DetectorZ_target < DetectorZ.Min || DetectorZ_target > DetectorZ.Max → 拒绝并提示
```
---
## 10. 关键安全机制
参考旧项目 `ControlDoor.openDoor()``_130kvMove` 的实现经验:
| 机制 | 说明 | 对标旧代码 |
|------|------|-----------|
| 运动中防重入 | `IsMoving` 标志位,运动中禁止再次触发运动命令 | `fdcRun` 标志位 |
| 开门联锁检查 | 开门前通过 `IEventAggregator` 检查是否有扫描进行中 | `ControlDoor.openDoor()` 前置逻辑 |
| 射线源联锁 | 开门前需确认射线源已关闭(暂不实现,预留扩展点,见下方说明) | `zeroXray2Correct()` 等待归零 |
| 可选轴隐藏 | `StageRotation``FixtureRotation` 通过配置 `Enabled` 控制 UI 可见性 | 无对应,新增 |
| Jog 安全 | Jog 操作通过 `MouseDown/Up` 事件控制,松开立即停止 | `radButton3_MouseDown/Up` |
---
## 11. Prism 模块注册
```csharp
// MotionControlModule.RegisterTypes()
containerRegistry.RegisterSingleton<MotionControlConfig>(); // 配置(单例)
containerRegistry.RegisterSingleton<IMotionSystem, PlcMotionSystem>(); // 系统管理(单例)
containerRegistry.RegisterSingleton<IMotionControlService, MotionControlService>(); // 服务(单例)
// MotionControlModule.OnInitialized()
// 注册多语言资源到 Fallback Chain
// 启动 PLC 轮询(若 PLC 已连接)
```
---
---
## 12. 关于射线源联锁的扩展说明(暂不实现)
### 12.1 什么是事件聚合器(EventAggregator
Prism 的 `IEventAggregator` 是一个**发布/订阅总线**,用于在模块之间传递消息,而不需要两个模块直接互相引用。
类比理解:就像一个广播电台,`RaySource` 模块是"电台"(发布者),`MotionControl` 模块是"收音机"(订阅者),两者只通过"频道"(事件类型)通信,彼此不知道对方的存在。
```
XP.Hardware.RaySource XP.Hardware.MotionControl
│ │
│ 发布 RaySourceStatusChangedEvent │
└──────────→ EventAggregator ──────→ 订阅并响应
```
### 12.2 如何订阅(当需要实现时)
**第一步:RaySource 模块已有的事件定义**
```csharp
// XP.Hardware.RaySource/Abstractions/Events/RaySourceStatusChangedEvent.cs
// 该事件已存在,携带射线源当前状态
public class RaySourceStatusChangedEvent : PubSubEvent<RaySourceStatus> { }
```
**第二步:MotionControlService 中注入并订阅**
```csharp
public class MotionControlService : IMotionControlService
{
private readonly IEventAggregator _eventAggregator;
private bool _isRaySourceOn = false; // 缓存射线源状态
public MotionControlService(IEventAggregator eventAggregator, ...)
{
_eventAggregator = eventAggregator;
// 订阅射线源状态变化事件
_eventAggregator
.GetEvent<RaySourceStatusChangedEvent>()
.Subscribe(OnRaySourceStatusChanged, ThreadOption.BackgroundThread);
}
private void OnRaySourceStatusChanged(RaySourceStatus status)
{
// 根据状态判断射线是否开启
_isRaySourceOn = (status == RaySourceStatus.XRayOn);
}
public async Task OpenDoor()
{
// 开门前检查射线源
if (_isRaySourceOn)
{
_logger.Warn("射线源未关闭,禁止开门 | Ray source is on, door open blocked");
return;
}
// ... 继续开门逻辑
}
}
```
**第三步:注意事项**
- 订阅时选择 `ThreadOption.BackgroundThread`,避免在 UI 线程处理硬件状态
- 模块卸载时需要取消订阅,防止内存泄漏:`GetEvent<...>().Unsubscribe(OnRaySourceStatusChanged)`
- 两个模块之间**不需要互相引用**,只需要共同引用定义事件的程序集(或在各自模块内定义相同结构的事件)
### 12.3 预留扩展点
当需要实现射线源联锁时,只需在 `MotionControlService.OpenDoor()` 方法开头加入上述订阅逻辑即可,不需要修改任何其他代码。
---
## 13. 事件定义(Prism EventAggregator
```
Abstractions/Events/
├── AxisStatusChangedEvent.cs # 轴状态变化事件(AxisId + AxisStatus
├── DoorStatusChangedEvent.cs # 门状态变化事件(DoorStatus
├── GeometryUpdatedEvent.cs # 几何参数更新事件(FOD + FDD + M
└── MotionErrorEvent.cs # 运动错误事件(AxisId + 错误信息)
```
---
## 13. 实现优先级建议
| 优先级 | 内容 |
|--------|------|
| P0 | 配置实体、枚举定义、MotionResult |
| P0 | ILinearAxis / IRotaryAxis / ISafetyDoor 接口 + 抽象基类 |
| P1 | PlcLinearAxis / PlcRotaryAxis / PlcSafetyDoor 实现 |
| P1 | GeometryCalculator |
| P1 | IMotionControlService + MotionControlService(含轮询) |
| P2 | MotionControlViewModel |
| P2 | MotionControlView.xaml(宽350 UserControl |
| P3 | MotionControlModule 注册 |
| P3 | 多语言资源文件 |
@@ -0,0 +1,139 @@
# XP.Hardware.MotionControl
工业运动控制模块 | Industrial Motion Control Module
---
## 项目概述 | Project Overview
XP.Hardware.MotionControl 是 XplorePlane 平面CT工业检测系统的核心运动控制模块,负责管理4个直线轴(SourceZ、DetectorZ、StageX、StageY)、3个旋转轴(DetectorSwing、StageRotation、FixtureRotation)及1个安全防护门的全部运动控制逻辑,并提供 FOD/FDD/放大倍率的几何正算与反算能力。
### 主要特性 | Key Features
- 4个直线轴 + 3个旋转轴统一管理(策略模式)
- 安全防护门控制(联锁检查、状态机)
- FOD/FDD/放大倍率几何正算与反算
- 多轴联动移动(原子性边界检查)
- Jog 点动调试(MouseDown/MouseUp 安全控制)
- 100ms 周期 PLC 状态轮询
- 基于 Prism EventAggregator 的跨模块事件通讯
- 可配置的轴启用/禁用(FixtureRotation 等可选轴)
- Telerik Crystal 主题 UI 控件
- 中/英/繁体中文多语言支持
---
## 框架架构 | Architecture
```
XP.Hardware.MotionControl/
├── Abstractions/ # 抽象层 | Abstraction Layer
│ ├── ILinearAxis.cs # 直线轴策略接口
│ ├── IRotaryAxis.cs # 旋转轴策略接口
│ ├── ISafetyDoor.cs # 安全门策略接口
│ ├── IMotionSystem.cs # 顶层系统管理接口
│ ├── LinearAxisBase.cs # 直线轴抽象基类(含边界检查)
│ ├── RotaryAxisBase.cs # 旋转轴抽象基类
│ ├── SafetyDoorBase.cs # 安全门抽象基类
│ ├── MotionResult.cs # 统一操作结果类型
│ ├── Enums/ # 枚举定义
│ │ ├── AxisId.cs # 直线轴标识
│ │ ├── RotaryAxisId.cs # 旋转轴标识
│ │ ├── AxisStatus.cs # 轴状态
│ │ └── DoorStatus.cs # 门状态
│ └── Events/ # Prism 事件定义
│ ├── AxisStatusChangedEvent.cs
│ ├── DoorStatusChangedEvent.cs
│ ├── DoorInterlockChangedEvent.cs
│ ├── GeometryUpdatedEvent.cs
│ └── MotionErrorEvent.cs
├── Implementations/ # PLC 实现层
│ ├── PlcLinearAxis.cs # 基于 PLC 的直线轴实现
│ ├── PlcRotaryAxis.cs # 基于 PLC 的旋转轴实现
│ ├── PlcSafetyDoor.cs # 基于 PLC 的安全门实现
│ └── PlcMotionSystem.cs # 运动系统管理器
├── Services/ # 业务服务层
│ ├── IMotionControlService.cs # 业务服务接口
│ ├── MotionControlService.cs # 服务实现(轮询、事件、日志)
│ └── GeometryCalculator.cs # 几何计算器
├── Config/ # 配置层
│ ├── MotionControlConfig.cs # 配置实体
│ ├── ConfigLoader.cs # 配置加载器
│ └── MotionSignalNames.cs # PLC 信号名称常量
├── ViewModels/ # 视图模型
│ ├── MotionControlViewModel.cs # 操作面板 ViewModel
│ └── MotionDebugViewModel.cs # 调试窗口 ViewModel
├── Views/ # WPF 视图
│ ├── MotionControlView.xaml # 操作面板(350px UserControl
│ ├── MotionDebugWindow.xaml # Jog 调试窗口
│ └── MotionControlView.xaml.cs
├── Module/
│ └── MotionControlModule.cs # Prism 模块注册
├── Resources/ # 多语言资源
│ ├── Resources.resx # 默认(英文)
│ ├── Resources.zh-CN.resx # 简体中文
│ ├── Resources.zh-TW.resx # 繁体中文
│ └── Resources.en-US.resx # 英文
└── Documents/ # 文档
├── README.md # 本文档
└── GUIDENCE.md # 外部集成指南
```
### 对外接口 | Public Interfaces
| 接口 | 用途 | 注册方式 |
|------|------|----------|
| `IMotionControlService` | 业务控制(移动、停止、回零、Jog、开关门、几何计算) | 单例 |
| `IMotionSystem` | 底层状态读取(轴位置、状态、门状态) | 单例 |
| `MotionControlConfig` | 配置参数(轴范围、几何原点、轮询周期) | 实例 |
| `GeometryCalculator` | 几何正算/反算工具 | 单例 |
### 事件 | Events
| 事件 | 载荷 | 触发时机 |
|------|------|----------|
| `AxisStatusChangedEvent` | `AxisStatusChangedData(AxisId, AxisStatus)` | 轴状态变化 |
| `DoorStatusChangedEvent` | `DoorStatus` | 门状态变化 |
| `DoorInterlockChangedEvent` | `bool` | 门联锁状态变化(true=已联锁, false=未联锁) |
| `GeometryUpdatedEvent` | `GeometryData(FOD, FDD, Magnification)` | 几何参数更新(每轮询周期) |
| `MotionErrorEvent` | `MotionErrorData(AxisId, ErrorMessage)` | 轴进入 Error/Alarm 状态 |
---
## 技术要求 | Technical Requirements
| 依赖 | 版本 | 用途 |
|------|------|------|
| .NET 8.0 | net8.0-windows7.0 | 运行时 |
| Prism.Wpf | 9.0.537 | MVVM + DI + EventAggregator |
| Telerik UI for WPF | 2024.1.408 | UI 控件(Crystal 主题) |
| XP.Common | - | 日志、多语言 |
| XP.Hardware.PLC | - | IPlcServicePLC 连接状态)、ISignalDataServicePLC 信号读写) |
---
## 配置参数 | Configuration
`App.config``<appSettings>` 中配置:
| 参数 | 默认值 | 说明 |
|------|--------|------|
| `MotionControl:SourceZ:Min/Max/Origin` | 0/500/0 | 射线源Z轴范围和原点(mm) |
| `MotionControl:DetectorZ:Min/Max/Origin` | 0/600/0 | 探测器Z轴范围和原点(mm) |
| `MotionControl:StageX:Min/Max/Origin` | -150/150/0 | 载物台X轴范围和原点(mm) |
| `MotionControl:StageY:Min/Max/Origin` | -150/150/0 | 载物台Y轴范围和原点(mm) |
| `MotionControl:DetectorSwing:Min/Max/Enabled` | -45/45/true | 探测器摆动范围和启用 |
| `MotionControl:StageRotation:Min/Max/Enabled` | -360/360/true | 载物台旋转范围和启用 |
| `MotionControl:FixtureRotation:Min/Max/Enabled` | -90/90/false | 夹具旋转范围和启用 |
| `MotionControl:Geometry:SourceZOrigin` | 0 | 射线源Z原点偏移(mm) |
| `MotionControl:Geometry:DetectorZOrigin` | 600 | 探测器Z原点偏移(mm |
| `MotionControl:Geometry:StageRotationCenterZ` | 300 | 旋转中心Z坐标(mm |
| `MotionControl:PollingInterval` | 100 | 轮询周期(ms |
| `MotionControl:DefaultVelocity` | 100 | 默认速度 |
PLC 信号名称硬编码在 `MotionSignalNames.cs` 中,信号定义在 `PlcAddrDfn.xml` 的 ReadCommon/WriteCommon 组。
---
**最后更新 | Last Updated**: 2026-04-14