面向后续维护/扩展者。先读
Docs/ARCHITECTURE.md(架构蓝图)与本文档; 每轮迭代的详细过程见Docs/NIGHT_LOG.md(追加轮①~㉒)。 技术栈:.NET Framework 4.8 / WPF / Prism.DryIoc / HandyControl 3.5.1 / PropertyGridLib 1.1.2 / NLog。
┌─────────────────────────────────────────────────────────┐
│ 5. UI(客户交付层) │
│ TeamAAS —— 主程序壳(导航/主页/产品管理),按客户裁剪 │
├─────────────────────────────────────────────────────────┤
│ 4. 插件层(开放给工程师写代码的层) │
│ TeamAAS.Plugins/* │
│ ├─ Plugins.Standard(逻辑/文件/通讯批量等通用节点) │
│ ├─ Plugins.Feeder(供料) │
│ ├─ Plugins.Vpp / .Vpp.Detection / .Vpp.Measurement │
│ ├─ Plugins.Vm / Plugins.Vm.Module │
│ └─ Plugins.Halcon / .Halcon.{ImageProcessing, │
│ Detection, Measurement, Logic} │
├─────────────────────────────────────────────────────────┤
│ 3. 流程引擎 + 视觉引擎 + 设备内核(地基的核心) │
│ TeamAAS.FlowEngine(流程画布/执行器/插件框架/公式绑定) │
│ TeamAAS.VisualEngine(视觉引擎发现与切换) │
│ TeamAAS.{Camera,Communication,Feeder,Motion,Robot, │
│ Database}(设备内核,统一接口 + 调试页) │
├─────────────────────────────────────────────────────────┤
│ 2. SDK / 公共(跨插件契约,只依赖无业务) │
│ TeamAAS.SDK(图层总线 Vision/、设备契约 Devices/) │
│ TeamAAS.Global(日志/对话框/本地化/主题) │
│ TeamAAS.Shared │
├─────────────────────────────────────────────────────────┤
│ 1. 宿主(ExE/win-x64 部署目录:EXE + Runtime + Config) │
└─────────────────────────────────────────────────────────┘
依赖方向永远向下:插件引用 Core(Core 聚合 SDK/Global/FlowEngine), SDK 不依赖任何上层;平台 SDK(Cognex/海康/HALCON)只被对应插件引用。
ExE/win-x64/
├─ TeamAAS.exe / .config
├─ Config/ Products/ Recipe/ Logs/ ← 配置与数据(根目录白名单)
└─ Runtime/ ← 其余全部下沉到这里
├─ *.dll ← 宿主/引擎/SDK 程序集
├─ Plugins/ ← 插件根(递归扫描)
│ ├─ Plugins.Standard.dll / Plugins.Vpp.dll / Plugins.Vm.dll / Plugins.Feeder.dll
│ ├─ HalconPlugins/ (Plugins.Halcon.* + halcondotnet.dll)
│ ├─ VppPlugins/ (Plugins.Vpp.Detection/Measurement)
│ ├─ VmPlugins/ (Plugins.Vm.Module)
│ └─ Vpp9.0/ Vm4.4/ dll/x64/ ← 平台原生/托管运行库
关键机制:
EXE/win-x64/Plugins/<平台目录>,PostBuild 再整体移到 Runtime/Plugins。Runtime/Plugins/**,只装载 Plugins.*.dll,同名 DLL 去重。RuntimeDir 递归查找 —— 平台运行库
(Vpp9.0/Vm4.4)就位即可用,不装全局。Runtime/dll/x64。| 概念 | 类型 | 说明 |
|---|---|---|
| 流程图 | FlowGraph |
节点+连线;组合模块持有 SubGraph |
| 节点 | FlowNode |
PluginId→插件实例;Registry=流程结果注册表 |
| 插件契约 | IFlowNodePlugin |
InitRun/PluginRun/DeclareOutputs/GetModel |
| 插件基类 | BasePlugin<TModel> |
Model 挂配置([Serializable] 随 .aas 序列化);RegistryOrDebug 取公式值 |
| 输出声明 | OutputField(Key, Type) |
Key(不是 Name)——下游公式 &{节点.Key} 绑定 |
| 执行器 | FlowExecutor |
SkipReset 保留输入变量;EndNodeEncountered 结束分支 |
| 结果注册表 | ResultRegistry |
SetInputVariable/GetValue;组合模块「输入.变量」注入 |
| 序列化 | BinaryFormatter + FlowSerializationBinder |
跨程序集类型迁移按全名兜底解析 |
[Serializable]
[Plugin("显示名", PluginCategory.视觉模块, typeof(MyModel), "图标Kind",
NodeShape = NodeCategory.Normal, IsSubFlowNode = true, VisionCategory = VisionPlugin.检测识别,
Description = "...")]
public class MyPlugin : BasePlugin<MyModel>
{
public override List<OutputField> DeclareOutputs() { ... } // 输出契约(绑定树可见)
public override NodeRunStatus PluginRun(CancellationToken ct, out Dictionary<string, object> res) { ... }
}
PropertyGridLib 特性标注([Category]/[DisplayName]/[Description]/[Button]/
[MultiSelect]/[NumberSlider]/[FilePath]/[CollectionEditor]/[FormulaEditor])——属性面板零 UI 代码;IsSubFlowNode = true(主工具箱不显示,只进对应平台子流程编辑器);每个视觉平台 = 主插件工程(引擎/显示/工具封装/容器节点)+ 分类算子工程(Plugins.<平台>.<功能>),
部署到 Runtime/Plugins/<平台目录>/。编辑器 = FlowEditorView(子流程工具箱按平台过滤) + 右侧大显示窗
(注入替换「节点信息」区)。
① 程序集是懒加载的
CogBlobTool 所在的 Cognex.VisionPro.Blob.dll 等,应用没显式用到之前不在 AppDomain,
反射按名扫描永远找不到。FindToolType/FindControlType 找不到时先
EnsureCognexAssembliesLoaded()(Assembly.Load 触发 AssemblyResolve → Runtime 递归解析)再重扫。
部署提醒:Runtime/Vpp9.0 必须含 Blob/PMAlign/Caliper 工具程序集
(本机安装的 ReferencedAssemblies 目录里有,bin 目录没有——这版很特殊)。
② 显示控件 RenderEngine 必须切 GDI
CogRecordDisplay 默认硬件加速渲染,向日葵远程/部分驱动下整块白屏
(本机实测:WinForms/WPF 都正常、仅 Cog 显示白屏)。修复 = RenderEngine = CogGDIRenderEngine(1)。
注意该属性只能在句柄创建后读写(否则 InvalidOperationException)。
统一入口 CogGdiRender.Apply(control):递归找带 RenderEngine 属性的 Cog 控件,HandleCreated 时机切换;
动态创建的面板(如 ToolBlockEditV2 内部)用短周期 DispatcherTimer 重扫兜底。
③ CogImage8Root 悬空指针(黑图/结果恒 0 的真根因)
// 错误:包裹托管数组 + 立即解除固定 → GC 移动数组后读到悬空指针
root.Initialize(w, h, handle.AddrOfPinnedObject(), stride, null);
grey.SetRoot(root); // handle.Free() 之后 → 全黑
// 正确:走 Bitmap 拷贝路径(该版 VisionPro 无 SetRootCopier/Freeze API,编译器证实)
using (var bmp = img.ToBitmap()) return new CogImage8Grey(bmp);
④ Blob 测量是 GetMeasure 机制,不是属性
CogBlobResult 上没有 Area/CenterMassX 属性——40 项官方测量
(CogBlobMeasureConstants:Label/Area/Perimeter/CenterMassX/Y/Inertia/Elongation/Angle/
Acircularity/BoundingBox*×3 族/NotClipped)全部经 GetMeasure(枚举) 查询,
且必须在工具的 Measurements 页启用,否则抛"测量未计算"。
→ 终端树对这类对象注入「测量项」分支(路径 ...@Measure:Area),取值走 GetMeasureValue。
⑤ 结果集合的数据在方法里,不在属性里
CogBlobResults(Results 的类型)零属性,数据全在无参方法:GetBlobs()/GetBlobMeasure(...)/
CreateBlobImage()/GetTopLevelBlobs()...——树/路径解析必须支持「无参 Get/Create 方法段」,
否则 Results 节点必然断层。
⑥ 编辑控件命名规则:工具 CogBlobTool ↔ 编辑控件 CogBlobEditV2(去掉 Tool),
且可能在不同程序集(ToolGroup.Controls)。查找按"原名+去名"两套候选、跨程序集扫描。
⑦ 预设输出终端(PresetTerminals) 各工具在插件类里定义「中文名 + 属性路径」的常用结果(Blob:面积1..3/重心X2 Y2/X3 Y3; PMAlign:第 2/3 目标 分数/位置/角度;Caliper:分数),基类自动声明为输出并每次运行赋值。 预设路径首次预勾选时逐条实测(GetPathStrict 区分"路径不存在"与"值为 null"),只保留真实存在的。
⑧ 终端路径体系(三方必须一致)
树节点 Tag = 存储的勾选路径 = 运行取值路径。格式:方法段带 ()、集合 [n]、
测量 @Measure:名。NormalizePath 负责旧格式(属性式)一次性迁移;GetPath 解析段名时剥 ()、
GetMember 带"无属性时尝试同名 GetXxx() 方法"兜底。任何一处格式漂移 = 预勾选不回显或输出 null。
HalconWindow 用
HOperatorSet.GetSystem("version") 探测 → 失败显示占位文案(画布/属性编辑不受影响),绝不闪退;VmSdk\(Private=False),运行时由本机 VM 4.4 提供;所有 SDK 调用经
VmRuntime 适配层(LoadSolution/GetProcedure/SetInputs/Run/GetOutputs/GetLayers);VmResourceGuard(1.5s 轮询 watchdog + 主题快照急救)+ 显示端坚持用 WinForms 渲染控件;HKCU\Software\Microsoft\AvalonGraphics\DisableHWAcceleration=1(仅 WPF 软渲染,无需重启)。
还原:reg delete ... /v DisableHWAcceleration /f。彻底解决 = 升级显卡驱动/向日葵。子流程编辑器(FlowEditorView,工具箱按平台过滤:Vision/VisionVpp/VisionVm 三种 Scope)
VisionLayerStore.Publish(LayerPublishArgs{FlowId,NodeName,RunStamp,PreferredLayer,Layers});LayerComboHelper:BuildEntries(下拉条目)/ResolveLayers(选中层+底图回退——
区域类节点自动垫最近发布的图像层)/FindLatestImageLayer。| 特性 | 用途 | 备注 |
|---|---|---|
[Button("文本", nameof(方法))] |
属性面板按钮 | 方法 public 无参/单参;宿主方法内自己 try/catch |
[MultiSelect(typeof(Provider))] |
多选勾选列表 | Provider : IMultiSelectProvider 返回候选项;属性 List |
[TypeConverter] 标准值 |
单选下拉 | GetStandardValuesSupported=true;不要依赖 context.Instance 是模型本体(可能传包装项,用 PluginLoader.SelectFlow 兜底) |
[CollectionEditor] |
集合编辑弹窗 | 远程环境下弹窗可能不显示(本机实测)→ 多选优先用 [MultiSelect] |
[NumberSlider] / [FilePath] / [DirectoryPath] |
滑块/路径 | |
[FormulaEditor(typeof(Provider))] |
公式绑定 | FormulaBound 属性 |