← Blog

Importing an FBX into Unreal from the command line, and reading what came out

If you want to know what an engine really did to your asset, you have to ask the engine. Here is how to do that without a human clicking anything, on Unreal Engine 5.8.

The command line

A commandlet run, with the Python plugin, no rendering hardware and no window:

UnrealEditor-Cmd.exe <project.uproject> -run=pythonscript
  -script="<script.py> <file.fbx> <out_dir>"
  -unattended -nullrhi -nosplash -nopause -stdout

-nullrhi is enough to import a mesh and inspect it. The process exits cleanly, with code 0. You do not need a GPU, and you do not need to compile any C++.

The minimal project

A .uproject enabling three plugins:

  • PythonScriptPlugin, to run the script at all.
  • EditorScriptingUtilities, for the asset import task.
  • ProceduralMeshComponent, which is the part most people miss, and the reason is below.

Plus a Config/DefaultEngine.ini with no default map, so nothing tries to load a level you do not have.

Reading the vertices, the part that is not obvious

Once the mesh is imported, the natural move is to open the StaticMeshDescription. It gives you the source data: vertex count, triangle count, polygon groups, UVs per instance. For our reference cube: 8 vertices, 36 instances, 12 triangles, 2 groups.

What it does not give you, in Python, is the normals. And the normals are exactly what you came for.

The path that works reads the render data instead:

unreal.ProceduralMeshLibrary.get_section_from_static_mesh(mesh, lod, section)

It returns positions, indices, normals, UVs and tangents for one section of one LOD. That is the data the renderer actually uses, which is the data worth comparing against your source.

One warning learned the hard way: summing the vertices of every section double counts vertices shared between sections. If you want the size of the vertex buffer, take the buffer, not the sum.

In 5.8, your FBX options go through Interchange

Even when the import is driven the classic way, with AssetImportTask and FbxImportUI, Unreal Engine 5.8 routes it through Interchange. The log says it plainly:

Interchange import: Using stack [OverridePipeline]

The good news: the FbxImportUI options are converted and respected. We proved it by importing the same file twice in one session with two different normal settings and getting two different meshes.

The consequence: the import data attached to the asset is an InterchangeAssetImportData. If you want to read back which settings were actually applied, do not look for FbxStaticMeshImportData, it is not there any more.

Two details that cost time

The import is synchronous in a commandlet. imported_object_paths is filled when the call returns. No waiting loop, no polling. The import also creates a MaterialInstanceConstant per material, which you will see in your output folder.

The derived data cache variable has a hyphen in its name. It is UE-LocalDataCachePath. A shell cannot export a variable with a hyphen, so you have to pass it through the environment of the child process explicitly. Miss it and Unreal writes several gigabytes to your system drive, which is how we found out.

Why this is in a product

Everything above is a day of work to discover and a week to make reliable across engine versions. It is also only one engine out of three: Godot and Unity have their own import pipelines, their own read paths, and their own quirks, such as Godot compressing normals so that an exact comparison always fails.

FixMyMesh is that work, packaged: it drives the engine you already have, reads what came out, and compares it with your Blender source. If you would rather build it yourself, the notes above are a real head start, and they are accurate as of Unreal Engine 5.8.

Test it on your own asset

FixMyMesh runs the same measurement on your files, in the engine installed on your machine. 3 full tests, free.