当前位置: 首页 > news >正文

AFSIM-模型导入导出-源码级Bug修改

ModelImport 隶属于 Wizard 模块下的专用插件,主要功能是将 A 项目内的各类平台类型脚本打包整合,批量导入到 B 项目中直接调用使用。

其实日常使用里也有简便办法,直接把 A 项目对应的脚本文件复制粘贴到 B 项目目录,同样能正常调用里面编写好的平台与组件脚本。但这种手动拷贝方式弊端很明显,极易打乱打乱 B 项目原本规整的目录架构,造成文件杂乱堆砌。

接下来博主就带着大家一步步实操教学,手把手教大家正确使用 ModelImport 插件,完成 AFSIM 模型的规范导出与跨项目导入操作。

第一步:源码Bug修改

Bug 描述:在使用 ModelImport 插件将 A 项目中的模型导入到 B 项目时,插件只会在 B 项目里创建对应文件夹,但无法将 A 项目中的脚本文件真正复制过去,导致导入后平台无法正常加载使用。

问题源码位于:

wizard/plugins/ModelImport/source/ModelImportPlugin.cpp

具体出问题的位置,是ImportRecursionHelper这个接口的实现部分。

void ModelImport::Plugin::ImportRecursionHelper(const QString& aFilePath, const QDir& aImportToDir) { // This should not throw because the FileData's existence was checked for in ImportOkay(). const ModelImport::FileData& file = LookupFileData(Path(GetPath(), aFilePath)); aImportToDir.mkpath(file.PathToDir()); // Create subdirectories if necessary // Check if file was already imported if (QFile::exists(Path(aImportToDir.absolutePath(), aFilePath)) && !mImportedFiles.contains(aFilePath)) { switch (mReimportSelection) { case Reimport::cYESTOALL: break; case Reimport::cNOTOALL: return; default: switch (QMessageBox::question( nullptr, QString(), QString("The file \"%1\" is already imported. Would you like Wizard to re-import it?").arg(aFilePath), QMessageBox::StandardButton::Yes | QMessageBox::StandardButton::No | QMessageBox::StandardButton::YesToAll | QMessageBox::StandardButton::NoToAll, QMessageBox::StandardButton::YesToAll)) { case QMessageBox::StandardButton::Yes: break; case QMessageBox::StandardButton::No: return; case QMessageBox::StandardButton::YesToAll: mReimportSelection = Reimport::cYESTOALL; break; case QMessageBox::StandardButton::NoToAll: mReimportSelection = Reimport::cNOTOALL; return; default: break; } } mImportedFiles << aFilePath; // Copy file QFile::copy(Path(GetPath(), aFilePath), Path(aImportToDir.absolutePath(), aFilePath)); // Import dependencies for (const QString& dependency : file.mDependencies) { ImportRecursionHelper(dependency, aImportToDir); } // Import additional dependencies for (const QString& dependency : file.mAdditionalDependencies) { ImportRecursionHelper(dependency, aImportToDir); } } }
if (QFile::exists(Path(aImportToDir.absolutePath(), aFilePath)) && !mImportedFiles.contains(aFilePath))

问题 :源码第400行,当首次导入时文件在目标位置 不存在 , QFile::exists() 返回 false ,整个 if 块被跳过,导致:

  • ❌ 文件不被复制

  • ❌ mImportedFiles 不更新

  • ❌ 依赖不被递归处理

原有设计意图分析:

从代码中可以推断出作者的原始设计意图:

  1. mImportedFiles :防止同一导入会话中重复处理同一个文件(如多个模型共享同一个依赖)

  2. QFile::exists() :检测目标位置是否已有文件,如果有则询问用户是否重新导入

  3. Reimport 对话框 :让用户选择 Yes/No/YesToAll/NoToAll

写这段代码的程序员,忽略了最关键的一个场景 —— 第一次导入时,目标文件根本还不存在!

修改方案:

把原来的单一条件 QFile::exists(...) && !mImportedFiles.contains(...) 拆分为两层独立的 if :

  1. 第一层(L400-403) : mImportedFiles.contains() → 避免同一会话中重复处理

  2. 第二层(L406-437) : QFile::exists() → 仅在文件已存在时询问用户是否重新导入

  3. L439-454 :移出所有 if 块,保证复制和依赖递归始终执行

Reimport 对话框的所有逻辑(Yes/No/YesToAll/NoToAll/cYESTOALL/cNOTOALL) 完全保留不变

修正后代码:

void ModelImport::Plugin::ImportRecursionHelper(const QString& aFilePath, const QDir& aImportToDir) { // This should not throw because the FileData's existence was checked for in ImportOkay(). const ModelImport::FileData& file = LookupFileData(Path(GetPath(), aFilePath)); aImportToDir.mkpath(file.PathToDir()); // Create subdirectories if necessary // Check if file was already imported in this session if (mImportedFiles.contains(aFilePath)) { return; } // Check if file already exists in the target location if (QFile::exists(Path(aImportToDir.absolutePath(), aFilePath))) { switch (mReimportSelection) { case Reimport::cYESTOALL: break; case Reimport::cNOTOALL: return; default: switch (QMessageBox::question( nullptr, QString(), QString("The file \"%1\" is already imported. Would you like Wizard to re-import it?").arg(aFilePath), QMessageBox::StandardButton::Yes | QMessageBox::StandardButton::No | QMessageBox::StandardButton::YesToAll | QMessageBox::StandardButton::NoToAll, QMessageBox::StandardButton::YesToAll)) { case QMessageBox::StandardButton::Yes: break; case QMessageBox::StandardButton::No: return; case QMessageBox::StandardButton::YesToAll: mReimportSelection = Reimport::cYESTOALL; break; case QMessageBox::StandardButton::NoToAll: mReimportSelection = Reimport::cNOTOALL; return; default: break; } } } mImportedFiles << aFilePath; // Copy file QFile::copy(Path(GetPath(), aFilePath), Path(aImportToDir.absolutePath(), aFilePath)); // Import dependencies for (const QString& dependency : file.mDependencies) { ImportRecursionHelper(dependency, aImportToDir); } // Import additional dependencies for (const QString& dependency : file.mAdditionalDependencies) { ImportRecursionHelper(dependency, aImportToDir); } }

修改后请重新编译WizModelPlugin插件

第二步:创建一个空项目

我们先新建一个目录,在目录内新建空白文件。我这边新建名为EmptyProj的文件夹,再在里面创建EmptyProj.txt空白文件,随后用 Wizard 工具将其打开,这样就搭建好了一个纯净的空白测试项目环境。

第三步:创建一个测试用的想定脚本

接下来博主准备好了现成的测试项目,准备把里面的模型脚本,导入到刚刚搭建好的空白项目里进行实测。

用 Wizard 打开测试项目后能清晰看到,项目内预置了 F22 机型平台以及各类配套组件。在Project Browser中可查看完整文件目录结构,Type Brower里则能直观浏览所有平台机型与功能组件类型,效果如图所示。

这里提醒大家一个实操重点:我们日常编写平台类型脚本时,常会调用已写好的各类组件类型,在用"include_once"文件命令引用文件时,路径引用规则一定要留意。

不少朋友会疑惑,平时直接写文件名不加../也能正常引用,确实,不加上级路径才是 AFSIM 正统编写规范。

那我这里为何要特意加上../呢?原因就出在模型导入功能上:该功能解析引用路径时,是以被调用类型脚本自身所在位置作为基准路径,而非项目启动文件的位置。

若是不提前手动调整路径层级,执行模型导入生成文件时,就会弹出大量路径报错。严格来说这不算程序 BUG,但使用体验十分别扭。

说实话遇上这类细节逻辑疏漏,不难看出这款插件没经过充分实测就正式发布了。虽说日常实操里这个功能并不算高频常用,但既然存在就自有其价值,文章最后博主大胆聊聊它的适用场景以及后续可拓展的开发方向。至于这个麻烦的路径适配问题,就留给大家亲手实操摸索解决吧

博主就以测试项目里的aircraft.txt文件为例给大家演示,效果如下图所示。

至此,准备工作就完成了。

第四步:空项目导入测试项目中的模型

用 Wizard 打开刚才建好的空项目,点击菜单栏 Options 里的偏好设置 Preference,接着选中 ModelImport 选项,界面如下图所示。

点击 Browse 浏览按钮,选中并定位到我们准备好的测试项目路径,操作界面如图所示。

我们先在顶部View 菜单里,把Model Importer选项勾选显示出来。接着点击界面上的Generate Model Mapping File,弹出提示后选择Yes。这里可以多点几遍,第一遍是让 Wizard 自动帮我们生成模型映射配置文件。再点一次,弹窗里选择Merge(融合)或者Overwrite(覆盖)都可以,操作界面就像下图这样。

此时在 Model Importer 停靠窗口中,就能清晰看到测试项目里的 F22 平台类型,界面效果如下图所示。

直接双击列表里的 F22 条目,弹出确认窗口后点击 OK 即可,ModelImport插件已经帮你完成模型导入了。

这时在项目浏览器里能看到自动生成了 imports 文件夹,里面存放着 F22 平台以及它所有依赖组件的全套脚本文件。

第五步:导入模型的使用

在启动文件EmptyModel.txt中输入 include_once imports/imports.txt

或者右键imports.txt文件选择Add to Startup Files,加入到启动文件中

在三维地球视图(Map Display)空白位置右键选择添加平台,就能找到 F22机型,说明该模型已经成功加载进项目中了。

我们查看添加完成后的整体效果,再分别打开项目浏览器与类型浏览器核对,确认平台结构、组件配置都和原测试项目保持一致。

后续再导入其他外部项目模型,所有相关脚本都会统一存放在 imports 目录中,不会打乱原有项目的文件架构,做到互不干扰了。

官方说明

可以搜索Model Import关键字

翻译一下就是:

模型导入 - Wizard 工具

Wizard 中的模型导入对话框,提供了一个将平台类型及其依赖项从外部目录导入到 AFSIM 项目中的交互界面。

外部目录中会使用一个JSON 文件来记录导入信息。如果该文件不存在,用户可以根据提示自动生成。

复制的文件会保留原有的目录结构,并统一放入 ** 导入文件(Imports File)** 所在的目录(详见偏好设置)。

为方便使用,工具会自动生成一个文本文件,包含所有已导入的文件。该文件必须手动引入到场景文件中才能生效。


偏好设置(Preferences)

在偏好设置菜单中,用户可配置以下选项:

搜索路径(Search Path)

需要从中导入类型的外部项目目录路径。界面提供浏览(Browse)按钮,方便快速选择路径。

模型映射文件(Model Mapping File)

用于记录导入信息的JSON 文件名。默认值:importData.json

导入文件(Imports File)

自动生成的、用于统一引用所有导入文件的文本文件名。默认值:imports/imports.txt

显示模式(View Mode)

名称列表(Name List)

可排序的平台类型名称列表,为默认显示模式。

分类列表(Category List)

基于category关键字自动生成的标签分类列表。每个平台类型会显示在对应的标签下。可手动编辑模型映射文件调整标签。

文件树(File Tree)

以树形结构展示外部目录的真实文件布局。

生成模型映射文件(Generate Model Mapping File)

扫描外部目录,更新模型映射文件。工具会提示用户:覆盖现有数据或尝试合并自定义修改。

重新加载模型映射文件(Reload Model Mapping File)

从磁盘重新读取映射文件,刷新界面显示内容。


界面展示结果

Model Import 停靠窗口包含以下区域:

目录(Directory)

显示当前外部项目的路径(只读,不可修改)。

搜索(Search)

支持实时搜索已识别的类型。输入内容时界面自动刷新;按回车键可保存当前搜索记录,通过下拉菜单可重复使用;点击 X 按钮清空搜索框与历史记录。

显示区域(View Area)

根据偏好设置,以列表或树形结构展示可导入内容。双击条目即可将其导入当前项目。

在分类列表模式下:双击一个分类,可一键导入整个分类下的所有内容。

在文件树模式下:双击文件可直接导入该文件;双击文件夹,会提示导入文件夹内的全部内容。


文件导入规则

导入一个类型时,该类型的定义文件及其所有依赖项会递归复制到项目中。依赖项指文件头部使用include或include_once引入的所有文件。

⚠️ 注意:在代码块内部或类型定义之后才引入的文件,可能不会被自动复制。如果需要手动添加依赖项,可在模型映射文件的AdditionalDependencies节点中配置。

如果导入的文件在当前项目中已存在,工具会提示用户:跳过或覆盖。

工具在导入过程中还会额外生成两个文件

如下图所示,测试项目下多了两个文件。

importErrors.log日志文件,它专门用来记录生成导入配置时遇到的错误。大家可以看一下,这个文件大小是0KB,说明它是空的,也就代表我们这次生成导入配置的过程没有报错。

importData.json核心配置片段如下:

[ { "File": "aircraft.txt", "Path": "platforms", "Dependencies": [ "sensors/radar/acq_radar.txt", "processors/single_large_sam_tactics.txt", "comms/base_comm.txt" ], "AdditionalDependencies": [], "Defines": [ { "Name": "F22", "Type": "platform_type", "Inherits": "WSF_PLATFORM", "Labels": [] } ] },

文件整体是一个配置信息数组,每一条记录都包含File、Path、Dependencies等核心字段,分别用来描述模型文件、路径信息和依赖关系。

总结

这款功能还有很大优化余地,实际使用体验着实不够顺手。但不得不承认,整体架构设计水准依旧顶尖,对比国内不少厂商开发的建模工具与模型库体系,二者差距十分明显。

单论代码细节而言,生成依赖路径时,理应以偏好设置里的检索路径作为基准统一转为相对路径,使用起来会合理很多。我学识有限,不便随意评判官方这样设计的用意,也不确定是否是我的操作方式存在偏差,精通这块的同行欢迎在评论区一同交流探讨。

为何需要模型导入这个功能?

场景一:

  1. 公司有一个共享的模型库(包含各种平台、传感器、武器)

  2. 用户打开自己的 AFSIM 项目

  3. 用户在 ModelImport 里配置共享模型库路径

  4. 用户双击选择需要的模型

  5. 插件自动复制模型到当前项目的 imports 目录

  6. 用户在自己的场景文件里 include imports/imports.txt

  7. 用户就可以使用这些模型了

这套用法优势十分明显,既方便全员快速调用公司标准化模型库内的各类资源,也便于企业集中统一维护管理模型资产。

彻底告别以往每个项目都各自存放模型文件的混乱局面,从根源上避免脚本冗余堆积,有效杜绝项目脚本逐渐堆砌成难以维护的乱象。

场景二:

ModelImport 就是 AFSIM 的"模型应用商店" :

  • 乙方上传(交付 imports 包)

  • 甲方下载(通过 ModelImport 导入)

  • 甲方使用(直接在场景中引用)

你只需要知道"我要用 F22",剩下的 ModelImport 帮你搞定。

个人认为的ModelImport 核心价值

将"建模能力"和"模型使用"分离,让不擅长建模的人也能轻松使用专业团队建好的模型。

这正是设计模式中的 Façade 模式 ——为复杂子系统提供一个简化的接口。

更深层次的思考

版本管理体系

当前只有Merge/Overwrite两种模式,缺少:

缺少能力

说明

版本追溯

知道模型是哪个版本的

增量更新

只更新变化的部分

回滚能力

恢复到之前的版本

兼容性声明

声明依赖的最低版本

质量保证体系

1.模型分级

等级

说明

验证要求

A级

官方认证,可用于作战

完整验证 + 实战测试

B级

已验证,可用于训练

完整验证

C级

社区贡献,需用户评估

基础验证

D级

实验性,不保证正确

无验证

2.持续集成

  1. CI 自动验证:语法检查、依赖检查、命名规范检查、冲突检测

  2. QA 人工审核:功能验证、文档审查

  3. 验证和审核通过后打上标签/签名发布到模型库

3.治理与生命周期

阶段

活动

规划

定义模型需求和接口

开发

乙方建模、验证

发布

签名、打包、发布

部署

甲方导入、使用

维护

乙方修复Bug、升级

退役

甲方移除、替换

ModelImport 不只是一个工具,而是一个生态系统的入口。工具本身的功能已经解决了"如何使用"的问题,但要实现健康可持续的生态,需要配套的 规范化体系 作为支撑。

http://www.jsqmd.com/news/856286/

相关文章:

  • 原生PHP到底如何缩短响应时间 TTFB?
  • VisionPro 相机集成与视觉测量
  • 摆脱论文困扰! AI论文工具2026最新测评与推荐
  • 【Perplexity词组搭配查询避坑清单】:8个致命误用场景+3类伪低困惑度陷阱,资深语言工程师紧急预警
  • Visa携手Jason Sudeikis,将足球赛场最简单的进球方式转化为2026年国际足联世界杯的最精彩球迷时刻
  • CSS锚点定位(Anchor Positioning)完全指南:实现精准定位
  • AUTOSAR Ea模块深度解析:EEPROM抽象原理、配置实战与性能优化
  • Win10开发环境搭建必看:彻底解决ping localhost返回::1导致服务启动失败的问题
  • AI Agent Harness Engineering 不是银弹:哪些场景用了 Multi-Agent 反而更差
  • Windows下安装OpenCode并配置oh-my-openagent和superpowers
  • STM32CubeMX 6.14版本保姆级安装教程(附CSDN下载链接,解决官网卡顿)
  • 1987年5月25日晚上23-24点出生性格、运势和命运
  • 昇腾CANN shmem:把多张 NPU 的 HBM 变成一块全局内存
  • HP Z66 G6 外接显示器无信号排查:amdgpu DCN 3.1 EDID 超时与 HDMI 2.1 FRL 协商问题
  • AI一周事件 · 2026-05-13 至 2026-05-19
  • 从Java到AI大模型:小白程序员必备转型指南,收藏学习不迷路!
  • ADI AD5940阻抗测量开发板开箱实测:从硬件连接到IAR工程配置的保姆级避坑指南
  • 2026年牵手红娘服务权威推荐深度分析:婚恋场景用户择偶效率低与线下见面率低困境 - 品牌推荐
  • 程序员修炼之道:从代码到思维的进阶指南
  • OpenWrt opkg配置进阶:手把手教你设置代理、跳过证书检查,解决国内下载慢问题
  • 平衡小车/四轴飞行器姿态解算实战:MPU6050三种滤波算法(四元数、互补、卡尔曼)代码详解与选型指南
  • Option ‘importsNotUsedAsValues‘ has been removed. Please remove it from your configuration
  • 5分钟掌握AI音频分离:Retrieval-based-Voice-Conversion-WebUI终极指南
  • SAP应收清账程序开发避坑指南:外币、超额收款、表更新这些细节别忽略
  • C语言编程实战:用ASCII码表玩转字符大小写转换(附完整代码)
  • 告别手写C代码!Matlab 2020b S-Function Builder保姆级配置教程(附避坑指南)
  • 2026年牵手红娘服务权威推荐深度分析:婚恋场景线上虚假信息泛滥与线下见面率低痛点 - 品牌推荐
  • uni-app视频播放二选一:手把手对比调试video.js与MuiPlayer插件(H5/m3u8实战)
  • DeepStream9.0 masktracker
  • 告别零散脚本:用Playwright+Pytest+Yaml+Allure搭建一个真正可维护的UI自动化项目