How can I convert a UObject to a derived type in a Factory Reimport?

I’ve made a new UObject type, UCustomMaterialInstanceConstant, that inherits from UMaterialInstanceConstant. I have also set up a UFactory and implemented the IReimportHandler interface to create these classes on import and reimports of a custom file extension. The system works until I need to import a new Custom material instance into a content browser location where a vanilla material instance lives. I’d like to have the (re)importer automatically convert the UMaterialInstanceConstant to my new class type. I have not been able to figure out how to do this. Currently, I"m attempting to do the conversion step with the following code.

EReimportResult::Type UCustomMaterialInfoFactory::Reimport( UObject* Obj)
{
	auto Result = EReimportResult::Failed;
	if (auto CustomMatConst = Cast(Obj))
	{
            // Do regular FactoryCreateText() behavior here...
            result = EReimportResult::Success;
	}

	// Support upgrading vanilla mat instances to custom mat instances
	else if (auto MatConst = Cast(Obj))
	{
             // What do I do here?
	}
	return Result;
}

I’ve attempted the conversion by:

  1. Deleting the old asset and creating a new asset of the correct type in its same package path.
  2. Creating the new custom type asset and performing a ConsolidateObjects() to collapse the old asset onto then ew one.
  3. Creating a transient asset of the new custom type, deleting the old asset, and calling DuplciateSingle onto the old asset’s package path.

Ultimately, none of these options have been successful because the calling function of UFactory::Reimport(UObject* Obj) always interacts with Obj afterwards. Deleting or mishandling the UObject in any way causes a fatal crash as soon as Reimport() finishes. What are my options here? Should I be approaching this from a different way?

Hi Eric,

Do I understand correctly you’re trying to replace these classes in an existing asset? If so you should look at ActiveClassRedirects in DefaultEngine.ini (look for examples in BaseEngine.ini). This will allow you to replace classes when the asset is being loaded.

Correct, I am trying to replace existing assets. This is an interesting solution. I tried adding this line to my DefaultEngine.ini and the editor wouldn’t start.

ActiveClassRedirects=(OldClassName="MaterialInstanceConstant",NewClassName="CustomMaterialInstanceConstant")

It’s worth noting that my CustomMaterialInstanceConstant class is defined in a plugin and might not be available during the class redirect phase. Can you confirm this?

I’m also not interested in replacing every instance of the class with my own. I’d prefer to keep it on-demand and not make a global change to assets across the project. Are there other solutions?

I was hoping to make the process on-demand as new assets were imported but it looks like that option won’t be easy to pursue. Instead, I’ve added a simple menu item that converts selected Material Instance Constants to my new Custom Material Instance Constant class. I experienced all of the same problems as above but finally arrived at a solution that seems to work!

  1. Load the Old Material
  2. Load all the referencers of this Material
  3. Create a transient package, Temp Material
  4. ConsolidateObjects() - Old Material → Temp Material
  5. Now that Old Material is deleted, create New Material with the correct class type at the same package path and name
  6. ConsolidateObjects() → Temp Material → New Material

The temp material safely holds the references across asset deletion and creation and allows me to copy over old properties to the new one.

Along the way, I tried UFactory::CreateOrOverwriteAsset() to write the new asset. This function correctly wrote the asset but left references in a very strange stage. If I opened the new material’s reference viewer, it showed links to meshes. If I opened one of the referenced meshes, they showed links to the material. However, loading one of the mesh assets by double clicking it would cause the new material to revert to the old material class. Are you aware of this issue?

If I skip step 2 (loading of referencers), I encounter the same weird issue. For whatever reason, deleting and asset and writing a new one of a subclass leaves references in a weird state unless the all referencers are loaded first.

Oh, yeah, if you try and replace a low level class like this everywhere it’s not going to work. I think commandlets would be best to achieve this. I’d make a new commandlet that loads only the package file summary of each package inside of the specified folder and if it contains the class you want to replace, replace it in the linker table just like LinkerLoad code would do it and load the rest of the package and resave it. PkgInfo commandlet is a good place to start - it can load the package file summary and linker tables as a separate step.

I’m not aware of this issue but it’s possible that if you did it in the same editor session it could simply still hold some references to the old assets in the old asset linker import tables. Have you tried restarting the editor after you replaced the references but before you loaded the meshes?