news 2026/9/4 17:51:39

CAD到Unity自动化导入:基于realvirtual的数字孪生实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CAD到Unity自动化导入:基于realvirtual的数字孪生实践

在 Unity 里做数字孪生时,真正耗时的往往不是业务逻辑,而是把大型 CAD 模型正确搬进场景。尤其是产线、机器人、机床这类大型装配体,手工导入不仅容易丢结构,还会因为单位、坐标系、材质和碰撞体设置不一致,导致后期调试 realvirtual 和 PLC 仿真时反复返工。realvirtual 作为 Unity 生态中常用于虚拟调试和数字孪生搭建的平台,本身提供了运行时框架,但 CAD 到 Unity 的自动化导入仍然需要在编辑器阶段处理干净。

这篇内容围绕 realvirtual 的数字孪生开发流程,整理一条从 CAD 数据到 Unity 场景的自动化导入链路:先在 CAD 侧或 DCC 工具中完成单位、层级、格式预处理,再用 Unity Editor 脚本批量设置导入参数并实例化到场景,最后用命名映射挂接 realvirtual 行为组件,并完成验证。读完你会得到一套可执行的自动化基线,后面可以按项目扩展成与 PDM、版本管理、构建流水线对接的完整方案。

1. 先理清 realvirtual 平台的 CAD 导入链路

1.1 CAD 原生文件为什么不能直接进 Unity

Unity 运行时只能理解网格、材质、Transform 和组件。而原生 CAD 文件记录的是参数化特征:拉伸、旋转、草图、约束、特征树。这些数据对建模软件有意义,对 Unity 来说没有直接用途。更麻烦的是,大型装配体通常包含大量外部引用,直接从某个子部件文件导入,经常会缺件或丢失装配位置。

CAD 原生格式还有第二个问题:它不是为实时渲染设计的。一个螺栓模型可能带有倒角、螺纹、孔特征,转换后会产生非常高密度的三角网格,Unity 的 Mesh Collider 计算和渲染压力都会大幅上升。realvirtual 做的是数字孪生和虚拟调试,它需要的是能在场景中实时运行、能被驱动组件控制、能被碰撞体识别的对象,而不是一份完整的设计图。

所以,CAD 导入的第一步不是“把文件拖进去”,而是确认数据在哪个环节被转换成 Unity 能用的网格资源。原生 CAD 文件、STEP/IGES 这类几何交换格式,应该停留在预处理阶段;进入 Assets 的应该是 FBX、glTF、OBJ 这类通用格式。

1.2 推荐的链路和格式选择

在 realvirtual 项目中,推荐走这条链路:

原生 CAD 设计软件 -> 导出中性格式或网格格式 -> DCC 工具批量整理单位、层级、命名 -> 导出 FBX / glTF -> Unity 导入管线 -> realvirtual 组件装配

不同格式在链路中的角色差别很大,整理成表更直观。

数据格式适合阶段保留信息在 Unity 中的可用性备注
原生 CAD 文件设计阶段参数特征、装配约束不能直接读需要设计软件或专用导出器
STEP / IGES几何交换实体几何、装配结构不能直接读需要先转换成网格格式
OBJ / STL通用网格网格,OBJ 可带材质可导入层级、单位、材质支持弱
FBXUnity 常用网格、层级、材质、动画推荐对 Unity 支持成熟
glTFWeb/轻量场景网格、层级、材质可导入适合程序化生成和 Web 展示

如果原始 CAD 软件能直接导出 FBX,优先导出 FBX。如果只能导出 STEP/IGES,则要先转入 Blender 或其他 DCC 工具,再导出 FBX。OBJ 和 STL 不是不能用,但它们对层级支持太弱,适合单件模型或碰撞代理网格,不适合整条产线装配体。

1.3 数字孪生场景下,CAD 模型不只是“看得见”

realvirtual 的价值在于用 Unity 的实时渲染和物理仿真去验证控制逻辑。一个机器人单元需要的不是单一完整网格,而是拆分成多个运动部件,并且每个部件要能被驱动、被传感、被碰撞检测。

如果直接把整个装配体导成一个 Mesh,运动仿真根本无法做。所以自动化导入的核心不只是文件复制,而是:

  • 拆分运动部件。
  • 设定层级和原点。
  • 挂接驱动组件。
  • 建立命名映射。
  • 生成适合物理引擎的碰撞体。

CAD 几何只是数字孪生模型的外壳,真正的行为信息要通过 Unity 组件补充。这也是为什么“自动导入 CAD”和“自动生成可运行数字孪生场景”之间还有一段距离。把这段距离用脚本和清单文件填平,是本文的关键目标。

2. 自动化预处理:先统一单位、坐标轴和层级

2.1 单位、原点、坐标轴为什么会影响物理仿真

Unity 的默认单位是米。CAD 软件常用毫米,部分行业用英寸。如果不统一,一个 5000 mm 长的设备在 Unity 里会变成 5000 个 Unity 单位,物理引擎会把模型当成巨大物体,刚体下落看似正常,但碰撞检测参数、关节力矩、传感器范围都会受影响。realvirtual 中的速度、加速度、位置反馈通常也按米每秒或弧度处理,单位错乱会直接污染仿真数据。

原点同样重要。CAD 装配体原点有时在设备角落,有时在基座底面,有时还在整个世界坐标的几公里之外。如果导入后原点位置不统一,脚本里设置position就会非常困难,所有模型都要手工平移回目标位置。

坐标轴方向也经常踩坑。很多 CAD 软件使用 Z 轴作为高度方向,Unity 默认 Y 轴向上。如果导出时不做转换,模型进入 Unity 后会整体旋转 90 度。这类问题不能在场景里手动旋转修正,因为后续 realvirtual 驱动部件时,旋转方向会一并受影响。

2.2 用 Blender Python 做批量模型整理

如果模型数量多,不建议手工打开软件逐个另存。Blender 支持 Python API,可以在不开图形界面的情况下批量转换。下面的脚本演示一个最小流程:清空场景、导入模型、设置缩放、导出 FBX。

import bpy import os input_dir = r"D:\cad_work\meshes" output_dir = r"D:\cad_work\fbx" unit_scale = 0.001 # 从毫米转到米 if not os.path.exists(output_dir): os.makedirs(output_dir) for file_name in os.listdir(input_dir): if not file_name.lower().endswith((".obj", ".stl")): continue # 清空当前场景,保持每次导入独立 bpy.ops.wm.read_factory_settings(use_empty=True) src_path = os.path.join(input_dir, file_name) if file_name.lower().endswith(".obj"): bpy.ops.wm.obj_import(filepath=src_path) else: bpy.ops.wm.stl_import(filepath=src_path) # 统一设置所有对象缩放到米 for obj in bpy.context.scene.objects: obj.scale = (unit_scale, unit_scale, unit_scale) export_name = os.path.splitext(file_name)[0] + ".fbx" bpy.ops.export_scene.fbx(filepath=os.path.join(output_dir, export_name))

不同 Blender 版本中导入导出算子名可能不同。Blender 4.x 使用wm.obj_import,旧版本使用import_scene.obj。脚本重点不是某个 API,而是整个批处理思路:每次清空、导入、设置缩放、导出,保证一批模型的单位、坐标和文件命名规则一致。

如果你的 CAD 软件能直接导出 FBX 且层级正确,Blender 这一步可以省略。但现实中经常遇到 CAD 导出结果混乱,这时用 Blender 统一处理会比在 Unity 里逐个修正更可靠。

2.3 清理模型和生成 LOD,避免把设计细节直接带进实时场景

CAD 里一个螺栓可能有几十条倒角边,导入后会在场景中生成大量三角形。实时场景不需要全部保留,尤其 realvirtual 做控制逻辑验证时,很多细节对逻辑没有意义。

常见做法有:

  • 对固定不动的结构件生成低模或使用简化代理。
  • 对需要真实碰撞运动的部分,碰撞体使用简单盒体、圆柱或凸包。
  • 对摄像头观察不到的内部零件可以删除。
  • 对距离远、体积小的零件使用 LOD Group 或减少实例数量。

生成 LOD 可以使用 Unity 的 LOD Group,也可以在 Blender 中用 Decimate 修改器预处理。核心原则是:视觉高模、碰撞低模、逻辑传动部件用简化网格。这样可以在保持画面可读性的同时,明显降低渲染和物理开销。

2.4 预处理检查清单

在进入 Unity 之前,建议按下面的清单逐项检查:

检查项要求常见问题
单位统一为米,或在导出时记录缩放值模型过大、碰撞异常
原点每个部件原点在运动旋转轴或易于定位的位置需要手工大幅平移
坐标轴高度方向符合 Unity Y 轴向上的约定模型整体旋转 90 度
层级运动部件独立,不能全部合并成一个 Meshrealvirtual 无法识别运动对象
命名每个部件名称清晰且可映射出现大量 Part_001 随机名
材质贴图使用相对路径,避免中文路径和空格导入后材质丢失
三角面导出前检查总三角形数量场景卡顿、DrawCall 失控

3. Unity Editor 脚本:把 CAD 资源自动化转换为数字孪生场景

3.1 目录结构和清单文件先定好

在 Assets 下建议创建以下目录结构:

Assets/ └── ImportedCAD/ ├── FBX/ # 原始 FBX 资源 ├── Generated/ # 生成的 Prefab 和材质 ├── Manifests/ # JSON 映射清单 └── Scripts/ # 编辑器导入脚本

同时创建一份manifest.json,记录每个模型的导入位置、旋转、缩放、命名映射和父节点。这样既方便重复导入,也方便核查。

{ "entries": [ { "name": "Conveyor_01", "fbx": "Assets/ImportedCAD/FBX/Conveyor_01.fbx", "virtualTag": "conveyor_01", "parentPath": "ProductionLine/RobotCell_01", "position": [10.0, 0.0, 0.0], "rotation": [0.0, 90.0, 0.0], "scale": [1.0, 1.0, 1.0] }, { "name": "Robot_Arm_01", "fbx": "Assets/ImportedCAD/FBX/Robot_Arm_01.fbx", "virtualTag": "robot_arm_01", "parentPath": "ProductionLine/RobotCell_01", "position": [0.0, 0.0, 5.0], "rotation": [0.0, 0.0, 0.0], "scale": [1.0, 1.0, 1.0] } ] }

如果不希望手工维护 JSON,也可以在 CAD 侧生成同名映射文本。核心是让脚本能自动读取清单,而不是在 Unity 场景里手工配置每个设备。

3.2 批量设置 FBX 导入参数

CAD 模型导入 Unity 时,默认导入参数不一定合适。摄像机和灯光标记对 CAD 模型大多无用,关闭它们可以减少资源体积。BlendShape、可见性标记也往往不是 CAD 数据需要的。

下面这段编辑器脚本可以批量设置指定目录下所有 FBX 的导入参数。

using UnityEditor; using UnityEngine; public static class CadFbxImporter { [MenuItem("Tools/CAD Import/Apply FBX Import Settings")] public static void ApplyFbxImportSettings() { string[] guids = AssetDatabase.FindAssets("t:Model", new[] { "Assets/ImportedCAD/FBX" }); foreach (string guid in guids) { string path = AssetDatabase.GUIDToAssetPath(guid); ModelImporter importer = AssetImporter.GetAtPath(path) as ModelImporter; if (importer == null) continue; importer.globalScale = 1.0f; importer.useFileScale = true; importer.importCameras = false; importer.importLights = false; importer.importVisibility = false; importer.importBlendShapes = false; importer.isReadable = false; importer.SaveAndReimport(); } AssetDatabase.Refresh(); } }

这段脚本的作用是把导入参数标准化。globalScale = 1的前提是预处理阶段已经统一了单位;如果 FBX 导出时已经做了毫米转米,则不需要在 Unity 里再缩放一次。isReadable = false可以降低运行时内存占用,但如果后续需要读取网格顶点数据,则需要保留可读。

需要注意,不同 Unity 版本的ModelImporter参数名可能不同,尤其是材质导入模式。如果项目使用 URP 或 HDRP,还需要处理材质转换,否则导入后可能出现粉色材质。

3.3 根据 manifest 自动实例化到场景

批量导入资源后,还需要把 FBX 预制体加载到场景,并按清单设置位置、层级、名称和 realvirtual 组件。下面脚本实现了一个最小的 manifest 导入流程。

using System; using System.IO; using UnityEditor; using UnityEngine; public class CadImportEntry { public string name; public string fbxPath; public string virtualTag; public string parentPath; public float[] position; public float[] rotation; public float[] scale; } public class CadImportManifest { public CadImportEntry[] entries; } public static class CadAutoImporter { [MenuItem("Tools/CAD Import/Import From Manifest")] public static void ImportFromManifest() { string manifestPath = EditorUtility.OpenFilePanel("选择 CAD 清单", Application.dataPath, "json"); if (string.IsNullOrEmpty(manifestPath)) return; string json = File.ReadAllText(manifestPath); CadImportManifest manifest = JsonUtility.FromJson<CadImportManifest>(json); if (manifest == null || manifest.entries == null) { Debug.LogError("清单格式不正确"); return; } Transform root = GameObject.Find("CAD_Root")?.transform; if (root == null) { root = new GameObject("CAD_Root").transform; } foreach (CadImportEntry entry in manifest.entries) { ImportEntry(entry, root); } AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); } private static void ImportEntry(CadImportEntry entry, Transform root) { GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>(entry.fbxPath); if (prefab == null) { Debug.LogWarning("FBX 不存在或尚未导入: " + entry.fbxPath); return; } GameObject instance = (GameObject)PrefabUtility.InstantiatePrefab(prefab); instance.name = entry.name; instance.transform.SetParent(root, true); instance.transform.localPosition = ToVector3(entry.position); instance.transform.localRotation = Quaternion.Euler(ToVector3(entry.rotation)); instance.transform.localScale = ToVector3(entry.scale); if (!string.IsNullOrEmpty(entry.parentPath)) { Transform parent = GameObject.Find(entry.parentPath)?.transform; if (parent != null) { instance.transform.SetParent(parent, true); instance.transform.localPosition = ToVector3(entry.position); instance.transform.localRotation = Quaternion.Euler(ToVector3(entry.rotation)); instance.transform.localScale = ToVector3(entry.scale); } else { Debug.LogWarning("父节点未找到: " + entry.parentPath); } } AttachRealvirtualDriver(instance, entry.virtualTag); } private static Vector3 ToVector3(float[] values) { if (values == null || values.Length < 3) return Vector3.zero; return new Vector3(values[0], values[1], values[2]); } private static void AttachRealvirtualDriver(GameObject target, string virtualTag) { if (string.IsNullOrEmpty(virtualTag)) return; Type driverType = Type.GetType("realvirtual.Core.Drive, realvirtual.Core"); if (driverType == null) { Debug.LogWarning($"当前 realvirtual 版本中未找到 Drive 类型: {virtualTag}"); return; } Component driver = target.GetComponent(driverType); if (driver == null) driver = target.AddComponent(driverType); var prop = driverType.GetProperty("Name"); if (prop != null) prop.SetValue(driver, virtualTag); } }

这段脚本使用反射挂接 realvirtual 组件,是因为不同版本中组件类名和命名空间可能不同。反射可以让脚本在版本不确定时不至于编译失败,但代价是不方便 IDE 补全和编译期检查。实际项目中,如果确认了 realvirtual 版本,可以直接在脚本顶部引用命名空间,用强类型方式编写,便于维护。

脚本中的parentPath需要在场景中已经存在对应对象。如果父对象由其他批次导入,要保证导入顺序正确,或者在父对象不存在时不报错,只打印 warning。

3.4 如何把“运动部件”自动挂接成 realvirtual 可以驱动的对象

CAD 几何导入后,realvirtual 需要知道哪些对象是“可驱动的”。驱动可以是传送带的运动、机器人关节的旋转、夹具的开关等。这个信息无法从 FBX 文件自动推断,必须通过命名规则或清单映射显式声明。

实现思路有两种:

  • 在 CAD 导出前,给需要运动的部件加固定前缀,例如DRV_Conveyor_01JNT_Robot_Axis1。脚本遍历根节点下所有子物体,按前缀查找并挂接驱动组件。
  • 在 manifest.json 中维护virtualTag字段,脚本按名称或标签查找目标对象,再挂接组件。

第一种方式更接近自动化,但要求 CAD 设计人员遵守命名规范。第二种方式更灵活,适合已有模型命名混乱的场景。实际项目中,可以把两者结合:脚本先按前缀识别,再读 JSON 中的显式映射。

驱动组件应该挂在运动部件的根节点上,而不是挂在整台设备的总根节点上。这样 realvirtual 才能单独控制该部件的运动,而不会把位姿叠加到错误层级。

4. 导入后的验证:从“模型显示出来”到“仿真能跑起来”

4.1 场景基本检查

打开 Unity 场景后,首先要检查模型的 Transform。正常数字孪生模型的 localPosition 和 localScale 应该是可控范围内的数值,不会出现几十万单位和巨大的缩放值。

选择根节点下的子物体,打开 Inspector,检查 Mesh Filter 和 Mesh Renderer 是否正常。如果模型只是一堆节点,没有 Renderer,说明导入时网格没有正确生成,或者 FBX 文件本身缺少几何信息。

也可以用编辑器脚本批量打印模型的包围盒,快速发现异常点。

using UnityEditor; using UnityEngine; public static class CadBoundsValidator { [MenuItem("Tools/CAD Import/Validate Model Bounds")] public static void ValidateBounds() { MeshRenderer[] renderers = Object.FindObjectsOfType<MeshRenderer>(); foreach (MeshRenderer renderer in renderers) { Bounds b = renderer.bounds; if (b.size.magnitude > 1000f) { Debug.LogWarning($"{renderer.name} 包围盒尺寸过大 {b.size}", renderer); } } } }

这个脚本只是辅助定位问题。实际执行时,还要检查是否有模型缺失、空节点、重复网格等问题。

4.2 网格性能和运行时验证

大型 CAD 导入后最典型的表现是场景卡顿。进入 Play 模式后,可以打开 Game 窗口右上角的 Stats 面板,查看 DrawCall、三角形数量、SetPassCalls 等指标。

如果场景中有多个高精度设备,不要单一追求视觉精度。实际项目通常会为机械运动件保留精细网格,为不参与运动的位置使用 LOD 或剔除。物理仿真的碰撞体也不必和视觉网格完全一致,使用 Box Collider、Capsule Collider 或凸包能显著降低物理计算压力。

建议记录导入前后的性能数据,用于验证自动化脚本的收益。例如:

指标导入前状态自动化后状态目标
总三角形数量需要手工统计脚本输出报告控制在目标平台预算内
DrawCall 数量不确定按材质合并观察不随模型数量线性增长
碰撞体数量未配置自动生成基础碰撞体后续按运动需求手工微调
导入耗时每人几小时脚本几分钟可重复执行

4.3 realvirtual 信号联动验证

模型能显示不代表 realvirtual 链路通。进入 Play 模式后,找到挂有驱动组件的对象,使用 realvirtual 的信号列表或自带仿真面板给一个测试值,观察部件是否按预期运动。

例如给传送带驱动一个速度值,给机器人关节一个角度值,确认方向、单位、运动范围都正确。如果对象没有反应,回到 Inspector 检查:

  • 组件是否成功挂接。
  • 信号名是否与虚拟标签匹配。
  • 驱动类型是否和运动方式匹配。
  • 物体层级是否允许被驱动组件控制。

不要只在编辑器里看到模型就认为导入完成。对数字孪生项目来说,真实标准是“外部信号能否驱动模型”。

4.4 验证清单

检查项操作通过标准
单位查看模型 import scale 和 Transform scale1 米对应 Unity 1 单位
坐标轴旋转设备,观察向上方向符合项目约定
层级展开根节点运动部件独立存在,没有被合并
碰撞体选中设备,查看 Collider碰撞体基本贴合,且物理运动不漂移
realvirtual 组件查看驱动组件 Inspector信号名存在,能与 PLC 信号对应
性能Game 窗口 Stats三角形和 DrawCall 符合目标预算

5. 大型 CAD 导入典型问题与排查链路

5.1 模型巨大、物理异常:单位没有统一

现象:设备在场景中显示很大,地面距离很远,刚体下落缓慢或抖动。

可能原因:CAD 使用毫米单位,FBX 导入时没有把缩放值设置为 0.001,或者预处理脚本没有生效。

检查方式:选中模型,看 Transform scale 和 ModelImporter globalScale。如果 localScale 是 1000,说明单位没有在预处理阶段处理好。

处理建议:重新回到预处理脚本,统一设置缩放,再导出 FBX。不要直接在 Unity 场景里把 scale 改成 0.001,因为后续每个子物体、碰撞体、物理参数都会受影响。

5.2 模型整体旋转 90 度:坐标系约定不一致

现象:模型进入 Unity 后整体侧躺,或者旋转轴指向错误方向

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/5 9:04:51

STM32F4 FFT信号测量实战:从ADC采集到幅频相精准分析

简介&#xff1a;本资源是一套基于STM32F4系列MCU实现正弦波信号高精度参数测量的完整嵌入式工程&#xff0c;面向嵌入式开发工程师、高校电赛/课程设计学生及数字信号处理初学者&#xff0c;解决时域信号中幅值、频率与相位差难以实时、准确提取的核心问题。工程采用ADC采样硬…

作者头像 李华
网站建设 2026/9/4 18:13:37

本地AI+Markdown笔记:VelocityNote如何重塑写作工作流

VelocityNote 这个项目标题里有三个词&#xff1a;tiny、Markdown notebook、local AI。这三个词放到一起&#xff0c;很容易让人以为又是一款“更轻的笔记软件”。但我更愿意把它看成一种信号&#xff1a;写作工具正在从“连接云端 AI”转向“把 AI 运行在文件旁边”。 我见过…

作者头像 李华
网站建设 2026/9/4 21:17:31

软考中项案例分析解题方法论:从知识映射到结构化作答

这次我们来看一个针对系统集成项目管理工程师&#xff08;中项&#xff09;2015年11月案例分析题的解析资源。对于备考软考中项的考生来说&#xff0c;历年真题的案例分析部分是复习的难点和重点&#xff0c;它考察的是将项目管理知识体系&#xff08;如九大知识领域、五大过程…

作者头像 李华
网站建设 2026/9/5 13:32:34

基于Spring Boot+Vue的在线教学系统毕业设计全流程实战

简介&#xff1a;本资源是一套完整的Java毕业设计实战项目——课程网上辅助教学系统&#xff0c;面向计算机专业本科生及Java初学者&#xff0c;解决高校课程线上辅助教学场景中的信息管理、作业提交与用户权限控制等核心需求。压缩包共489个文件&#xff0c;涵盖83个Java源码文…

作者头像 李华
网站建设 2026/9/4 12:49:23

Qt多媒体与图形模块实战:从媒体播放到动态图表绘制

在开发桌面应用、嵌入式界面或跨平台多媒体系统时&#xff0c;你是否遇到过这样的困境&#xff1a;需要播放视频却卡在格式兼容上&#xff0c;想绘制复杂图表但性能堪忧&#xff0c;或者界面动画总是达不到流畅效果&#xff1f;这些挑战的核心&#xff0c;往往在于对 Qt 多媒体…

作者头像 李华
网站建设 2026/9/4 14:38:06

SeedRealtime:原生音视频全双工大模型部署与实战指南

这次我们来看一个能同时处理看、听、说的原生音视频大模型。字节跳动Seed团队最近开源的SeedRealtime&#xff0c;直接把“全双工”这个概念带到了多模态AI的前沿。简单说&#xff0c;它不再是你问一句、它答一句的“半双工”模式&#xff0c;而是能像真人对话一样&#xff0c;…

作者头像 李华