219 lines
6.7 KiB
C#
219 lines
6.7 KiB
C#
#if TOOLS
|
|
|
|
using Godot;
|
|
|
|
namespace Rokojori.Tools
|
|
{
|
|
using Rokojori;
|
|
|
|
public static class WriteSceneFragmentRoute
|
|
{
|
|
public static JSONObject ToolDefinition()
|
|
{
|
|
var filePathSchema = new JSONObject();
|
|
filePathSchema.Set( "type", "string" );
|
|
filePathSchema.Set( "description", "res:// path to the scene (.tscn)" );
|
|
|
|
var scenePathSchema = new JSONObject();
|
|
scenePathSchema.Set( "type", "array" );
|
|
scenePathSchema.Set( "description", "Child names from the scene root to the node the fragment patches. Empty array = scene root. The node itself must already exist - only its descendants can be newly created." );
|
|
|
|
var fragmentSchema = new JSONObject();
|
|
fragmentSchema.Set( "type", "object" );
|
|
fragmentSchema.Set( "description", "Patch data: { properties: {...}, children: { childName: {...} | { \"__remove\": true } } }. Children are keyed by name, omitted children are left untouched." );
|
|
|
|
var properties = new JSONObject();
|
|
properties.Set( "filePath", filePathSchema );
|
|
properties.Set( "scenePath", scenePathSchema );
|
|
properties.Set( "sceneFragmentData", fragmentSchema );
|
|
|
|
var required = new JSONArray();
|
|
required.Push( "filePath" );
|
|
required.Push( "scenePath" );
|
|
required.Push( "sceneFragmentData" );
|
|
|
|
var schema = new JSONObject();
|
|
schema.Set( "type", "object" );
|
|
schema.Set( "properties", properties );
|
|
schema.Set( "required", required );
|
|
|
|
var tool = new JSONObject();
|
|
tool.Set( "name", "write_scene_fragment" );
|
|
tool.Set( "description", "Patch a node subtree in a scene file: set properties, add/patch/remove children by name." );
|
|
tool.Set( "inputSchema", schema );
|
|
|
|
return tool;
|
|
}
|
|
|
|
public static ToolCallOutcome Execute( JSONObject arguments )
|
|
{
|
|
var filePath = JsonUtil.GetStringOrNull( arguments, "filePath" );
|
|
var scenePathArray = arguments != null ? arguments.GetArray( "scenePath" ) : null;
|
|
var fragmentData = arguments != null ? arguments.GetObject( "sceneFragmentData" ) : null;
|
|
|
|
if ( string.IsNullOrEmpty( filePath ) )
|
|
{
|
|
return ToolCallOutcome.Error( "Missing required argument: filePath" );
|
|
}
|
|
|
|
if ( fragmentData == null )
|
|
{
|
|
return ToolCallOutcome.Error( "Missing required argument: sceneFragmentData" );
|
|
}
|
|
|
|
if ( ! ResourceLoader.Exists( filePath, "PackedScene" ) )
|
|
{
|
|
return ToolCallOutcome.Error( "Scene not found: " + filePath );
|
|
}
|
|
|
|
var packedScene = ResourceLoader.Load<PackedScene>( filePath );
|
|
|
|
if ( packedScene == null )
|
|
{
|
|
return ToolCallOutcome.Error( "Failed to load scene: " + filePath );
|
|
}
|
|
|
|
// Plain Instantiate() resolves every property on every node to a concrete value, including
|
|
// ones a node only inherits from an instanced sub-scene. Pack() then has no way to tell
|
|
// "inherited" apart from "actually overridden" and bakes the current value of every property
|
|
// on every instanced node in the whole tree into an explicit override - corrupting unrelated
|
|
// sub-scenes (e.g. a vehicle model) far outside the patched fragment. GenEditState.Main keeps
|
|
// the instance-vs-override bookkeeping alive so Pack() only stores genuine overrides, matching
|
|
// how the editor itself instantiates a scene it's about to re-save as the main edited scene.
|
|
var root = packedScene.Instantiate( PackedScene.GenEditState.Main );
|
|
|
|
if ( root == null )
|
|
{
|
|
return ToolCallOutcome.Error( "Failed to instantiate scene: " + filePath );
|
|
}
|
|
|
|
var scenePath = ScenePathResolver.ToStringArray( scenePathArray );
|
|
var target = ScenePathResolver.FindNode( root, scenePath );
|
|
|
|
if ( target == null )
|
|
{
|
|
root.Free();
|
|
return ToolCallOutcome.Error( "Node not found at scenePath: " + ScenePathResolver.ToRelativePath( scenePath ) );
|
|
}
|
|
|
|
ApplyFragment( root, target, fragmentData );
|
|
|
|
var packError = packedScene.Pack( root );
|
|
|
|
if ( packError != Error.Ok )
|
|
{
|
|
root.Free();
|
|
return ToolCallOutcome.Error( "Failed to pack scene: " + packError );
|
|
}
|
|
|
|
var saveError = ResourceSaver.Save( packedScene, filePath );
|
|
root.Free();
|
|
|
|
if ( saveError != Error.Ok )
|
|
{
|
|
return ToolCallOutcome.Error( "Failed to save scene: " + saveError );
|
|
}
|
|
|
|
var result = new JSONObject();
|
|
result.Set( "filePath", filePath );
|
|
result.Set( "scenePath", ScenePathResolver.ToRelativePath( scenePath ) );
|
|
|
|
return ToolCallOutcome.Ok( JSON.Stringify( result ) );
|
|
}
|
|
|
|
static void ApplyFragment( Node root, Node target, JSONObject fragmentData )
|
|
{
|
|
var properties = fragmentData.GetObject( "properties" );
|
|
|
|
if ( properties != null )
|
|
{
|
|
PropertyReflection.ApplyFromJson( target, properties );
|
|
}
|
|
|
|
var children = fragmentData.GetObject( "children" );
|
|
|
|
if ( children == null )
|
|
{
|
|
return;
|
|
}
|
|
|
|
for ( var i = 0; i < children.keys.Count; i++ )
|
|
{
|
|
var childName = children.keys[ i ];
|
|
var childData = children.values[ i ].AsObject();
|
|
|
|
if ( childData == null )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var existingChild = FindDirectChild( target, childName );
|
|
|
|
if ( childData.Contains( "__remove" ) )
|
|
{
|
|
if ( existingChild != null )
|
|
{
|
|
target.RemoveChild( existingChild );
|
|
existingChild.Free();
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
if ( existingChild != null )
|
|
{
|
|
ApplyFragment( root, existingChild, childData );
|
|
continue;
|
|
}
|
|
|
|
var typeName = JsonUtil.GetStringOrNull( childData, "type" );
|
|
|
|
if ( string.IsNullOrEmpty( typeName ) )
|
|
{
|
|
RJLog.Error( "MCP WriteSceneFragment: new child needs a 'type'", childName );
|
|
continue;
|
|
}
|
|
|
|
if ( ! ClassDB.ClassExists( typeName ) )
|
|
{
|
|
RJLog.Error( "MCP WriteSceneFragment: unknown class", typeName );
|
|
continue;
|
|
}
|
|
|
|
var newNode = ClassDB.Instantiate( typeName ).As<Node>();
|
|
|
|
if ( newNode == null )
|
|
{
|
|
RJLog.Error( "MCP WriteSceneFragment: class is not a Node", typeName );
|
|
continue;
|
|
}
|
|
|
|
newNode.Name = childName;
|
|
target.AddChild( newNode );
|
|
newNode.Owner = root;
|
|
|
|
ApplyFragment( root, newNode, childData );
|
|
}
|
|
}
|
|
|
|
static Node FindDirectChild( Node parent, string name )
|
|
{
|
|
var count = parent.GetChildCount( false );
|
|
|
|
for ( var i = 0; i < count; i++ )
|
|
{
|
|
var child = parent.GetChild( i, false );
|
|
|
|
if ( child.Name.ToString() == name )
|
|
{
|
|
return child;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|