Depricated set_property_name_and_path works while replacement does not

I noticed a deprecation warning when I make a call to shot_asset.add_spawnable_from_instance(temp_actor) where it wanted me to use the LevelSeqeuenceEditorSubsystem call LSE.add_spawnable_from_instance(shot_asset, temp_actor) instead.

Upon switching things over to this call my scripts stop working. Making this call stand-alone, in the Output log python terminal seems fine. But using it within a script fails and returns an empty binding where the sequence (shot_asset in my code) has not be properly set.

I am wondering if there is something I need to do in addition to swapping out the version of the function call. Maybe this function is now not completing before it returns as the previous version did so we end up with an incomplete result. I know there are issues like this in various parts of the python api codebase such that I have to break scripts into multiple scripts so the first can complete, return control to the main thread, and then the various callbacks can compete thus finishing the work the script was attempting. Is it possible this new subsystem version has a similar problem?

Steps to Reproduce

ECS = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
LSE = unreal.get_editor_subsystem(unreal.LevelSequenceEditorSubsystem)

# This works:
temp_actor = ECS.spawn_actor_from_object(actor_asset, unreal.Vector(0.0, 0.0, 0.0))
actor_binding = shot_asset.add_spawnable_from_instance(temp_actor)
visibility_track = actor_binding.add_track(unreal.MovieSceneVisibilityTrack)
visibility_track.set_property_name_and_path('bHidden', 'bHidden')

# This fails:
temp_actor = ECS.spawn_actor_from_object(actor_asset, unreal.Vector(0.0, 0.0, 0.0))
actor_binding = LSE.add_spawnable_from_instance(shot_asset, temp_actor)
visibility_track = actor_binding.add_track(unreal.MovieSceneVisibilityTrack)
visibility_track.set_property_name_and_path('bHidden', 'bHidden')

Hi Tom,

Thank you for the report with very clear repro steps. I was able to repro the behavior you described on my end.

I am looking at the native source code for function AddSpawnableFromInstance() as defined in UMovieSceneSequenceExtensions (the one that works) and in ULevelSequenceEditorSubsystem (the one that fails). Both of them are supposed to return immediately, so no need to worry about possibly having an incomplete result. On the other hand, I can see that these two functions execute very different code paths, which is a little unusual. Normally, when a static library function is deprecated in favor of a subsystem-based replacement, the old version simply forwards the call to the new one, which is not the case here.

Interestingly, the new function implemented in the subsystem seems to have an extra requirement for some reason: the sequence being modified needs to be open in Sequencer when the function is called. With this in mind, I made the following change to your Python function:

unreal.LevelSequenceEditorBlueprintLibrary.open_level_sequence(shot_asset)
 
temp_actor = ECS.spawn_actor_from_object(actor_asset, unreal.Vector(0.0, 0.0, 0.0))
actor_binding = LSE.add_spawnable_from_instance(shot_asset, temp_actor)
visibility_track = actor_binding.add_track(unreal.MovieSceneVisibilityTrack)
visibility_track.set_property_name_and_path('bHidden', 'bHidden')
 
unreal.LevelSequenceEditorBlueprintLibrary.close_level_sequence()

Please let me know if the approach above works for you. Note that if you are making changes to several sequences, it might be a good idea to close Sequencer only after all operations that require it, instead of after each one.

Best regards,

Vitor

Interesting to know, but that isn’t going to really work. The whole idea behind using python here is to be able to script up actions and pipeline them. In this case, I am bringing in a full cinematic, creating master sequence, creating shots tracks, creating shot sequences, adding bindings, lights, cameras, etc. It’s all scripted. I can’t have it flickering back and forth between different shots in the sequencer. In my opinion, this is a regressions. There is a sequencer tools subsystem for doing things with a loaded sequence in the sequencer, these other subsystems should allow operating on the sequence assets directly. Also, we have had issues with scripts that require something be in the sequencer. One such problem is what if the sequencer itself isn’t open? If we try to open the sequencer and then do the action we often crash. It appears to be a race condition where the call to open returns, python continues, but the editor actually doesn’t open until after the script completes. We also have problems modifying a sequence when it is in the sequencer and things crash. For example, if I remove a shot from the shots track while the sequencer is open this will often crash. So, our current tools force the user to CLOSE the sequencer before they run. This puts us at an impasse if most tools require the sequencer be closed by now this open not only requires it open, but that the sequence be loaded.

Can you talk to the people behind the new code in the subsystem and find out why they needed this new requirement? Ideally, none of the python api that operates on sequences, tracks, bindings, etc require the sequencer be open or the sequence itself be current in the sequencer. That really just slows things down - especially give the sheer number of ways sequencer crashes. Thanks!

Hey there,

As Vitor mentioned, in the release 5.6 there was a refactor of bindings to better support dynamic binding. This was essential for sequencer as it becomes a more generic tool beyond cinematics. Unfortunately, this also broke AddSpawnableFromInstance as you’ve found and then we deprecated that function in favor of the new one, but as a limitation of the new dynamic bindings we introduced the compromise to have the sequence open and focused as a requirement for that to work.

All of that said, there are initiatives to have our python APIs get better, so feedback like this is good, but may not be actioned on right away.

But this crashes almost every time if the sequencer is open. It crashes about 50% of the time when the sequencer is closed. It seems like something in the code is holding onto sequencer callbacks for events like track deletion so this call causes the sequencer to crash and bring the editor down with it.

If we can get a callstack for this, we can hopefully get a code fix/workaround so that this doesn’t happen. At the very least we can get an issue in the system to get it resolved.

This ends up calling GetSequencer which for some reasons succeeds even when the sequencer is closed. It believes sequencer is there by checking a WeakPointer and then calls Pin on it. Down it goes.

Yeah this is a case where a callstack for that would be helpful as well. More than likely we’ve run into and already addressed the issue and ideally I can get you a code snippet or fix for that issue particularly.

Anyway, I would be fine with one or the other. Sequencer must be open or sequencer cannot be open. Just make it not crash on one of those pathways so that I can get work done.

This is the feedback I’ve just fed into the team with. Any code adjustments we make should point at one or the other direction and we can address that pathway over time.

Oh, I also have popups that call close sequencer before running my code but this doesn’t work since the close call returns immediately even thought the sequencer close has not completed. So, this ends up being a bit of a race condition

Do you have an example of this?

Again, this is all good feedback, and ideally I can help you with your crashes immediately.

Dustin

Hi Tom,

Indeed, this is not the first time I come across a sequence-scripting function that requires that the Sequencer UI be open, and I also think that such a requirement makes the entire scripting flow quite clunky to say the least.

I am taking a deeper look into the new function to see if I can come up quickly with a custom version of it that works without an open Sequencer, so that you don’t have to wait for an official fix. And I’m going to reach out to the devs to know more about this requirement. Meanwhile, perhaps you can keep using the deprecated version for the time being until a better replacement is available.

Best regards,

Vitor

Hi Tom,

I have reached out to the devs about this, and I’ll keep you informed about any feedback from them. It is also possible that someone more directly involved with the subsystem shows up here to talk to you directly.

Now, about my investigation. Interestingly, the subsystem implementation seems capable of performing the operation when the Sequence is not currently open for editing. It explicitly handles this case and simply skips some steps that would interact with the Sequencer Editor. I tested removing the requirement check from the beginning of the function and it seemed to work correctly, much like the old deprecated version.

If you want to try to edit the source code yourself, you can look for function ULevelSequenceEditorSubsystem::AddSpawnableFromInstance() in file [LevelSequenceEditorSubsystem.cpp] and change it to the following:

FMovieSceneBindingProxy ULevelSequenceEditorSubsystem::AddSpawnableFromInstance(UMovieSceneSequence* Sequence, UObject* ObjectToSpawn)
{
	if (!Sequence)
	{
		UE_LOG(LogLevelSequenceEditor, Error, TEXT("AddSpawnableFromInstance requires a valid Sequence"));
		return FMovieSceneBindingProxy();
	}
 
	if (!ObjectToSpawn)
	{
		UE_LOG(LogLevelSequenceEditor, Error, TEXT("AddSpawnableFromInstance requires a valid ObjectToSpawn"));
		return FMovieSceneBindingProxy();
	}
 
	TSharedPtr<ISequencer> Sequencer = GetActiveSequencer();
	if (Sequencer)
	{
		UMovieSceneSequence* FocusedSequence = Sequencer->GetFocusedMovieSceneSequence();
		if (!FocusedSequence || FocusedSequence != Sequence)
		{
			UE_LOG(LogLevelSequenceEditor, Error, TEXT("AddSpawnableFromInstance requires that the requested sequence %s be open in the editor"), *GetNameSafe(Sequence));
			return FMovieSceneBindingProxy();
		}
	}
 
	UE::Sequencer::FCreateBindingParams Params;
	Params.BindingNameOverride = ObjectToSpawn->GetName();
	Params.bSpawnable = true;
 
	FGuid Guid = FSequencerUtilities::CreateOrReplaceBinding(Sequencer, Sequence, ObjectToSpawn, Params);
	FMovieSceneBindingProxy BindingProxy(Guid, Sequence);
	return BindingProxy;
}

Alternatively, you can implement a similar function in a separate BlueprintFunctionLibrary inside a project or plugin. You could simply remove the GetActiveSequencer() check and call FSequencerUtilities::CreateOrReplaceBinding() with a nullptr Sequencer.

Let me know if there is anything else I can assist you with.

Best regards,

Vitor

Hi Tom,

Sorry about the delay. According to the devs, the requirement to have the sequence focused before performing operations on it was necessary to allow supporting a new dynamic bindings feature.

I am escalating this ticket now, and one of the devs should come here soon to provide additional details and talk to you directly about this.

All the best,

Vitor

In my “update sequencer” code I have a call like:

tracks = seq_asset.find_tracks_by_exact_type(unreal.MovieSceneCinematicShotTrack)
if tracks:
    seq_asset.remove_track(tracks[0])

I do this so that later the code can re-add all of the shots to a new shots track. Doing this allows cut tracks to be removed, existing tracks to be reordered, and so forth in a very straight forward “delete the existing stuff and add back in what I want in the new order” way.

But this crashes almost every time if the sequencer is open. It crashes about 50% of the time when the sequencer is closed. It seems like something in the code is holding onto sequencer callbacks for events like track deletion so this call causes the sequencer to crash and bring the editor down with it. For this reason I have that block commented out and people have to manually delete the track before calling the update tool if they need a shot deleted or moved.

This is just one example and there are a number of others where the code works fine but only if the sequencer is closed before running it. Another is changing the values of keys on a track. This ends up calling GetSequencer which for some reasons succeeds even when the sequencer is closed. It believes sequencer is there by checking a WeakPointer and then calls Pin on it. Down it goes.

Anyway, I would be fine with one or the other. Sequencer must be open or sequencer cannot be open. Just make it not crash on one of those pathways so that I can get work done.

Oh, I also have popups that call close sequencer before running my code but this doesn’t work since the close call returns immediately even thought the sequencer close has not completed. So, this ends up being a bit of a race condition. If the sequencer finishes closing then the script works. If it doesn’t finish closing fast enough my code hits a call that crashes if the sequencer is still up and down we go.