Add kitchen sink structure with catalogue, recipes, and presets
Structure: - RalphaData/catalogue/ - Machine-readable asset index with JSON schema - RalphaData/recipes/ - Scene composition templates (living room, office, portrait) - RalphaData/presets/ - Lighting, post-processing, camera presets - RalphaData/scripts/ - Catalogue generation tooling - Content/Ralpha/Assets/ - Folder structure for all asset categories Key files: - catalogue.schema.json - Asset metadata schema with tags, bounds, licensing - recipe.schema.json - Scene composition schema with placement modes - generate_catalogue.py - Asset indexing script - LICENSE_POLICY.md - Asset provenance requirements Updated .gitattributes to keep RalphaData in regular git (not LFS) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
cd3dd56f26
commit
22cac6b96b
|
|
@ -39,3 +39,18 @@
|
|||
*.dll filter=lfs diff=lfs merge=lfs -text
|
||||
*.so filter=lfs diff=lfs merge=lfs -text
|
||||
*.dylib filter=lfs diff=lfs merge=lfs -text
|
||||
|
||||
# UE bulk data
|
||||
*.ubulk filter=lfs diff=lfs merge=lfs -text
|
||||
*.uexp filter=lfs diff=lfs merge=lfs -text
|
||||
*.upk filter=lfs diff=lfs merge=lfs -text
|
||||
|
||||
# Keep RalphaData in regular git (not LFS)
|
||||
# These are small JSON/text files for tooling
|
||||
RalphaData/** !filter !diff !merge text
|
||||
RalphaData/**/*.json text
|
||||
RalphaData/**/*.py text
|
||||
RalphaData/**/*.md text
|
||||
|
||||
# Small thumbnails OK in regular git
|
||||
RalphaData/thumbnails/*.png !filter !diff !merge -text
|
||||
|
|
|
|||
|
|
@ -0,0 +1,110 @@
|
|||
# Asset License Policy
|
||||
|
||||
All assets in this repository must have clear licensing and provenance.
|
||||
|
||||
## Allowed License Types
|
||||
|
||||
### Tier 1: Unrestricted (Preferred)
|
||||
|
||||
| License | Requirements | Folder |
|
||||
|---------|--------------|--------|
|
||||
| **CC0 / Public Domain** | None | `Content/Ralpha/Assets/` |
|
||||
| **Unlicense** | None | `Content/Ralpha/Assets/` |
|
||||
| **MIT** | Include license file | `Content/Ralpha/Assets/` |
|
||||
|
||||
### Tier 2: Attribution Required
|
||||
|
||||
| License | Requirements | Folder |
|
||||
|---------|--------------|--------|
|
||||
| **CC-BY** | Credit in catalogue.json | `Content/Ralpha/Assets/` |
|
||||
| **CC-BY-SA** | Credit + share-alike | `Content/Ralpha/Assets/` |
|
||||
|
||||
### Tier 3: Platform-Specific
|
||||
|
||||
| License | Requirements | Folder |
|
||||
|---------|--------------|--------|
|
||||
| **Quixel Megascans** | Free with UE, UE projects only | `Content/Ralpha/Assets/` |
|
||||
| **UE Marketplace** | Purchased, UE projects only | `Content/Ralpha/Assets/` |
|
||||
| **Sketchfab Store** | Purchased, per-asset license | `Content/Ralpha/Assets/` |
|
||||
|
||||
### Tier 4: Restricted
|
||||
|
||||
| License | Requirements | Folder |
|
||||
|---------|--------------|--------|
|
||||
| **NC (Non-Commercial)** | Non-commercial use only | `Content/Ralpha/Restricted/` |
|
||||
| **Custom/Unknown** | Case-by-case review | `Content/Ralpha/Restricted/` |
|
||||
|
||||
## Prohibited
|
||||
|
||||
- **ND (No Derivatives)** - Cannot modify, doesn't work for our use case
|
||||
- **Copyrighted without license** - No unlicensed assets
|
||||
- **AI-generated without rights** - Must have clear generation rights
|
||||
|
||||
## Catalogue Requirements
|
||||
|
||||
Every asset in `catalogue.json` must include:
|
||||
|
||||
```json
|
||||
{
|
||||
"license": {
|
||||
"type": "CC0", // Required: license identifier
|
||||
"source": "Poly Haven", // Required: where asset came from
|
||||
"attribution": null, // Required for CC-BY: credit text
|
||||
"url": "https://..." // Optional: source URL
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Recommended Sources
|
||||
|
||||
### Free / CC0
|
||||
|
||||
| Source | License | Quality | Notes |
|
||||
|--------|---------|---------|-------|
|
||||
| [Poly Haven](https://polyhaven.com) | CC0 | Excellent | HDRIs, textures, models |
|
||||
| [Sketchfab CC0](https://sketchfab.com/search?features=downloadable&licenses=cc0) | CC0 | Variable | Filter by license |
|
||||
| [cgbookcase](https://cgbookcase.com) | CC0 | Good | Textures |
|
||||
| [ambientCG](https://ambientcg.com) | CC0 | Good | PBR textures |
|
||||
| [Kenney](https://kenney.nl) | CC0 | Stylized | Low-poly assets |
|
||||
|
||||
### With UE License
|
||||
|
||||
| Source | License | Quality | Notes |
|
||||
|--------|---------|---------|-------|
|
||||
| [Quixel Megascans](https://quixel.com/megascans) | Megascans | Excellent | Free with UE |
|
||||
| [UE Marketplace](https://unrealengine.com/marketplace) | Marketplace | Variable | Many free monthly |
|
||||
|
||||
### Paid
|
||||
|
||||
| Source | License | Quality | Notes |
|
||||
|--------|---------|---------|-------|
|
||||
| [TurboSquid](https://turbosquid.com) | Per-asset | Variable | Check each license |
|
||||
| [CGTrader](https://cgtrader.com) | Per-asset | Variable | Check each license |
|
||||
| [Sketchfab Store](https://sketchfab.com/store) | Per-asset | Variable | Royalty-free options |
|
||||
|
||||
## Adding New Assets
|
||||
|
||||
1. **Verify license** before importing
|
||||
2. **Document source** in commit message
|
||||
3. **Add to catalogue** with full license info
|
||||
4. **Place in correct folder** based on license tier
|
||||
|
||||
## Audit Process
|
||||
|
||||
Periodically run:
|
||||
|
||||
```bash
|
||||
# Find assets missing license info
|
||||
python RalphaData/scripts/audit_licenses.py
|
||||
```
|
||||
|
||||
## Questions?
|
||||
|
||||
If unsure about an asset's license:
|
||||
1. Don't add it
|
||||
2. Ask in PR/issue
|
||||
3. Find alternative with clear license
|
||||
|
||||
---
|
||||
|
||||
*Last Updated: 2026-01-20*
|
||||
|
|
@ -0,0 +1 @@
|
|||
../../ralpha/plugin
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
[FilterPlugin]
|
||||
; This section lists additional files which will be packaged along with your plugin. Paths should be listed relative to the root plugin directory, and
|
||||
; may include "...", "*", and "?" wildcards to match directories, files, and individual characters respectively.
|
||||
;
|
||||
; Examples:
|
||||
; /README.txt
|
||||
; /Extras/...
|
||||
; /Binaries/ThirdParty/*.dll
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
{
|
||||
"FileVersion": 3,
|
||||
"Version": 1,
|
||||
"VersionName": "1.0",
|
||||
"FriendlyName": "Ralpha - AI Style Transfer",
|
||||
"Description": "AI-driven style transfer system for Unreal Engine 5. Enables Claude to control rendering parameters via MCP.",
|
||||
"Category": "Rendering",
|
||||
"CreatedBy": "Ralpha Team",
|
||||
"CreatedByURL": "",
|
||||
"DocsURL": "",
|
||||
"MarketplaceURL": "",
|
||||
"SupportURL": "",
|
||||
"CanContainContent": true,
|
||||
"IsBetaVersion": true,
|
||||
"IsExperimentalVersion": false,
|
||||
"Installed": false,
|
||||
"Modules": [
|
||||
{
|
||||
"Name": "RalphaCore",
|
||||
"Type": "Runtime",
|
||||
"LoadingPhase": "Default"
|
||||
},
|
||||
{
|
||||
"Name": "RalphaEditor",
|
||||
"Type": "Editor",
|
||||
"LoadingPhase": "PostEngineInit"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20">
|
||||
<rect width="20" height="20" rx="3" fill="#1a1a2e"/>
|
||||
<path d="M4 10 L8 6 L12 10 L16 4" stroke="#00d4ff" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<circle cx="10" cy="14" r="2" fill="#ff6b6b"/>
|
||||
<path d="M6 16 L14 16" stroke="#4ecdc4" stroke-width="1.5" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 412 B |
|
|
@ -1,29 +0,0 @@
|
|||
// Copyright Ralpha Team. All Rights Reserved.
|
||||
|
||||
#include "RalphaCore.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "FRalphaCoreModule"
|
||||
|
||||
void FRalphaCoreModule::StartupModule()
|
||||
{
|
||||
UE_LOG(LogTemp, Log, TEXT("Ralpha Core Module Started"));
|
||||
}
|
||||
|
||||
void FRalphaCoreModule::ShutdownModule()
|
||||
{
|
||||
UE_LOG(LogTemp, Log, TEXT("Ralpha Core Module Shutdown"));
|
||||
}
|
||||
|
||||
FRalphaCoreModule& FRalphaCoreModule::Get()
|
||||
{
|
||||
return FModuleManager::LoadModuleChecked<FRalphaCoreModule>("RalphaCore");
|
||||
}
|
||||
|
||||
bool FRalphaCoreModule::IsAvailable()
|
||||
{
|
||||
return FModuleManager::Get().IsModuleLoaded("RalphaCore");
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
|
||||
IMPLEMENT_MODULE(FRalphaCoreModule, RalphaCore)
|
||||
|
|
@ -1,965 +0,0 @@
|
|||
// Copyright Ralpha Team. All Rights Reserved.
|
||||
|
||||
#include "RalphaMCPServer.h"
|
||||
#include "RalphaParameterBridge.h"
|
||||
#include "RalphaScreenCapture.h"
|
||||
#include "Dom/JsonObject.h"
|
||||
#include "Dom/JsonValue.h"
|
||||
#include "Serialization/JsonSerializer.h"
|
||||
#include "Serialization/JsonReader.h"
|
||||
#include "Serialization/JsonWriter.h"
|
||||
#include "Misc/Base64.h"
|
||||
#include "Engine/World.h"
|
||||
#include "Engine/Engine.h"
|
||||
#include "Kismet/GameplayStatics.h"
|
||||
|
||||
DEFINE_LOG_CATEGORY(LogRalphaMCP);
|
||||
|
||||
URalphaMCPServer* URalphaMCPServer::Instance = nullptr;
|
||||
|
||||
URalphaMCPServer::URalphaMCPServer()
|
||||
: ListenerSocket(nullptr)
|
||||
, ServerPort(30010)
|
||||
, bIsRunning(false)
|
||||
, ParameterBridge(nullptr)
|
||||
, ScreenCapture(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
URalphaMCPServer::~URalphaMCPServer()
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
|
||||
URalphaMCPServer* URalphaMCPServer::Get()
|
||||
{
|
||||
if (!Instance)
|
||||
{
|
||||
Instance = NewObject<URalphaMCPServer>();
|
||||
Instance->AddToRoot(); // Prevent garbage collection
|
||||
}
|
||||
return Instance;
|
||||
}
|
||||
|
||||
bool URalphaMCPServer::Start(int32 Port)
|
||||
{
|
||||
if (bIsRunning)
|
||||
{
|
||||
UE_LOG(LogRalphaMCP, Warning, TEXT("Server already running on port %d"), ServerPort);
|
||||
return true;
|
||||
}
|
||||
|
||||
ServerPort = Port;
|
||||
|
||||
// Create socket
|
||||
ISocketSubsystem* SocketSubsystem = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM);
|
||||
if (!SocketSubsystem)
|
||||
{
|
||||
UE_LOG(LogRalphaMCP, Error, TEXT("Failed to get socket subsystem"));
|
||||
return false;
|
||||
}
|
||||
|
||||
ListenerSocket = SocketSubsystem->CreateSocket(NAME_Stream, TEXT("RalphaMCPServer"), false);
|
||||
if (!ListenerSocket)
|
||||
{
|
||||
UE_LOG(LogRalphaMCP, Error, TEXT("Failed to create socket"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Configure socket
|
||||
ListenerSocket->SetReuseAddr(true);
|
||||
ListenerSocket->SetNonBlocking(true);
|
||||
|
||||
// Bind to port
|
||||
TSharedRef<FInternetAddr> Addr = SocketSubsystem->CreateInternetAddr();
|
||||
Addr->SetAnyAddress();
|
||||
Addr->SetPort(ServerPort);
|
||||
|
||||
if (!ListenerSocket->Bind(*Addr))
|
||||
{
|
||||
UE_LOG(LogRalphaMCP, Error, TEXT("Failed to bind to port %d"), ServerPort);
|
||||
SocketSubsystem->DestroySocket(ListenerSocket);
|
||||
ListenerSocket = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Start listening
|
||||
if (!ListenerSocket->Listen(8))
|
||||
{
|
||||
UE_LOG(LogRalphaMCP, Error, TEXT("Failed to listen on port %d"), ServerPort);
|
||||
SocketSubsystem->DestroySocket(ListenerSocket);
|
||||
ListenerSocket = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create parameter bridge and screen capture
|
||||
ParameterBridge = NewObject<URalphaParameterBridge>(this);
|
||||
ScreenCapture = NewObject<URalphaScreenCapture>(this);
|
||||
|
||||
// Register tick
|
||||
TickDelegateHandle = FTSTicker::GetCoreTicker().AddTicker(
|
||||
FTickerDelegate::CreateUObject(this, &URalphaMCPServer::Tick), 0.0f);
|
||||
|
||||
bIsRunning = true;
|
||||
UE_LOG(LogRalphaMCP, Log, TEXT("Ralpha MCP Server started on port %d"), ServerPort);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void URalphaMCPServer::Stop()
|
||||
{
|
||||
if (!bIsRunning)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Unregister tick
|
||||
FTSTicker::GetCoreTicker().RemoveTicker(TickDelegateHandle);
|
||||
|
||||
// Close all client connections
|
||||
ClientConnections.Empty();
|
||||
|
||||
// Close listener socket
|
||||
if (ListenerSocket)
|
||||
{
|
||||
ListenerSocket->Close();
|
||||
ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->DestroySocket(ListenerSocket);
|
||||
ListenerSocket = nullptr;
|
||||
}
|
||||
|
||||
bIsRunning = false;
|
||||
UE_LOG(LogRalphaMCP, Log, TEXT("Ralpha MCP Server stopped"));
|
||||
}
|
||||
|
||||
bool URalphaMCPServer::IsRunning() const
|
||||
{
|
||||
return bIsRunning;
|
||||
}
|
||||
|
||||
bool URalphaMCPServer::Tick(float DeltaTime)
|
||||
{
|
||||
if (!bIsRunning || !ListenerSocket)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for new connections
|
||||
bool bHasPendingConnection = false;
|
||||
if (ListenerSocket->HasPendingConnection(bHasPendingConnection) && bHasPendingConnection)
|
||||
{
|
||||
TSharedRef<FInternetAddr> RemoteAddress = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateInternetAddr();
|
||||
FSocket* ClientSocket = ListenerSocket->Accept(*RemoteAddress, TEXT("RalphaClient"));
|
||||
|
||||
if (ClientSocket)
|
||||
{
|
||||
ClientSocket->SetNonBlocking(true);
|
||||
|
||||
TSharedPtr<FRalphaClientConnection> NewConnection = MakeShared<FRalphaClientConnection>(ClientSocket, this);
|
||||
ClientConnections.Add(NewConnection);
|
||||
|
||||
UE_LOG(LogRalphaMCP, Log, TEXT("New client connected from %s"), *RemoteAddress->ToString(true));
|
||||
}
|
||||
}
|
||||
|
||||
// Process existing connections
|
||||
for (int32 i = ClientConnections.Num() - 1; i >= 0; --i)
|
||||
{
|
||||
if (ClientConnections[i]->IsValid())
|
||||
{
|
||||
ClientConnections[i]->Tick();
|
||||
}
|
||||
else
|
||||
{
|
||||
ClientConnections.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
FString URalphaMCPServer::ProcessCommand(const FString& JsonCommand)
|
||||
{
|
||||
TSharedPtr<FJsonObject> JsonObject;
|
||||
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(JsonCommand);
|
||||
|
||||
if (!FJsonSerializer::Deserialize(Reader, JsonObject) || !JsonObject.IsValid())
|
||||
{
|
||||
return TEXT("{\"success\": false, \"error\": \"Invalid JSON\"}");
|
||||
}
|
||||
|
||||
FString CommandType;
|
||||
if (!JsonObject->TryGetStringField(TEXT("type"), CommandType))
|
||||
{
|
||||
return TEXT("{\"success\": false, \"error\": \"Missing command type\"}");
|
||||
}
|
||||
|
||||
TSharedPtr<FJsonObject> ResponseObject = MakeShared<FJsonObject>();
|
||||
|
||||
// Route commands
|
||||
if (CommandType == TEXT("capture_screenshot"))
|
||||
{
|
||||
int32 Width = JsonObject->HasField(TEXT("width")) ? JsonObject->GetIntegerField(TEXT("width")) : 1920;
|
||||
int32 Height = JsonObject->HasField(TEXT("height")) ? JsonObject->GetIntegerField(TEXT("height")) : 1080;
|
||||
bool bUseCamera = JsonObject->HasField(TEXT("use_camera")) ? JsonObject->GetBoolField(TEXT("use_camera")) : true;
|
||||
|
||||
FString Base64Image;
|
||||
FString FilePath;
|
||||
bool bSuccess = false;
|
||||
|
||||
if (bUseCamera)
|
||||
{
|
||||
// Capture from CineCamera using SceneCapture2D
|
||||
bSuccess = ScreenCapture->CaptureFromCamera(Width, Height, Base64Image, FilePath);
|
||||
}
|
||||
|
||||
// Fallback to viewport capture if camera capture failed or not requested
|
||||
if (!bSuccess)
|
||||
{
|
||||
bSuccess = ScreenCapture->CaptureScreenshot(Width, Height, Base64Image, FilePath);
|
||||
}
|
||||
|
||||
ResponseObject->SetBoolField(TEXT("success"), bSuccess);
|
||||
if (bSuccess)
|
||||
{
|
||||
ResponseObject->SetStringField(TEXT("base64"), Base64Image);
|
||||
ResponseObject->SetStringField(TEXT("path"), FilePath);
|
||||
}
|
||||
}
|
||||
else if (CommandType == TEXT("get_all_parameters"))
|
||||
{
|
||||
TSharedPtr<FJsonObject> ParamsObject = ParameterBridge->GetAllParameters();
|
||||
ResponseObject->SetBoolField(TEXT("success"), true);
|
||||
ResponseObject->SetObjectField(TEXT("data"), ParamsObject);
|
||||
}
|
||||
else if (CommandType == TEXT("set_post_process"))
|
||||
{
|
||||
const TSharedPtr<FJsonObject>* Params;
|
||||
if (JsonObject->TryGetObjectField(TEXT("parameters"), Params))
|
||||
{
|
||||
bool bSuccess = ParameterBridge->SetPostProcessParameters(*Params);
|
||||
ResponseObject->SetBoolField(TEXT("success"), bSuccess);
|
||||
}
|
||||
else
|
||||
{
|
||||
ResponseObject->SetBoolField(TEXT("success"), false);
|
||||
ResponseObject->SetStringField(TEXT("error"), TEXT("Missing parameters"));
|
||||
}
|
||||
}
|
||||
else if (CommandType == TEXT("set_directional_light"))
|
||||
{
|
||||
const TSharedPtr<FJsonObject>* Params;
|
||||
if (JsonObject->TryGetObjectField(TEXT("parameters"), Params))
|
||||
{
|
||||
bool bSuccess = ParameterBridge->SetDirectionalLightParameters(*Params);
|
||||
ResponseObject->SetBoolField(TEXT("success"), bSuccess);
|
||||
}
|
||||
else
|
||||
{
|
||||
ResponseObject->SetBoolField(TEXT("success"), false);
|
||||
ResponseObject->SetStringField(TEXT("error"), TEXT("Missing parameters"));
|
||||
}
|
||||
}
|
||||
else if (CommandType == TEXT("set_sky_light"))
|
||||
{
|
||||
const TSharedPtr<FJsonObject>* Params;
|
||||
if (JsonObject->TryGetObjectField(TEXT("parameters"), Params))
|
||||
{
|
||||
bool bSuccess = ParameterBridge->SetSkyLightParameters(*Params);
|
||||
ResponseObject->SetBoolField(TEXT("success"), bSuccess);
|
||||
}
|
||||
else
|
||||
{
|
||||
ResponseObject->SetBoolField(TEXT("success"), false);
|
||||
ResponseObject->SetStringField(TEXT("error"), TEXT("Missing parameters"));
|
||||
}
|
||||
}
|
||||
else if (CommandType == TEXT("set_exponential_height_fog"))
|
||||
{
|
||||
const TSharedPtr<FJsonObject>* Params;
|
||||
if (JsonObject->TryGetObjectField(TEXT("parameters"), Params))
|
||||
{
|
||||
bool bSuccess = ParameterBridge->SetFogParameters(*Params);
|
||||
ResponseObject->SetBoolField(TEXT("success"), bSuccess);
|
||||
}
|
||||
else
|
||||
{
|
||||
ResponseObject->SetBoolField(TEXT("success"), false);
|
||||
ResponseObject->SetStringField(TEXT("error"), TEXT("Missing parameters"));
|
||||
}
|
||||
}
|
||||
else if (CommandType == TEXT("set_camera"))
|
||||
{
|
||||
const TSharedPtr<FJsonObject>* Params;
|
||||
if (JsonObject->TryGetObjectField(TEXT("parameters"), Params))
|
||||
{
|
||||
bool bSuccess = ParameterBridge->SetCameraParameters(*Params);
|
||||
ResponseObject->SetBoolField(TEXT("success"), bSuccess);
|
||||
}
|
||||
else
|
||||
{
|
||||
ResponseObject->SetBoolField(TEXT("success"), false);
|
||||
ResponseObject->SetStringField(TEXT("error"), TEXT("Missing parameters"));
|
||||
}
|
||||
}
|
||||
else if (CommandType == TEXT("search_assets"))
|
||||
{
|
||||
FString Query = JsonObject->GetStringField(TEXT("query"));
|
||||
FString AssetType = JsonObject->HasField(TEXT("asset_type")) ? JsonObject->GetStringField(TEXT("asset_type")) : TEXT("All");
|
||||
int32 MaxResults = JsonObject->HasField(TEXT("max_results")) ? JsonObject->GetIntegerField(TEXT("max_results")) : 20;
|
||||
|
||||
TArray<TSharedPtr<FJsonValue>> Results = ParameterBridge->SearchAssets(Query, AssetType, MaxResults);
|
||||
ResponseObject->SetBoolField(TEXT("success"), true);
|
||||
ResponseObject->SetArrayField(TEXT("results"), Results);
|
||||
}
|
||||
else if (CommandType == TEXT("spawn_actor"))
|
||||
{
|
||||
FString AssetPath = JsonObject->GetStringField(TEXT("asset_path"));
|
||||
|
||||
const TSharedPtr<FJsonObject>* LocationObj;
|
||||
FVector Location = FVector::ZeroVector;
|
||||
if (JsonObject->TryGetObjectField(TEXT("location"), LocationObj))
|
||||
{
|
||||
Location.X = (*LocationObj)->GetNumberField(TEXT("x"));
|
||||
Location.Y = (*LocationObj)->GetNumberField(TEXT("y"));
|
||||
Location.Z = (*LocationObj)->GetNumberField(TEXT("z"));
|
||||
}
|
||||
|
||||
FRotator Rotation = FRotator::ZeroRotator;
|
||||
const TSharedPtr<FJsonObject>* RotationObj;
|
||||
if (JsonObject->TryGetObjectField(TEXT("rotation"), RotationObj))
|
||||
{
|
||||
Rotation.Pitch = (*RotationObj)->HasField(TEXT("pitch")) ? (*RotationObj)->GetNumberField(TEXT("pitch")) : 0.0f;
|
||||
Rotation.Yaw = (*RotationObj)->HasField(TEXT("yaw")) ? (*RotationObj)->GetNumberField(TEXT("yaw")) : 0.0f;
|
||||
Rotation.Roll = (*RotationObj)->HasField(TEXT("roll")) ? (*RotationObj)->GetNumberField(TEXT("roll")) : 0.0f;
|
||||
}
|
||||
|
||||
float Scale = JsonObject->HasField(TEXT("scale")) ? JsonObject->GetNumberField(TEXT("scale")) : 1.0f;
|
||||
FString ActorLabel = JsonObject->HasField(TEXT("actor_label")) ? JsonObject->GetStringField(TEXT("actor_label")) : TEXT("");
|
||||
|
||||
FString ActorId;
|
||||
bool bSuccess = ParameterBridge->SpawnActor(AssetPath, Location, Rotation, Scale, ActorLabel, ActorId);
|
||||
ResponseObject->SetBoolField(TEXT("success"), bSuccess);
|
||||
if (bSuccess)
|
||||
{
|
||||
ResponseObject->SetStringField(TEXT("actor_id"), ActorId);
|
||||
}
|
||||
}
|
||||
else if (CommandType == TEXT("delete_actor"))
|
||||
{
|
||||
FString ActorId = JsonObject->GetStringField(TEXT("actor_id"));
|
||||
bool bSuccess = ParameterBridge->DeleteActor(ActorId);
|
||||
ResponseObject->SetBoolField(TEXT("success"), bSuccess);
|
||||
}
|
||||
else if (CommandType == TEXT("spawn_ocean"))
|
||||
{
|
||||
FVector Location = FVector::ZeroVector;
|
||||
const TSharedPtr<FJsonObject>* LocationObj;
|
||||
if (JsonObject->TryGetObjectField(TEXT("location"), LocationObj))
|
||||
{
|
||||
Location.X = (*LocationObj)->HasField(TEXT("x")) ? (*LocationObj)->GetNumberField(TEXT("x")) : 0.0f;
|
||||
Location.Y = (*LocationObj)->HasField(TEXT("y")) ? (*LocationObj)->GetNumberField(TEXT("y")) : 0.0f;
|
||||
Location.Z = (*LocationObj)->HasField(TEXT("z")) ? (*LocationObj)->GetNumberField(TEXT("z")) : 0.0f;
|
||||
}
|
||||
float Size = JsonObject->HasField(TEXT("size")) ? JsonObject->GetNumberField(TEXT("size")) : 100000.0f;
|
||||
|
||||
FString ActorId;
|
||||
bool bSuccess = ParameterBridge->SpawnOcean(Location, Size, ActorId);
|
||||
ResponseObject->SetBoolField(TEXT("success"), bSuccess);
|
||||
if (bSuccess)
|
||||
{
|
||||
ResponseObject->SetStringField(TEXT("actor_id"), ActorId);
|
||||
}
|
||||
}
|
||||
else if (CommandType == TEXT("set_ocean_waves"))
|
||||
{
|
||||
float WaveAmplitude = JsonObject->HasField(TEXT("amplitude")) ? JsonObject->GetNumberField(TEXT("amplitude")) : 100.0f;
|
||||
float WaveLength = JsonObject->HasField(TEXT("wavelength")) ? JsonObject->GetNumberField(TEXT("wavelength")) : 1000.0f;
|
||||
float Steepness = JsonObject->HasField(TEXT("steepness")) ? JsonObject->GetNumberField(TEXT("steepness")) : 0.5f;
|
||||
int32 NumWaves = JsonObject->HasField(TEXT("num_waves")) ? JsonObject->GetIntegerField(TEXT("num_waves")) : 4;
|
||||
|
||||
bool bSuccess = ParameterBridge->SetOceanWaves(WaveAmplitude, WaveLength, Steepness, NumWaves);
|
||||
ResponseObject->SetBoolField(TEXT("success"), bSuccess);
|
||||
}
|
||||
else if (CommandType == TEXT("create_landscape"))
|
||||
{
|
||||
float Size = JsonObject->HasField(TEXT("size")) ? JsonObject->GetNumberField(TEXT("size")) : 10000.0f;
|
||||
float HeightOffset = JsonObject->HasField(TEXT("height_offset")) ? JsonObject->GetNumberField(TEXT("height_offset")) : -500.0f;
|
||||
|
||||
bool bSuccess = ParameterBridge->CreateLandscape(Size, HeightOffset);
|
||||
ResponseObject->SetBoolField(TEXT("success"), bSuccess);
|
||||
}
|
||||
else if (CommandType == TEXT("set_actor_material"))
|
||||
{
|
||||
FString ActorId = JsonObject->GetStringField(TEXT("actor_id"));
|
||||
FString MaterialPath = JsonObject->GetStringField(TEXT("material_path"));
|
||||
int32 MaterialIndex = JsonObject->HasField(TEXT("material_index")) ? JsonObject->GetIntegerField(TEXT("material_index")) : 0;
|
||||
bool bSuccess = ParameterBridge->SetActorMaterial(ActorId, MaterialPath, MaterialIndex);
|
||||
ResponseObject->SetBoolField(TEXT("success"), bSuccess);
|
||||
}
|
||||
else if (CommandType == TEXT("set_actor_simple_material"))
|
||||
{
|
||||
FString ActorId = JsonObject->GetStringField(TEXT("actor_id"));
|
||||
|
||||
// Parse color - supports hex string or RGB object
|
||||
FLinearColor BaseColor = FLinearColor(0.1f, 0.2f, 0.4f); // Default dark blue
|
||||
if (JsonObject->HasField(TEXT("color")))
|
||||
{
|
||||
const TSharedPtr<FJsonObject>* ColorObj;
|
||||
if (JsonObject->TryGetObjectField(TEXT("color"), ColorObj))
|
||||
{
|
||||
BaseColor.R = (*ColorObj)->HasField(TEXT("r")) ? (*ColorObj)->GetNumberField(TEXT("r")) : 0.1f;
|
||||
BaseColor.G = (*ColorObj)->HasField(TEXT("g")) ? (*ColorObj)->GetNumberField(TEXT("g")) : 0.2f;
|
||||
BaseColor.B = (*ColorObj)->HasField(TEXT("b")) ? (*ColorObj)->GetNumberField(TEXT("b")) : 0.4f;
|
||||
BaseColor.A = (*ColorObj)->HasField(TEXT("a")) ? (*ColorObj)->GetNumberField(TEXT("a")) : 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
float Metallic = JsonObject->HasField(TEXT("metallic")) ? JsonObject->GetNumberField(TEXT("metallic")) : 0.0f;
|
||||
float Roughness = JsonObject->HasField(TEXT("roughness")) ? JsonObject->GetNumberField(TEXT("roughness")) : 0.5f;
|
||||
float Opacity = JsonObject->HasField(TEXT("opacity")) ? JsonObject->GetNumberField(TEXT("opacity")) : 1.0f;
|
||||
|
||||
bool bSuccess = ParameterBridge->SetActorSimpleMaterial(ActorId, BaseColor, Metallic, Roughness, Opacity);
|
||||
ResponseObject->SetBoolField(TEXT("success"), bSuccess);
|
||||
}
|
||||
else if (CommandType == TEXT("set_actor_emissive_material"))
|
||||
{
|
||||
FString ActorId = JsonObject->GetStringField(TEXT("actor_id"));
|
||||
|
||||
// Parse emissive color - supports RGB object
|
||||
FLinearColor EmissiveColor = FLinearColor(1.0f, 0.8f, 0.3f); // Default warm yellow
|
||||
if (JsonObject->HasField(TEXT("color")))
|
||||
{
|
||||
const TSharedPtr<FJsonObject>* ColorObj;
|
||||
if (JsonObject->TryGetObjectField(TEXT("color"), ColorObj))
|
||||
{
|
||||
EmissiveColor.R = (*ColorObj)->HasField(TEXT("r")) ? (*ColorObj)->GetNumberField(TEXT("r")) : 1.0f;
|
||||
EmissiveColor.G = (*ColorObj)->HasField(TEXT("g")) ? (*ColorObj)->GetNumberField(TEXT("g")) : 0.8f;
|
||||
EmissiveColor.B = (*ColorObj)->HasField(TEXT("b")) ? (*ColorObj)->GetNumberField(TEXT("b")) : 0.3f;
|
||||
}
|
||||
}
|
||||
|
||||
float Intensity = JsonObject->HasField(TEXT("intensity")) ? JsonObject->GetNumberField(TEXT("intensity")) : 10.0f;
|
||||
|
||||
bool bSuccess = ParameterBridge->SetActorEmissiveMaterial(ActorId, EmissiveColor, Intensity);
|
||||
ResponseObject->SetBoolField(TEXT("success"), bSuccess);
|
||||
}
|
||||
else if (CommandType == TEXT("list_actors"))
|
||||
{
|
||||
FString ClassFilter = JsonObject->HasField(TEXT("class_filter")) ? JsonObject->GetStringField(TEXT("class_filter")) : TEXT("");
|
||||
FString NameFilter = JsonObject->HasField(TEXT("name_filter")) ? JsonObject->GetStringField(TEXT("name_filter")) : TEXT("");
|
||||
bool bIncludeTransforms = JsonObject->HasField(TEXT("include_transforms")) ? JsonObject->GetBoolField(TEXT("include_transforms")) : true;
|
||||
|
||||
TArray<TSharedPtr<FJsonValue>> Actors = ParameterBridge->ListActors(ClassFilter, NameFilter, bIncludeTransforms);
|
||||
ResponseObject->SetBoolField(TEXT("success"), true);
|
||||
ResponseObject->SetArrayField(TEXT("actors"), Actors);
|
||||
}
|
||||
else if (CommandType == TEXT("set_render_quality"))
|
||||
{
|
||||
const TSharedPtr<FJsonObject>* Settings;
|
||||
if (JsonObject->TryGetObjectField(TEXT("settings"), Settings))
|
||||
{
|
||||
bool bSuccess = ParameterBridge->SetRenderQuality(*Settings);
|
||||
ResponseObject->SetBoolField(TEXT("success"), bSuccess);
|
||||
}
|
||||
else
|
||||
{
|
||||
ResponseObject->SetBoolField(TEXT("success"), false);
|
||||
ResponseObject->SetStringField(TEXT("error"), TEXT("Missing settings"));
|
||||
}
|
||||
}
|
||||
else if (CommandType == TEXT("setup_scene"))
|
||||
{
|
||||
TSharedPtr<FJsonObject> SetupResult = ParameterBridge->SetupScene();
|
||||
ResponseObject = SetupResult;
|
||||
}
|
||||
else if (CommandType == TEXT("exec_command"))
|
||||
{
|
||||
FString Command = JsonObject->GetStringField(TEXT("command"));
|
||||
if (!Command.IsEmpty())
|
||||
{
|
||||
UWorld* World = GEngine->GetWorldContexts()[0].World();
|
||||
if (World)
|
||||
{
|
||||
GEngine->Exec(World, *Command);
|
||||
ResponseObject->SetBoolField(TEXT("success"), true);
|
||||
ResponseObject->SetStringField(TEXT("executed"), Command);
|
||||
}
|
||||
else
|
||||
{
|
||||
ResponseObject->SetBoolField(TEXT("success"), false);
|
||||
ResponseObject->SetStringField(TEXT("error"), TEXT("No world available"));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ResponseObject->SetBoolField(TEXT("success"), false);
|
||||
ResponseObject->SetStringField(TEXT("error"), TEXT("Missing command"));
|
||||
}
|
||||
}
|
||||
else if (CommandType == TEXT("new_level"))
|
||||
{
|
||||
FString Template = JsonObject->HasField(TEXT("template")) ? JsonObject->GetStringField(TEXT("template")) : TEXT("Basic");
|
||||
#if WITH_EDITOR
|
||||
if (GEditor)
|
||||
{
|
||||
// Create new level from template
|
||||
GEditor->Exec(nullptr, TEXT("MAP NEW"));
|
||||
ResponseObject->SetBoolField(TEXT("success"), true);
|
||||
ResponseObject->SetStringField(TEXT("message"), TEXT("New level created"));
|
||||
}
|
||||
else
|
||||
{
|
||||
ResponseObject->SetBoolField(TEXT("success"), false);
|
||||
ResponseObject->SetStringField(TEXT("error"), TEXT("Editor not available"));
|
||||
}
|
||||
#else
|
||||
ResponseObject->SetBoolField(TEXT("success"), false);
|
||||
ResponseObject->SetStringField(TEXT("error"), TEXT("Only available in editor"));
|
||||
#endif
|
||||
}
|
||||
else if (CommandType == TEXT("spawn_point_light"))
|
||||
{
|
||||
FVector Location = FVector::ZeroVector;
|
||||
const TSharedPtr<FJsonObject>* LocationObj;
|
||||
if (JsonObject->TryGetObjectField(TEXT("location"), LocationObj))
|
||||
{
|
||||
Location.X = (*LocationObj)->HasField(TEXT("x")) ? (*LocationObj)->GetNumberField(TEXT("x")) : 0.0f;
|
||||
Location.Y = (*LocationObj)->HasField(TEXT("y")) ? (*LocationObj)->GetNumberField(TEXT("y")) : 0.0f;
|
||||
Location.Z = (*LocationObj)->HasField(TEXT("z")) ? (*LocationObj)->GetNumberField(TEXT("z")) : 0.0f;
|
||||
}
|
||||
|
||||
float Intensity = JsonObject->HasField(TEXT("intensity")) ? JsonObject->GetNumberField(TEXT("intensity")) : 5000.0f;
|
||||
float AttenuationRadius = JsonObject->HasField(TEXT("attenuation_radius")) ? JsonObject->GetNumberField(TEXT("attenuation_radius")) : 1000.0f;
|
||||
FString ActorLabel = JsonObject->HasField(TEXT("actor_label")) ? JsonObject->GetStringField(TEXT("actor_label")) : TEXT("");
|
||||
|
||||
FLinearColor Color = FLinearColor::White;
|
||||
if (JsonObject->HasField(TEXT("color")))
|
||||
{
|
||||
const TSharedPtr<FJsonObject>* ColorObj;
|
||||
if (JsonObject->TryGetObjectField(TEXT("color"), ColorObj))
|
||||
{
|
||||
Color.R = (*ColorObj)->HasField(TEXT("r")) ? (*ColorObj)->GetNumberField(TEXT("r")) : 1.0f;
|
||||
Color.G = (*ColorObj)->HasField(TEXT("g")) ? (*ColorObj)->GetNumberField(TEXT("g")) : 1.0f;
|
||||
Color.B = (*ColorObj)->HasField(TEXT("b")) ? (*ColorObj)->GetNumberField(TEXT("b")) : 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
FString ActorId;
|
||||
bool bSuccess = ParameterBridge->SpawnPointLight(Location, Intensity, Color, AttenuationRadius, ActorLabel, ActorId);
|
||||
ResponseObject->SetBoolField(TEXT("success"), bSuccess);
|
||||
if (bSuccess)
|
||||
{
|
||||
ResponseObject->SetStringField(TEXT("actor_id"), ActorId);
|
||||
}
|
||||
}
|
||||
else if (CommandType == TEXT("set_point_light"))
|
||||
{
|
||||
FString ActorId = JsonObject->GetStringField(TEXT("actor_id"));
|
||||
const TSharedPtr<FJsonObject>* Params;
|
||||
if (JsonObject->TryGetObjectField(TEXT("parameters"), Params))
|
||||
{
|
||||
bool bSuccess = ParameterBridge->SetPointLightParameters(ActorId, *Params);
|
||||
ResponseObject->SetBoolField(TEXT("success"), bSuccess);
|
||||
}
|
||||
else
|
||||
{
|
||||
ResponseObject->SetBoolField(TEXT("success"), false);
|
||||
ResponseObject->SetStringField(TEXT("error"), TEXT("Missing parameters"));
|
||||
}
|
||||
}
|
||||
else if (CommandType == TEXT("spawn_spot_light"))
|
||||
{
|
||||
FVector Location = FVector::ZeroVector;
|
||||
const TSharedPtr<FJsonObject>* LocationObj;
|
||||
if (JsonObject->TryGetObjectField(TEXT("location"), LocationObj))
|
||||
{
|
||||
Location.X = (*LocationObj)->HasField(TEXT("x")) ? (*LocationObj)->GetNumberField(TEXT("x")) : 0.0f;
|
||||
Location.Y = (*LocationObj)->HasField(TEXT("y")) ? (*LocationObj)->GetNumberField(TEXT("y")) : 0.0f;
|
||||
Location.Z = (*LocationObj)->HasField(TEXT("z")) ? (*LocationObj)->GetNumberField(TEXT("z")) : 0.0f;
|
||||
}
|
||||
|
||||
FRotator Rotation = FRotator::ZeroRotator;
|
||||
const TSharedPtr<FJsonObject>* RotationObj;
|
||||
if (JsonObject->TryGetObjectField(TEXT("rotation"), RotationObj))
|
||||
{
|
||||
Rotation.Pitch = (*RotationObj)->HasField(TEXT("pitch")) ? (*RotationObj)->GetNumberField(TEXT("pitch")) : 0.0f;
|
||||
Rotation.Yaw = (*RotationObj)->HasField(TEXT("yaw")) ? (*RotationObj)->GetNumberField(TEXT("yaw")) : 0.0f;
|
||||
Rotation.Roll = (*RotationObj)->HasField(TEXT("roll")) ? (*RotationObj)->GetNumberField(TEXT("roll")) : 0.0f;
|
||||
}
|
||||
|
||||
float Intensity = JsonObject->HasField(TEXT("intensity")) ? JsonObject->GetNumberField(TEXT("intensity")) : 5000.0f;
|
||||
float AttenuationRadius = JsonObject->HasField(TEXT("attenuation_radius")) ? JsonObject->GetNumberField(TEXT("attenuation_radius")) : 1000.0f;
|
||||
float InnerConeAngle = JsonObject->HasField(TEXT("inner_cone_angle")) ? JsonObject->GetNumberField(TEXT("inner_cone_angle")) : 20.0f;
|
||||
float OuterConeAngle = JsonObject->HasField(TEXT("outer_cone_angle")) ? JsonObject->GetNumberField(TEXT("outer_cone_angle")) : 40.0f;
|
||||
FString ActorLabel = JsonObject->HasField(TEXT("actor_label")) ? JsonObject->GetStringField(TEXT("actor_label")) : TEXT("");
|
||||
|
||||
FLinearColor Color = FLinearColor::White;
|
||||
if (JsonObject->HasField(TEXT("color")))
|
||||
{
|
||||
const TSharedPtr<FJsonObject>* ColorObj;
|
||||
if (JsonObject->TryGetObjectField(TEXT("color"), ColorObj))
|
||||
{
|
||||
Color.R = (*ColorObj)->HasField(TEXT("r")) ? (*ColorObj)->GetNumberField(TEXT("r")) : 1.0f;
|
||||
Color.G = (*ColorObj)->HasField(TEXT("g")) ? (*ColorObj)->GetNumberField(TEXT("g")) : 1.0f;
|
||||
Color.B = (*ColorObj)->HasField(TEXT("b")) ? (*ColorObj)->GetNumberField(TEXT("b")) : 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
FString ActorId;
|
||||
bool bSuccess = ParameterBridge->SpawnSpotLight(Location, Rotation, Intensity, Color, AttenuationRadius, InnerConeAngle, OuterConeAngle, ActorLabel, ActorId);
|
||||
ResponseObject->SetBoolField(TEXT("success"), bSuccess);
|
||||
if (bSuccess)
|
||||
{
|
||||
ResponseObject->SetStringField(TEXT("actor_id"), ActorId);
|
||||
}
|
||||
}
|
||||
else if (CommandType == TEXT("set_spot_light"))
|
||||
{
|
||||
FString ActorId = JsonObject->GetStringField(TEXT("actor_id"));
|
||||
const TSharedPtr<FJsonObject>* Params;
|
||||
if (JsonObject->TryGetObjectField(TEXT("parameters"), Params))
|
||||
{
|
||||
bool bSuccess = ParameterBridge->SetSpotLightParameters(ActorId, *Params);
|
||||
ResponseObject->SetBoolField(TEXT("success"), bSuccess);
|
||||
}
|
||||
else
|
||||
{
|
||||
ResponseObject->SetBoolField(TEXT("success"), false);
|
||||
ResponseObject->SetStringField(TEXT("error"), TEXT("Missing parameters"));
|
||||
}
|
||||
}
|
||||
else if (CommandType == TEXT("spawn_rect_light"))
|
||||
{
|
||||
FVector Location = FVector::ZeroVector;
|
||||
const TSharedPtr<FJsonObject>* LocationObj;
|
||||
if (JsonObject->TryGetObjectField(TEXT("location"), LocationObj))
|
||||
{
|
||||
Location.X = (*LocationObj)->HasField(TEXT("x")) ? (*LocationObj)->GetNumberField(TEXT("x")) : 0.0f;
|
||||
Location.Y = (*LocationObj)->HasField(TEXT("y")) ? (*LocationObj)->GetNumberField(TEXT("y")) : 0.0f;
|
||||
Location.Z = (*LocationObj)->HasField(TEXT("z")) ? (*LocationObj)->GetNumberField(TEXT("z")) : 0.0f;
|
||||
}
|
||||
|
||||
FRotator Rotation = FRotator::ZeroRotator;
|
||||
const TSharedPtr<FJsonObject>* RotationObj;
|
||||
if (JsonObject->TryGetObjectField(TEXT("rotation"), RotationObj))
|
||||
{
|
||||
Rotation.Pitch = (*RotationObj)->HasField(TEXT("pitch")) ? (*RotationObj)->GetNumberField(TEXT("pitch")) : 0.0f;
|
||||
Rotation.Yaw = (*RotationObj)->HasField(TEXT("yaw")) ? (*RotationObj)->GetNumberField(TEXT("yaw")) : 0.0f;
|
||||
Rotation.Roll = (*RotationObj)->HasField(TEXT("roll")) ? (*RotationObj)->GetNumberField(TEXT("roll")) : 0.0f;
|
||||
}
|
||||
|
||||
float Intensity = JsonObject->HasField(TEXT("intensity")) ? JsonObject->GetNumberField(TEXT("intensity")) : 5000.0f;
|
||||
float AttenuationRadius = JsonObject->HasField(TEXT("attenuation_radius")) ? JsonObject->GetNumberField(TEXT("attenuation_radius")) : 1000.0f;
|
||||
float SourceWidth = JsonObject->HasField(TEXT("source_width")) ? JsonObject->GetNumberField(TEXT("source_width")) : 100.0f;
|
||||
float SourceHeight = JsonObject->HasField(TEXT("source_height")) ? JsonObject->GetNumberField(TEXT("source_height")) : 100.0f;
|
||||
FString ActorLabel = JsonObject->HasField(TEXT("actor_label")) ? JsonObject->GetStringField(TEXT("actor_label")) : TEXT("");
|
||||
|
||||
FLinearColor Color = FLinearColor::White;
|
||||
if (JsonObject->HasField(TEXT("color")))
|
||||
{
|
||||
const TSharedPtr<FJsonObject>* ColorObj;
|
||||
if (JsonObject->TryGetObjectField(TEXT("color"), ColorObj))
|
||||
{
|
||||
Color.R = (*ColorObj)->HasField(TEXT("r")) ? (*ColorObj)->GetNumberField(TEXT("r")) : 1.0f;
|
||||
Color.G = (*ColorObj)->HasField(TEXT("g")) ? (*ColorObj)->GetNumberField(TEXT("g")) : 1.0f;
|
||||
Color.B = (*ColorObj)->HasField(TEXT("b")) ? (*ColorObj)->GetNumberField(TEXT("b")) : 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
FString ActorId;
|
||||
bool bSuccess = ParameterBridge->SpawnRectLight(Location, Rotation, Intensity, Color, SourceWidth, SourceHeight, AttenuationRadius, ActorLabel, ActorId);
|
||||
ResponseObject->SetBoolField(TEXT("success"), bSuccess);
|
||||
if (bSuccess)
|
||||
{
|
||||
ResponseObject->SetStringField(TEXT("actor_id"), ActorId);
|
||||
}
|
||||
}
|
||||
else if (CommandType == TEXT("set_rect_light"))
|
||||
{
|
||||
FString ActorId = JsonObject->GetStringField(TEXT("actor_id"));
|
||||
const TSharedPtr<FJsonObject>* Params;
|
||||
if (JsonObject->TryGetObjectField(TEXT("parameters"), Params))
|
||||
{
|
||||
bool bSuccess = ParameterBridge->SetRectLightParameters(ActorId, *Params);
|
||||
ResponseObject->SetBoolField(TEXT("success"), bSuccess);
|
||||
}
|
||||
else
|
||||
{
|
||||
ResponseObject->SetBoolField(TEXT("success"), false);
|
||||
ResponseObject->SetStringField(TEXT("error"), TEXT("Missing parameters"));
|
||||
}
|
||||
}
|
||||
else if (CommandType == TEXT("spawn_skeletal_mesh"))
|
||||
{
|
||||
FString SkeletalMeshPath = JsonObject->GetStringField(TEXT("skeletal_mesh_path"));
|
||||
|
||||
FVector Location = FVector::ZeroVector;
|
||||
const TSharedPtr<FJsonObject>* LocationObj;
|
||||
if (JsonObject->TryGetObjectField(TEXT("location"), LocationObj))
|
||||
{
|
||||
Location.X = (*LocationObj)->HasField(TEXT("x")) ? (*LocationObj)->GetNumberField(TEXT("x")) : 0.0f;
|
||||
Location.Y = (*LocationObj)->HasField(TEXT("y")) ? (*LocationObj)->GetNumberField(TEXT("y")) : 0.0f;
|
||||
Location.Z = (*LocationObj)->HasField(TEXT("z")) ? (*LocationObj)->GetNumberField(TEXT("z")) : 0.0f;
|
||||
}
|
||||
|
||||
FRotator Rotation = FRotator::ZeroRotator;
|
||||
const TSharedPtr<FJsonObject>* RotationObj;
|
||||
if (JsonObject->TryGetObjectField(TEXT("rotation"), RotationObj))
|
||||
{
|
||||
Rotation.Pitch = (*RotationObj)->HasField(TEXT("pitch")) ? (*RotationObj)->GetNumberField(TEXT("pitch")) : 0.0f;
|
||||
Rotation.Yaw = (*RotationObj)->HasField(TEXT("yaw")) ? (*RotationObj)->GetNumberField(TEXT("yaw")) : 0.0f;
|
||||
Rotation.Roll = (*RotationObj)->HasField(TEXT("roll")) ? (*RotationObj)->GetNumberField(TEXT("roll")) : 0.0f;
|
||||
}
|
||||
|
||||
float Scale = JsonObject->HasField(TEXT("scale")) ? JsonObject->GetNumberField(TEXT("scale")) : 1.0f;
|
||||
FString ActorLabel = JsonObject->HasField(TEXT("actor_label")) ? JsonObject->GetStringField(TEXT("actor_label")) : TEXT("");
|
||||
|
||||
FString ActorId;
|
||||
bool bSuccess = ParameterBridge->SpawnSkeletalMesh(SkeletalMeshPath, Location, Rotation, Scale, ActorLabel, ActorId);
|
||||
ResponseObject->SetBoolField(TEXT("success"), bSuccess);
|
||||
if (bSuccess)
|
||||
{
|
||||
ResponseObject->SetStringField(TEXT("actor_id"), ActorId);
|
||||
}
|
||||
}
|
||||
else if (CommandType == TEXT("play_animation"))
|
||||
{
|
||||
FString ActorId = JsonObject->GetStringField(TEXT("actor_id"));
|
||||
FString AnimationPath = JsonObject->GetStringField(TEXT("animation_path"));
|
||||
bool bLooping = JsonObject->HasField(TEXT("looping")) ? JsonObject->GetBoolField(TEXT("looping")) : true;
|
||||
float PlayRate = JsonObject->HasField(TEXT("play_rate")) ? JsonObject->GetNumberField(TEXT("play_rate")) : 1.0f;
|
||||
|
||||
bool bSuccess = ParameterBridge->PlayAnimation(ActorId, AnimationPath, bLooping, PlayRate);
|
||||
ResponseObject->SetBoolField(TEXT("success"), bSuccess);
|
||||
}
|
||||
else if (CommandType == TEXT("stop_animation"))
|
||||
{
|
||||
FString ActorId = JsonObject->GetStringField(TEXT("actor_id"));
|
||||
bool bSuccess = ParameterBridge->StopAnimation(ActorId);
|
||||
ResponseObject->SetBoolField(TEXT("success"), bSuccess);
|
||||
}
|
||||
else if (CommandType == TEXT("set_animation_pose"))
|
||||
{
|
||||
FString ActorId = JsonObject->GetStringField(TEXT("actor_id"));
|
||||
FString AnimationPath = JsonObject->GetStringField(TEXT("animation_path"));
|
||||
float Time = JsonObject->HasField(TEXT("time")) ? JsonObject->GetNumberField(TEXT("time")) : 0.0f;
|
||||
|
||||
bool bSuccess = ParameterBridge->SetAnimationPose(ActorId, AnimationPath, Time);
|
||||
ResponseObject->SetBoolField(TEXT("success"), bSuccess);
|
||||
}
|
||||
else if (CommandType == TEXT("spawn_sphere_reflection_capture"))
|
||||
{
|
||||
FVector Location = FVector::ZeroVector;
|
||||
const TSharedPtr<FJsonObject>* LocationObj;
|
||||
if (JsonObject->TryGetObjectField(TEXT("location"), LocationObj))
|
||||
{
|
||||
Location.X = (*LocationObj)->HasField(TEXT("x")) ? (*LocationObj)->GetNumberField(TEXT("x")) : 0.0f;
|
||||
Location.Y = (*LocationObj)->HasField(TEXT("y")) ? (*LocationObj)->GetNumberField(TEXT("y")) : 0.0f;
|
||||
Location.Z = (*LocationObj)->HasField(TEXT("z")) ? (*LocationObj)->GetNumberField(TEXT("z")) : 0.0f;
|
||||
}
|
||||
|
||||
float InfluenceRadius = JsonObject->HasField(TEXT("influence_radius")) ? JsonObject->GetNumberField(TEXT("influence_radius")) : 3000.0f;
|
||||
FString ActorLabel = JsonObject->HasField(TEXT("actor_label")) ? JsonObject->GetStringField(TEXT("actor_label")) : TEXT("");
|
||||
|
||||
FString ActorId;
|
||||
bool bSuccess = ParameterBridge->SpawnSphereReflectionCapture(Location, InfluenceRadius, ActorLabel, ActorId);
|
||||
ResponseObject->SetBoolField(TEXT("success"), bSuccess);
|
||||
if (bSuccess)
|
||||
{
|
||||
ResponseObject->SetStringField(TEXT("actor_id"), ActorId);
|
||||
}
|
||||
}
|
||||
else if (CommandType == TEXT("spawn_box_reflection_capture"))
|
||||
{
|
||||
FVector Location = FVector::ZeroVector;
|
||||
const TSharedPtr<FJsonObject>* LocationObj;
|
||||
if (JsonObject->TryGetObjectField(TEXT("location"), LocationObj))
|
||||
{
|
||||
Location.X = (*LocationObj)->HasField(TEXT("x")) ? (*LocationObj)->GetNumberField(TEXT("x")) : 0.0f;
|
||||
Location.Y = (*LocationObj)->HasField(TEXT("y")) ? (*LocationObj)->GetNumberField(TEXT("y")) : 0.0f;
|
||||
Location.Z = (*LocationObj)->HasField(TEXT("z")) ? (*LocationObj)->GetNumberField(TEXT("z")) : 0.0f;
|
||||
}
|
||||
|
||||
FVector BoxExtent = FVector(1000.0f, 1000.0f, 400.0f);
|
||||
const TSharedPtr<FJsonObject>* ExtentObj;
|
||||
if (JsonObject->TryGetObjectField(TEXT("box_extent"), ExtentObj))
|
||||
{
|
||||
BoxExtent.X = (*ExtentObj)->HasField(TEXT("x")) ? (*ExtentObj)->GetNumberField(TEXT("x")) : 1000.0f;
|
||||
BoxExtent.Y = (*ExtentObj)->HasField(TEXT("y")) ? (*ExtentObj)->GetNumberField(TEXT("y")) : 1000.0f;
|
||||
BoxExtent.Z = (*ExtentObj)->HasField(TEXT("z")) ? (*ExtentObj)->GetNumberField(TEXT("z")) : 400.0f;
|
||||
}
|
||||
|
||||
FString ActorLabel = JsonObject->HasField(TEXT("actor_label")) ? JsonObject->GetStringField(TEXT("actor_label")) : TEXT("");
|
||||
|
||||
FString ActorId;
|
||||
bool bSuccess = ParameterBridge->SpawnBoxReflectionCapture(Location, BoxExtent, ActorLabel, ActorId);
|
||||
ResponseObject->SetBoolField(TEXT("success"), bSuccess);
|
||||
if (bSuccess)
|
||||
{
|
||||
ResponseObject->SetStringField(TEXT("actor_id"), ActorId);
|
||||
}
|
||||
}
|
||||
else if (CommandType == TEXT("update_reflection_captures"))
|
||||
{
|
||||
bool bSuccess = ParameterBridge->UpdateReflectionCaptures();
|
||||
ResponseObject->SetBoolField(TEXT("success"), bSuccess);
|
||||
}
|
||||
else if (CommandType == TEXT("set_light_ies_profile"))
|
||||
{
|
||||
FString ActorId = JsonObject->GetStringField(TEXT("actor_id"));
|
||||
FString IESProfilePath = JsonObject->GetStringField(TEXT("ies_profile_path"));
|
||||
|
||||
bool bSuccess = ParameterBridge->SetLightIESProfile(ActorId, IESProfilePath);
|
||||
ResponseObject->SetBoolField(TEXT("success"), bSuccess);
|
||||
}
|
||||
else if (CommandType == TEXT("list_assets"))
|
||||
{
|
||||
FString FolderPath = JsonObject->HasField(TEXT("folder_path")) ? JsonObject->GetStringField(TEXT("folder_path")) : TEXT("/Game");
|
||||
FString AssetType = JsonObject->HasField(TEXT("asset_type")) ? JsonObject->GetStringField(TEXT("asset_type")) : TEXT("All");
|
||||
bool bRecursive = JsonObject->HasField(TEXT("recursive")) ? JsonObject->GetBoolField(TEXT("recursive")) : true;
|
||||
|
||||
TArray<TSharedPtr<FJsonValue>> Results = ParameterBridge->ListAssets(FolderPath, AssetType, bRecursive);
|
||||
ResponseObject->SetBoolField(TEXT("success"), true);
|
||||
ResponseObject->SetArrayField(TEXT("assets"), Results);
|
||||
}
|
||||
else if (CommandType == TEXT("set_actor_transform"))
|
||||
{
|
||||
FString ActorId = JsonObject->GetStringField(TEXT("actor_id"));
|
||||
bool bRelative = JsonObject->HasField(TEXT("relative")) ? JsonObject->GetBoolField(TEXT("relative")) : false;
|
||||
|
||||
const FVector* LocationPtr = nullptr;
|
||||
FVector Location;
|
||||
const TSharedPtr<FJsonObject>* LocationObj;
|
||||
if (JsonObject->TryGetObjectField(TEXT("location"), LocationObj))
|
||||
{
|
||||
Location.X = (*LocationObj)->HasField(TEXT("x")) ? (*LocationObj)->GetNumberField(TEXT("x")) : 0.0f;
|
||||
Location.Y = (*LocationObj)->HasField(TEXT("y")) ? (*LocationObj)->GetNumberField(TEXT("y")) : 0.0f;
|
||||
Location.Z = (*LocationObj)->HasField(TEXT("z")) ? (*LocationObj)->GetNumberField(TEXT("z")) : 0.0f;
|
||||
LocationPtr = &Location;
|
||||
}
|
||||
|
||||
const FRotator* RotationPtr = nullptr;
|
||||
FRotator Rotation;
|
||||
const TSharedPtr<FJsonObject>* RotationObj;
|
||||
if (JsonObject->TryGetObjectField(TEXT("rotation"), RotationObj))
|
||||
{
|
||||
Rotation.Pitch = (*RotationObj)->HasField(TEXT("pitch")) ? (*RotationObj)->GetNumberField(TEXT("pitch")) : 0.0f;
|
||||
Rotation.Yaw = (*RotationObj)->HasField(TEXT("yaw")) ? (*RotationObj)->GetNumberField(TEXT("yaw")) : 0.0f;
|
||||
Rotation.Roll = (*RotationObj)->HasField(TEXT("roll")) ? (*RotationObj)->GetNumberField(TEXT("roll")) : 0.0f;
|
||||
RotationPtr = &Rotation;
|
||||
}
|
||||
|
||||
const float* ScalePtr = nullptr;
|
||||
float Scale;
|
||||
if (JsonObject->HasField(TEXT("scale")))
|
||||
{
|
||||
Scale = JsonObject->GetNumberField(TEXT("scale"));
|
||||
ScalePtr = &Scale;
|
||||
}
|
||||
|
||||
bool bSuccess = ParameterBridge->SetActorTransform(ActorId, LocationPtr, RotationPtr, ScalePtr, bRelative);
|
||||
ResponseObject->SetBoolField(TEXT("success"), bSuccess);
|
||||
}
|
||||
else if (CommandType == TEXT("get_actor_details"))
|
||||
{
|
||||
FString ActorId = JsonObject->GetStringField(TEXT("actor_id"));
|
||||
bool bIncludeComponents = JsonObject->HasField(TEXT("include_components")) ? JsonObject->GetBoolField(TEXT("include_components")) : true;
|
||||
bool bIncludeMaterials = JsonObject->HasField(TEXT("include_materials")) ? JsonObject->GetBoolField(TEXT("include_materials")) : true;
|
||||
|
||||
TSharedPtr<FJsonObject> Details = ParameterBridge->GetActorDetails(ActorId, bIncludeComponents, bIncludeMaterials);
|
||||
if (Details.IsValid())
|
||||
{
|
||||
ResponseObject->SetBoolField(TEXT("success"), true);
|
||||
ResponseObject->SetObjectField(TEXT("actor"), Details);
|
||||
}
|
||||
else
|
||||
{
|
||||
ResponseObject->SetBoolField(TEXT("success"), false);
|
||||
ResponseObject->SetStringField(TEXT("error"), TEXT("Actor not found"));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ResponseObject->SetBoolField(TEXT("success"), false);
|
||||
ResponseObject->SetStringField(TEXT("error"), FString::Printf(TEXT("Unknown command type: %s"), *CommandType));
|
||||
}
|
||||
|
||||
// Serialize response
|
||||
FString ResponseString;
|
||||
TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&ResponseString);
|
||||
FJsonSerializer::Serialize(ResponseObject.ToSharedRef(), Writer);
|
||||
|
||||
return ResponseString;
|
||||
}
|
||||
|
||||
// FRalphaClientConnection implementation
|
||||
|
||||
FRalphaClientConnection::FRalphaClientConnection(FSocket* InSocket, URalphaMCPServer* InServer)
|
||||
: Socket(InSocket)
|
||||
, Server(InServer)
|
||||
{
|
||||
}
|
||||
|
||||
FRalphaClientConnection::~FRalphaClientConnection()
|
||||
{
|
||||
if (Socket)
|
||||
{
|
||||
Socket->Close();
|
||||
ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->DestroySocket(Socket);
|
||||
Socket = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool FRalphaClientConnection::IsValid() const
|
||||
{
|
||||
return Socket != nullptr && Socket->GetConnectionState() == SCS_Connected;
|
||||
}
|
||||
|
||||
void FRalphaClientConnection::Tick()
|
||||
{
|
||||
if (!IsValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Receive data
|
||||
uint32 PendingDataSize = 0;
|
||||
while (Socket->HasPendingData(PendingDataSize) && PendingDataSize > 0)
|
||||
{
|
||||
TArray<uint8> ReceivedData;
|
||||
ReceivedData.SetNumUninitialized(FMath::Min(PendingDataSize, 65536u));
|
||||
|
||||
int32 BytesRead = 0;
|
||||
if (Socket->Recv(ReceivedData.GetData(), ReceivedData.Num(), BytesRead))
|
||||
{
|
||||
ReceiveBuffer += FString(UTF8_TO_TCHAR(reinterpret_cast<const char*>(ReceivedData.GetData())));
|
||||
}
|
||||
}
|
||||
|
||||
// Process complete messages (newline-delimited JSON)
|
||||
int32 NewlineIndex;
|
||||
while (ReceiveBuffer.FindChar(TEXT('\n'), NewlineIndex))
|
||||
{
|
||||
FString Message = ReceiveBuffer.Left(NewlineIndex);
|
||||
ReceiveBuffer = ReceiveBuffer.Mid(NewlineIndex + 1);
|
||||
|
||||
if (!Message.IsEmpty())
|
||||
{
|
||||
FString Response = Server->ProcessCommand(Message);
|
||||
SendResponse(Response);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FRalphaClientConnection::SendResponse(const FString& Response)
|
||||
{
|
||||
if (!IsValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FString ResponseWithNewline = Response + TEXT("\n");
|
||||
FTCHARToUTF8 Converter(*ResponseWithNewline);
|
||||
int32 BytesSent = 0;
|
||||
Socket->Send(reinterpret_cast<const uint8*>(Converter.Get()), Converter.Length(), BytesSent);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,326 +0,0 @@
|
|||
// Copyright Ralpha Team. All Rights Reserved.
|
||||
|
||||
#include "RalphaScreenCapture.h"
|
||||
#include "RalphaMCPServer.h"
|
||||
#include "Modules/ModuleManager.h"
|
||||
#include "Engine/GameViewportClient.h"
|
||||
#include "Engine/Engine.h"
|
||||
#include "Slate/SceneViewport.h"
|
||||
#include "ImageUtils.h"
|
||||
#include "IImageWrapper.h"
|
||||
#include "IImageWrapperModule.h"
|
||||
#include "Misc/Base64.h"
|
||||
#include "Misc/FileHelper.h"
|
||||
#include "Misc/Paths.h"
|
||||
#include "HAL/PlatformFileManager.h"
|
||||
#include "Framework/Application/SlateApplication.h"
|
||||
#include "Widgets/SWindow.h"
|
||||
#include "UnrealClient.h"
|
||||
#include "HighResScreenshot.h"
|
||||
#include "Components/SceneCaptureComponent2D.h"
|
||||
#include "Engine/TextureRenderTarget2D.h"
|
||||
#include "TextureResource.h"
|
||||
#include "RenderingThread.h"
|
||||
#include "CineCameraActor.h"
|
||||
#include "CineCameraComponent.h"
|
||||
#include "Kismet/GameplayStatics.h"
|
||||
#include "EngineUtils.h"
|
||||
|
||||
#if WITH_EDITOR
|
||||
#include "Editor.h"
|
||||
#include "LevelEditorViewport.h"
|
||||
#include "SLevelViewport.h"
|
||||
#include "LevelEditor.h"
|
||||
#include "ILevelEditor.h"
|
||||
#endif
|
||||
|
||||
URalphaScreenCapture::URalphaScreenCapture()
|
||||
{
|
||||
}
|
||||
|
||||
bool URalphaScreenCapture::CaptureScreenshot(int32 Width, int32 Height, FString& OutBase64, FString& OutFilePath)
|
||||
{
|
||||
FViewport* Viewport = nullptr;
|
||||
int32 ViewportWidth = 0;
|
||||
int32 ViewportHeight = 0;
|
||||
|
||||
#if WITH_EDITOR
|
||||
// In editor, find the active level viewport with valid size
|
||||
if (GEditor)
|
||||
{
|
||||
// Try to find a viewport with a valid size
|
||||
for (FLevelEditorViewportClient* LevelVC : GEditor->GetLevelViewportClients())
|
||||
{
|
||||
if (LevelVC && LevelVC->Viewport)
|
||||
{
|
||||
FIntPoint Size = LevelVC->Viewport->GetSizeXY();
|
||||
if (Size.X > 0 && Size.Y > 0)
|
||||
{
|
||||
Viewport = LevelVC->Viewport;
|
||||
ViewportWidth = Size.X;
|
||||
ViewportHeight = Size.Y;
|
||||
UE_LOG(LogRalphaMCP, Log, TEXT("Found editor viewport: %dx%d"), ViewportWidth, ViewportHeight);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If still no viewport, try to get it from the level editor module
|
||||
if (!Viewport)
|
||||
{
|
||||
FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked<FLevelEditorModule>("LevelEditor");
|
||||
TSharedPtr<ILevelEditor> LevelEditor = LevelEditorModule.GetFirstLevelEditor();
|
||||
if (LevelEditor.IsValid())
|
||||
{
|
||||
TSharedPtr<SLevelViewport> ActiveViewport = LevelEditor->GetActiveViewportInterface();
|
||||
if (ActiveViewport.IsValid())
|
||||
{
|
||||
FLevelEditorViewportClient& ViewportClient = ActiveViewport->GetLevelViewportClient();
|
||||
if (ViewportClient.Viewport)
|
||||
{
|
||||
FIntPoint Size = ViewportClient.Viewport->GetSizeXY();
|
||||
if (Size.X > 0 && Size.Y > 0)
|
||||
{
|
||||
Viewport = ViewportClient.Viewport;
|
||||
ViewportWidth = Size.X;
|
||||
ViewportHeight = Size.Y;
|
||||
UE_LOG(LogRalphaMCP, Log, TEXT("Found active viewport: %dx%d"), ViewportWidth, ViewportHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Fallback to game viewport
|
||||
if (!Viewport)
|
||||
{
|
||||
UGameViewportClient* ViewportClient = GEngine ? GEngine->GameViewport : nullptr;
|
||||
if (ViewportClient && ViewportClient->Viewport)
|
||||
{
|
||||
FIntPoint Size = ViewportClient->Viewport->GetSizeXY();
|
||||
if (Size.X > 0 && Size.Y > 0)
|
||||
{
|
||||
Viewport = ViewportClient->Viewport;
|
||||
ViewportWidth = Size.X;
|
||||
ViewportHeight = Size.Y;
|
||||
UE_LOG(LogRalphaMCP, Log, TEXT("Using game viewport: %dx%d"), ViewportWidth, ViewportHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!Viewport || ViewportWidth <= 0 || ViewportHeight <= 0)
|
||||
{
|
||||
UE_LOG(LogRalphaMCP, Warning, TEXT("No valid viewport available for screenshot"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read pixels from the viewport
|
||||
TArray<FColor> Bitmap;
|
||||
bool bSuccess = Viewport->ReadPixels(Bitmap);
|
||||
|
||||
if (!bSuccess || Bitmap.Num() == 0)
|
||||
{
|
||||
UE_LOG(LogRalphaMCP, Warning, TEXT("Failed to read pixels from viewport"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Convert to base64 PNG
|
||||
OutBase64 = PixelsToBase64PNG(Bitmap, ViewportWidth, ViewportHeight);
|
||||
|
||||
// Save to disk as well
|
||||
FString ScreenshotDir = GetScreenshotDirectory();
|
||||
FString Filename = FString::Printf(TEXT("Ralpha_%s.png"), *FDateTime::Now().ToString(TEXT("%Y%m%d_%H%M%S")));
|
||||
OutFilePath = ScreenshotDir / Filename;
|
||||
|
||||
// Save the PNG
|
||||
IImageWrapperModule& ImageWrapperModule = FModuleManager::LoadModuleChecked<IImageWrapperModule>(FName("ImageWrapper"));
|
||||
TSharedPtr<IImageWrapper> ImageWrapper = ImageWrapperModule.CreateImageWrapper(EImageFormat::PNG);
|
||||
|
||||
if (ImageWrapper.IsValid() && ImageWrapper->SetRaw(Bitmap.GetData(), Bitmap.Num() * sizeof(FColor), ViewportWidth, ViewportHeight, ERGBFormat::BGRA, 8))
|
||||
{
|
||||
TArray64<uint8> PNGData = ImageWrapper->GetCompressed(0);
|
||||
if (PNGData.Num() > 0)
|
||||
{
|
||||
FFileHelper::SaveArrayToFile(PNGData, *OutFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
UE_LOG(LogRalphaMCP, Log, TEXT("Screenshot captured: %dx%d, saved to %s"), ViewportWidth, ViewportHeight, *OutFilePath);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool URalphaScreenCapture::CaptureHighResScreenshot(int32 Width, int32 Height, FString& OutBase64)
|
||||
{
|
||||
// For high-res screenshots, we would use FHighResScreenshotConfig
|
||||
// This is a simplified implementation
|
||||
|
||||
FString FilePath;
|
||||
return CaptureScreenshot(Width, Height, OutBase64, FilePath);
|
||||
}
|
||||
|
||||
FString URalphaScreenCapture::PixelsToBase64PNG(const TArray<FColor>& Pixels, int32 Width, int32 Height)
|
||||
{
|
||||
// Load the image wrapper module
|
||||
IImageWrapperModule& ImageWrapperModule = FModuleManager::LoadModuleChecked<IImageWrapperModule>(FName("ImageWrapper"));
|
||||
TSharedPtr<IImageWrapper> ImageWrapper = ImageWrapperModule.CreateImageWrapper(EImageFormat::PNG);
|
||||
|
||||
if (!ImageWrapper.IsValid())
|
||||
{
|
||||
UE_LOG(LogRalphaMCP, Warning, TEXT("Failed to create image wrapper"));
|
||||
return FString();
|
||||
}
|
||||
|
||||
// Set the raw pixel data
|
||||
if (!ImageWrapper->SetRaw(Pixels.GetData(), Pixels.Num() * sizeof(FColor), Width, Height, ERGBFormat::BGRA, 8))
|
||||
{
|
||||
UE_LOG(LogRalphaMCP, Warning, TEXT("Failed to set raw pixel data"));
|
||||
return FString();
|
||||
}
|
||||
|
||||
// Compress to PNG
|
||||
TArray64<uint8> PNGData = ImageWrapper->GetCompressed(0);
|
||||
if (PNGData.Num() == 0)
|
||||
{
|
||||
UE_LOG(LogRalphaMCP, Warning, TEXT("Failed to compress to PNG"));
|
||||
return FString();
|
||||
}
|
||||
|
||||
// Convert to base64
|
||||
TArray<uint8> PNGData32;
|
||||
PNGData32.Append(PNGData.GetData(), PNGData.Num());
|
||||
|
||||
return FBase64::Encode(PNGData32);
|
||||
}
|
||||
|
||||
bool URalphaScreenCapture::CaptureFromCamera(int32 Width, int32 Height, FString& OutBase64, FString& OutFilePath)
|
||||
{
|
||||
// Get the world
|
||||
UWorld* World = nullptr;
|
||||
if (GEngine && GEngine->GetWorldContexts().Num() > 0)
|
||||
{
|
||||
World = GEngine->GetWorldContexts()[0].World();
|
||||
}
|
||||
|
||||
if (!World)
|
||||
{
|
||||
UE_LOG(LogRalphaMCP, Warning, TEXT("No world available for camera capture"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find the CineCamera
|
||||
ACineCameraActor* CineCamera = nullptr;
|
||||
for (TActorIterator<ACineCameraActor> It(World); It; ++It)
|
||||
{
|
||||
CineCamera = *It;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!CineCamera)
|
||||
{
|
||||
UE_LOG(LogRalphaMCP, Warning, TEXT("No CineCameraActor found for capture"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Use reasonable defaults if width/height not specified
|
||||
if (Width <= 0) Width = 1920;
|
||||
if (Height <= 0) Height = 1080;
|
||||
|
||||
// Create render target
|
||||
UTextureRenderTarget2D* RenderTarget = NewObject<UTextureRenderTarget2D>();
|
||||
RenderTarget->InitAutoFormat(Width, Height);
|
||||
RenderTarget->UpdateResourceImmediate(true);
|
||||
|
||||
// Create scene capture component
|
||||
USceneCaptureComponent2D* SceneCapture = NewObject<USceneCaptureComponent2D>(CineCamera);
|
||||
SceneCapture->RegisterComponent();
|
||||
SceneCapture->SetWorldLocation(CineCamera->GetActorLocation());
|
||||
SceneCapture->SetWorldRotation(CineCamera->GetActorRotation());
|
||||
|
||||
// Copy camera settings
|
||||
UCineCameraComponent* CameraComp = CineCamera->GetCineCameraComponent();
|
||||
if (CameraComp)
|
||||
{
|
||||
SceneCapture->FOVAngle = CameraComp->FieldOfView;
|
||||
}
|
||||
|
||||
// Configure capture settings
|
||||
SceneCapture->TextureTarget = RenderTarget;
|
||||
SceneCapture->CaptureSource = SCS_FinalToneCurveHDR;
|
||||
SceneCapture->bCaptureEveryFrame = false;
|
||||
SceneCapture->bCaptureOnMovement = false;
|
||||
SceneCapture->bAlwaysPersistRenderingState = true;
|
||||
|
||||
// Enable atmosphere and sky rendering in capture
|
||||
SceneCapture->ShowFlags.SetAtmosphere(true);
|
||||
SceneCapture->ShowFlags.SetFog(true);
|
||||
SceneCapture->ShowFlags.SetVolumetricFog(true);
|
||||
SceneCapture->ShowFlags.SetCloud(true);
|
||||
SceneCapture->ShowFlags.SetLighting(true);
|
||||
SceneCapture->ShowFlags.SetPostProcessing(true);
|
||||
|
||||
// Capture the scene
|
||||
SceneCapture->CaptureScene();
|
||||
|
||||
// Wait for rendering to complete
|
||||
FlushRenderingCommands();
|
||||
|
||||
// Read pixels from render target
|
||||
TArray<FColor> Bitmap;
|
||||
FTextureRenderTargetResource* RTResource = RenderTarget->GameThread_GetRenderTargetResource();
|
||||
if (RTResource)
|
||||
{
|
||||
FReadSurfaceDataFlags ReadFlags(RCM_UNorm);
|
||||
RTResource->ReadPixels(Bitmap, ReadFlags);
|
||||
}
|
||||
|
||||
// Clean up
|
||||
SceneCapture->UnregisterComponent();
|
||||
SceneCapture->DestroyComponent();
|
||||
|
||||
if (Bitmap.Num() == 0)
|
||||
{
|
||||
UE_LOG(LogRalphaMCP, Warning, TEXT("Failed to read pixels from render target"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Convert to base64 PNG
|
||||
OutBase64 = PixelsToBase64PNG(Bitmap, Width, Height);
|
||||
|
||||
// Save to disk
|
||||
FString ScreenshotDir = GetScreenshotDirectory();
|
||||
FString Filename = FString::Printf(TEXT("Ralpha_%s.png"), *FDateTime::Now().ToString(TEXT("%Y%m%d_%H%M%S")));
|
||||
OutFilePath = ScreenshotDir / Filename;
|
||||
|
||||
IImageWrapperModule& ImageWrapperModule = FModuleManager::LoadModuleChecked<IImageWrapperModule>(FName("ImageWrapper"));
|
||||
TSharedPtr<IImageWrapper> ImageWrapper = ImageWrapperModule.CreateImageWrapper(EImageFormat::PNG);
|
||||
|
||||
if (ImageWrapper.IsValid() && ImageWrapper->SetRaw(Bitmap.GetData(), Bitmap.Num() * sizeof(FColor), Width, Height, ERGBFormat::BGRA, 8))
|
||||
{
|
||||
TArray64<uint8> PNGData = ImageWrapper->GetCompressed(0);
|
||||
if (PNGData.Num() > 0)
|
||||
{
|
||||
FFileHelper::SaveArrayToFile(PNGData, *OutFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
UE_LOG(LogRalphaMCP, Log, TEXT("Camera capture: %dx%d, saved to %s"), Width, Height, *OutFilePath);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
FString URalphaScreenCapture::GetScreenshotDirectory()
|
||||
{
|
||||
FString ScreenshotDir = FPaths::ProjectSavedDir() / TEXT("Screenshots") / TEXT("Ralpha");
|
||||
|
||||
// Create directory if it doesn't exist
|
||||
IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
|
||||
if (!PlatformFile.DirectoryExists(*ScreenshotDir))
|
||||
{
|
||||
PlatformFile.CreateDirectoryTree(*ScreenshotDir);
|
||||
}
|
||||
|
||||
return ScreenshotDir;
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
// Copyright Ralpha Team. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Modules/ModuleManager.h"
|
||||
|
||||
class RALPHACORE_API FRalphaCoreModule : public IModuleInterface
|
||||
{
|
||||
public:
|
||||
/** IModuleInterface implementation */
|
||||
virtual void StartupModule() override;
|
||||
virtual void ShutdownModule() override;
|
||||
|
||||
/** Get the module instance */
|
||||
static FRalphaCoreModule& Get();
|
||||
|
||||
/** Check if module is loaded */
|
||||
static bool IsAvailable();
|
||||
};
|
||||
|
|
@ -1,98 +0,0 @@
|
|||
// Copyright Ralpha Team. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "UObject/NoExportTypes.h"
|
||||
#include "Sockets.h"
|
||||
#include "SocketSubsystem.h"
|
||||
#include "Containers/Ticker.h"
|
||||
#include "RalphaMCPServer.generated.h"
|
||||
|
||||
class FRalphaClientConnection;
|
||||
class URalphaParameterBridge;
|
||||
class URalphaScreenCapture;
|
||||
|
||||
DECLARE_LOG_CATEGORY_EXTERN(LogRalphaMCP, Log, All);
|
||||
|
||||
/**
|
||||
* TCP Server that receives JSON-RPC commands from the Python MCP server
|
||||
* and executes them against UE5 systems.
|
||||
*/
|
||||
UCLASS(BlueprintType)
|
||||
class RALPHACORE_API URalphaMCPServer : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
URalphaMCPServer();
|
||||
virtual ~URalphaMCPServer();
|
||||
|
||||
/** Get the singleton instance */
|
||||
UFUNCTION(BlueprintCallable, Category = "Ralpha")
|
||||
static URalphaMCPServer* Get();
|
||||
|
||||
/** Start the TCP server */
|
||||
UFUNCTION(BlueprintCallable, Category = "Ralpha")
|
||||
bool Start(int32 Port = 30010);
|
||||
|
||||
/** Stop the TCP server */
|
||||
UFUNCTION(BlueprintCallable, Category = "Ralpha")
|
||||
void Stop();
|
||||
|
||||
/** Check if server is running */
|
||||
UFUNCTION(BlueprintCallable, Category = "Ralpha")
|
||||
bool IsRunning() const;
|
||||
|
||||
/** Get the port number */
|
||||
UFUNCTION(BlueprintCallable, Category = "Ralpha")
|
||||
int32 GetPort() const { return ServerPort; }
|
||||
|
||||
/** Process a JSON command and return response */
|
||||
FString ProcessCommand(const FString& JsonCommand);
|
||||
|
||||
protected:
|
||||
/** Tick function to accept new connections */
|
||||
bool Tick(float DeltaTime);
|
||||
|
||||
private:
|
||||
static URalphaMCPServer* Instance;
|
||||
|
||||
FSocket* ListenerSocket;
|
||||
int32 ServerPort;
|
||||
bool bIsRunning;
|
||||
|
||||
TArray<TSharedPtr<FRalphaClientConnection>> ClientConnections;
|
||||
|
||||
UPROPERTY()
|
||||
URalphaParameterBridge* ParameterBridge;
|
||||
|
||||
UPROPERTY()
|
||||
URalphaScreenCapture* ScreenCapture;
|
||||
|
||||
FTSTicker::FDelegateHandle TickDelegateHandle;
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles a single client connection
|
||||
*/
|
||||
class RALPHACORE_API FRalphaClientConnection : public TSharedFromThis<FRalphaClientConnection>
|
||||
{
|
||||
public:
|
||||
FRalphaClientConnection(FSocket* InSocket, URalphaMCPServer* InServer);
|
||||
~FRalphaClientConnection();
|
||||
|
||||
/** Process incoming data */
|
||||
void Tick();
|
||||
|
||||
/** Check if connection is valid */
|
||||
bool IsValid() const;
|
||||
|
||||
/** Send response to client */
|
||||
void SendResponse(const FString& Response);
|
||||
|
||||
private:
|
||||
FSocket* Socket;
|
||||
URalphaMCPServer* Server;
|
||||
FString ReceiveBuffer;
|
||||
};
|
||||
|
|
@ -1,128 +0,0 @@
|
|||
// Copyright Ralpha Team. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "UObject/NoExportTypes.h"
|
||||
#include "Dom/JsonObject.h"
|
||||
#include "Dom/JsonValue.h"
|
||||
#include "RalphaParameterBridge.generated.h"
|
||||
|
||||
class APostProcessVolume;
|
||||
class ADirectionalLight;
|
||||
class ASkyLight;
|
||||
class AExponentialHeightFog;
|
||||
class ACineCameraActor;
|
||||
|
||||
/**
|
||||
* Bridge between MCP commands and UE5 rendering systems.
|
||||
* Handles getting/setting parameters on post-process volumes, lights, fog, cameras, etc.
|
||||
*/
|
||||
UCLASS(BlueprintType)
|
||||
class RALPHACORE_API URalphaParameterBridge : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
URalphaParameterBridge();
|
||||
|
||||
// Get all current rendering parameters
|
||||
TSharedPtr<FJsonObject> GetAllParameters();
|
||||
|
||||
// Post-Process Volume
|
||||
bool SetPostProcessParameters(const TSharedPtr<FJsonObject>& Params);
|
||||
TSharedPtr<FJsonObject> GetPostProcessParameters();
|
||||
|
||||
// Directional Light (Sun)
|
||||
bool SetDirectionalLightParameters(const TSharedPtr<FJsonObject>& Params);
|
||||
TSharedPtr<FJsonObject> GetDirectionalLightParameters();
|
||||
|
||||
// Sky Light
|
||||
bool SetSkyLightParameters(const TSharedPtr<FJsonObject>& Params);
|
||||
TSharedPtr<FJsonObject> GetSkyLightParameters();
|
||||
|
||||
// Exponential Height Fog
|
||||
bool SetFogParameters(const TSharedPtr<FJsonObject>& Params);
|
||||
TSharedPtr<FJsonObject> GetFogParameters();
|
||||
|
||||
// Camera
|
||||
bool SetCameraParameters(const TSharedPtr<FJsonObject>& Params);
|
||||
TSharedPtr<FJsonObject> GetCameraParameters();
|
||||
|
||||
// Render Quality
|
||||
bool SetRenderQuality(const TSharedPtr<FJsonObject>& Settings);
|
||||
|
||||
// Asset Search
|
||||
TArray<TSharedPtr<FJsonValue>> SearchAssets(const FString& Query, const FString& AssetType, int32 MaxResults);
|
||||
TArray<TSharedPtr<FJsonValue>> ListAssets(const FString& FolderPath, const FString& AssetType, bool bRecursive);
|
||||
|
||||
// Actor Management
|
||||
bool SpawnActor(const FString& AssetPath, const FVector& Location, const FRotator& Rotation, float Scale, const FString& ActorLabel, FString& OutActorId);
|
||||
bool DeleteActor(const FString& ActorId);
|
||||
TArray<TSharedPtr<FJsonValue>> ListActors(const FString& ClassFilter, const FString& NameFilter, bool bIncludeTransforms);
|
||||
bool SetActorTransform(const FString& ActorId, const FVector* Location, const FRotator* Rotation, const float* Scale, bool bRelative);
|
||||
TSharedPtr<FJsonObject> GetActorDetails(const FString& ActorId, bool bIncludeComponents, bool bIncludeMaterials);
|
||||
bool SetActorMaterial(const FString& ActorId, const FString& MaterialPath, int32 MaterialIndex);
|
||||
|
||||
// Create a simple material with specified properties and apply to actor
|
||||
bool SetActorSimpleMaterial(const FString& ActorId, const FLinearColor& BaseColor, float Metallic, float Roughness, float Opacity);
|
||||
|
||||
// Create an emissive (unlit) material for glowing objects like sun, lights, screens
|
||||
bool SetActorEmissiveMaterial(const FString& ActorId, const FLinearColor& EmissiveColor, float EmissiveIntensity);
|
||||
|
||||
// Scene Setup - creates required actors for rendering control
|
||||
TSharedPtr<FJsonObject> SetupScene();
|
||||
|
||||
// Spawn WaterBodyOcean from Water plugin
|
||||
bool SpawnOcean(const FVector& Location, float Size, FString& OutActorId);
|
||||
|
||||
// Configure ocean wave parameters
|
||||
bool SetOceanWaves(float WaveAmplitude, float WaveLength, float Steepness, int32 NumWaves);
|
||||
|
||||
// Create a minimal landscape for water plugin support
|
||||
bool CreateLandscape(float Size, float HeightOffset);
|
||||
|
||||
// Point Light
|
||||
bool SpawnPointLight(const FVector& Location, float Intensity, const FLinearColor& Color, float AttenuationRadius, const FString& ActorLabel, FString& OutActorId);
|
||||
bool SetPointLightParameters(const FString& ActorId, const TSharedPtr<FJsonObject>& Params);
|
||||
|
||||
// Spot Light
|
||||
bool SpawnSpotLight(const FVector& Location, const FRotator& Rotation, float Intensity, const FLinearColor& Color, float AttenuationRadius, float InnerConeAngle, float OuterConeAngle, const FString& ActorLabel, FString& OutActorId);
|
||||
bool SetSpotLightParameters(const FString& ActorId, const TSharedPtr<FJsonObject>& Params);
|
||||
|
||||
// Rect Light (area lighting for interiors)
|
||||
bool SpawnRectLight(const FVector& Location, const FRotator& Rotation, float Intensity, const FLinearColor& Color, float SourceWidth, float SourceHeight, float AttenuationRadius, const FString& ActorLabel, FString& OutActorId);
|
||||
bool SetRectLightParameters(const FString& ActorId, const TSharedPtr<FJsonObject>& Params);
|
||||
|
||||
// Skeletal Mesh (characters, animated objects)
|
||||
bool SpawnSkeletalMesh(const FString& SkeletalMeshPath, const FVector& Location, const FRotator& Rotation, float Scale, const FString& ActorLabel, FString& OutActorId);
|
||||
bool PlayAnimation(const FString& ActorId, const FString& AnimationPath, bool bLooping, float PlayRate);
|
||||
bool StopAnimation(const FString& ActorId);
|
||||
bool SetAnimationPose(const FString& ActorId, const FString& AnimationPath, float Time);
|
||||
|
||||
// Reflection Captures
|
||||
bool SpawnSphereReflectionCapture(const FVector& Location, float InfluenceRadius, const FString& ActorLabel, FString& OutActorId);
|
||||
bool SpawnBoxReflectionCapture(const FVector& Location, const FVector& BoxExtent, const FString& ActorLabel, FString& OutActorId);
|
||||
bool UpdateReflectionCaptures();
|
||||
|
||||
// IES Light Profiles
|
||||
bool SetLightIESProfile(const FString& ActorId, const FString& IESProfilePath);
|
||||
|
||||
private:
|
||||
// Find actors of specific types in the world
|
||||
APostProcessVolume* FindPostProcessVolume();
|
||||
ADirectionalLight* FindDirectionalLight();
|
||||
ASkyLight* FindSkyLight();
|
||||
AExponentialHeightFog* FindExponentialHeightFog();
|
||||
ACineCameraActor* FindCineCamera();
|
||||
|
||||
// Get the current world
|
||||
UWorld* GetCurrentWorld();
|
||||
|
||||
// Parse hex color string to FLinearColor
|
||||
FLinearColor ParseHexColor(const FString& HexColor);
|
||||
FString ColorToHex(const FLinearColor& Color);
|
||||
|
||||
// Refresh BP_Sky_Sphere when sun position changes
|
||||
void RefreshSkySphere();
|
||||
};
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
// Copyright Ralpha Team. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "UObject/NoExportTypes.h"
|
||||
#include "RalphaScreenCapture.generated.h"
|
||||
|
||||
/**
|
||||
* Handles viewport screenshot capture for the style convergence loop.
|
||||
*/
|
||||
UCLASS(BlueprintType)
|
||||
class RALPHACORE_API URalphaScreenCapture : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
URalphaScreenCapture();
|
||||
|
||||
/**
|
||||
* Capture the current viewport as a PNG image.
|
||||
* @param Width Desired width (may be clamped to viewport size)
|
||||
* @param Height Desired height (may be clamped to viewport size)
|
||||
* @param OutBase64 The captured image as base64-encoded PNG
|
||||
* @param OutFilePath Path where the image was saved (if saved to disk)
|
||||
* @return True if capture succeeded
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Ralpha")
|
||||
bool CaptureScreenshot(int32 Width, int32 Height, FString& OutBase64, FString& OutFilePath);
|
||||
|
||||
/**
|
||||
* Capture a high-resolution screenshot.
|
||||
* @param Width Desired width
|
||||
* @param Height Desired height
|
||||
* @param OutBase64 The captured image as base64-encoded PNG
|
||||
* @return True if capture succeeded
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Ralpha")
|
||||
bool CaptureHighResScreenshot(int32 Width, int32 Height, FString& OutBase64);
|
||||
|
||||
/**
|
||||
* Capture from a specific camera using SceneCapture2D.
|
||||
* @param Width Desired width
|
||||
* @param Height Desired height
|
||||
* @param OutBase64 The captured image as base64-encoded PNG
|
||||
* @param OutFilePath Path where the image was saved
|
||||
* @return True if capture succeeded
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Ralpha")
|
||||
bool CaptureFromCamera(int32 Width, int32 Height, FString& OutBase64, FString& OutFilePath);
|
||||
|
||||
private:
|
||||
/** Convert raw pixel data to base64 PNG */
|
||||
FString PixelsToBase64PNG(const TArray<FColor>& Pixels, int32 Width, int32 Height);
|
||||
|
||||
/** Get the screenshot save directory */
|
||||
FString GetScreenshotDirectory();
|
||||
};
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
// Copyright Ralpha Team. All Rights Reserved.
|
||||
|
||||
using UnrealBuildTool;
|
||||
|
||||
public class RalphaCore : ModuleRules
|
||||
{
|
||||
public RalphaCore(ReadOnlyTargetRules Target) : base(Target)
|
||||
{
|
||||
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||
|
||||
PublicIncludePaths.AddRange(
|
||||
new string[] {
|
||||
}
|
||||
);
|
||||
|
||||
PrivateIncludePaths.AddRange(
|
||||
new string[] {
|
||||
}
|
||||
);
|
||||
|
||||
PublicDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"Core",
|
||||
"CoreUObject",
|
||||
"Engine",
|
||||
"Sockets",
|
||||
"Networking",
|
||||
"Json",
|
||||
"JsonUtilities",
|
||||
"RenderCore",
|
||||
"Renderer",
|
||||
"RHI",
|
||||
"ImageWrapper",
|
||||
"AssetRegistry",
|
||||
}
|
||||
);
|
||||
|
||||
PrivateDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"Slate",
|
||||
"SlateCore",
|
||||
"CinematicCamera",
|
||||
"Water",
|
||||
"Landscape",
|
||||
}
|
||||
);
|
||||
|
||||
if (Target.bBuildEditor)
|
||||
{
|
||||
PrivateDependencyModuleNames.AddRange(new string[]
|
||||
{
|
||||
"UnrealEd",
|
||||
"LevelEditor",
|
||||
});
|
||||
}
|
||||
|
||||
DynamicallyLoadedModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,82 +0,0 @@
|
|||
// Copyright Ralpha Team. All Rights Reserved.
|
||||
|
||||
#include "RalphaEditor.h"
|
||||
#include "RalphaEditorCommands.h"
|
||||
#include "RalphaEditorStyle.h"
|
||||
#include "ToolMenus.h"
|
||||
#include "RalphaMCPServer.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "FRalphaEditorModule"
|
||||
|
||||
void FRalphaEditorModule::StartupModule()
|
||||
{
|
||||
FRalphaEditorStyle::Initialize();
|
||||
FRalphaEditorStyle::ReloadTextures();
|
||||
|
||||
FRalphaEditorCommands::Register();
|
||||
|
||||
PluginCommands = MakeShareable(new FUICommandList);
|
||||
|
||||
PluginCommands->MapAction(
|
||||
FRalphaEditorCommands::Get().StartServer,
|
||||
FExecuteAction::CreateLambda([]()
|
||||
{
|
||||
URalphaMCPServer* Server = URalphaMCPServer::Get();
|
||||
if (Server)
|
||||
{
|
||||
if (!Server->IsRunning())
|
||||
{
|
||||
Server->Start();
|
||||
}
|
||||
else
|
||||
{
|
||||
Server->Stop();
|
||||
}
|
||||
}
|
||||
}),
|
||||
FCanExecuteAction()
|
||||
);
|
||||
|
||||
UToolMenus::RegisterStartupCallback(FSimpleMulticastDelegate::FDelegate::CreateRaw(this, &FRalphaEditorModule::RegisterMenus));
|
||||
|
||||
// Auto-start the MCP server
|
||||
URalphaMCPServer* Server = URalphaMCPServer::Get();
|
||||
if (Server && !Server->IsRunning())
|
||||
{
|
||||
Server->Start();
|
||||
UE_LOG(LogTemp, Log, TEXT("Ralpha MCP Server auto-started on port 30010"));
|
||||
}
|
||||
|
||||
UE_LOG(LogTemp, Log, TEXT("Ralpha Editor Module Started"));
|
||||
}
|
||||
|
||||
void FRalphaEditorModule::ShutdownModule()
|
||||
{
|
||||
UToolMenus::UnRegisterStartupCallback(this);
|
||||
UToolMenus::UnregisterOwner(this);
|
||||
|
||||
FRalphaEditorStyle::Shutdown();
|
||||
FRalphaEditorCommands::Unregister();
|
||||
|
||||
UE_LOG(LogTemp, Log, TEXT("Ralpha Editor Module Shutdown"));
|
||||
}
|
||||
|
||||
void FRalphaEditorModule::RegisterMenus()
|
||||
{
|
||||
FToolMenuOwnerScoped OwnerScoped(this);
|
||||
|
||||
{
|
||||
UToolMenu* ToolbarMenu = UToolMenus::Get()->ExtendMenu("LevelEditor.LevelEditorToolBar.PlayToolBar");
|
||||
{
|
||||
FToolMenuSection& Section = ToolbarMenu->FindOrAddSection("Ralpha");
|
||||
{
|
||||
FToolMenuEntry& Entry = Section.AddEntry(FToolMenuEntry::InitToolBarButton(FRalphaEditorCommands::Get().StartServer));
|
||||
Entry.SetCommandList(PluginCommands);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
|
||||
IMPLEMENT_MODULE(FRalphaEditorModule, RalphaEditor)
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
// Copyright Ralpha Team. All Rights Reserved.
|
||||
|
||||
#include "RalphaEditorCommands.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "FRalphaEditorModule"
|
||||
|
||||
void FRalphaEditorCommands::RegisterCommands()
|
||||
{
|
||||
UI_COMMAND(StartServer, "Ralpha", "Start/Stop the Ralpha MCP Server", EUserInterfaceActionType::Button, FInputChord());
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
// Copyright Ralpha Team. All Rights Reserved.
|
||||
|
||||
#include "RalphaEditorStyle.h"
|
||||
#include "Styling/SlateStyleRegistry.h"
|
||||
#include "Framework/Application/SlateApplication.h"
|
||||
#include "Slate/SlateGameResources.h"
|
||||
#include "Interfaces/IPluginManager.h"
|
||||
#include "Styling/SlateStyleMacros.h"
|
||||
|
||||
#define RootToContentDir Style->RootToContentDir
|
||||
|
||||
TSharedPtr<FSlateStyleSet> FRalphaEditorStyle::StyleInstance = nullptr;
|
||||
|
||||
void FRalphaEditorStyle::Initialize()
|
||||
{
|
||||
if (!StyleInstance.IsValid())
|
||||
{
|
||||
StyleInstance = Create();
|
||||
FSlateStyleRegistry::RegisterSlateStyle(*StyleInstance);
|
||||
}
|
||||
}
|
||||
|
||||
void FRalphaEditorStyle::Shutdown()
|
||||
{
|
||||
FSlateStyleRegistry::UnRegisterSlateStyle(*StyleInstance);
|
||||
ensure(StyleInstance.IsUnique());
|
||||
StyleInstance.Reset();
|
||||
}
|
||||
|
||||
FName FRalphaEditorStyle::GetStyleSetName()
|
||||
{
|
||||
static FName StyleSetName(TEXT("RalphaEditorStyle"));
|
||||
return StyleSetName;
|
||||
}
|
||||
|
||||
const ISlateStyle& FRalphaEditorStyle::Get()
|
||||
{
|
||||
return *StyleInstance;
|
||||
}
|
||||
|
||||
void FRalphaEditorStyle::ReloadTextures()
|
||||
{
|
||||
if (FSlateApplication::IsInitialized())
|
||||
{
|
||||
FSlateApplication::Get().GetRenderer()->ReloadTextureResources();
|
||||
}
|
||||
}
|
||||
|
||||
TSharedRef<FSlateStyleSet> FRalphaEditorStyle::Create()
|
||||
{
|
||||
TSharedRef<FSlateStyleSet> Style = MakeShareable(new FSlateStyleSet("RalphaEditorStyle"));
|
||||
Style->SetContentRoot(IPluginManager::Get().FindPlugin("RalphaPlugin")->GetBaseDir() / TEXT("Resources"));
|
||||
|
||||
Style->Set("Ralpha.StartServer", new IMAGE_BRUSH_SVG(TEXT("Icon20"), FVector2D(20.0f, 20.0f)));
|
||||
|
||||
return Style;
|
||||
}
|
||||
|
||||
#undef RootToContentDir
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
// Copyright Ralpha Team. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Modules/ModuleManager.h"
|
||||
|
||||
class FToolBarBuilder;
|
||||
class FMenuBuilder;
|
||||
|
||||
class RALPHAEDITOR_API FRalphaEditorModule : public IModuleInterface
|
||||
{
|
||||
public:
|
||||
/** IModuleInterface implementation */
|
||||
virtual void StartupModule() override;
|
||||
virtual void ShutdownModule() override;
|
||||
|
||||
private:
|
||||
void RegisterMenus();
|
||||
|
||||
TSharedPtr<class FUICommandList> PluginCommands;
|
||||
};
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
// Copyright Ralpha Team. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Framework/Commands/Commands.h"
|
||||
#include "RalphaEditorStyle.h"
|
||||
|
||||
class FRalphaEditorCommands : public TCommands<FRalphaEditorCommands>
|
||||
{
|
||||
public:
|
||||
FRalphaEditorCommands()
|
||||
: TCommands<FRalphaEditorCommands>(
|
||||
TEXT("Ralpha"),
|
||||
NSLOCTEXT("Contexts", "Ralpha", "Ralpha Plugin"),
|
||||
NAME_None,
|
||||
FRalphaEditorStyle::GetStyleSetName())
|
||||
{
|
||||
}
|
||||
|
||||
virtual void RegisterCommands() override;
|
||||
|
||||
public:
|
||||
TSharedPtr<FUICommandInfo> StartServer;
|
||||
};
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
// Copyright Ralpha Team. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Styling/SlateStyle.h"
|
||||
|
||||
class FRalphaEditorStyle
|
||||
{
|
||||
public:
|
||||
static void Initialize();
|
||||
static void Shutdown();
|
||||
static void ReloadTextures();
|
||||
static const ISlateStyle& Get();
|
||||
static FName GetStyleSetName();
|
||||
|
||||
private:
|
||||
static TSharedRef<class FSlateStyleSet> Create();
|
||||
static TSharedPtr<class FSlateStyleSet> StyleInstance;
|
||||
};
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
// Copyright Ralpha Team. All Rights Reserved.
|
||||
|
||||
using UnrealBuildTool;
|
||||
|
||||
public class RalphaEditor : ModuleRules
|
||||
{
|
||||
public RalphaEditor(ReadOnlyTargetRules Target) : base(Target)
|
||||
{
|
||||
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||
|
||||
PublicIncludePaths.AddRange(
|
||||
new string[] {
|
||||
}
|
||||
);
|
||||
|
||||
PrivateIncludePaths.AddRange(
|
||||
new string[] {
|
||||
}
|
||||
);
|
||||
|
||||
PublicDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"Core",
|
||||
"CoreUObject",
|
||||
"Engine",
|
||||
"Slate",
|
||||
"SlateCore",
|
||||
"RalphaCore",
|
||||
}
|
||||
);
|
||||
|
||||
PrivateDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"UnrealEd",
|
||||
"EditorStyle",
|
||||
"ToolMenus",
|
||||
"LevelEditor",
|
||||
"AssetRegistry",
|
||||
"ContentBrowser",
|
||||
"Projects",
|
||||
}
|
||||
);
|
||||
|
||||
DynamicallyLoadedModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,188 @@
|
|||
# Ralpha UE5 - Kitchen Sink Asset Project
|
||||
|
||||
Comprehensive UE5 asset library and scene templates for AI-controlled rendering with [Ralpha](https://github.com/jamestagg/ralpha).
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Clone with LFS
|
||||
git lfs install
|
||||
git clone https://github.com/jamestagg/ralpha-ue5.git
|
||||
|
||||
# Open in UE5.4+
|
||||
# File > Open Project > ralpha-ue5/Ralpha.uproject
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
This repo contains **assets only**. The MCP plugin and tooling live in the main [ralpha](https://github.com/jamestagg/ralpha) repo.
|
||||
|
||||
```
|
||||
ralpha-ue5/ <- You are here (assets, templates, presets)
|
||||
│
|
||||
└── Plugins/RalphaPlugin/ <- Symlink to ralpha/plugin
|
||||
│
|
||||
├── MCP Server (port 30010)
|
||||
└── 69 Command Handlers
|
||||
|
||||
ralpha/ <- Main repo (tooling, API, frontend)
|
||||
│
|
||||
├── plugin/ <- UE5 plugin source
|
||||
├── ralpha-api/ <- Backend API
|
||||
└── frontend/ <- Web interface
|
||||
```
|
||||
|
||||
## Repository Structure
|
||||
|
||||
```
|
||||
ralpha-ue5/
|
||||
├── RalphaData/ # NOT in LFS - text/JSON files
|
||||
│ ├── catalogue/
|
||||
│ │ ├── catalogue.json # Machine-readable asset index
|
||||
│ │ └── catalogue.schema.json # JSON schema
|
||||
│ ├── thumbnails/ # Small preview images
|
||||
│ ├── recipes/ # Scene composition templates
|
||||
│ │ ├── recipe.schema.json
|
||||
│ │ ├── living_room_modern.json
|
||||
│ │ ├── portrait_studio_white.json
|
||||
│ │ └── office_corporate.json
|
||||
│ ├── presets/
|
||||
│ │ ├── lighting/ # Lighting configurations
|
||||
│ │ ├── post/ # Post-processing profiles
|
||||
│ │ ├── scene/ # Full scene configs
|
||||
│ │ └── camera/ # Camera presets
|
||||
│ └── scripts/
|
||||
│ └── generate_catalogue.py # Asset indexing tool
|
||||
│
|
||||
├── Content/ # IN LFS - large binary files
|
||||
│ └── Ralpha/
|
||||
│ ├── Maps/ # Template levels
|
||||
│ │ ├── Template_Empty.umap
|
||||
│ │ ├── Template_Studio.umap
|
||||
│ │ ├── Template_Outdoor.umap
|
||||
│ │ └── Template_Interior.umap
|
||||
│ ├── Assets/ # Props, furniture, etc.
|
||||
│ │ ├── Furniture/
|
||||
│ │ ├── Electronics/
|
||||
│ │ ├── Food/
|
||||
│ │ ├── Vehicles/
|
||||
│ │ ├── Nature/
|
||||
│ │ └── ...
|
||||
│ ├── Materials/
|
||||
│ ├── HDRI/
|
||||
│ └── LUTs/
|
||||
│
|
||||
└── Plugins/
|
||||
└── RalphaPlugin/ # Symlink to ralpha/plugin
|
||||
```
|
||||
|
||||
## Key Concepts
|
||||
|
||||
### Template Maps vs Heavy Maps
|
||||
|
||||
Instead of 50+ bespoke maps, we use **4 templates** + **JSON presets**:
|
||||
|
||||
| Template | Use Case |
|
||||
|----------|----------|
|
||||
| `Template_Empty` | Bare minimum - sky + ground |
|
||||
| `Template_Studio` | Portrait/product - cyclorama + lights |
|
||||
| `Template_Outdoor` | Landscapes - terrain + atmosphere |
|
||||
| `Template_Interior` | Rooms - walls + ceiling + basic lighting |
|
||||
|
||||
Scene variation comes from presets, not map duplication.
|
||||
|
||||
### Recipe System
|
||||
|
||||
Recipes define composite scenes that can be spawned instantly:
|
||||
|
||||
```json
|
||||
{
|
||||
"recipe_id": "living_room_modern",
|
||||
"components": [
|
||||
{"asset": "sofa_modern_3seat", "position": "center"},
|
||||
{"asset": "coffee_table_rect", "position": "in_front_of:sofa"},
|
||||
{"asset": "tv_65inch", "position": "wall:opposite_sofa"}
|
||||
],
|
||||
"lighting_preset": "Interior_Evening"
|
||||
}
|
||||
```
|
||||
|
||||
MCP command: `{"type":"spawn_recipe","parameters":{"recipe":"living_room_modern"}}`
|
||||
|
||||
### Asset Catalogue
|
||||
|
||||
All assets are indexed in `catalogue.json` for programmatic discovery:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "sofa_modern_3seat",
|
||||
"name": "Modern 3-Seat Sofa",
|
||||
"category": "Furniture/Seating",
|
||||
"unreal_path": "/Game/Ralpha/Assets/Furniture/Seating/Sofa_Modern_3Seat",
|
||||
"tags": ["sofa", "couch", "seating", "modern", "living room"],
|
||||
"bounds": {"x": 220, "y": 90, "z": 85},
|
||||
"placement_modes": ["floor"]
|
||||
}
|
||||
```
|
||||
|
||||
MCP command: `{"type":"search_catalogue","parameters":{"query":"modern sofa"}}`
|
||||
|
||||
## MCP Commands
|
||||
|
||||
These commands interface with this asset library:
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `load_template` | Load a base template map |
|
||||
| `apply_scene_preset` | Apply lighting/post/camera preset |
|
||||
| `spawn_recipe` | Spawn a full scene composition |
|
||||
| `spawn_catalogue_item` | Spawn single asset from catalogue |
|
||||
| `search_catalogue` | Search assets by tags/name |
|
||||
| `get_version` | Get plugin + catalogue version |
|
||||
|
||||
## Asset Standards
|
||||
|
||||
All assets must meet these requirements:
|
||||
|
||||
- **Scale**: 1 unit = 1 cm
|
||||
- **Pivot**: Bottom center (for floor items)
|
||||
- **Collision**: Present for interactive items
|
||||
- **LODs**: At least 2 levels for complex meshes
|
||||
- **Materials**: PBR with proper slots
|
||||
- **Naming**: `Category_Type_Variant` (e.g., `Sofa_Modern_Grey`)
|
||||
|
||||
## Regenerating Catalogue
|
||||
|
||||
After adding assets, regenerate the catalogue:
|
||||
|
||||
```bash
|
||||
cd ralpha-ue5
|
||||
python RalphaData/scripts/generate_catalogue.py --scan-only
|
||||
```
|
||||
|
||||
For full metadata extraction (requires UE Editor):
|
||||
```bash
|
||||
python RalphaData/scripts/generate_catalogue.py --project Ralpha.uproject --ue-path /path/to/UnrealEditor
|
||||
```
|
||||
|
||||
## License Policy
|
||||
|
||||
See [LICENSE_POLICY.md](LICENSE_POLICY.md) for asset provenance requirements.
|
||||
|
||||
- **CC0/Public Domain**: Preferred, no restrictions
|
||||
- **CC-BY**: Allowed, attribution in catalogue
|
||||
- **Megascans**: Allowed (free with UE)
|
||||
- **Marketplace**: Allowed for purchased assets
|
||||
- **Restricted**: Separate folder, excluded by default
|
||||
|
||||
## Related Repositories
|
||||
|
||||
- [ralpha](https://github.com/jamestagg/ralpha) - Main tooling, plugin, API
|
||||
- [ralpha-ue5-metahumans](https://github.com/jamestagg/ralpha-ue5-metahumans) - MetaHuman characters (optional)
|
||||
|
||||
## Contributing
|
||||
|
||||
1. Import asset following standards above
|
||||
2. Run `generate_catalogue.py`
|
||||
3. Add to appropriate recipe if applicable
|
||||
4. Submit PR with asset source/license info
|
||||
|
|
@ -15,6 +15,7 @@
|
|||
}
|
||||
],
|
||||
"TargetPlatforms": [
|
||||
"Mac",
|
||||
"Windows"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
{
|
||||
"$schema": "./catalogue.schema.json",
|
||||
"version": "1.0.0",
|
||||
"generated_at": "2026-01-20T00:00:00Z",
|
||||
"asset_count": 0,
|
||||
"categories": [
|
||||
"Furniture/Seating",
|
||||
"Furniture/Tables",
|
||||
"Furniture/Storage",
|
||||
"Furniture/Bedroom",
|
||||
"Electronics/Computing",
|
||||
"Electronics/Entertainment",
|
||||
"Electronics/Appliances",
|
||||
"Food/Packaged",
|
||||
"Food/Fresh",
|
||||
"Food/Prepared",
|
||||
"Food/Beverages",
|
||||
"Kitchenware",
|
||||
"Office",
|
||||
"Personal",
|
||||
"Decor/Wall",
|
||||
"Decor/Tabletop",
|
||||
"Decor/Lighting",
|
||||
"Vehicles/Cars",
|
||||
"Vehicles/Motorcycles",
|
||||
"Vehicles/Bicycles",
|
||||
"Vehicles/Other",
|
||||
"Nature/Trees",
|
||||
"Nature/Plants",
|
||||
"Nature/Rocks",
|
||||
"Architecture/Structural",
|
||||
"Architecture/Exterior",
|
||||
"Urban",
|
||||
"Sports",
|
||||
"Tools",
|
||||
"Animals"
|
||||
],
|
||||
"assets": []
|
||||
}
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Ralpha Asset Catalogue",
|
||||
"description": "Machine-readable index of all assets in ralpha-ue5",
|
||||
"type": "object",
|
||||
"required": ["version", "generated_at", "assets"],
|
||||
"properties": {
|
||||
"version": {
|
||||
"type": "string",
|
||||
"pattern": "^\\d+\\.\\d+\\.\\d+$"
|
||||
},
|
||||
"generated_at": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"asset_count": {
|
||||
"type": "integer"
|
||||
},
|
||||
"categories": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
},
|
||||
"assets": {
|
||||
"type": "array",
|
||||
"items": { "$ref": "#/definitions/Asset" }
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"Asset": {
|
||||
"type": "object",
|
||||
"required": ["id", "name", "category", "unreal_path"],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "Stable unique identifier (snake_case)",
|
||||
"pattern": "^[a-z][a-z0-9_]*$"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Human-readable display name"
|
||||
},
|
||||
"category": {
|
||||
"type": "string",
|
||||
"description": "Category path (e.g., Furniture/Seating)"
|
||||
},
|
||||
"unreal_path": {
|
||||
"type": "string",
|
||||
"description": "Full Unreal asset path",
|
||||
"pattern": "^/Game/"
|
||||
},
|
||||
"tags": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
"description": "Searchable tags/synonyms"
|
||||
},
|
||||
"thumbnail": {
|
||||
"type": "string",
|
||||
"description": "Relative path to thumbnail image"
|
||||
},
|
||||
"bounds": {
|
||||
"type": "object",
|
||||
"description": "Bounding box in cm",
|
||||
"properties": {
|
||||
"x": { "type": "number" },
|
||||
"y": { "type": "number" },
|
||||
"z": { "type": "number" }
|
||||
}
|
||||
},
|
||||
"pivot": {
|
||||
"type": "string",
|
||||
"enum": ["bottom_center", "center", "bottom_back", "custom"],
|
||||
"default": "bottom_center"
|
||||
},
|
||||
"placement_modes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": ["floor", "wall", "ceiling", "surface", "hanging", "floating"]
|
||||
},
|
||||
"default": ["floor"]
|
||||
},
|
||||
"collision": {
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"lods": {
|
||||
"type": "integer",
|
||||
"description": "Number of LOD levels"
|
||||
},
|
||||
"triangle_count": {
|
||||
"type": "integer",
|
||||
"description": "LOD0 triangle count"
|
||||
},
|
||||
"materials": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
"description": "Material slot names"
|
||||
},
|
||||
"variants": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
"description": "IDs of variant assets (e.g., color options)"
|
||||
},
|
||||
"license": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["CC0", "CC-BY", "CC-BY-SA", "Megascans", "Marketplace", "Custom", "Restricted"]
|
||||
},
|
||||
"source": { "type": "string" },
|
||||
"attribution": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"added_at": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
{
|
||||
"preset_id": "Natural_GoldenHour",
|
||||
"name": "Golden Hour",
|
||||
"description": "Warm afternoon/evening sun with long shadows",
|
||||
"category": "natural",
|
||||
"directional_light": {
|
||||
"rotation": [-15, 45, 0],
|
||||
"intensity": 8.0,
|
||||
"color": "#FFB366",
|
||||
"temperature": 4000,
|
||||
"cast_shadows": true,
|
||||
"shadow_cascades": 4,
|
||||
"atmosphere_sun_light": true
|
||||
},
|
||||
"sky_light": {
|
||||
"intensity": 1.0,
|
||||
"color": "#FFE4C4",
|
||||
"real_time_capture": true
|
||||
},
|
||||
"sky_atmosphere": {
|
||||
"enabled": true,
|
||||
"rayleigh_scattering": [0.175, 0.409, 1.0],
|
||||
"mie_scattering_scale": 0.004,
|
||||
"absorption_scale": 0.75
|
||||
},
|
||||
"volumetric_cloud": {
|
||||
"enabled": true,
|
||||
"layer_bottom_altitude": 5.0,
|
||||
"layer_height": 10.0,
|
||||
"cloud_density": 0.3
|
||||
},
|
||||
"exponential_height_fog": {
|
||||
"enabled": true,
|
||||
"fog_density": 0.02,
|
||||
"fog_height_falloff": 0.2,
|
||||
"fog_inscattering_color": "#FFCC99",
|
||||
"volumetric_fog": true,
|
||||
"volumetric_fog_scattering": 0.5
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
{
|
||||
"preset_id": "Studio_3Point_Soft",
|
||||
"name": "Studio 3-Point Soft",
|
||||
"description": "Classic soft 3-point lighting for portraits and products",
|
||||
"category": "studio",
|
||||
"lights": [
|
||||
{
|
||||
"name": "KeyLight",
|
||||
"type": "rect",
|
||||
"position": [-200, 150, 200],
|
||||
"rotation": [0, -45, -30],
|
||||
"intensity": 15.0,
|
||||
"color": "#FFF5E6",
|
||||
"temperature": 5500,
|
||||
"source_width": 100,
|
||||
"source_height": 150,
|
||||
"barn_door_angle": 45,
|
||||
"cast_shadows": true,
|
||||
"soft_shadows": true
|
||||
},
|
||||
{
|
||||
"name": "FillLight",
|
||||
"type": "rect",
|
||||
"position": [150, 100, 180],
|
||||
"rotation": [0, 45, -20],
|
||||
"intensity": 5.0,
|
||||
"color": "#E6F0FF",
|
||||
"temperature": 6500,
|
||||
"source_width": 120,
|
||||
"source_height": 120,
|
||||
"cast_shadows": false
|
||||
},
|
||||
{
|
||||
"name": "RimLight",
|
||||
"type": "rect",
|
||||
"position": [0, -200, 220],
|
||||
"rotation": [0, 180, -30],
|
||||
"intensity": 8.0,
|
||||
"color": "#FFFFFF",
|
||||
"temperature": 6000,
|
||||
"source_width": 50,
|
||||
"source_height": 150,
|
||||
"cast_shadows": false
|
||||
}
|
||||
],
|
||||
"sky_light": {
|
||||
"intensity": 0.5,
|
||||
"color": "#D4E5F7",
|
||||
"cubemap": null
|
||||
},
|
||||
"environment": {
|
||||
"ambient_intensity": 0.1,
|
||||
"fog_enabled": false
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
{
|
||||
"preset_id": "PP_Cinematic",
|
||||
"name": "Cinematic",
|
||||
"description": "Film-like look with crushed blacks and teal/orange color grading",
|
||||
"category": "stylized",
|
||||
"bloom": {
|
||||
"intensity": 0.5,
|
||||
"threshold": -1.0
|
||||
},
|
||||
"exposure": {
|
||||
"method": "manual",
|
||||
"exposure_compensation": 0.0,
|
||||
"min_brightness": 0.03,
|
||||
"max_brightness": 2.0
|
||||
},
|
||||
"color_grading": {
|
||||
"temperature": 6500,
|
||||
"tint": 0,
|
||||
"saturation": 1.05,
|
||||
"contrast": 1.1,
|
||||
"gamma": 1.0,
|
||||
"gain": 1.0,
|
||||
"offset": 0,
|
||||
"shadows_saturation": 0.9,
|
||||
"shadows_contrast": 1.1,
|
||||
"shadows_tint": "#1A3D5C",
|
||||
"highlights_saturation": 1.1,
|
||||
"highlights_tint": "#FFE4C4",
|
||||
"toe": 0.55,
|
||||
"shoulder": 0.26,
|
||||
"black_clip": 0.02,
|
||||
"white_clip": 0.04
|
||||
},
|
||||
"vignette": {
|
||||
"intensity": 0.3
|
||||
},
|
||||
"chromatic_aberration": {
|
||||
"intensity": 0.1,
|
||||
"start_offset": 0.5
|
||||
},
|
||||
"film_grain": {
|
||||
"intensity": 0.15,
|
||||
"response": 0.8
|
||||
},
|
||||
"lut": null
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
{
|
||||
"$schema": "./recipe.schema.json",
|
||||
"recipe_id": "living_room_modern",
|
||||
"name": "Modern Living Room",
|
||||
"description": "Contemporary living room with sofa, coffee table, TV, and accent lighting",
|
||||
"category": "interior",
|
||||
"template_map": "Template_Interior",
|
||||
"components": [
|
||||
{
|
||||
"asset": "sofa_modern_3seat",
|
||||
"position": "center",
|
||||
"facing": "focal_point",
|
||||
"placement": "floor"
|
||||
},
|
||||
{
|
||||
"asset": "coffee_table_rect_glass",
|
||||
"position": "in_front_of:sofa_modern_3seat",
|
||||
"offset": [0, 100, 0],
|
||||
"placement": "floor"
|
||||
},
|
||||
{
|
||||
"asset": "tv_65inch_mounted",
|
||||
"position": "wall:opposite_sofa",
|
||||
"offset": [0, 0, 120],
|
||||
"placement": "wall",
|
||||
"facing": "sofa_modern_3seat"
|
||||
},
|
||||
{
|
||||
"asset": "tv_stand_modern",
|
||||
"position": "below:tv_65inch_mounted",
|
||||
"placement": "floor"
|
||||
},
|
||||
{
|
||||
"asset": "floor_lamp_arc",
|
||||
"position": "beside:sofa_modern_3seat",
|
||||
"offset": [-50, 0, 0],
|
||||
"placement": "floor"
|
||||
},
|
||||
{
|
||||
"asset": "side_table_round",
|
||||
"position": "beside:sofa_modern_3seat",
|
||||
"offset": [50, 0, 0],
|
||||
"placement": "floor"
|
||||
},
|
||||
{
|
||||
"asset": "plant_potted_fiddle",
|
||||
"position": "corner:back_left",
|
||||
"placement": "floor",
|
||||
"optional": true
|
||||
},
|
||||
{
|
||||
"asset": "rug_modern_geometric",
|
||||
"position": "under:coffee_table_rect_glass",
|
||||
"placement": "floor",
|
||||
"optional": true
|
||||
},
|
||||
{
|
||||
"asset": "book_stack_decorative",
|
||||
"position": "on:coffee_table_rect_glass",
|
||||
"placement": "surface",
|
||||
"optional": true
|
||||
}
|
||||
],
|
||||
"lighting_preset": "Interior_Evening",
|
||||
"post_preset": "PP_Warm",
|
||||
"camera_preset": "Interior_Wide",
|
||||
"tags": ["living room", "modern", "contemporary", "interior", "home"]
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
{
|
||||
"$schema": "./recipe.schema.json",
|
||||
"recipe_id": "office_corporate",
|
||||
"name": "Corporate Office",
|
||||
"description": "Professional office space with desk, computer, and meeting area",
|
||||
"category": "interior",
|
||||
"template_map": "Template_Interior",
|
||||
"components": [
|
||||
{
|
||||
"asset": "desk_executive",
|
||||
"position": "center_back",
|
||||
"facing": "camera",
|
||||
"placement": "floor"
|
||||
},
|
||||
{
|
||||
"asset": "chair_office_executive",
|
||||
"position": "behind:desk_executive",
|
||||
"facing": "camera",
|
||||
"placement": "floor"
|
||||
},
|
||||
{
|
||||
"asset": "monitor_27inch",
|
||||
"position": "on:desk_executive",
|
||||
"offset": [0, -20, 0],
|
||||
"placement": "surface"
|
||||
},
|
||||
{
|
||||
"asset": "keyboard_wireless",
|
||||
"position": "on:desk_executive",
|
||||
"offset": [0, 20, 0],
|
||||
"placement": "surface"
|
||||
},
|
||||
{
|
||||
"asset": "mouse_wireless",
|
||||
"position": "on:desk_executive",
|
||||
"offset": [30, 20, 0],
|
||||
"placement": "surface"
|
||||
},
|
||||
{
|
||||
"asset": "phone_desk",
|
||||
"position": "on:desk_executive",
|
||||
"offset": [-40, 10, 0],
|
||||
"placement": "surface",
|
||||
"optional": true
|
||||
},
|
||||
{
|
||||
"asset": "chair_guest",
|
||||
"position": "in_front_of:desk_executive",
|
||||
"offset": [-60, 150, 0],
|
||||
"facing": "desk_executive",
|
||||
"placement": "floor"
|
||||
},
|
||||
{
|
||||
"asset": "chair_guest",
|
||||
"position": "in_front_of:desk_executive",
|
||||
"offset": [60, 150, 0],
|
||||
"facing": "desk_executive",
|
||||
"placement": "floor"
|
||||
},
|
||||
{
|
||||
"asset": "bookshelf_modern",
|
||||
"position": "wall:left",
|
||||
"placement": "floor",
|
||||
"optional": true
|
||||
},
|
||||
{
|
||||
"asset": "plant_potted_snake",
|
||||
"position": "corner:back_right",
|
||||
"placement": "floor",
|
||||
"optional": true
|
||||
}
|
||||
],
|
||||
"lighting_preset": "Interior_Office",
|
||||
"post_preset": "PP_Neutral",
|
||||
"camera_preset": "Interior_Medium",
|
||||
"tags": ["office", "corporate", "business", "professional", "desk", "workspace"]
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
{
|
||||
"$schema": "./recipe.schema.json",
|
||||
"recipe_id": "portrait_studio_white",
|
||||
"name": "White Portrait Studio",
|
||||
"description": "Classic white cyclorama with 3-point lighting for portraits",
|
||||
"category": "portrait",
|
||||
"template_map": "Template_Studio",
|
||||
"components": [
|
||||
{
|
||||
"asset": "cyclorama_white",
|
||||
"position": [0, 0, 0],
|
||||
"scale": 1.0,
|
||||
"placement": "floor"
|
||||
},
|
||||
{
|
||||
"asset": "light_key_softbox",
|
||||
"position": [-200, 150, 200],
|
||||
"facing": "center",
|
||||
"placement": "floating"
|
||||
},
|
||||
{
|
||||
"asset": "light_fill_panel",
|
||||
"position": [150, 100, 180],
|
||||
"facing": "center",
|
||||
"placement": "floating"
|
||||
},
|
||||
{
|
||||
"asset": "light_rim_strip",
|
||||
"position": [0, -200, 220],
|
||||
"facing": "center",
|
||||
"placement": "floating"
|
||||
},
|
||||
{
|
||||
"asset": "apple_box_full",
|
||||
"position": [0, 0, 0],
|
||||
"placement": "floor",
|
||||
"optional": true
|
||||
}
|
||||
],
|
||||
"lighting_preset": "Studio_3Point_Soft",
|
||||
"post_preset": "PP_Portrait_Clean",
|
||||
"camera_preset": "Portrait_Headshot",
|
||||
"tags": ["portrait", "studio", "white", "cyclorama", "headshot", "professional"]
|
||||
}
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Ralpha Scene Recipe",
|
||||
"description": "Composite scene definition for instant spawning",
|
||||
"type": "object",
|
||||
"required": ["recipe_id", "name", "components"],
|
||||
"properties": {
|
||||
"recipe_id": {
|
||||
"type": "string",
|
||||
"pattern": "^[a-z][a-z0-9_]*$"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"category": {
|
||||
"type": "string",
|
||||
"enum": ["interior", "exterior", "portrait", "product", "vehicle", "custom"]
|
||||
},
|
||||
"template_map": {
|
||||
"type": "string",
|
||||
"description": "Base template map to use",
|
||||
"enum": ["Template_Empty", "Template_Studio", "Template_Outdoor", "Template_Interior"]
|
||||
},
|
||||
"components": {
|
||||
"type": "array",
|
||||
"items": { "$ref": "#/definitions/Component" }
|
||||
},
|
||||
"lighting_preset": {
|
||||
"type": "string",
|
||||
"description": "Lighting preset ID to apply"
|
||||
},
|
||||
"post_preset": {
|
||||
"type": "string",
|
||||
"description": "Post-processing preset ID to apply"
|
||||
},
|
||||
"camera_preset": {
|
||||
"type": "string",
|
||||
"description": "Camera preset ID to apply"
|
||||
},
|
||||
"sky_preset": {
|
||||
"type": "string",
|
||||
"description": "Sky/atmosphere preset to apply"
|
||||
},
|
||||
"dependencies": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
"description": "Other recipe IDs that must be spawned first"
|
||||
},
|
||||
"tags": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"Component": {
|
||||
"type": "object",
|
||||
"required": ["asset"],
|
||||
"properties": {
|
||||
"asset": {
|
||||
"type": "string",
|
||||
"description": "Asset ID from catalogue"
|
||||
},
|
||||
"position": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "array",
|
||||
"items": { "type": "number" },
|
||||
"minItems": 3,
|
||||
"maxItems": 3,
|
||||
"description": "Absolute [x, y, z] position"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Relative position (e.g., 'center', 'in_front_of:Sofa', 'wall:north')"
|
||||
}
|
||||
]
|
||||
},
|
||||
"rotation": {
|
||||
"type": "array",
|
||||
"items": { "type": "number" },
|
||||
"minItems": 3,
|
||||
"maxItems": 3,
|
||||
"description": "[pitch, yaw, roll] in degrees"
|
||||
},
|
||||
"scale": {
|
||||
"oneOf": [
|
||||
{ "type": "number" },
|
||||
{
|
||||
"type": "array",
|
||||
"items": { "type": "number" },
|
||||
"minItems": 3,
|
||||
"maxItems": 3
|
||||
}
|
||||
],
|
||||
"default": 1.0
|
||||
},
|
||||
"facing": {
|
||||
"type": "string",
|
||||
"description": "What the object should face",
|
||||
"enum": ["focal_point", "camera", "center", "north", "south", "east", "west", "away"]
|
||||
},
|
||||
"placement": {
|
||||
"type": "string",
|
||||
"enum": ["floor", "wall", "surface", "hanging", "floating"],
|
||||
"default": "floor"
|
||||
},
|
||||
"offset": {
|
||||
"type": "array",
|
||||
"items": { "type": "number" },
|
||||
"minItems": 3,
|
||||
"maxItems": 3,
|
||||
"description": "Offset from calculated position"
|
||||
},
|
||||
"attach_to": {
|
||||
"type": "string",
|
||||
"description": "Asset ID to attach to"
|
||||
},
|
||||
"socket": {
|
||||
"type": "string",
|
||||
"description": "Socket name for attachment"
|
||||
},
|
||||
"optional": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Skip if asset not found"
|
||||
},
|
||||
"variants": {
|
||||
"type": "object",
|
||||
"description": "Material/property overrides"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,186 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Generate catalogue.json from Unreal Engine assets.
|
||||
|
||||
This script can be run:
|
||||
1. Standalone with Python (parses .uasset files for metadata)
|
||||
2. As a wrapper for UE commandlet (for full asset inspection)
|
||||
|
||||
Usage:
|
||||
python generate_catalogue.py --project /path/to/Ralpha.uproject
|
||||
python generate_catalogue.py --scan-only # Just scan folders
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import hashlib
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
# Asset categories based on folder structure
|
||||
CATEGORY_MAP = {
|
||||
"Furniture/Seating": ["sofa", "chair", "stool", "bench", "armchair"],
|
||||
"Furniture/Tables": ["table", "desk", "counter"],
|
||||
"Furniture/Storage": ["shelf", "cabinet", "drawer", "wardrobe"],
|
||||
"Furniture/Bedroom": ["bed", "nightstand", "mattress"],
|
||||
"Electronics/Computing": ["laptop", "monitor", "keyboard", "mouse", "computer"],
|
||||
"Electronics/Entertainment": ["tv", "speaker", "console", "headphone"],
|
||||
"Electronics/Appliances": ["refrigerator", "microwave", "oven", "washer"],
|
||||
"Food/Packaged": ["can", "bottle", "box", "bag", "jar"],
|
||||
"Food/Fresh": ["fruit", "vegetable", "bread", "meat"],
|
||||
"Food/Prepared": ["pizza", "burger", "sandwich", "cake"],
|
||||
"Food/Beverages": ["coffee", "tea", "juice", "wine", "beer"],
|
||||
"Vehicles/Cars": ["sedan", "suv", "truck", "van", "sports"],
|
||||
"Vehicles/Motorcycles": ["motorcycle", "scooter", "bike"],
|
||||
"Nature/Trees": ["tree", "pine", "oak", "palm"],
|
||||
"Nature/Plants": ["plant", "flower", "bush", "grass"],
|
||||
"Nature/Rocks": ["rock", "boulder", "stone", "cliff"],
|
||||
}
|
||||
|
||||
|
||||
def scan_asset_folder(content_path: Path) -> list[dict[str, Any]]:
|
||||
"""Scan Content/Ralpha/Assets for .uasset files."""
|
||||
assets = []
|
||||
asset_root = content_path / "Ralpha" / "Assets"
|
||||
|
||||
if not asset_root.exists():
|
||||
print(f"Warning: Asset folder not found: {asset_root}")
|
||||
return assets
|
||||
|
||||
for uasset_path in asset_root.rglob("*.uasset"):
|
||||
# Skip generated files
|
||||
if "Intermediate" in str(uasset_path) or "Saved" in str(uasset_path):
|
||||
continue
|
||||
|
||||
rel_path = uasset_path.relative_to(content_path)
|
||||
category = str(rel_path.parent).replace("Ralpha/Assets/", "").replace("/", "/")
|
||||
|
||||
# Generate stable ID from path
|
||||
asset_name = uasset_path.stem
|
||||
asset_id = asset_name.lower().replace(" ", "_").replace("-", "_")
|
||||
|
||||
# Build Unreal path
|
||||
unreal_path = f"/Game/{str(rel_path.with_suffix('')).replace(os.sep, '/')}"
|
||||
|
||||
# Infer tags from name
|
||||
tags = [word.lower() for word in asset_name.replace("_", " ").split()]
|
||||
|
||||
asset = {
|
||||
"id": asset_id,
|
||||
"name": asset_name.replace("_", " ").title(),
|
||||
"category": category,
|
||||
"unreal_path": unreal_path,
|
||||
"tags": tags,
|
||||
"thumbnail": f"thumbnails/{asset_id}.png",
|
||||
"pivot": "bottom_center",
|
||||
"placement_modes": ["floor"],
|
||||
"collision": True,
|
||||
"license": {
|
||||
"type": "Unknown",
|
||||
"source": "TBD"
|
||||
},
|
||||
"added_at": datetime.utcnow().isoformat() + "Z"
|
||||
}
|
||||
|
||||
assets.append(asset)
|
||||
|
||||
return assets
|
||||
|
||||
|
||||
def run_ue_commandlet(project_path: Path, ue_path: Path | None = None) -> list[dict[str, Any]]:
|
||||
"""Run UE commandlet to extract full asset metadata."""
|
||||
# This would invoke the UE editor in commandlet mode
|
||||
# For now, just use scan method
|
||||
print("Note: Full UE commandlet integration not implemented yet")
|
||||
print("Using folder scan method instead")
|
||||
|
||||
content_path = project_path.parent / "Content"
|
||||
return scan_asset_folder(content_path)
|
||||
|
||||
|
||||
def generate_thumbnails(assets: list[dict], project_path: Path) -> None:
|
||||
"""Generate thumbnail images for assets (placeholder)."""
|
||||
thumbnail_dir = project_path.parent / "RalphaData" / "thumbnails"
|
||||
thumbnail_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
print(f"Thumbnail directory: {thumbnail_dir}")
|
||||
print("Note: Actual thumbnail generation requires UE Editor Utility")
|
||||
|
||||
# Create placeholder file
|
||||
placeholder = thumbnail_dir / ".gitkeep"
|
||||
placeholder.touch()
|
||||
|
||||
|
||||
def compute_catalogue_hash(assets: list[dict]) -> str:
|
||||
"""Compute hash of catalogue for version tracking."""
|
||||
content = json.dumps(sorted([a["id"] for a in assets]))
|
||||
return hashlib.sha256(content.encode()).hexdigest()[:12]
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Generate Ralpha asset catalogue")
|
||||
parser.add_argument("--project", type=Path, help="Path to .uproject file")
|
||||
parser.add_argument("--scan-only", action="store_true", help="Only scan folders, no UE")
|
||||
parser.add_argument("--output", type=Path, help="Output catalogue path")
|
||||
parser.add_argument("--ue-path", type=Path, help="Path to UE Editor executable")
|
||||
args = parser.parse_args()
|
||||
|
||||
# Find project
|
||||
if args.project:
|
||||
project_path = args.project
|
||||
else:
|
||||
# Default to current directory
|
||||
project_files = list(Path.cwd().glob("*.uproject"))
|
||||
if project_files:
|
||||
project_path = project_files[0]
|
||||
else:
|
||||
print("Error: No .uproject file found. Use --project to specify.")
|
||||
return 1
|
||||
|
||||
print(f"Project: {project_path}")
|
||||
|
||||
# Scan or run commandlet
|
||||
content_path = project_path.parent / "Content"
|
||||
if args.scan_only or not args.ue_path:
|
||||
assets = scan_asset_folder(content_path)
|
||||
else:
|
||||
assets = run_ue_commandlet(project_path, args.ue_path)
|
||||
|
||||
print(f"Found {len(assets)} assets")
|
||||
|
||||
# Build catalogue
|
||||
catalogue = {
|
||||
"$schema": "./catalogue.schema.json",
|
||||
"version": "1.0.0",
|
||||
"generated_at": datetime.utcnow().isoformat() + "Z",
|
||||
"asset_count": len(assets),
|
||||
"catalogue_hash": compute_catalogue_hash(assets),
|
||||
"categories": sorted(list(set(a["category"] for a in assets))),
|
||||
"assets": sorted(assets, key=lambda a: (a["category"], a["id"]))
|
||||
}
|
||||
|
||||
# Output
|
||||
if args.output:
|
||||
output_path = args.output
|
||||
else:
|
||||
output_path = project_path.parent / "RalphaData" / "catalogue" / "catalogue.json"
|
||||
|
||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
with open(output_path, "w") as f:
|
||||
json.dump(catalogue, f, indent=2)
|
||||
|
||||
print(f"Catalogue written to: {output_path}")
|
||||
print(f"Hash: {catalogue['catalogue_hash']}")
|
||||
|
||||
# Generate thumbnails placeholder
|
||||
generate_thumbnails(assets, project_path)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
exit(main())
|
||||
Loading…
Reference in New Issue