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
|
*.dll filter=lfs diff=lfs merge=lfs -text
|
||||||
*.so filter=lfs diff=lfs merge=lfs -text
|
*.so filter=lfs diff=lfs merge=lfs -text
|
||||||
*.dylib 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": [
|
"TargetPlatforms": [
|
||||||
|
"Mac",
|
||||||
"Windows"
|
"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