#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( 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(); 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