UE 4-27 : Automating Datasmith to FBX or GLTF export with Python

Hi,

At the animation studio we work at we usually receive CAD files in the form of .step or .sldasm (and .sldprt) files. These are usually a pain to work with, as they contain loads of different objects with weird naming conventions.

We setup some sort of guidelines for upcoming clients now, trying to smooth out the workflow. These include converting the assembly to one part, then use a naming convention to declare if the part will be animated or is static, which material it has etc.

On our end we are trying to automate the process after we get either a .step or .sldprt file back. We found that using Datasmith we can export an .fbx file with the correct naming conventions of the bodies/geometries of the CAD file.

Using the code in this as a base we I pretty far: Batch translate Solidworks Headlessly via Python - #5 by Ro-Su

However, I can’t seem to export the actors/scenes as is, like when you would use the export function inside of unreal itself, instead of running it headless like we are trying to. When running the script it will merge all the actors into a new mesh, and only export that, while we need the different actors with the correct naming conventions. I tried to use the .gltf plugin founded on the marketplace but to no avail. It seems that whenever I try to export the scene without the actors merged, it will not produce the desired results.

Does anyone have any experience with this kind of workflow, or could point us in the right direction?

Things you need to pay attention to:

  • in the script provided by Ro-Su he merges all the actors in one mesh. So you should not do that
  • for the FBX exporter as well as the gltf exporter you can either put an object in the “task.object” but that will export only an actor or an asset, or you can put a reference to the world to export the full scene. On top of that you can put the “task.selected” at true to only export what is selected in your level.
    Here for FBX: PythonSamples/Export_selected_actors_to_FBX.py at main · ue4plugins/PythonSamples · GitHub

here to export a single mesh with gltf : Export mesh as GLTF with Python - #3 by UE_FlavienP
here to export a scene with gltf: How to batch export scene components to glb/glTF file - #2 by UE_FlavienP

Thanks a lot for your response! I had a look at the links you provided and this is my code so far:

import unreal

# Construct the Datasmith CAD Scene from a file on disk.
# Your destination folder must start with /Game/
ds_file_on_disk = "C:/Users/remco/Desktop/Datasmith_Automate/unreal/Assy_Tip_Cystoscope_PART.SLDPRT"
ds_scene_in_memory = unreal.DatasmithSceneElement.construct_datasmith_scene_from_file(ds_file_on_disk)

if ds_scene_in_memory is None:
    print("Scene loading failed.")
    quit()

# Set import options.

# Main import options:
import_options = ds_scene_in_memory.get_options(unreal.DatasmithImportOptions)
import_options.base_options.scene_handling = unreal.DatasmithImportScene.NEW_LEVEL

# CAD-only surface tessellation options:
tessellation_options = ds_scene_in_memory.get_options(unreal.DatasmithCommonTessellationOptions)
tessellation_options.options.chord_tolerance = 0.1
tessellation_options.options.max_edge_length = 0
tessellation_options.options.normal_tolerance = 30
tessellation_options.options.stitching_technique = unreal.DatasmithCADStitchingTechnique.STITCHING_NONE

# Finalize the process by creating assets and actors.
ds_scene_in_memory.import_scene("/Game/MyCADScene")

# Clean up the Datasmith Scene.
ds_scene_in_memory.destroy_scene()
unreal.log_warning("Custom import process complete!")

#export
actors = unreal.EditorLevelLibrary.get_all_level_actors()
unreal.log_warning(actors)
task = unreal.AssetExportTask()
# this could be just one asset if you only want to export one single asset
task.object = actors[0].get_world()
task.filename = "C:/Users/remco/Desktop/Datasmith_Automate/unreal/test.glb"
task.automated = True
task.replace_identical = True
task.exporter = unreal.GLTFStaticMeshExporter()
task.options = unreal.GLTFExportOptions()
task.selected = True
task.prompt = False
unreal.Exporter.run_asset_export_task(task)

However, whenever I run it (using a headless UE4), it crashes at the last line:

unreal.Exporter.run_asset_export_task(task)

Not sure what is causing this. The line

unreal.log_warning(actors)

Actually logs the actors in the scene, like so:

[2022.03.07-14.03.03:351][  0]LogPython: Warning: [DatasmithSceneActor'"/Temp/Untitled_1.Untitled:PersistentLevel.Assy_Tip_Cystoscope_PART"', Actor'"/Temp/Untitled_1.Untitled:PersistentLevel.Assy_Tip_Cystoscope_PART_1"', StaticMeshActor'"/Temp/Untitled_1.Untitled:PersistentLevel.MainLens_S_Glass"', StaticMeshActor'"/Temp/Untitled_1.Untitled:PersistentLevel.Lens1_S_Glass"', StaticMeshActor'"/Temp/Untitled_1.Untitled:PersistentLevel.Lens2_S_Glass"', StaticMeshActor'"/Temp/Untitled_1.Untitled:PersistentLevel.Shaft1_A_BlackShinyPlastic"', StaticMeshActor'"/Temp/Untitled_1.Untitled:PersistentLevel.Shaft2_A_BlackRoughPlastic"', StaticMeshActor'"/Temp/Untitled_1.Untitled:PersistentLevel.Shaft3_A_BlackShinyPlastic"', StaticMeshActor'"/Temp/Untitled_1.Untitled:PersistentLevel.Shaft4_A_BlackRoughPlastic"', StaticMeshActor'"/Temp/Untitled_1.Untitled:PersistentLevel.PCB_S_GreenRoughPlastic"']

For reference, this is the hierarchy of inside of unreal when I object the .step or .sldasm file with Datasmith:
UE4Editor_LKPPjpuZKP

Thanks for your time!

Try with
task.selected = false => at the moment nothing is selected.
also, comment the line on task.exporter = unreal.GLTFStaticMeshExporter()

Thanks again.

It is not crashing anymore, however it’s not exporting anything as I get this warning:

LogExporter: Warning: No glb exporter found for Actor /Temp/Untitled_1.Untitled:PersistentLevel.Assy_Tip_Cystoscope_STEP_1

My GLTFExporter .uplugin file looks like this:

{
	"FileVersion": 3,
	"Version": 106,
	"VersionName": "1.0.6",
	"FriendlyName": "glTF Exporter",
	"Description": "An exporter for Khronos glTF 2.0.",
	"Category": "Exporters",
	"CreatedBy": "Epic Games, Inc.",
	"CreatedByURL": "http://epicgames.com",
	"DocsURL": "",
	"MarketplaceURL": "https://www.unrealengine.com/marketplace/en-US/product/3c2c81b4b44542e8867263707f518480",
	"SupportURL": "",
	"EngineVersion": "4.27.0",
	"EnabledByDefault" : true,
	"CanContainContent": true,
	"IsBetaVersion": true,
	"Installed": false,
	"Modules": [
		{
			"Name": "GLTFExporter",
			"Type": "Editor",
			"LoadingPhase": "Default",
			"WhitelistPlatforms": [
				"Win64",
				"Win32"
			]
		},
		{
			"Name": "GLTFExporterRuntime",
			"Type": "Runtime",
			"LoadingPhase": "Default",
			"WhitelistPlatforms": [
				"Win64",
				"Win32"
			]
		},
		{
			"Name": "GLTFMaterialAnalyzer",
			"Type": "Editor",
			"LoadingPhase": "Default",
			"WhitelistPlatforms": [
				"Win64",
				"Win32"
			]
		},
		{
			"Name": "GLTFMaterialBaking",
			"Type": "Editor",
			"LoadingPhase": "Default",
			"WhitelistPlatforms": [
				"Win64",
				"Win32"
			]
		}
	],
	"Plugins": [
		{
			"Name": "VariantManagerContent",
			"Enabled": true
		}
	]
}

Is there anything I’m missing here?

Are you sure you pass the world and not just one object in the line
task.object = actors[0].get_world()

I wasn’t, probably changed the code before implementing your suggestions. I am now, but when I run it unreal crashes with the following log:

Unhandled Exception: EXCEPTION_ACCESS_VIOLATION reading address 0x0000000000000000

UE4Editor_Engine
UE4Editor_GLTFMaterialAnalyzer!UGLTFMaterialAnalyzer::AnalyzeMaterialPropertyEx() [D:\build\++Portal\Sync\LocalBuilds\PluginTemp\HostProject\Plugins\GLTFExporter\Source\GLTFMaterialAnalyzer\Private\GLTFMaterialAnalyzer.cpp:51]
UE4Editor_GLTFExporter!FGLTFMaterialUtility::AnalyzeMaterialProperty() [D:\build\++Portal\Sync\LocalBuilds\PluginTemp\HostProject\Plugins\GLTFExporter\Source\GLTFExporter\Private\Converters\GLTFMaterialUtility.cpp:481]
UE4Editor_GLTFExporter!FGLTFMaterialUtility::NeedsMeshData() [D:\build\++Portal\Sync\LocalBuilds\PluginTemp\HostProject\Plugins\GLTFExporter\Source\GLTFExporter\Private\Converters\GLTFMaterialUtility.cpp:453]
UE4Editor_GLTFExporter!FGLTFMaterialUtility::NeedsMeshData() [D:\build\++Portal\Sync\LocalBuilds\PluginTemp\HostProject\Plugins\GLTFExporter\Source\GLTFExporter\Private\Converters\GLTFMaterialUtility.cpp:463]
UE4Editor_GLTFExporter!FGLTFStaticMeshConverter::Sanitize() [D:\build\++Portal\Sync\LocalBuilds\PluginTemp\HostProject\Plugins\GLTFExporter\Source\GLTFExporter\Private\Converters\GLTFMeshConverters.cpp:33]
UE4Editor_GLTFExporter!TGLTFConverter<FGLTFJsonMeshIndex,UStaticMesh const *,UStaticMeshComponent const *,TArray<UMaterialInterface const *,TSizedDefaultAllocator<32> >,int>::GetOrAdd() [D:\build\++Portal\Sync\LocalBuilds\PluginTemp\HostProject\Plugins\GLTFExporter\Source\GLTFExporter\Private\Converters\GLTFConverter.h:41]
UE4Editor_GLTFExporter!FGLTFComponentConverter::Convert() [D:\build\++Portal\Sync\LocalBuilds\PluginTemp\HostProject\Plugins\GLTFExporter\Source\GLTFExporter\Private\Converters\GLTFNodeConverters.cpp:122]
UE4Editor_GLTFExporter!FGLTFConvertBuilder::GetOrAddNode() [D:\build\++Portal\Sync\LocalBuilds\PluginTemp\HostProject\Plugins\GLTFExporter\Source\GLTFExporter\Private\Builders\GLTFConvertBuilder.cpp:297]
UE4Editor_GLTFExporter!FGLTFActorConverter::Convert() [D:\build\++Portal\Sync\LocalBuilds\PluginTemp\HostProject\Plugins\GLTFExporter\Source\GLTFExporter\Private\Converters\GLTFNodeConverters.cpp:21]
UE4Editor_GLTFExporter!FGLTFSceneConverter::Convert() [D:\build\++Portal\Sync\LocalBuilds\PluginTemp\HostProject\Plugins\GLTFExporter\Source\GLTFExporter\Private\Converters\GLTFSceneConverters.cpp:52]
UE4Editor_GLTFExporter!FGLTFConvertBuilder::GetOrAddScene() [D:\build\++Portal\Sync\LocalBuilds\PluginTemp\HostProject\Plugins\GLTFExporter\Source\GLTFExporter\Private\Builders\GLTFConvertBuilder.cpp:347]
UE4Editor_GLTFExporter!UGLTFLevelExporter::AddObject() [D:\build\++Portal\Sync\LocalBuilds\PluginTemp\HostProject\Plugins\GLTFExporter\Source\GLTFExporter\Private\Exporters\GLTFLevelExporter.cpp:18]
UE4Editor_GLTFExporter!UGLTFExporter::ExportBinary() [D:\build\++Portal\Sync\LocalBuilds\PluginTemp\HostProject\Plugins\GLTFExporter\Source\GLTFExporter\Private\Exporters\GLTFExporter.cpp:38]
UE4Editor_Engine
UE4Editor_Engine
UE4Editor_Engine
UE4Editor_CoreUObject
UE4Editor_CoreUObject
UE4Editor_PythonScriptPlugin
UE4Editor_PythonScriptPlugin
UE4Editor_PythonScriptPlugin
UE4Editor_PythonScriptPlugin
python37!_PyObject_FastCallKeywords() [c:\a\18\s\objects\call.c:199]
python37!call_function() [c:\a\18\s\python\ceval.c:4619]
python37!_PyEval_EvalFrameDefault() [c:\a\18\s\python\ceval.c:3095]
python37!_PyEval_EvalCodeWithName() [c:\a\18\s\python\ceval.c:3930]
python37!PyEval_EvalCodeEx() [c:\a\18\s\python\ceval.c:3966]
python37!PyEval_EvalCode() [c:\a\18\s\python\ceval.c:530]
UE4Editor_PythonScriptPlugin
UE4Editor_PythonScriptPlugin
UE4Editor_PythonScriptPlugin
UE4Editor_PythonScriptPlugin
UE4Editor_Cmd
UE4Editor_Cmd
UE4Editor_Cmd
UE4Editor_Cmd
UE4Editor_Cmd
UE4Editor_Cmd
kernel32
ntdll