Hooking into UFbxFactory::FactoryCreateBinary()

Hey guys

I’m trying to import some obj files programmatically. I’m starting off with an import of a custom format that then should fetch some models and textures for import. I might be going about this wrong as there are definitely import pipelines that import additional files, so tell me if I’ve done this wrong.

So I’m reading in the obj to a buffer and passing it to an import factory:

std::vector<char> Buffer((std::istreambuf_iterator<char>(input)), (std::istreambuf_iterator<char>()));		bool bAndOutOperationCancelled = false;
		UFbxFactory * factory = NewObject<UFbxFactory>(UFbxFactory::StaticClass(), FName("Factory"), RF_NoFlags);

		factory->FactoryCreateBinary(UStaticMesh::StaticClass(), Actor, FName(*Filename), RF_NoFlags, nullptr, TEXT("obj"), (const uint8 *&)Buffer, (const uint8 *)(&Buffer[0] + Buffer.size()), GWarn, bAndOutOperationCancelled);

It’s failing on the last line. Right now I’m getting:

LogTemp:Warning: Found file: CUBE_01.objLogTemp:Warning: Importing C:\Temp\Import\assets\CUBE_01.obj...
LogFbx:Error: Call to FbxImporter::Initialize() failed.

So I’m going to go look at how some other importers kick off texture imports, for example, but has anyone done this before and can just tell me what I should be doing?

Bump because still stuck after messing with it all weekend. Also another side question that I’ve asked and asked and nobody has ever answered - how do you put a breakpoint in engine code? Can it be walked through so I can trace the import code path?

I found something!

const FString Filepath = FString("C:\\Temp\\MyProject\\Import\\assets\\") + Filename;
FFbxImporter * Importer = FFbxImporter::GetInstance();

Which is surprisingly simple yet was hard to find. But it doesn’t work! I’m still getting this same problem from months ago:

1>C:\Program Files\Epic Games\4.9\Engine\Source\Editor/UnrealEd/Private/FbxImporter.h(76): fatal error C1083: Cannot open include file: 'fbxsdk.h': No such file or directory

I’ve figured out what it needs is the UnrealEd module (which I’ve had included for ages) and the FBX third party module. Unfortunately adding the last one in makes no difference. I’ve also tried adding EVERYTHING from UnrealEd.Build.cs and also linking directly to the FBX SDK in the engine source. Nothing helps.

Are there any Epic staff members out there who can explain this?

UFbxFactory ignores any binary buffers you pass to it for no real reason.

Instead it loads file specified in its CurrentFileName field. So, set “yourFbxFactory->CurrentFilename” to the filepath you want to load and pass nullptr (you’ll have to create temporary vairable - “const uint8 *tmp = 0;”) as a buffer to FactoryCreateBinary method.

You also cannot set default import settings, you’ll have to enable import dialogue (yourFbxFactory->EnableShowOption()) and let the user select them.

If you’re loading multiple meshes, reuse the factory. That way the user will be able to utilize “import all” button.

You don’t really need to go that route, you can use UFbxFactory instead.

That’s actually a good import setup for the user. I’ll give it a burl. Thanks!

Those three lines are really optimistically tempting though. :slight_smile:

That works perfectly now, thanks!

@NegInfinity, did you do texture import as well? It seems to be totally different.

The same code hasn’t worked for textures, so I’m using UTexture2D::FactoryCreateNew() and instead of providing an AActor as the outer, I’m creating a UPackage object. I have dynamically generated textures saving in a similar way (except using FactoryCreateNew instead of just calling NewObject) which is where I’m lifting the new code from, but that doesn’t seem to work as the asset being imported still hasn’t been created when I call FAssetRegistryModule::AssetCreated(Texture).

There’s like this hidden async stuff going on or something.

Yep, if you enable import ui AND user checks “import materials”, then the engine will import textures and materials for you. Same deal as with dragging stuff into content browser.

You can also create your own materials from code, and assign them to mesh once it is loaded.

Either way works fine.

There’s no point in compring FbxFactory to TextureFactory because they’re different classes.

P.S> Mesh will need UMaterail, not UTexture/UTexture2D, though.

Hmm. The only problem with that is that I’m only importing OBJ files so there’s no texture information to speak of, just loosely associated image files that I have a separate mapping for applying to the mesh. Because of this I need to import everything and then walk my other structure to create and assign all of the materials.

How do you enable the import UI anyway? Also, what is “context” during these calls?

Ultimately I have to kick off a texture import somehow. I guess I’ve actually looked at all of this before and just need to check my notes.

This is what I have right now:

        UE_LOG(LogTemp, Warning, TEXT("Importing texture: %s"), *Filename);
        const FString Filepath = FString("C:\\Temp\\Import\\assets\\") + Filename;
        TextureFactory->CurrentFilename = Filepath;
        const uint8 * Buffer = 0;
        bool bAndOutOperationCancelled = false;
        Filename.ReplaceInline(TEXT(" "), TEXT(""));

        FString PackageName = FString("/Game/ImportedAssets/") + *Filename;
        FString PackageFileName = FPackageName::LongPackageNameToFilename(PackageName, FPackageName::GetAssetPackageExtension());

        UPackage * Package = NewObject<UPackage>(UPackage::StaticClass(), FName(*PackageName), RF_Standalone | RF_Public);
        Package->SetFlags(RF_Standalone | RF_Public);*/
        UTexture2D * Texture = (UTexture2D *)UFactory::StaticImportObject(UTexture2D::StaticClass(), Package, FName(*Filename), RF_Standalone | RF_Public, *Filepath);
        Package->SavePackage(Package, Texture, RF_Standalone | RF_Public, *Filename, GWarn);


So the path is “/Game/ImportedAssets/”, but instead I get this:

LogTemp:Warning: Importing texture: Farmhouse Texture Bump Map .jpg
LogFactory: FactoryCreateBinary: Texture2D with TextureFactory (0 0 C:\Temp\Import\assets\Farmhouse Texture Bump Map .jpg)
LogTexture:Display: Building textures: FarmhouseTextureBumpMap.jpg (AutoDXT)
LogSavePackage: Save=73.681198ms
LogSavePackage: Moving '../../../../../../UnrealEngine/Projects/UE4Importer/Saved/FarmhouseTextureBumpMap65B2A84C4C48C8DBD2D1D095631A1E97.tmp' to 'FarmhouseTextureBumpMap.jpg'
LogSavePackage: Total save time: 75.882137ms
LogSavePackage:Warning: Finished SavePackage FarmhouseTextureBumpMap.jpg

And this appears in the content browser:

Those images aren’t openable:

LogUObjectGlobals:Warning: Failed to find object 'Object /Script/CoreUObject.Package./Game/BakedAssets/FarmhouseTextureBumpMap.jpg.FarmhouseTextureBumpMap.FarmhouseTextureBumpMap.jpg'

The naming structure seems odd and it’s definitely not where I specified it should be saved. So it looks like it’s importing the texture fine but then it’s lost to the void.

*.obj files store material information in *.mtl files.

Already told you that. yourFbxFactory->EnableShowOption().

There are two parameters called context. Which one? You can pass 0 to 5th parameter, and GWarn to the last one.

Load texture into buffer, pass buffer to UTextureFactory. See SpeedTree-related and FbxMaterail-import related functions for examples.

Sorry, missed that. I’ll give it a go now as I need the options, thanks! :slight_smile:

My particular obj files don’t have any mtl files with them and likely never will, but it should be alright.

I mean the one called “Context” which appears in a lot of calls that also have an inner and an outer object. This would be the 5th argument in this case, I’ve been passing nullptr to it but wondered what it is.

Loading the texture into a buffer might work but it still has to be parsed somehow. I have all that from the earlier attempt, just need to work through it. I guess there’s no simple solution that will just kick off a normal texture import as if it was dragged into the content browser.

You can look into one of the factory methods and see what “context” is used for. I never needed it, so I didn’t look into it.

It is parsed by UTextureFactory::CreateBinary().

I think I mentioned several times in other discussions that “UnFbx::FFbxImporter::ImportTexture” (located in FbxMaterialImport.cpp) shows you how to do that.

I think you’ve been asking same questions few weeks ago and were given same answers. Code that is related to FbxImport and SpeedTree import shows how to import textures and even create materials. Did you look into that?

Many, many times. I know you’re repeating yourself a lot and your help is appreciated but it only seems that way because you don’t always read the question. :slight_smile: My textures are importing, they’re just ending up in the wrong place. This is also a totally different problem from what I was working on several weeks ago. Generally though I’m trying to find out more about the import process so I can get a clearer picture of it. Just reading through code isn’t giving me any more context than it did with the last task.

Texture path is determined by path passed into CreatePackage function and the name passed into factory. Both paths should be sanitized first (using FPackageTools and ObjectTools)

How so?

I’ll check my sanitization but I’m down to passing in a hard coded path just to see if it’ll work and they’re ending up looking like that screenshot above. The meshes themselves are ending up in random but slightly more acceptable locations too.

It’s difficult to see how it all goes together and what everything is doing. Meshes and images clearly use the same basic factory but have very different outcomes in behaviour but I can’t see why. I’m getting there slowly though.

I highly recommend to avoid trying to understand “everything”. That doesn’t work well in large code bases. The best approach is to get rough understanding, then concentrate on what seems to be related to the problem and forget most of it once you’re done fixing it.

Mesh, Material and Texture creation can be split into two parts:
First where you check if UPackage exists and generate/sanitize name. That one can be templated.
Second is the factory call which is different for every factory.

Also, you have different outcomes because they’re different factories.
There is no guarantee anywhere that different factories will behave in similar fashion even though they share the same base class.

Yeah, that’s what I’m finding. All good, I think I’m nearly done. Thanks for your help :slight_smile:

Actually, quick question: why would UPackage NOT exist? I haven’t seen any good examples of how to use UPackage, so I just make a new one for each asset. Is that the right way to do it?

UPackage represents asset that is visible in content browser. If there’s no asset, there’s no package. And you really should check for existing package. Texture import is slow, so if you have 500 meshes that wants to import the same texture, the texture will be imported 500 times if you don’t ever check if you really need to import that texture again. I think that LoadObject will return null with non-existent path.

There’s only one instance of each asset in the content I’m importing, so that’s not a concern. Reimport will probably necessitate that though.

So if I follow this correctly, a new asset that I want to import doesn’t need a package created and supplied, it’s only a reference for existing content? What should I use as the outer if I’m not using a package?

Right now I’m still creating a package for each texture for lack of knowing what else I could use as an outer, and this is what I get:

LogFactory: FactoryCreateBinary: Texture2D with TextureFactory (0 0 C:\Temp\Import\assets\Farmhouse Texture.jpg)
LogTexture:Display: Building textures: /Game/BakedAssets/FarmhouseTexture.jpg (AutoDXT)

…but the resulting file is in /Script Content and is named “/Game/BakedAssets/FarmhouseTextureBumpMap.jpg”. That seems to be because I’m passing a path to the Name argument of UFactory::StaticImportObject (which wraps the binary importers and does a bunch of validation, so seems like a better choice).

I’ve changed the folder used by the package to be “/Game/BasedAssets” but it isn’t having any effect.

First things first, why is it ending up in /Script Content? This must be because I’m not setting a necessary path somewhere, but I’m not sure where.

Along the lines of the context question, what does it mean when a package is “dirty”?

Okay, so I took the path out of the name being supplied to StaticImportObject() and now the files in /Script Content look better name-wise. But opening them still gets this:

LogUObjectGlobals:Warning: Failed to find object 'Object /Script/CoreUObject.Package./Game/BakedAssets/FarmhouseTexture.jpg.FarmhouseTexture.FarmhouseTexture.jpg'

I’m not sure what the object path SHOULD look like but repeating the image name three times seems wrong.

Ahahaha, got it. It was the name sanitation. It needed the period removed too. Once I got around to implementing it properly and stopped trying to hard code it, it was fine.