Custom Actor Component BP

Hello!

In my K2 nodes editor plugin, I use that code to have access to my c++ component where I need to have a read & write access during edition:

if (IsValid(bp) && IsValid(bp->SimpleConstructionScript))
{
	for (USCS_Node* node : bp->SimpleConstructionScript->GetAllNodes())
	{
		if (IsValid(node) && node->ComponentClass->IsChildOf<MyComponent>())
		{
			return Cast<MyComponent>(node->ComponentTemplate);
		}
	}
}

It’s working fine from actor bp graph but not for component bp graph (SimpleConstructionScript is null).
I tried several approach by getting cdo from differents sources (parent, most recent, generated, skeleton) but they all have their members to the constructor value (not the values I edited in the class defaut property panel).

I need to read/write to my component (in the same way I do from actor bp) in:

  • KismetCompilerContext::PreCompile()
  • ::AllocateDefaultPins()
  • ::ExpandNode(FKismetCompilerContext& compilerContext, UEdGraph* sourceGraph)

Help will be really appreciated.

Thanks!

I tried to use the LLM assistant (asked the same question), it hallucinated a lot for the answers, here is the chat:


LLM:

For component Blueprints, SimpleConstructionScript is always null because this system is specific to Actor Blueprints. Your strategy to access the component template via SCS nodes works only for actors.

To read/write the edited property values of your component Blueprint during compilation phases (PreCompile, AllocateDefaultPins, ExpandNode) in your K2 nodes plugin, follow this approach:

  1. Get the Blueprint’s GeneratedClass (bp->GeneratedClass).
  2. Get the Class Default Object (CDO) of the generated class via GetDefaultObject().
  3. Cast the CDO to your component class.
  4. Access the properties directly on this CDO, which reflects the edited values from the Class Defaults panel.

Example snippet inside your KismetCompilerContext or node code:

C++


if (bp && bp->GeneratedClass)
{
undefined ----
UObject* CDO = bp->GeneratedClass->GetDefaultObject();
undefined ----
if (UMyComponent* ComponentCDO = Cast<UMyComponent>(CDO))
undefined ----
{
undefined ----
// Read property example
undefined ----
float EditedValue = ComponentCDO->MyProperty;
undefined ----
undefined ----
// Write property example
undefined ----
ComponentCDO->MyProperty = NewValue;
undefined ----
undefined ----
// Optionally mark Blueprint dirty if you modify properties
undefined ----
bp->Modify();
undefined ----
}
undefined ----
}
undefined ----

Important notes:

  • bp here is your UBlueprint* representing the component Blueprint.

  • The CDO holds the runtime default with all Class Default panel edits applied, unlike the constructor object or skeleton class.

  • Do not expect SCS nodes or ComponentTemplate for component Blueprints.

  • If you want changes to persist, modify the CDO and mark the Blueprint dirty.

  • This works in all compile-time/editor phases you mentioned, as long as you have UBlueprint* available.

This is the idiomatic way to eliminate reliance on SCS and get true default property values in component Blueprints during K2 node compilation.

Let me know if you want example code adapted for each lifecycle hook (PreCompile(), AllocateDefaultPins(), ExpandNode()), or with detailed plugin module dependencies.


Me:

I tried this approach by getting cdo from differents sources (parent, most recent, generated, skeleton) but they all have their members to the constructor value (not the values I edited in the class defaut property panel).


LLM:

To access the true edited default properties of a component Blueprint’s CDO during K2 node compilation (reflecting changes made in the Class Defaults panel, not just constructor values), the key is to use DiffUtils::GetCDO(UBlueprint*) instead of directly calling GeneratedClass->GetDefaultObject().

DiffUtils::GetCDO() returns the CDO instance with all Blueprint-editor-applied default property changes properly applied, unlike a plain CDO from GeneratedClass which may reflect only constructor defaults.

Correct workflow for your plugin’s compile-time code:

C++


#include "DiffUtils.h" // For DiffUtils::GetCDO
undefined ----
if (bp) // UBlueprint*
undefined ----
{
undefined ----
UObject* ComponentCDO = DiffUtils::GetCDO(bp);
undefined ----
if (ComponentCDO)
undefined ----
{
undefined ----
if (UMyComponent* MyComponentCDO = Cast<UMyComponent>(ComponentCDO))
undefined ----
{
undefined ----
// Now MyComponentCDO->MyProperty reflects Class Default edits
undefined ----
undefined ----
float EditedValue = MyComponentCDO->MyProperty; // Read
undefined ----
MyComponentCDO->MyProperty = NewValue; // Write
undefined ----
undefined ----
// Mark Blueprint dirty if modified
undefined ----
bp->Modify();
undefined ----
}
undefined ----
}
undefined ----
}
undefined ----

Why DiffUtils::GetCDO()?

  • Internally it handles getting the proper CDO that includes any Blueprint default overrides saved in the editor.

  • It internally deals with issues like skeleton class CDOs vs generated CDOs.

  • It avoids usage of stale or constructor-only default objects.

Common pitfall avoiding:

  • Do not cast bp->GeneratedClass->GetDefaultObject() directly if you see stale constructor values.

  • Do not use bp->SkeletonGeneratedClass CDO.

  • Do not access SimpleConstructionScript on component blueprints (always null).

  • Always use DiffUtils::GetCDO.

Module Dependencies:

Add "UnrealEd" module to your Build.cs for DiffUtils.h.

C#

PublicDependencyModuleNames.AddRange(new string[] { "Engine", "UnrealEd" });

Summary: To read/write the true Blueprint-edited component defaults during PreCompile, AllocateDefaultPins, ExpandNode, use DiffUtils::GetCDO(bp) instead of bp->GeneratedClass->GetDefaultObject(). This reliably returns the CDO with all user changes applied.

If you want, I can provide sample plugin code snippets showing usage within those KismetCompilerContext lifecycle methods.


Me:

yes, tell me how to update class default panel values if I modify some component members in c++ side


LLM:

To update the Class Defaults panel values after modifying component member variables in C++ (e.g., during K2 node compilation), follow these steps:

  1. Modify the component CDO properties via DiffUtils::GetCDO(UBlueprint*).
  2. Mark the Blueprint as modified so the editor knows the asset has changed and needs saving.
  3. Request a refresh of any open Blueprint editors to immediately reflect changes in the Class Defaults panel.

Example: Updating Component Defaults and Reflecting in Editor

C++


#include "Kismet2/DiffUtils.h"
#include "Kismet2/BlueprintEditorUtils.h"
undefined ----
#include "BlueprintEditorLibrary.h"
undefined ----
undefined ----
void UpdateComponentDefaults(UBlueprint* Blueprint, float NewValue)
undefined ----
{
undefined ----
if (!Blueprint)
undefined ----
{
undefined ----
return;
undefined ----
}
undefined ----
undefined ----
UObject* ComponentCDO = FDiffUtils::GetCDO(Blueprint);
undefined ----
if (UMyComponent* MyComponentCDO = Cast<UMyComponent>(ComponentCDO))
undefined ----
{
undefined ----
// 1. Modify property on the CDO
undefined ----
MyComponentCDO->MyProperty = NewValue;
undefined ----
undefined ----
// 2. Mark Blueprint dirty to persist changes
undefined ----
FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint);
undefined ----
undefined ----
// 3. Refresh open editors so Class Defaults panel updates immediately
undefined ----
UBlueprintEditorLibrary::RefreshOpenEditorsForBlueprint(Blueprint);
undefined ----
}
undefined ----
}
undefined ----

Where to call this?

Call UpdateComponentDefaults() in your K2 node plugin during:

  • KismetCompilerContext::PreCompile()

  • AllocateDefaultPins()

  • ExpandNode()

whenever you want to update the default values based on your logic.

Important tips:

  • Mark Blueprint dirty using FBlueprintEditorUtils::MarkBlueprintAsModified after you modify CDO properties to ensure the editor tracks the change.

  • Use FDiffUtils::GetCDO to get the true editable CDO with Blueprint user changes included.

  • Refresh the editor UI only if you want live update of Class Defaults panel; can be skipped if not necessary.

  • Avoid modifying properties directly on GeneratedClass->GetDefaultObject() because it may not persist Blueprint-edited overrides.

Module dependencies (.Build.cs):

C#


PublicDependencyModuleNames.AddRange(new string[] {
"Engine",
undefined ----
"UnrealEd",
undefined ----
"BlueprintEditorLibrary"
undefined ----
});
undefined ----

This approach ensures your C++ property changes on component Blueprints:

  • Persist in the Blueprint asset.

  • Are visible and editable immediately in the Class Defaults panel.

  • Are compatible with K2 node compilation lifecycle hooks.

Let me know if you want example integration for a specific lifecycle method!


Me:

I don’t find FDiffUtils::GetCDO in my Unreal solution, in what Unreal version the function has been implemented ?


LLM:

FDiffUtils::GetCDO(UBlueprint*) is not part of the public Unreal Engine API in UE5.6 or earlier official releases. It is likely an internal or Epic-specific utility not exposed in mainline engine versions.

For UE5.6 and earlier, the recommended alternative to get the Blueprint’s edited Class Default Object (CDO) reliably is:

C++

UObject* EditedCDO = FBlueprintEditorUtils::GetClassCDO(UBlueprint* Blueprint);
  • FBlueprintEditorUtils::GetClassCDO returns the CDO including Blueprint-edited default property changes.

  • It is available in UnrealEd module from earlier versions.

  • Use this function instead of a non-existent FDiffUtils::GetCDO.

Example usage:

C++


#include "Kismet2/BlueprintEditorUtils.h"
undefined ----
if (bp)
undefined ----
{
undefined ----
UObject* ComponentCDO = FBlueprintEditorUtils::GetClassCDO(bp);
undefined ----
if (UMyComponent* MyComponentCDO = Cast<UMyComponent>(ComponentCDO))
undefined ----
{
undefined ----
// Read/write properties correctly here
undefined ----
}
undefined ----
}
undefined ----

Ensure "UnrealEd" module is added to your Build.cs dependencies to access FBlueprintEditorUtils.

Summary:

  • FDiffUtils::GetCDO is not present in UE5.6.

  • Use FBlueprintEditorUtils::GetClassCDO(UBlueprint*) for reliable Blueprint edited defaults access instead.

  • This function provides the CDO that includes all user edits made in the Class Defaults panel.

Let me know if you need example integration with this API.


Me:

‘GetClassCDO’: is not a member of ‘FBlueprintEditorUtils’


LLM:

In UE5.6, FBlueprintEditorUtils does not have GetClassCDO().

To reliably get the Blueprint’s edited Class Default Object (CDO) in UE5.6, use this approach instead:

C++


if (Blueprint && Blueprint->GeneratedClass)
{
undefined ----
UObject* EditedCDO = Blueprint->GeneratedClass->GetDefaultObject();
undefined ----
undefined ----
// Note: To ensure the CDO reflects edited defaults,
undefined ----
// make sure the Blueprint is compiled before calling this.
undefined ----
undefined ----
if (UMyComponent* MyComponentCDO = Cast<UMyComponent>(EditedCDO))
undefined ----
{
undefined ----
// Read/write properties here...
undefined ----
}
undefined ----
}
undefined ----

Important notes for UE5.6:

  • The GeneratedClass->GetDefaultObject() returns the edited CDO only after the Blueprint is compiled.

  • Before accessing the CDO, call:

C++

FBlueprintEditorUtils::CompileBlueprint(Blueprint);

to ensure compiled and updated defaults.

  • No public utility exists to get a “fresh” CDO other than compiling the Blueprint and accessing GeneratedClass->GetDefaultObject().

Example full code snippet to get updated defaults:

C++


#include "Kismet2/BlueprintEditorUtils.h"
undefined ----
void UpdateComponentDefaults(UBlueprint* Blueprint, float NewValue)
undefined ----
{
undefined ----
` if (!Blueprint
undefined ----
{
undefined ----
return;
undefined ----
}
undefined ----
undefined ----
// Ensure Blueprint is compiled and up to date
undefined ----
FBlueprintEditorUtils::CompileBlueprint(Blueprint);
undefined ----
undefined ----
UObject* EditedCDO = Blueprint->GeneratedClass->GetDefaultObject();
undefined ----
if (UMyComponent* MyComponentCDO = Cast<UMyComponent>(EditedCDO))
undefined ----
{
undefined ----
MyComponentCDO->MyProperty = NewValue;
undefined ----
undefined ----
// Mark Blueprint dirty after modification
undefined ----
FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint);
undefined ----
}
undefined ----
}
undefined ----

Summary:

  • In UE5.6, there is no DiffUtils::GetCDO or GetClassCDO in FBlueprintEditorUtils.

  • Use GeneratedClass->GetDefaultObject(), only after compiling the Blueprint using FBlueprintEditorUtils::CompileBlueprint().

  • This is the recommended way to get Blueprint-edited default values in UE5.6.

  • Always mark Blueprint dirty after modifying defaults via FBlueprintEditorUtils::MarkBlueprintAsModified().

Let me know if you want me to clarify how to integrate this in your K2 nodes plugin lifecycle hooks.


Me:

is calling CompileBlueprint() in PreCompile or ExplandNode will works ?


LLM:

Calling FBlueprintEditorUtils::CompileBlueprint() inside PreCompile() or ExpandNode() is not safe or recommended because it can cause recursive compilation and unpredictable side effects during the Blueprint compile lifecycle.

Why avoid calling CompileBlueprint() in these hooks?

  • PreCompile() and ExpandNode() are themselves part of the Blueprint compilation process.

  • Calling CompileBlueprint() here risks triggering a nested compile, leading to infinite recursion or breaking internal compile state.

  • It can cause crashes, inconsistent CDO states, or asset corruption.

Best practice to access up-to-date CDO during compilation:

  • Ensure your Blueprint is fully compiled before your K2 node compiler phase begins (outside or before entering KismetCompilerContext).

  • In PreCompile(), AllocateDefaultPins(), or ExpandNode(), use the current GeneratedClass->GetDefaultObject(), which should reflect the latest compiled CDO.

  • Avoid forcing compilation inside these lifecycle functions.

If you need to update defaults safely:

  • Modify properties on the CDO retrieved from GeneratedClass->GetDefaultObject() during compilation phases.

  • Mark the Blueprint dirty via FBlueprintEditorUtils::MarkBlueprintAsModified() but do not invoke CompileBlueprint() here.

  • Let the normal compilation process proceed after your node expansions.

Summary:

  • Do not call CompileBlueprint() in PreCompile() or ExpandNode() to eliminate recursion risk.

  • Rely on the already compiled CDO instance available during these phases.

  • Compile Blueprint explicitly beforehand if necessary, outside of K2 node compilation lifecycle.

Let me know if you want sample code patterns for proper CDO access without recursive compilation!


Me:

when accessing to GeneratedClass->GetDefaultObject() members of my component, it’s always constructor values, not value I edited (even long time before the compilation).


LLM:

If GeneratedClass->GetDefaultObject() returns only constructor defaults in your component Blueprint during compile-time phases, this usually means the Blueprint is not fully compiled or the CDO is stale at that moment.

To eliminate this stale CDO issue:

  1. Ensure the Blueprint is fully compiled before K2 node compilation begins.
    Calling CompileBlueprint() inside PreCompile or ExpandNode is unsafe, so do it earlier:
    • Explicitly compile the Blueprint before starting compilation session (e.g., manually via Editor or external tools).

    • Or, in your plugin/tool, trigger compilation before your node’s compile context is created.

  2. Access component defaults from the correct CDO instance:
    For component Blueprints, the CDO is the generated class default object, but it may be nested inside the actor CDO or have subobject templates. You can access your component subobject template like this:

C++


if (bp && bp->GeneratedClass)
{
undefined ----
UObject* ActorCDO = bp->GeneratedClass->GetDefaultObject();
undefined ----
UActorComponent* ComponentTemplate = nullptr;
undefined ----
undefined ----
// For component Blueprints, GeneratedClass's CDO is the component itself
undefined ----
if (UMyComponent* ComponentCDO = Cast<UMyComponent>(ActorCDO))
undefined ----
{
undefined ----
// Use ComponentCDO directly
undefined ----
}
undefined ----
else
undefined ----
{
undefined ----
// For actor Blueprints, find component template among subobjects
undefined ----
// (Not needed for component-typed Blueprints)
undefined ----
}
undefined ----
}
undefined ----
  1. Verify that your edits are saved and propagated:
    If you edited properties in the Class Defaults panel and then saved and compiled the Blueprint, those edits should be baked into the CDO.
  2. Avoid caching or using SkeletonGeneratedClass CDO, which only holds constructor defaults.

Additional Suggestions to eliminate stale/default constructor values:

  • Before compilation phase access, call this externally (prior to K2 compiler or plugin node call):

C++

FBlueprintEditorUtils::CompileBlueprint(Blueprint);
  • After compiling, then your calls to GeneratedClass->GetDefaultObject() will reflect user-edited defaults.

Summary

  • The only way to get Blueprint-edited defaults in the CDO is to query GeneratedClass->GetDefaultObject() after the Blueprint is compiled.

  • Ensure compilation happens before your node/plugin access.

  • Do not rely on SkeletonGeneratedClass->GetDefaultObject(), SimpleConstructionScript, or component templates during compilation.

  • For component Blueprints, the GeneratedClass->GetDefaultObject() is the component CDO.

  • Mark Blueprint dirty after changes but do not call compile inside compiler hooks.

If you want, I can help you with a workflow or tool code snippet to ensure external compilation before your node access.


Me:

ensureMsgf(!bLayoutChanging, TEXT(“Class named %s creating its CDO while changing its layout”), *GetName());


LLM:

This ensure triggers because your code is trying to create or access the Class Default Object (CDO) while the class’s memory layout is being changed during Blueprint compilation. Unreal Engine forbids CDO creation during layout changes to maintain internal consistency.

To eliminate this ensure warning:

  • Avoid accessing or instantiating the CDO during class layout changes, which typically happen early or mid Blueprint compilation phases.

  • Ensure the Blueprint is fully compiled and stable before accessing the CDO (e.g., in K2 node hooks like ExpandNode(), avoid forcing compile or CDO access if layout is changing).

  • Design your plugin to defer CDO access until after compilation completes; don’t call CompileBlueprint() inside compile hooks (PreCompile, ExpandNode) to eliminate recursion and layout-change conflicts.

  • To read/write component default values during compilation, access the CDO only after compile, typically via GeneratedClass->GetDefaultObject() after external compilation.

  • Mark Blueprint dirty to propagate changes but do not force recompile during these critical phases.

In summary: The ensure means your code is accessing the CDO too early in compilation while the class layout is still unstable. Eliminate this by postponing CDO access until after compilation finalizes and never trigger compile recursively inside compile hooks.


Me:

ok, I need to access to cdo in PreCompile, AllocateDefaultPins and ExpandNode.

How could I apply your advices ?


LLM:

To safely access the CDO in PreCompile(), AllocateDefaultPins(), and ExpandNode() without triggering layout-change asserts:

  • Ensure external compilation: Before your compiler runs, the Blueprint must be compiled fully outside these methods. Do not call CompileBlueprint() inside these methods.

  • Access CDO cautiously:

C++


if (bp && bp->GeneratedClass && !bp->bIsRegeneratingClass)
{
undefined ----
UObject* CDO = bp->GeneratedClass->GetDefaultObject();
undefined ----
if (UMyComponent* MyCompCDO = Cast<UMyComponent>(CDO))
undefined ----
{
undefined ----
// Safe to read/write properties here
undefined ----
}
undefined ----
}
undefined ----
  • bp->bIsRegeneratingClass (or check equivalent flags) indicates if layout change or regeneration is ongoing. Avoid accessing CDO if true.

  • Defer property writes if you detect layout-change phase; cache modifications and apply them once compilation completes.

  • Always mark Blueprint dirty via FBlueprintEditorUtils::MarkBlueprintAsModified() after changes but do not trigger recompile inside these methods.

  • If needed, use a flag or mechanism external to compilation to ensure your node runs only after Blueprint is stable.

Summary:

Lifecycle Hook Action Notes
PreCompile Access GeneratedClass->GetDefaultObject() only if blueprint is stable. Avoid if class layout changing.
AllocateDefaultPins Same as PreCompile
ExpandNode Same as above

This eliminates the ensure message by:

  • Preventing CDO queries during layout changes.

  • Avoiding recursive or forced compilation inside compile hooks.

  • Deferring writes during unstable phases.

If you want code samples showing a safe flag check or deferred apply pattern, I can provide those.


Me:

‘bIsRegeneratingClass’: is not a member of ‘UBlueprint’


LLM:

In UE5.6, UBlueprint does not have a public bIsRegeneratingClass flag to detect layout changes.

To safely access the CDO in PreCompile, AllocateDefaultPins, and ExpandNode without such a flag, use this approach:

C++


if (bp && bp->GeneratedClass)
{
undefined ----
// Check if GeneratedClass has the CLASS_IntrinsicClass flag which indicates layout stability
undefined ----
const bool bIsClassStable = !(bp->GeneratedClass->HasAnyClassFlags(CLASS_LayoutChanging));
undefined ----
undefined ----
if (bIsClassStable)
undefined ----
{
undefined ----
UObject* CDO = bp->GeneratedClass->GetDefaultObject();
undefined ----
if (UMyComponent* MyComponentCDO = Cast<UMyComponent>(CDO))
undefined ----
{
undefined ----
// Safe to read/write here
undefined ----
}
undefined ----
}
undefined ----
else
undefined ----
{
undefined ----
// Defer CDO access/writes until class layout stabilizes
undefined ----
}
undefined ----
}
undefined ----

Explanation:

  • CLASS_LayoutChanging is an internal ClassFlag set during layout changes.

  • Checking GeneratedClass->HasAnyClassFlags(CLASS_LayoutChanging) helps determine if the class layout is unstable.

  • If the flag is set, avoid accessing the CDO.

Summary:

  • Use GeneratedClass->HasAnyClassFlags(CLASS_LayoutChanging) to detect unsafe CDO access times.

  • Avoid bIsRegeneratingClass—not available in UE5.6.

  • Defer default property changes if CLASS_LayoutChanging is set.

  • Always avoid recursive compilation inside compile hooks.

This method eliminates the ensure error safely without relying on non-existent UBlueprint flags.

Let me know if you need example deferred update code.


Me:

‘CLASS_LayoutChanging’: undeclared identifier

please stop halucinating


LLM:

In UE5.6, there is no public flag like bIsRegeneratingClass on UBlueprint, nor a CLASS_LayoutChanging class flag accessible for checking layout changes.

To eliminate the ensure about accessing CDO during layout changes when you must access CDO in PreCompile(), AllocateDefaultPins(), or ExpandNode(), follow this practical pattern:

  1. Assume GeneratedClass->GetDefaultObject() may return stale constructor defaults if Blueprint isn’t fully compiled yet.
  2. Do not trigger Blueprint compilation (CompileBlueprint()) inside these methods to avoid recursion and layout-change errors.
  3. Instead, rely on the fact that Unreal’s Kismet compiler ensures these lifecycle methods are only called for fully compiled Blueprints.
  4. If you still get stale defaults, you likely access bp or GeneratedClass too early or on Skeleton classes or intermediate states.
  5. Use safe checks to avoid accessing Blueprint Skeleton classes or during regeneration:

C++


if (bp && bp->GeneratedClass
&& !bp->bIsRegeneratingClass // This is not public, so can't use in UE5.6
undefined ----
&& !bp->bBeingCompiled) // No such flag either in UE5.6
undefined ----
{
undefined ----
UObject* CDO = bp->GeneratedClass->GetDefaultObject();
undefined ----
if (UMyComponent* MyComponentCDO = Cast<UMyComponent>(CDO))
undefined ----
{
undefined ----
// safe access
undefined ----
}
undefined ----
}
undefined ----

Since such flags aren’t available, do this instead:

  • Check if bp->SkeletonGeneratedClass equals GeneratedClass. If yes, skip accessing CDO — this indicates compile not done.

Example:

C++


if (bp && bp->GeneratedClass && bp->SkeletonGeneratedClass != bp->GeneratedClass)
{
undefined ----
UObject* CDO = bp->GeneratedClass->GetDefaultObject();
undefined ----
if (UMyComponent* MyComponentCDO = Cast<UMyComponent>(CDO))
undefined ----
{
undefined ----
// safe to access edited defaults
undefined ----
}
undefined ----
}
undefined ----
  • Blueprint compiler normally calls your code only after the generated class is ready, so this check avoids accessing CDO too early.

Summary

  • UE5.6 does not expose internal flags for layout changing or regeneration.

  • Use the test bp->SkeletonGeneratedClass != bp->GeneratedClass to confirm Blueprint compilation completion.

  • Access GeneratedClass->GetDefaultObject() only if compiled (above).

  • Do not call CompileBlueprint() inside compile hooks.

  • Defer any property modifications if CDO is not ready.

This approach eliminates CDO layout error ensures safely without internal or unavailable flags.

Let me know if you want a minimal example integrated with your plugin lifecycle methods applying this check.


Me:

my code is:

if (bp->GeneratedClass && bp->SkeletonGeneratedClass != bp->GeneratedClass)

{

UObject* cdo = bp->GeneratedClass->GetDefaultObject();

return Cast(cdo);

}

and I have that error:

ensureMsgf(!bLayoutChanging, TEXT(“Class named %s creating its CDO while changing its layout”), *GetName());

occuring during PreCompile


LLM:

The ensure triggers because during PreCompile the class layout may still be changing, so even with your SkeletonGeneratedClass != GeneratedClass check, accessing GeneratedClass->GetDefaultObject() creates the CDO too early.

In UE5.6, PreCompile() occurs before the class layout is finalized and the CDO is safe to access.

How to apply safe CDO access during PreCompile and similar hooks:

  • Avoid accessing the full CDO in PreCompile(). Instead, defer CDO-dependent logic until after layout finalization, e.g. in ExpandNode() or later.

  • If you must use PreCompile(), do not call GetDefaultObject() directly. Instead, cache intended changes or gather required data from Blueprint graphs or external sources without resolving the CDO here.

  • Alternatively, wrap GetDefaultObject() with a try/catch guard or use bLayoutChanging detection by querying internal compiler flags, but as no public API exists, the safest is to skip CDO access in PreCompile() entirely.

Suggested pattern:

C++


// In PreCompile()
void YourK2Node::PreCompile(FKismetCompilerContext& CompilerContext)
undefined ----
{
undefined ----
if (bp && bp->GeneratedClass && bp->SkeletonGeneratedClass != bp->GeneratedClass)
undefined ----
{
undefined ----
// Avoid GetDefaultObject() here to eliminate ensure
undefined ----
undefined ----
// Instead, defer or cache needed info, e.g.
undefined ----
// Store flag or info in CompilerContext or your node to apply in ExpandNode()
undefined ----
}
undefined ----
}
undefined ----

C++


// In ExpandNode()
void YourK2Node::ExpandNode(FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph)
undefined ----
{
undefined ----
if (bp && bp->GeneratedClass && bp->SkeletonGeneratedClass != bp->GeneratedClass)
undefined ----
{
undefined ----
UObject* cdo = bp->GeneratedClass->GetDefaultObject();
undefined ----
// Safe to access CDO now
undefined ----
}
undefined ----
}
undefined ----

Summary:

  • PreCompile is too early for safe CDO access in UE5.6.

  • Avoid or defer property reads/writes needing CDO to ExpandNode() or later.

  • Accessing CDO in PreCompile() causes layout changing ensure.

  • Use SkeletonGeneratedClass != GeneratedClass as you do, but only for ExpandNode() or post-layout phases.

This eliminates the ensure and respects Unreal’s class layout timing constraints.

Let me know if you need deferred update code samples for this workflow.


Me:

I need to modify the cdo in Precompile too.

maybe I could all by hand:

get cdo

deserialized class default property panel into it

modify cdo

reserialize cdo to class default properties


LLM:

To modify the Blueprint CDO in PreCompile() safely despite layout-changing restrictions, you need to manually:

  • Deserialize the Blueprint’s class default properties into a temporary structure or memory block (not directly relying on GeneratedClass->GetDefaultObject()).

  • Modify those deserialized values.

  • Serialize the modified values back into the Blueprint’s default property storage.

  • This avoids creating the actual CDO instance during layout change phase.

Conceptual steps for manual deserialization/reserialization of class defaults in PreCompile:

  1. Get the Blueprint’s serialized class default object data (its property blob storage).
  2. Use FStructOnScope with the generated class’s GetStructure() to create a temporary memory buffer matching the class layout.
  3. Deserialize the raw class default property data into this temporary struct (simulate what the engine does on CDO creation).
  4. Modify properties on this temporary struct data.
  5. Serialize the updated struct back into the Blueprint’s class default property storage.
  6. Do not call GetDefaultObject() to avoid CDO creation during layout change.

Key API and classes:

  • FStructOnScope — Allocate temporary memory for a UStruct or UClass layout.

  • FMemoryReader/FMemoryWriter — Serialize/deserialize raw property blobs.

  • UObject::SerializeTaggedProperties() / FProperty::SerializeItem() — Deserialize/serialize individual properties.

  • Blueprint asset member: UBlueprint::GetClassDefaultObjectData() or raw default data bytes. (May require internal access.)

Minimal C++ pseudo-steps (conceptual; adapt as needed):

C++


#include "UObject/StructOnScope.h"
#include "Serialization/MemoryReader.h"
undefined ----
#include "Serialization/MemoryWriter.h"
undefined ----
#include "UObject/UnrealType.h"
undefined ----
undefined ----
void ModifyClassDefaultsManually(UBlueprint* Blueprint)
undefined ----
{
undefined ----
` if (!Blueprint
undefined ----
{
undefined ----
return;
undefined ----
}
undefined ----
undefined ----
// 1. Get serialized class default data from Blueprint asset
undefined ----
TArray<uint8> RawDefaults = Blueprint->GetClassDefaultObjectData(); // Example, may be internal
undefined ----
undefined ----
// 2. Setup temporary struct with class layout
undefined ----
FStructOnScope TempStruct(Blueprint->GeneratedClass);
undefined ----
uint8* TempData = TempStruct.GetStructMemory();
undefined ----
undefined ----
// 3. Deserialize raw data into TempStruct
undefined ----
{
undefined ----
FMemoryReader Reader(RawDefaults, /*bIsPersistent=*/ true);
undefined ----
Blueprint->GeneratedClass->SerializeTaggedProperties(Reader, TempData, nullptr);
undefined ----
}
undefined ----
undefined ----
// 4. Modify TempStruct properties directly
undefined ----
// Example: Find property by name and set value
undefined ----
if (FProperty* MyProp = Blueprint->GeneratedClass->FindPropertyByName(FName("MyProperty")))
undefined ----
{
undefined ----
void* PropPtr = MyProp->ContainerPtrToValuePtr<void>(TempData);
undefined ----
// Cast and set new value, e.g. for float:
undefined ----
if (FFloatProperty* FloatProp = CastField<FFloatProperty>(MyProp))
undefined ----
{
undefined ----
FloatProp->SetPropertyValue(PropPtr, 42.0f); // new value
undefined ----
}
undefined ----
}
undefined ----
undefined ----
// 5. Serialize modified TempStruct back to a byte array
undefined ----
TArray<uint8> NewRawDefaults;
undefined ----
{
undefined ----
FMemoryWriter Writer(NewRawDefaults, /*bIsPersistent=*/ true);
undefined ----
Blueprint->GeneratedClass->SerializeTaggedProperties(Writer, TempData, nullptr);
undefined ----
}
undefined ----
undefined ----
// 6. Write back to Blueprint's default property storage
undefined ----
Blueprint->SetClassDefaultObjectData(NewRawDefaults);
undefined ----
undefined ----
// 7. Mark Blueprint dirty to propagate changes
undefined ----
FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint);
undefined ----
}
undefined ----

Important notes:

  • GetClassDefaultObjectData() and SetClassDefaultObjectData() are placeholders; Unreal Engine does not provide public functions with these names. You must access the Blueprint’s class default property blob data member, usually UBlueprint::GeneratedClass->ClassDefaultObject raw data or via FBlueprintEditorUtils or FBlueprintEditor::SerializeToArchive.

  • You might need to access internal private members or replicate engine serialization logic.

  • FStructOnScope is key to get a proper memory buffer of the class layout without instantiating a UObject.

  • This approach eliminates CDO creation during layout change but requires careful serialization matching.

  • Test carefully to avoid corrupting Blueprint data.

Summary

  • Manually deserialize Blueprint default property data into a FStructOnScope.

  • Modify properties on the temp struct in memory.

  • Serialize the modified struct back into the Blueprint’s default property byte storage.

  • This eliminates the ensure triggered by creating the CDO during layout change.

  • Mark Blueprint dirty after changes.

If you want, I can help provide more specific working code for fetching and writing the Blueprint class default blob in UE5.6. Let me know.


Me:

I can call GetDefaultObject(bool bCreateIfNeeded = true)

with bCreateIfNeeded to false, it will always work.

then, is there functions to automatically deserialize properties into it ?


LLM:

Yes. Calling GetDefaultObject(false) returns the CDO without creating it if it doesn’t exist, which avoids layout-change issues. However, by default, this CDO contains only constructor defaults.

To deserialize Blueprint-edited class default properties into that existing CDO instance, you need to manually deserialize the saved default property data (the serialized class default properties blob) into the CDO using Unreal’s property serialization system, notably UClass::SerializeTaggedProperties.

How to deserialize class default properties into the CDO:

  1. Obtain the Blueprint’s serialized default properties blob (usually stored inside the Blueprint asset).
  2. Create an FMemoryReader over the raw data.
  3. Call SerializeTaggedProperties on the GeneratedClass to deserialize property values into the CDO instance returned by GetDefaultObject(false).

Example C++ snippet using manual deserialization:

C++


#include "UObject/Class.h"
#include "Serialization/MemoryReader.h"
undefined ----
undefined ----
void DeserializeBlueprintDefaultsToCDO(UBlueprint* Blueprint)
undefined ----
{
undefined ----
` if (!Blueprint
undefined ----
{
undefined ----
return;
undefined ----
}
undefined ----
undefined ----
UObject* CDO = Blueprint->GeneratedClass->GetDefaultObject(false);
undefined ----
if (!CDO)
undefined ----
{
undefined ----
return;
undefined ----
}
undefined ----
undefined ----
// Access the Blueprint's raw default property data buffer (example: from asset)
undefined ----
const TArray<uint8>* DefaultPropertyData = Blueprint->GetClassDefaultObjectData(); // Pseudo-code: actual access depends on UE version
undefined ----
undefined ----
` if (!DefaultPropertyData
undefined ----
{
undefined ----
return;
undefined ----
}
undefined ----
undefined ----
// Create memory reader for property blob
undefined ----
FMemoryReader Ar(*DefaultPropertyData);
undefined ----
Ar.Seek(0);
undefined ----
undefined ----
// Deserialize tagged properties into the existing CDO memory
undefined ----
Blueprint->GeneratedClass->SerializeTaggedProperties(Ar, (uint8*)CDO, nullptr);
undefined ----
}
undefined ----

Important considerations:

  • GetClassDefaultObjectData() is pseudo-code; in UE5.6 the actual Blueprint class default property data is stored internally and not exposed publicly. You may need engine source inspection or helper functions to access it.

  • SerializeTaggedProperties deserializes only non-transient properties properly.

  • This manually applies Blueprint edited defaults to the existing CDO in memory.

  • After modifying the CDO, if you want to save changes back to the Blueprint asset, you will need to serialize the updated properties back to the Blueprint’s default property storage blob.

Summary

  • GetDefaultObject(false) safely returns existing CDO without layout errors.

  • Use FMemoryReader + UClass::SerializeTaggedProperties on Blueprint raw default property bytes to deserialize into this CDO.

  • Blueprint’s raw default property data access is internal; you need to extract it from the Blueprint asset or use engine internals.

  • This approach allows applying Blueprint defaults into the CDO manually, useful in PreCompile.

If you want, I can help detail how to access Blueprint internal default property data and provide full working code for it.


Me:

‘GetClassDefaultObjectData’: is not a member of ‘UBlueprint’


LLM:

UBlueprint in UE5.6 does not expose a public function like GetClassDefaultObjectData() to access the raw serialized default property blob.

To manually deserialize Blueprint defaults into the CDO in PreCompile() you must:

  1. Access the Blueprint’s serialized class default property data directly from its private members (usually stored in the Blueprint’s GeneratedClass or associated data).
  2. This data is stored internally in UBlueprint as Blueprint->SerializedData or similar private member (not accessible via public API).

Practical approaches:

  • Use engine source modification or friend declarations to access the private property blob.

  • Or, read the Blueprint asset bytes from disk (UAsset serialization) and extract default property blob manually (complex and error-prone).

For typical plugin/code without engine changes:

  • You cannot access the Blueprint’s serialized default properties blob in UE5.6 via public API.

  • The engine only applies those defaults to the CDO internally during compilation.

What you can do instead:

  • Rely on Blueprint compilation process to apply defaults to CDO.

  • Access fully compiled CDO via GeneratedClass->GetDefaultObject() after compilation.

  • Do not attempt manual deserialization unless you modify the engine or use private reflection hacks.

Summary:

  • No public GetClassDefaultObjectData() in UE5.6 UBlueprint.

  • Manual deserialization requires internal/private data access or modified engine source.

  • Safest workflow: modify constructor defaults and let Blueprint compiler apply property overrides.

  • Access Blueprint edited defaults only after Blueprint compilation completes.

If you want to modify CDO defaults in PreCompile(), you must either:

  • Only modify constructor defaults (safe and public).

  • Or use custom engine changes to expose Blueprint default property blob for manual deserialization.

Let me know if you want guidance on modifying constructor defaults safely in PreCompile().

Try compare AActor and UActorComponent, what they inherit from. See if you can rewrite what you’re doing base on component.

Hello Kiray :slight_smile:
In AActor side, I use bp->SimpleConstructionScript and it’s working in PreCompile, ::AllocateDefaultPins() and ::ExpandNode (in my k2 node editor plugin).
SimpleConstructionScript is containing nodes and ComponentTemplate who are the object that are ready for read and write.
For a independent component bp, there’s nothing under (like components under AActor), I didn’t found the equivalent yet (ComponentTemplate).
Thanks for your answer!