Deformer settings object in skinned asset component is not updated correctly on archetype instances

The MeshDeformerInstanceSettings property of USkinnedAssetComponent class is not updated correctly when the bSetMeshDeformer and MeshDeformer properties are updated for archetype instances. MeshDeformerInstanceSettings is updated in PostEditChangeProperty and in SetMeshDeformer (as a side note, SetMeshDeformer could avoid recreating default settings for MeshDeformerInstanceSettings if the new active deformer matches the current one). Normally, PostEditChangeProperty would run on the component when the property is set in the instance. However, if the component is part of the archetype (or in general if it was built during the construction script, simple or user), then it will be reconstructed when the property changes. PostEditChangeProperty gets eventually called, but only on the component to be deleted. The new component inherits the properties from the deleted component before PostEditChangeProperty is called on it, so it gets the updated bSetMeshDeformer / MeshDeformer but the old MeshDeformerInstanceSettings. Normally, the constructor of the component would fix this. However, the constructor cannot simply create new default settings for the active deformer because there could be a useful settings object there already.

The “steps to reproduce” show one variation of the problem. You can also have a deformer set in the archetype and clear it in the instance, leaving you with an unused settings object. And, maybe worst, you can set one deformer in the archetype and a different one in the instance, which leaves you with the wrong settings object for the updated deformer.

Possible solutions or workarounds that I can think of are:

  • Set the deformer in the archetype only, i.e. mark bSetMeshDeformer and MeshDeformer with EditDefaultsOnly.
  • Create new MeshDeformerInstanceSettings in the constructor if there is an active deformer and the property is null (or, conversely, delete them if there is no active deformer and there is a settings object). This is not a full solution, as you could have a settings object for a different deformer (and atm there is no way to tell what deformer a settings object is for).
  • When bSetMeshDeformer / MeshDeformer are changed, somehow have MeshDeformerInstanceSettings updated in the component before its properties are copied to the new recreated component. This seems difficult.

Steps to Reproduce

  1. Create an actor Blueprint class with a skeletal mesh. Make sure that “Mesh Deformer” is disabled.
  2. Place an instance of that actor class in a level.
  3. Select the skeletal mesh component in the placed instance, enable “Mesh Deformer” and select a deformer graph.
  4. BUG: The “Deformer Settings” field is still greyed-out and cannot be edited.

Hi, thanks for reporting this (and for the detailed info).

This looks like it’s specific to the bp component reinstancing workflow. Calling USkinnedMeshComponent::PostEditChangeProperty triggers the construction script to run which reinstances the component. During that process, we serialize properties that have been modified on the component instance, and then deserialize them back when the component has been reinstanced. The problem here appears to be that the serialization happens as part of the call to Super::PostEditChangeProperty within USkinnedMeshComponent::PostEditChangeProperty. But at that point, we haven’t actually set MeshDeformerInstanceSettings so we serialize out null which means that the value doesn’t get set back onto the new instance of the component.

I think there’s a straightforward fix here, which is just to reverse the order of the work in USkinnedMeshComponent::PostEditChangeProperty. So the new version of the function would look like this:

void USkinnedMeshComponent::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{	
	if (const FProperty* Property = PropertyChangedEvent.Property)
	{
		if (Property->GetFName() == GET_MEMBER_NAME_CHECKED(USkinnedMeshComponent, MeshDeformer) ||
			Property->GetFName() == GET_MEMBER_NAME_CHECKED(USkinnedMeshComponent, bSetMeshDeformer) ||
			Property->GetFName() == GET_MEMBER_NAME_CHECKED(USkinnedMeshComponent, bAlwaysUseMeshDeformer))
		{
			const FMeshDeformerSet ActiveDeformers = GetActiveMeshDeformers();
 
			// Only one deformer is supported for now
			check(ActiveDeformers.Deformers.Num() <= 1);
			UMeshDeformer* ActiveMeshDeformer = ActiveDeformers.Deformers.Num() > 0 ? ActiveDeformers.Deformers[0] : nullptr;
 
			MeshDeformerInstanceSettings = ActiveMeshDeformer ? ActiveMeshDeformer->CreateSettingsInstance(this) : nullptr;
		}
	}
 
	Super::PostEditChangeProperty(PropertyChangedEvent);
}

Can you confirm whether that fixes things for you? I’m going to talk to the dev team to make sure that this change is okay. We do have other component types (like UPrimitiveComponent) which don’t call their Super implementations immediately from PostEditChangeProperty so I think this should be fine.

Thanks for confirming that gets things working for you. I’m still waiting to hear back from the dev team so I’ll keep this issue open until that happens.

I talked with the dev team and they’re happy so I’m going to get that change committed. It’ll go into the 5.8 release, so I’m going to close out this thread. Feel free to reopen it if you happen to run into any problems.

Thank you, Euan, that seems to solve the problem. I think I assumed Super::PostEditChangeProperty had to be called always first (and also I didn’t realise PostEditChangeProperty itself was in the callstack leading to the component recreation), but it works perfectly now.