776 lines
28 KiB
TypeScript
776 lines
28 KiB
TypeScript
import { ToolDefinition, ToolResponse, ToolExecutor } from '../types';
|
|
|
|
export class SceneAdvancedTools implements ToolExecutor {
|
|
getTools(): ToolDefinition[] {
|
|
return [
|
|
{
|
|
name: 'reset_node_property',
|
|
description: 'Reset node property to default value',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
uuid: {
|
|
type: 'string',
|
|
description: 'Node UUID'
|
|
},
|
|
path: {
|
|
type: 'string',
|
|
description: 'Property path (e.g., position, rotation, scale)'
|
|
}
|
|
},
|
|
required: ['uuid', 'path']
|
|
}
|
|
},
|
|
{
|
|
name: 'move_array_element',
|
|
description: 'Move array element position',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
uuid: {
|
|
type: 'string',
|
|
description: 'Node UUID'
|
|
},
|
|
path: {
|
|
type: 'string',
|
|
description: 'Array property path (e.g., __comps__)'
|
|
},
|
|
target: {
|
|
type: 'number',
|
|
description: 'Target item original index'
|
|
},
|
|
offset: {
|
|
type: 'number',
|
|
description: 'Offset amount (positive or negative)'
|
|
}
|
|
},
|
|
required: ['uuid', 'path', 'target', 'offset']
|
|
}
|
|
},
|
|
{
|
|
name: 'remove_array_element',
|
|
description: 'Remove array element at specific index',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
uuid: {
|
|
type: 'string',
|
|
description: 'Node UUID'
|
|
},
|
|
path: {
|
|
type: 'string',
|
|
description: 'Array property path'
|
|
},
|
|
index: {
|
|
type: 'number',
|
|
description: 'Target item index to remove'
|
|
}
|
|
},
|
|
required: ['uuid', 'path', 'index']
|
|
}
|
|
},
|
|
{
|
|
name: 'copy_node',
|
|
description: 'Copy node for later paste operation',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
uuids: {
|
|
oneOf: [
|
|
{ type: 'string' },
|
|
{ type: 'array', items: { type: 'string' } }
|
|
],
|
|
description: 'Node UUID or array of UUIDs to copy'
|
|
}
|
|
},
|
|
required: ['uuids']
|
|
}
|
|
},
|
|
{
|
|
name: 'paste_node',
|
|
description: 'Paste previously copied nodes',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
target: {
|
|
type: 'string',
|
|
description: 'Target parent node UUID'
|
|
},
|
|
uuids: {
|
|
oneOf: [
|
|
{ type: 'string' },
|
|
{ type: 'array', items: { type: 'string' } }
|
|
],
|
|
description: 'Node UUIDs to paste'
|
|
},
|
|
keepWorldTransform: {
|
|
type: 'boolean',
|
|
description: 'Keep world transform coordinates',
|
|
default: false
|
|
}
|
|
},
|
|
required: ['target', 'uuids']
|
|
}
|
|
},
|
|
{
|
|
name: 'cut_node',
|
|
description: 'Cut node (copy + mark for move)',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
uuids: {
|
|
oneOf: [
|
|
{ type: 'string' },
|
|
{ type: 'array', items: { type: 'string' } }
|
|
],
|
|
description: 'Node UUID or array of UUIDs to cut'
|
|
}
|
|
},
|
|
required: ['uuids']
|
|
}
|
|
},
|
|
{
|
|
name: 'reset_node_transform',
|
|
description: 'Reset node position, rotation and scale',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
uuid: {
|
|
type: 'string',
|
|
description: 'Node UUID'
|
|
}
|
|
},
|
|
required: ['uuid']
|
|
}
|
|
},
|
|
{
|
|
name: 'reset_component',
|
|
description: 'Reset component to default values',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
uuid: {
|
|
type: 'string',
|
|
description: 'Component UUID'
|
|
}
|
|
},
|
|
required: ['uuid']
|
|
}
|
|
},
|
|
{
|
|
name: 'restore_prefab',
|
|
description: 'Restore prefab instance from asset',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
nodeUuid: {
|
|
type: 'string',
|
|
description: 'Node UUID'
|
|
},
|
|
assetUuid: {
|
|
type: 'string',
|
|
description: 'Prefab asset UUID'
|
|
}
|
|
},
|
|
required: ['nodeUuid', 'assetUuid']
|
|
}
|
|
},
|
|
{
|
|
name: 'execute_component_method',
|
|
description: 'Execute method on component',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
uuid: {
|
|
type: 'string',
|
|
description: 'Component UUID'
|
|
},
|
|
name: {
|
|
type: 'string',
|
|
description: 'Method name'
|
|
},
|
|
args: {
|
|
type: 'array',
|
|
description: 'Method arguments',
|
|
default: []
|
|
}
|
|
},
|
|
required: ['uuid', 'name']
|
|
}
|
|
},
|
|
{
|
|
name: 'execute_scene_script',
|
|
description: 'Execute scene script method',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
name: {
|
|
type: 'string',
|
|
description: 'Plugin name'
|
|
},
|
|
method: {
|
|
type: 'string',
|
|
description: 'Method name'
|
|
},
|
|
args: {
|
|
type: 'array',
|
|
description: 'Method arguments',
|
|
default: []
|
|
}
|
|
},
|
|
required: ['name', 'method']
|
|
}
|
|
},
|
|
{
|
|
name: 'scene_snapshot',
|
|
description: 'Create scene state snapshot',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {}
|
|
}
|
|
},
|
|
{
|
|
name: 'scene_snapshot_abort',
|
|
description: 'Abort scene snapshot creation',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {}
|
|
}
|
|
},
|
|
{
|
|
name: 'begin_undo_recording',
|
|
description: 'Begin recording undo data',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
nodeUuid: {
|
|
type: 'string',
|
|
description: 'Node UUID to record'
|
|
}
|
|
},
|
|
required: ['nodeUuid']
|
|
}
|
|
},
|
|
{
|
|
name: 'end_undo_recording',
|
|
description: 'End recording undo data',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
undoId: {
|
|
type: 'string',
|
|
description: 'Undo recording ID from begin_undo_recording'
|
|
}
|
|
},
|
|
required: ['undoId']
|
|
}
|
|
},
|
|
{
|
|
name: 'cancel_undo_recording',
|
|
description: 'Cancel undo recording',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
undoId: {
|
|
type: 'string',
|
|
description: 'Undo recording ID to cancel'
|
|
}
|
|
},
|
|
required: ['undoId']
|
|
}
|
|
},
|
|
{
|
|
name: 'soft_reload_scene',
|
|
description: 'Soft reload current scene',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {}
|
|
}
|
|
},
|
|
{
|
|
name: 'query_scene_ready',
|
|
description: 'Check if scene is ready',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {}
|
|
}
|
|
},
|
|
{
|
|
name: 'query_scene_dirty',
|
|
description: 'Check if scene has unsaved changes',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {}
|
|
}
|
|
},
|
|
{
|
|
name: 'query_scene_classes',
|
|
description: 'Query all registered classes',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
extends: {
|
|
type: 'string',
|
|
description: 'Filter classes that extend this base class'
|
|
}
|
|
}
|
|
}
|
|
},
|
|
{
|
|
name: 'query_scene_components',
|
|
description: 'Query available scene components',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {}
|
|
}
|
|
},
|
|
{
|
|
name: 'query_component_has_script',
|
|
description: 'Check if component has script',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
className: {
|
|
type: 'string',
|
|
description: 'Script class name to check'
|
|
}
|
|
},
|
|
required: ['className']
|
|
}
|
|
},
|
|
{
|
|
name: 'query_nodes_by_asset_uuid',
|
|
description: 'Find nodes that use specific asset UUID',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
assetUuid: {
|
|
type: 'string',
|
|
description: 'Asset UUID to search for'
|
|
}
|
|
},
|
|
required: ['assetUuid']
|
|
}
|
|
}
|
|
];
|
|
}
|
|
|
|
async execute(toolName: string, args: any): Promise<ToolResponse> {
|
|
switch (toolName) {
|
|
case 'reset_node_property':
|
|
return await this.resetNodeProperty(args.uuid, args.path);
|
|
case 'move_array_element':
|
|
return await this.moveArrayElement(args.uuid, args.path, args.target, args.offset);
|
|
case 'remove_array_element':
|
|
return await this.removeArrayElement(args.uuid, args.path, args.index);
|
|
case 'copy_node':
|
|
return await this.copyNode(args.uuids);
|
|
case 'paste_node':
|
|
return await this.pasteNode(args.target, args.uuids, args.keepWorldTransform);
|
|
case 'cut_node':
|
|
return await this.cutNode(args.uuids);
|
|
case 'reset_node_transform':
|
|
return await this.resetNodeTransform(args.uuid);
|
|
case 'reset_component':
|
|
return await this.resetComponent(args.uuid);
|
|
case 'restore_prefab':
|
|
return await this.restorePrefab(args.nodeUuid, args.assetUuid);
|
|
case 'execute_component_method':
|
|
return await this.executeComponentMethod(args.uuid, args.name, args.args);
|
|
case 'execute_scene_script':
|
|
return await this.executeSceneScript(args.name, args.method, args.args);
|
|
case 'scene_snapshot':
|
|
return await this.sceneSnapshot();
|
|
case 'scene_snapshot_abort':
|
|
return await this.sceneSnapshotAbort();
|
|
case 'begin_undo_recording':
|
|
return await this.beginUndoRecording(args.nodeUuid);
|
|
case 'end_undo_recording':
|
|
return await this.endUndoRecording(args.undoId);
|
|
case 'cancel_undo_recording':
|
|
return await this.cancelUndoRecording(args.undoId);
|
|
case 'soft_reload_scene':
|
|
return await this.softReloadScene();
|
|
case 'query_scene_ready':
|
|
return await this.querySceneReady();
|
|
case 'query_scene_dirty':
|
|
return await this.querySceneDirty();
|
|
case 'query_scene_classes':
|
|
return await this.querySceneClasses(args.extends);
|
|
case 'query_scene_components':
|
|
return await this.querySceneComponents();
|
|
case 'query_component_has_script':
|
|
return await this.queryComponentHasScript(args.className);
|
|
case 'query_nodes_by_asset_uuid':
|
|
return await this.queryNodesByAssetUuid(args.assetUuid);
|
|
default:
|
|
throw new Error(`Unknown tool: ${toolName}`);
|
|
}
|
|
}
|
|
|
|
private async resetNodeProperty(uuid: string, path: string): Promise<ToolResponse> {
|
|
return new Promise((resolve) => {
|
|
Editor.Message.request('scene', 'reset-property', {
|
|
uuid,
|
|
path,
|
|
dump: { value: null }
|
|
}).then(() => {
|
|
resolve({
|
|
success: true,
|
|
message: `Property '${path}' reset to default value`
|
|
});
|
|
}).catch((err: Error) => {
|
|
resolve({ success: false, error: err.message });
|
|
});
|
|
});
|
|
}
|
|
|
|
private async moveArrayElement(uuid: string, path: string, target: number, offset: number): Promise<ToolResponse> {
|
|
return new Promise((resolve) => {
|
|
Editor.Message.request('scene', 'move-array-element', {
|
|
uuid,
|
|
path,
|
|
target,
|
|
offset
|
|
}).then(() => {
|
|
resolve({
|
|
success: true,
|
|
message: `Array element at index ${target} moved by ${offset}`
|
|
});
|
|
}).catch((err: Error) => {
|
|
resolve({ success: false, error: err.message });
|
|
});
|
|
});
|
|
}
|
|
|
|
private async removeArrayElement(uuid: string, path: string, index: number): Promise<ToolResponse> {
|
|
return new Promise((resolve) => {
|
|
Editor.Message.request('scene', 'remove-array-element', {
|
|
uuid,
|
|
path,
|
|
index
|
|
}).then(() => {
|
|
resolve({
|
|
success: true,
|
|
message: `Array element at index ${index} removed`
|
|
});
|
|
}).catch((err: Error) => {
|
|
resolve({ success: false, error: err.message });
|
|
});
|
|
});
|
|
}
|
|
|
|
private async copyNode(uuids: string | string[]): Promise<ToolResponse> {
|
|
return new Promise((resolve) => {
|
|
Editor.Message.request('scene', 'copy-node', uuids).then((result: string | string[]) => {
|
|
resolve({
|
|
success: true,
|
|
data: {
|
|
copiedUuids: result,
|
|
message: 'Node(s) copied successfully'
|
|
}
|
|
});
|
|
}).catch((err: Error) => {
|
|
resolve({ success: false, error: err.message });
|
|
});
|
|
});
|
|
}
|
|
|
|
private async pasteNode(target: string, uuids: string | string[], keepWorldTransform: boolean = false): Promise<ToolResponse> {
|
|
return new Promise((resolve) => {
|
|
Editor.Message.request('scene', 'paste-node', {
|
|
target,
|
|
uuids,
|
|
keepWorldTransform
|
|
}).then((result: string | string[]) => {
|
|
resolve({
|
|
success: true,
|
|
data: {
|
|
newUuids: result,
|
|
message: 'Node(s) pasted successfully'
|
|
}
|
|
});
|
|
}).catch((err: Error) => {
|
|
resolve({ success: false, error: err.message });
|
|
});
|
|
});
|
|
}
|
|
|
|
private async cutNode(uuids: string | string[]): Promise<ToolResponse> {
|
|
return new Promise((resolve) => {
|
|
Editor.Message.request('scene', 'cut-node', uuids).then((result: any) => {
|
|
resolve({
|
|
success: true,
|
|
data: {
|
|
cutUuids: result,
|
|
message: 'Node(s) cut successfully'
|
|
}
|
|
});
|
|
}).catch((err: Error) => {
|
|
resolve({ success: false, error: err.message });
|
|
});
|
|
});
|
|
}
|
|
|
|
private async resetNodeTransform(uuid: string): Promise<ToolResponse> {
|
|
return new Promise((resolve) => {
|
|
Editor.Message.request('scene', 'reset-node', { uuid }).then(() => {
|
|
resolve({
|
|
success: true,
|
|
message: 'Node transform reset to default'
|
|
});
|
|
}).catch((err: Error) => {
|
|
resolve({ success: false, error: err.message });
|
|
});
|
|
});
|
|
}
|
|
|
|
private async resetComponent(uuid: string): Promise<ToolResponse> {
|
|
return new Promise((resolve) => {
|
|
Editor.Message.request('scene', 'reset-component', { uuid }).then(() => {
|
|
resolve({
|
|
success: true,
|
|
message: 'Component reset to default values'
|
|
});
|
|
}).catch((err: Error) => {
|
|
resolve({ success: false, error: err.message });
|
|
});
|
|
});
|
|
}
|
|
|
|
private async restorePrefab(nodeUuid: string, assetUuid: string): Promise<ToolResponse> {
|
|
return new Promise((resolve) => {
|
|
(Editor.Message.request as any)('scene', 'restore-prefab', nodeUuid, assetUuid).then(() => {
|
|
resolve({
|
|
success: true,
|
|
message: 'Prefab restored successfully'
|
|
});
|
|
}).catch((err: Error) => {
|
|
resolve({ success: false, error: err.message });
|
|
});
|
|
});
|
|
}
|
|
|
|
private async executeComponentMethod(uuid: string, name: string, args: any[] = []): Promise<ToolResponse> {
|
|
return new Promise((resolve) => {
|
|
Editor.Message.request('scene', 'execute-component-method', {
|
|
uuid,
|
|
name,
|
|
args
|
|
}).then((result: any) => {
|
|
resolve({
|
|
success: true,
|
|
data: {
|
|
result: result,
|
|
message: `Method '${name}' executed successfully`
|
|
}
|
|
});
|
|
}).catch((err: Error) => {
|
|
resolve({ success: false, error: err.message });
|
|
});
|
|
});
|
|
}
|
|
|
|
private async executeSceneScript(name: string, method: string, args: any[] = []): Promise<ToolResponse> {
|
|
return new Promise((resolve) => {
|
|
Editor.Message.request('scene', 'execute-scene-script', {
|
|
name,
|
|
method,
|
|
args
|
|
}).then((result: any) => {
|
|
resolve({
|
|
success: true,
|
|
data: result
|
|
});
|
|
}).catch((err: Error) => {
|
|
resolve({ success: false, error: err.message });
|
|
});
|
|
});
|
|
}
|
|
|
|
private async sceneSnapshot(): Promise<ToolResponse> {
|
|
return new Promise((resolve) => {
|
|
Editor.Message.request('scene', 'snapshot').then(() => {
|
|
resolve({
|
|
success: true,
|
|
message: 'Scene snapshot created'
|
|
});
|
|
}).catch((err: Error) => {
|
|
resolve({ success: false, error: err.message });
|
|
});
|
|
});
|
|
}
|
|
|
|
private async sceneSnapshotAbort(): Promise<ToolResponse> {
|
|
return new Promise((resolve) => {
|
|
Editor.Message.request('scene', 'snapshot-abort').then(() => {
|
|
resolve({
|
|
success: true,
|
|
message: 'Scene snapshot aborted'
|
|
});
|
|
}).catch((err: Error) => {
|
|
resolve({ success: false, error: err.message });
|
|
});
|
|
});
|
|
}
|
|
|
|
private async beginUndoRecording(nodeUuid: string): Promise<ToolResponse> {
|
|
return new Promise((resolve) => {
|
|
Editor.Message.request('scene', 'begin-recording', nodeUuid).then((undoId: string) => {
|
|
resolve({
|
|
success: true,
|
|
data: {
|
|
undoId: undoId,
|
|
message: 'Undo recording started'
|
|
}
|
|
});
|
|
}).catch((err: Error) => {
|
|
resolve({ success: false, error: err.message });
|
|
});
|
|
});
|
|
}
|
|
|
|
private async endUndoRecording(undoId: string): Promise<ToolResponse> {
|
|
return new Promise((resolve) => {
|
|
Editor.Message.request('scene', 'end-recording', undoId).then(() => {
|
|
resolve({
|
|
success: true,
|
|
message: 'Undo recording ended'
|
|
});
|
|
}).catch((err: Error) => {
|
|
resolve({ success: false, error: err.message });
|
|
});
|
|
});
|
|
}
|
|
|
|
private async cancelUndoRecording(undoId: string): Promise<ToolResponse> {
|
|
return new Promise((resolve) => {
|
|
Editor.Message.request('scene', 'cancel-recording', undoId).then(() => {
|
|
resolve({
|
|
success: true,
|
|
message: 'Undo recording cancelled'
|
|
});
|
|
}).catch((err: Error) => {
|
|
resolve({ success: false, error: err.message });
|
|
});
|
|
});
|
|
}
|
|
|
|
private async softReloadScene(): Promise<ToolResponse> {
|
|
return new Promise((resolve) => {
|
|
Editor.Message.request('scene', 'soft-reload').then(() => {
|
|
resolve({
|
|
success: true,
|
|
message: 'Scene soft reloaded successfully'
|
|
});
|
|
}).catch((err: Error) => {
|
|
resolve({ success: false, error: err.message });
|
|
});
|
|
});
|
|
}
|
|
|
|
private async querySceneReady(): Promise<ToolResponse> {
|
|
return new Promise((resolve) => {
|
|
Editor.Message.request('scene', 'query-is-ready').then((ready: boolean) => {
|
|
resolve({
|
|
success: true,
|
|
data: {
|
|
ready: ready,
|
|
message: ready ? 'Scene is ready' : 'Scene is not ready'
|
|
}
|
|
});
|
|
}).catch((err: Error) => {
|
|
resolve({ success: false, error: err.message });
|
|
});
|
|
});
|
|
}
|
|
|
|
private async querySceneDirty(): Promise<ToolResponse> {
|
|
return new Promise((resolve) => {
|
|
Editor.Message.request('scene', 'query-dirty').then((dirty: boolean) => {
|
|
resolve({
|
|
success: true,
|
|
data: {
|
|
dirty: dirty,
|
|
message: dirty ? 'Scene has unsaved changes' : 'Scene is clean'
|
|
}
|
|
});
|
|
}).catch((err: Error) => {
|
|
resolve({ success: false, error: err.message });
|
|
});
|
|
});
|
|
}
|
|
|
|
private async querySceneClasses(extendsClass?: string): Promise<ToolResponse> {
|
|
return new Promise((resolve) => {
|
|
const options: any = {};
|
|
if (extendsClass) {
|
|
options.extends = extendsClass;
|
|
}
|
|
|
|
Editor.Message.request('scene', 'query-classes', options).then((classes: any[]) => {
|
|
resolve({
|
|
success: true,
|
|
data: {
|
|
classes: classes,
|
|
count: classes.length,
|
|
extendsFilter: extendsClass
|
|
}
|
|
});
|
|
}).catch((err: Error) => {
|
|
resolve({ success: false, error: err.message });
|
|
});
|
|
});
|
|
}
|
|
|
|
private async querySceneComponents(): Promise<ToolResponse> {
|
|
return new Promise((resolve) => {
|
|
Editor.Message.request('scene', 'query-components').then((components: any[]) => {
|
|
resolve({
|
|
success: true,
|
|
data: {
|
|
components: components,
|
|
count: components.length
|
|
}
|
|
});
|
|
}).catch((err: Error) => {
|
|
resolve({ success: false, error: err.message });
|
|
});
|
|
});
|
|
}
|
|
|
|
private async queryComponentHasScript(className: string): Promise<ToolResponse> {
|
|
return new Promise((resolve) => {
|
|
Editor.Message.request('scene', 'query-component-has-script', className).then((hasScript: boolean) => {
|
|
resolve({
|
|
success: true,
|
|
data: {
|
|
className: className,
|
|
hasScript: hasScript,
|
|
message: hasScript ? `Component '${className}' has script` : `Component '${className}' does not have script`
|
|
}
|
|
});
|
|
}).catch((err: Error) => {
|
|
resolve({ success: false, error: err.message });
|
|
});
|
|
});
|
|
}
|
|
|
|
private async queryNodesByAssetUuid(assetUuid: string): Promise<ToolResponse> {
|
|
return new Promise((resolve) => {
|
|
Editor.Message.request('scene', 'query-nodes-by-asset-uuid', assetUuid).then((nodeUuids: string[]) => {
|
|
resolve({
|
|
success: true,
|
|
data: {
|
|
assetUuid: assetUuid,
|
|
nodeUuids: nodeUuids,
|
|
count: nodeUuids.length,
|
|
message: `Found ${nodeUuids.length} nodes using asset`
|
|
}
|
|
});
|
|
}).catch((err: Error) => {
|
|
resolve({ success: false, error: err.message });
|
|
});
|
|
});
|
|
}
|
|
} |