Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
* Support of masked materials in instance segmentation, resulting in fine-grained annotations on e.g. leaves or fences (as in semantic segmentation)
* Added API function `world.set_annotations_traverse_translucency` and implemented functionality to configure, whether depth and semantic + instance segmentation traverse translucent materials or not.
* Fixed `frame`, `timestamp` and `transform` of `SensorData` not matching to the actually sent image for camera sensors.
* Added support for importing custom blueprints to Carla's blueprint library (ported from ue5-dev branch)

## CARLA 0.9.15

Expand Down
37 changes: 37 additions & 0 deletions Docs/add_custom_blueprint_to_library.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Adding custom blueprints to Carla blueprint library

This guide describes a process of adding custom blueprints created in Unreal Editor 4 to Carla's blueprint library, that makes it possible to spawn the blueprint using the Python API.

In order for your blueprints to be correctly parsed by Carla, they must have the `Actor (AActor)` class as their parent or a class that inherits `Actor` in its hierarchy.

Create a file named `BlueprintParameters.json` in `/CarlaUE4/Content/Carla/Config/` directory with following format:

```
{
"Blueprints": [
{
"Name": "SomeBlueprintName",
"Path": "/Game/Path/To/SomeBlueprintName"
},
{
"Name": "SomeOtherBlueprintName",
"Path": "/Game/Path/To/SomeOtherBlueprintName"
}
]
}
```
If, for example, in your UE4 Content Browser you have a blueprint with path `"Content -> Blueprints -> SomeBlueprintName"`, then you would modify the `Path` in `BlueprintParameters.json` as `"Path": "/Game/Blueprints/SomeBlueprintName"`. Repeat for other blueprints. That's it! When you run the simulator and retrieve the blueprint library using the Python API, you should see your custom blueprints listed there. In order to retrieve your custom blueprint, you coould do the following:

```
world = client.get_world()
blueprint_library = world.get_blueprint_library()
blueprint_name = 'SomeBlueprintName'
bp = blueprint_library.find(blueprint_name)
```

You have now successfully retrieved your blueprint in Python API and can spawn it using:

```
spawn_point = random.choice(world.get_map().get_spawn_points())
actor = world.spawn_actor(bp, spawn_point)
```
Original file line number Diff line number Diff line change
Expand Up @@ -1699,6 +1699,45 @@ void UActorBlueprintFunctionLibrary::MakePropDefinitions(
FillActorDefinitionArray(ParameterArray, Definitions, &MakePropDefinition);
}


void UActorBlueprintFunctionLibrary::MakeBlueprintDefinition(
const FBlueprintParameters &Parameters,
bool &Success,
FActorDefinition &Definition)
{
FillIdAndTags(Definition, TEXT("blueprint"), Parameters.Name);
AddRecommendedValuesForActorRoleName(Definition, {TEXT("blueprint")});

// Use StaticLoadObject to load the UBlueprint asset itself from the path in the JSON.
UBlueprint* BlueprintAsset = Cast<UBlueprint>(StaticLoadObject(UBlueprint::StaticClass(), nullptr, *Parameters.Path));

if (BlueprintAsset)
{
UClass* BlueprintClass = BlueprintAsset->GeneratedClass;
if (BlueprintClass)
{
Definition.Class = BlueprintClass;
Success = CheckActorDefinition(Definition);
}
else
{
Success = false;
}
}
else
{
UE_LOG(LogCarla, Error, TEXT("Failed to load Blueprint asset for path: %s. The path is likely incorrect."), *Parameters.Path);
Success = false;
}
}

void UActorBlueprintFunctionLibrary::MakeBlueprintDefinitions(
const TArray<FBlueprintParameters> &ParameterArray,
TArray<FActorDefinition> &Definitions)
{
FillActorDefinitionArray(ParameterArray, Definitions, &MakeBlueprintDefinition);
}

void UActorBlueprintFunctionLibrary::MakeObstacleDetectorDefinitions(
const FString &Type,
const FString &Id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,17 @@ class UActorBlueprintFunctionLibrary : public UBlueprintFunctionLibrary
const TArray<FPropParameters> &ParameterArray,
TArray<FActorDefinition> &Definitions);

UFUNCTION(Category = "Carla Actor", BlueprintCallable)
static void MakeBlueprintDefinition(
const FBlueprintParameters &Parameters,
bool &Success,
FActorDefinition &Definition);

UFUNCTION(Category = "Carla Actor", BlueprintCallable)
static void MakeBlueprintDefinitions(
const TArray<FBlueprintParameters> &ParameterArray,
TArray<FActorDefinition> &Definitions);

UFUNCTION()
static void MakeObstacleDetectorDefinitions(
const FString &Type,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
// Copyright (c) 2025 Computer Vision Center (CVC) at the Universitat Autonoma
// de Barcelona (UAB).
//
// This work is licensed under the terms of the MIT license.
// For a copy, see <https://opensource.org/licenses/MIT>.


#include "Carla/Actor/Factory/BlueprintActorFactory.h"
#include "Carla/Actor/ActorBlueprintFunctionLibrary.h"
#include "Carla/Actor/ActorDefinition.h"
#include "Carla/Game/CarlaEpisode.h"

#include "Json.h"
#include "JsonUtilities.h"
#include "Misc/FileHelper.h"
#include "Misc/Paths.h"

TArray<FActorDefinition> ABlueprintActorFactory::GetDefinitions()
{
// Load blueprint parameter list from JSON
LoadBlueprintParametersArrayFromFile(TEXT("BlueprintParameters.json"), BlueprintsParams);
UActorBlueprintFunctionLibrary::MakeBlueprintDefinitions(BlueprintsParams, Definitions);
return Definitions;
}

FActorSpawnResult ABlueprintActorFactory::SpawnActor(
const FTransform &SpawnAtTransform,
const FActorDescription &ActorDescription)
{
FActorSpawnResult SpawnResult;

if(!IsValid(ActorDescription.Class))
{
UE_LOG(LogCarla, Error, TEXT("Actor Description Class is null."));
SpawnResult.Status = EActorSpawnResultStatus::InvalidDescription;
return SpawnResult;
}

AActor* SpawnedActor = GetWorld()->SpawnActor<AActor>(ActorDescription.Class, SpawnAtTransform);
SpawnResult.Actor = SpawnedActor;

if(SpawnedActor == nullptr)
{
SpawnResult.Status = EActorSpawnResultStatus::Collision;
return SpawnResult;
}

UFunction* PostProcessFunction = FindFunction(FName("PostProcessBlueprint"));

if (PostProcessFunction && PostProcessFunction->Script.Num() > 0)
{
const bool bSuccess = PostProcessBlueprint(SpawnedActor, ActorDescription);
if (bSuccess)
{
SpawnResult.Status = EActorSpawnResultStatus::Success;
}
else
{
UE_LOG(LogCarla, Error, TEXT("PostProcessBlueprint returned 'false', indicating a failure during post-processing."));
SpawnResult.Status = EActorSpawnResultStatus::UnknownError;
}
}
else
{
SpawnResult.Status = EActorSpawnResultStatus::Success;
}

return SpawnResult;
}

TSharedPtr<FJsonObject> ABlueprintActorFactory::FBlueprintParametersToJsonObject(const FBlueprintParameters& BlueprintParams)
{
TSharedPtr<FJsonObject> JsonObject = MakeShareable(new FJsonObject);
JsonObject->SetStringField(TEXT("Name"), BlueprintParams.Name);
JsonObject->SetStringField(TEXT("Path"), BlueprintParams.Path);

return JsonObject;
}

FString ABlueprintActorFactory::FBlueprintParametersArrayToJson(const TArray<FBlueprintParameters>& BlueprintParamsArray)
{
TArray<TSharedPtr<FJsonValue>> JsonArray;

for (const FBlueprintParameters& BlueprintParams : BlueprintParamsArray)
{
// Convert each FBlueprintParameters to a JSON object
TSharedPtr<FJsonObject> JsonObject = FBlueprintParametersToJsonObject(BlueprintParams);
JsonArray.Add(MakeShareable(new FJsonValueObject(JsonObject)));
}

// Convert the array of JSON objects into a single JSON object
TSharedRef<FJsonObject> RootObject = MakeShareable(new FJsonObject);
RootObject->SetArrayField(TEXT("Blueprints"), JsonArray);

// Serialize the JSON object into an FString
FString OutputString;
TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&OutputString);
FJsonSerializer::Serialize(RootObject, Writer);

return OutputString;
}

void ABlueprintActorFactory::SaveBlueprintParametersArrayToFile(const TArray<FBlueprintParameters>& BlueprintParametersArray, const FString& FileName)
{
FString FilePath = FPaths::ProjectContentDir() + TEXT("Carla/Config/") + FileName;
// Convert the array to an FString in JSON format
FString JsonContent = FBlueprintParametersArrayToJson(BlueprintParametersArray);

// Save the JSON to a file
if (FFileHelper::SaveStringToFile(JsonContent, *FilePath))
{
UE_LOG(LogCarla, Log, TEXT("JSON file successfully saved at: %s"), *FilePath);
}
else
{
UE_LOG(LogCarla, Error, TEXT("Failed to save JSON file at: %s"), *FilePath);
}
}

bool ABlueprintActorFactory::JsonToFBlueprintParameters(const TSharedPtr<FJsonObject> JsonObject, FBlueprintParameters& OutBlueprintParams)
{
if (!JsonObject.IsValid()) return false;

JsonObject->TryGetStringField(TEXT("Name"), OutBlueprintParams.Name);
JsonObject->TryGetStringField(TEXT("Path"), OutBlueprintParams.Path);

return true;
}

bool ABlueprintActorFactory::JsonToFBlueprintParametersArray(const FString& JsonString, TArray<FBlueprintParameters>& OutBlueprintParamsArray)
{
TSharedPtr<FJsonObject> RootObject;
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(JsonString);

if (!FJsonSerializer::Deserialize(Reader, RootObject) || !RootObject.IsValid())
{
UE_LOG(LogCarla, Error, TEXT("Failed to parse JSON."));
return false;
}

// Get the "Blueprints" array from the JSON root object
const TArray<TSharedPtr<FJsonValue>>* BlueprintsArray;
if (RootObject->TryGetArrayField(TEXT("Blueprints"), BlueprintsArray))
{
OutBlueprintParamsArray.Empty();
for (const TSharedPtr<FJsonValue>& BlueprintValue : *BlueprintsArray)
{
TSharedPtr<FJsonObject> BlueprintObject = BlueprintValue->AsObject();
if (BlueprintObject.IsValid())
{
FBlueprintParameters BlueprintParams;
if (JsonToFBlueprintParameters(BlueprintObject, BlueprintParams))
{
OutBlueprintParamsArray.Add(BlueprintParams);
}
}
}
}

return true;
}

void ABlueprintActorFactory::LoadBlueprintParametersArrayFromFile(const FString& FileName, TArray<FBlueprintParameters>& OutBlueprintParamsArray)
{
FString JsonString;
FString FilePath = FPaths::ProjectContentDir() + TEXT("Carla/Config/") + FileName;
FString JsonContent;

// Load the JSON file content into an FString
if (FFileHelper::LoadFileToString(JsonContent, *FilePath))
{
// Parse the JSON and populate the TArray<FBlueprintParameters>
if (JsonToFBlueprintParametersArray(JsonContent, OutBlueprintParamsArray))
{
UE_LOG(LogCarla, Log, TEXT("Blueprint parameters loaded successfully from %s"), *FilePath);
}
else
{
UE_LOG(LogCarla, Error, TEXT("Failed to parse Blueprint parameters from %s"), *FilePath);
}
}
else
{
UE_LOG(LogCarla, Error, TEXT("Failed to load file: %s"), *FilePath);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright (c) 2025 Computer Vision Center (CVC) at the Universitat Autonoma
// de Barcelona (UAB).
//
// This work is licensed under the terms of the MIT license.
// For a copy, see <https://opensource.org/licenses/MIT>.

#pragma once

#include "Carla/Actor/ActorSpawnResult.h"
#include "Carla/Actor/CarlaActorFactory.h"
#include "Carla/Actor/BlueprintParameters.h"

#include "Json.h"
#include "JsonUtilities.h"

#include "BlueprintActorFactory.generated.h"

UCLASS()
class CARLA_API ABlueprintActorFactory : public ACarlaActorFactory
{
GENERATED_BODY()

/// Retrieve the definitions of the static mesh actor
virtual TArray<FActorDefinition> GetDefinitions() override;

virtual FActorSpawnResult SpawnActor(
const FTransform &SpawnAtTransform,
const FActorDescription &ActorDescription) override;

public:
UFUNCTION(BlueprintCallable, Category = "BlueprintActorFactory")
static void SaveBlueprintParametersArrayToFile(const TArray<FBlueprintParameters> &BlueprintParamsArray, const FString &FileName);
UFUNCTION(BlueprintCallable, Category = "BlueprintActorFactory")
static void LoadBlueprintParametersArrayFromFile(const FString &FileName, TArray<FBlueprintParameters> &OutBlueprintParamsArray);

UFUNCTION(BlueprintImplementableEvent, Category = "BlueprintActorFactory")
bool PostProcessBlueprint(AActor* SpawnedActor, const FActorDescription& BlueprintParams);

private:
static TSharedPtr<FJsonObject> FBlueprintParametersToJsonObject(const FBlueprintParameters& BlueprintParams);
static FString FBlueprintParametersArrayToJson(const TArray<FBlueprintParameters>& BlueprintParamsArray);
static bool JsonToFBlueprintParameters(const TSharedPtr<FJsonObject> JsonObject, FBlueprintParameters& OutBlueprintParams);
static bool JsonToFBlueprintParametersArray(const FString& JsonString, TArray<FBlueprintParameters>& OutBlueprintParamsArray);

protected:
UPROPERTY(EditAnywhere)
TArray<FActorDefinition> Definitions;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
TArray<FBlueprintParameters> BlueprintsParams;
};
Loading