202 lines
6.2 KiB
GDScript
202 lines
6.2 KiB
GDScript
@tool
|
|
extends "res://addons/godot_mcp/commands/base_command.gd"
|
|
|
|
const PropertyParser := preload("res://addons/godot_mcp/utils/property_parser.gd")
|
|
|
|
|
|
func get_commands() -> Dictionary:
|
|
return {
|
|
"read_resource": _read_resource,
|
|
"edit_resource": _edit_resource,
|
|
"create_resource": _create_resource,
|
|
"get_resource_preview": _get_resource_preview,
|
|
}
|
|
|
|
|
|
func _read_resource(params: Dictionary) -> Dictionary:
|
|
var result := require_string(params, "path")
|
|
if result[1] != null:
|
|
return result[1]
|
|
var path: String = result[0]
|
|
|
|
if not FileAccess.file_exists(path):
|
|
return error_not_found("Resource '%s'" % path)
|
|
|
|
var guard := guard_offline_scene_save(path)
|
|
if not guard.is_empty():
|
|
return guard
|
|
|
|
var resource: Resource = ResourceLoader.load(path)
|
|
if resource == null:
|
|
return error_internal("Failed to load resource: %s" % path)
|
|
|
|
var props: Dictionary = {}
|
|
for prop_info in resource.get_property_list():
|
|
var prop_name: String = prop_info["name"]
|
|
var usage: int = prop_info["usage"]
|
|
if not (usage & PROPERTY_USAGE_EDITOR):
|
|
continue
|
|
if prop_name.begins_with("_") or prop_name == "script" or prop_name == "resource_local_to_scene" or prop_name == "resource_name" or prop_name == "resource_path":
|
|
continue
|
|
props[prop_name] = PropertyParser.serialize_value(resource.get(prop_name))
|
|
|
|
return success({
|
|
"path": path,
|
|
"type": resource.get_class(),
|
|
"resource_name": resource.resource_name,
|
|
"properties": props,
|
|
})
|
|
|
|
|
|
func _edit_resource(params: Dictionary) -> Dictionary:
|
|
var result := require_string(params, "path")
|
|
if result[1] != null:
|
|
return result[1]
|
|
var path: String = result[0]
|
|
|
|
if not params.has("properties") or not params["properties"] is Dictionary:
|
|
return error_invalid_params("'properties' dictionary is required")
|
|
var new_props: Dictionary = params["properties"]
|
|
|
|
if not FileAccess.file_exists(path):
|
|
return error_not_found("Resource '%s'" % path)
|
|
|
|
var guard := guard_offline_scene_save(path)
|
|
if not guard.is_empty():
|
|
return guard
|
|
|
|
var resource: Resource = ResourceLoader.load(path)
|
|
if resource == null:
|
|
return error_internal("Failed to load resource: %s" % path)
|
|
|
|
var changed: Dictionary = {}
|
|
for prop_name: String in new_props:
|
|
if not prop_name in resource:
|
|
continue
|
|
var old_value: Variant = resource.get(prop_name)
|
|
var target_type := typeof(old_value)
|
|
var new_value: Variant = PropertyParser.parse_value(new_props[prop_name], target_type)
|
|
resource.set(prop_name, new_value)
|
|
changed[prop_name] = {
|
|
"old": PropertyParser.serialize_value(old_value),
|
|
"new": PropertyParser.serialize_value(resource.get(prop_name)),
|
|
}
|
|
|
|
if changed.is_empty():
|
|
return success({"path": path, "changed": {}, "message": "No properties were changed"})
|
|
|
|
var err := ResourceSaver.save(resource, path)
|
|
if err != OK:
|
|
return error_internal("Failed to save resource: %s" % error_string(err))
|
|
|
|
return success({
|
|
"path": path,
|
|
"type": resource.get_class(),
|
|
"changed": changed,
|
|
})
|
|
|
|
|
|
func _create_resource(params: Dictionary) -> Dictionary:
|
|
var result := require_string(params, "path")
|
|
if result[1] != null:
|
|
return result[1]
|
|
var path: String = result[0]
|
|
|
|
var result2 := require_string(params, "type")
|
|
if result2[1] != null:
|
|
return result2[1]
|
|
var resource_type: String = result2[0]
|
|
|
|
if not ClassDB.class_exists(resource_type):
|
|
return error_invalid_params("Unknown resource type: %s" % resource_type)
|
|
if not ClassDB.is_parent_class(resource_type, "Resource"):
|
|
return error_invalid_params("'%s' is not a Resource type" % resource_type)
|
|
|
|
var overwrite: bool = optional_bool(params, "overwrite", false)
|
|
if FileAccess.file_exists(path) and not overwrite:
|
|
return error(-32000, "Resource already exists: %s" % path, {"suggestion": "Set overwrite=true to replace"})
|
|
|
|
var guard := guard_offline_scene_save(path)
|
|
if not guard.is_empty():
|
|
return guard
|
|
|
|
var resource: Resource = ClassDB.instantiate(resource_type)
|
|
if resource == null:
|
|
return error_internal("Failed to instantiate: %s" % resource_type)
|
|
|
|
# Apply properties
|
|
var properties: Dictionary = params.get("properties", {})
|
|
for prop_name: String in properties:
|
|
if prop_name in resource:
|
|
var current: Variant = resource.get(prop_name)
|
|
resource.set(prop_name, PropertyParser.parse_value(properties[prop_name], typeof(current)))
|
|
|
|
var err := ResourceSaver.save(resource, path)
|
|
if err != OK:
|
|
return error_internal("Failed to save resource: %s" % error_string(err))
|
|
|
|
# Rescan filesystem
|
|
EditorInterface.get_resource_filesystem().scan()
|
|
|
|
return success({
|
|
"path": path,
|
|
"type": resource_type,
|
|
"properties_set": properties.keys(),
|
|
})
|
|
|
|
|
|
func _get_resource_preview(params: Dictionary) -> Dictionary:
|
|
var result := require_string(params, "path")
|
|
if result[1] != null:
|
|
return result[1]
|
|
var path: String = result[0]
|
|
|
|
if not FileAccess.file_exists(path):
|
|
return error_not_found("Resource '%s'" % path)
|
|
|
|
var max_size: int = optional_int(params, "max_size", 256)
|
|
var image: Image = null
|
|
|
|
# Try loading as image file directly
|
|
var ext := path.get_extension().to_lower()
|
|
if ext in ["png", "jpg", "jpeg", "bmp", "webp", "svg"]:
|
|
image = Image.new()
|
|
var err := image.load(path)
|
|
if err != OK:
|
|
return error_internal("Failed to load image: %s" % error_string(err))
|
|
else:
|
|
# Try loading as resource and extracting image
|
|
var resource: Resource = ResourceLoader.load(path)
|
|
if resource == null:
|
|
return error_internal("Failed to load resource: %s" % path)
|
|
|
|
if resource is Texture2D:
|
|
image = (resource as Texture2D).get_image()
|
|
elif resource is Image:
|
|
image = resource as Image
|
|
else:
|
|
return error_invalid_params("Resource type '%s' does not have an image preview" % resource.get_class())
|
|
|
|
if image == null:
|
|
return error_internal("Could not extract image from resource")
|
|
|
|
# Resize if needed
|
|
if image.get_width() > max_size or image.get_height() > max_size:
|
|
var scale_x := float(max_size) / float(image.get_width())
|
|
var scale_y := float(max_size) / float(image.get_height())
|
|
var scale := minf(scale_x, scale_y)
|
|
var new_w := int(image.get_width() * scale)
|
|
var new_h := int(image.get_height() * scale)
|
|
image.resize(new_w, new_h, Image.INTERPOLATE_LANCZOS)
|
|
|
|
var png_buffer := image.save_png_to_buffer()
|
|
var base64 := Marshalls.raw_to_base64(png_buffer)
|
|
|
|
return success({
|
|
"image_base64": base64,
|
|
"width": image.get_width(),
|
|
"height": image.get_height(),
|
|
"format": "png",
|
|
"path": path,
|
|
})
|