Some checks failed
Deploy Promiscuity Auth API / deploy (push) Successful in 1m59s
Deploy Promiscuity Character API / deploy (push) Successful in 1m16s
Deploy Promiscuity Inventory API / deploy (push) Has been cancelled
Deploy Promiscuity Locations API / deploy (push) Has been cancelled
Deploy Promiscuity Mail API / deploy (push) Has been cancelled
Deploy Promiscuity World API / deploy (push) Has been cancelled
Deploy Promiscuity Crafting API / deploy (push) Has been cancelled
k8s smoke test / test (push) Has been cancelled
2583 lines
81 KiB
GDScript
2583 lines
81 KiB
GDScript
# Structure for addons/godot_mcp/command_handler.gd
|
|
@tool
|
|
extends RefCounted
|
|
|
|
var editor_plugin: EditorPlugin = null
|
|
|
|
# Initialize with reference to the editor plugin
|
|
func set_editor_plugin(plugin):
|
|
editor_plugin = plugin
|
|
|
|
# Main command handling function
|
|
func handle_command(command_type, params):
|
|
match command_type:
|
|
"GET_SCENE_INFO":
|
|
return handle_get_scene_info()
|
|
"OPEN_SCENE":
|
|
return handle_open_scene(params)
|
|
"SAVE_SCENE":
|
|
return handle_save_scene()
|
|
"CREATE_CHILD_OBJECT":
|
|
return handle_create_child_object(params)
|
|
"NEW_SCENE":
|
|
return handle_new_scene(params)
|
|
"CREATE_OBJECT":
|
|
return handle_create_object(params)
|
|
"DELETE_OBJECT":
|
|
return handle_delete_object(params)
|
|
"FIND_OBJECTS_BY_NAME":
|
|
return handle_find_objects_by_name(params)
|
|
"GET_OBJECT_PROPERTIES":
|
|
return handle_get_object_properties(params)
|
|
"SET_PROPERTY":
|
|
return handle_set_property(params)
|
|
"SET_COLLISION_SHAPE":
|
|
return handle_set_collision_shape(params)
|
|
"SET_OBJECT_TRANSFORM":
|
|
return handle_set_object_transform(params)
|
|
"CREATE_CHILD_OBJECT":
|
|
return handle_create_child_object(params)
|
|
"GET_ASSET_LIST":
|
|
return handle_get_asset_list(params)
|
|
"VIEW_SCRIPT":
|
|
return handle_view_script(params)
|
|
"SET_NESTED_PROPERTY":
|
|
return handle_set_nested_property(params)
|
|
"SET_PARENT":
|
|
return handle_set_parent(params)
|
|
"CREATE_SCRIPT":
|
|
return handle_create_script(params)
|
|
"UPDATE_SCRIPT":
|
|
return handle_update_script(params)
|
|
"LIST_SCRIPTS":
|
|
return handle_list_scripts(params)
|
|
"DELETE_SCRIPT":
|
|
return handle_delete_script(params)
|
|
"DELETE_FILE":
|
|
return handle_delete_file(params)
|
|
"EDITOR_CONTROL":
|
|
return handle_editor_control(params)
|
|
"SET_MATERIAL":
|
|
return handle_set_material(params)
|
|
"IMPORT_ASSET":
|
|
return handle_import_asset(params)
|
|
"SET_MESH":
|
|
return handle_set_mesh(params)
|
|
"CREATE_PREFAB":
|
|
return handle_create_packed_scene(params)
|
|
"INSTANTIATE_PREFAB":
|
|
return handle_instantiate_prefab(params)
|
|
"SHOW_MESSAGE":
|
|
return handle_show_message(params)
|
|
"REIMPORT_ASSET":
|
|
return handle_reimport_asset(params)
|
|
"IMPORT_GLB_SCENE":
|
|
return handle_import_glb_scene(params)
|
|
_:
|
|
return {"error": "Unknown command type: " + command_type}
|
|
|
|
# Scene commands
|
|
func handle_get_scene_info():
|
|
var editor_interface = editor_plugin.get_editor_interface()
|
|
var current_scene = editor_interface.get_edited_scene_root()
|
|
|
|
if current_scene == null:
|
|
return {"error": "No scene is currently open"}
|
|
|
|
var scene_info = {
|
|
"name": current_scene.name,
|
|
"path": current_scene.scene_file_path,
|
|
"hierarchy": _get_hierarchy_recursive(current_scene),
|
|
"root_objects": []
|
|
}
|
|
|
|
# Get root-level nodes
|
|
for child in current_scene.get_children():
|
|
scene_info.root_objects.append({
|
|
"name": child.name,
|
|
"type": child.get_class()
|
|
})
|
|
|
|
return scene_info
|
|
|
|
# Helper function to recursively build hierarchy
|
|
func _get_hierarchy_recursive(node):
|
|
var hierarchy = {
|
|
"name": node.name,
|
|
"type": node.get_class(),
|
|
"children": []
|
|
}
|
|
|
|
# Add transform info for spatial nodes
|
|
if node is Node3D:
|
|
hierarchy["transform"] = {
|
|
"position": [node.position.x, node.position.y, node.position.z],
|
|
"rotation": [node.rotation_degrees.x, node.rotation_degrees.y, node.rotation_degrees.z],
|
|
"scale": [node.scale.x, node.scale.y, node.scale.z]
|
|
}
|
|
elif node is Node2D:
|
|
hierarchy["transform"] = {
|
|
"position": [node.position.x, node.position.y],
|
|
"rotation": node.rotation_degrees,
|
|
"scale": [node.scale.x, node.scale.y]
|
|
}
|
|
|
|
# Add script info if available
|
|
if node.get_script():
|
|
hierarchy["script"] = node.get_script().resource_path
|
|
|
|
# Add all child nodes recursively
|
|
for child in node.get_children():
|
|
hierarchy["children"].append(_get_hierarchy_recursive(child))
|
|
|
|
return hierarchy
|
|
|
|
func handle_open_scene(params):
|
|
if not params.has("scene_path"):
|
|
return {"error": "Missing required parameter: scene_path"}
|
|
|
|
var scene_path = params.scene_path
|
|
var editor_interface = editor_plugin.get_editor_interface()
|
|
|
|
# Check if file exists
|
|
if not FileAccess.file_exists(scene_path):
|
|
return {"error": "Scene file does not exist: " + scene_path}
|
|
|
|
# Save current scene if needed
|
|
if params.get("save_current", false):
|
|
editor_interface.save_scene()
|
|
|
|
# Open the scene
|
|
editor_interface.open_scene_from_path(scene_path)
|
|
return {"message": "Scene opened successfully: " + scene_path}
|
|
|
|
func handle_save_scene():
|
|
var editor_interface = editor_plugin.get_editor_interface()
|
|
var current_scene = editor_interface.get_edited_scene_root()
|
|
|
|
if current_scene == null:
|
|
return {"error": "No scene is currently open"}
|
|
|
|
editor_interface.save_scene()
|
|
return {"message": "Scene saved successfully: " + current_scene.scene_file_path}
|
|
|
|
func handle_new_scene(params):
|
|
if not params.has("scene_path"):
|
|
return {"error": "Missing required parameter: scene_path"}
|
|
|
|
var scene_path = params.scene_path
|
|
var editor_interface = editor_plugin.get_editor_interface()
|
|
|
|
# Check if file exists and handle overwrite
|
|
if FileAccess.file_exists(scene_path) and not params.get("overwrite", false):
|
|
return {"error": "Scene file already exists. Use overwrite=true to replace it."}
|
|
|
|
# Create directory if needed
|
|
var directory = scene_path.get_base_dir()
|
|
if not DirAccess.dir_exists_absolute(directory):
|
|
var error = DirAccess.make_dir_recursive_absolute(directory)
|
|
if error != OK:
|
|
return {"error": "Failed to create directory: " + directory}
|
|
|
|
# Create a new scene with a Node as root
|
|
var root_node = Node.new()
|
|
root_node.name = scene_path.get_file().get_basename()
|
|
|
|
# Create a packed scene and save it
|
|
var packed_scene = PackedScene.new()
|
|
var result = packed_scene.pack(root_node)
|
|
if result != OK:
|
|
return {"error": "Failed to pack scene: " + str(result)}
|
|
|
|
var error = ResourceSaver.save(packed_scene, scene_path)
|
|
if error != OK:
|
|
return {"error": "Failed to save new scene: " + str(error)}
|
|
|
|
# Open the newly created scene
|
|
editor_interface.open_scene_from_path(scene_path)
|
|
|
|
return {"message": "New scene created successfully: " + scene_path}
|
|
|
|
# Object commands
|
|
func handle_create_object(params):
|
|
var type = params.get("type", "EMPTY")
|
|
var name = params.get("name", "")
|
|
var location = params.get("location", [0, 0, 0])
|
|
var rotation = params.get("rotation", [0, 0, 0])
|
|
var scale = params.get("scale", [1, 1, 1])
|
|
var replace_if_exists = params.get("replace_if_exists", false)
|
|
|
|
var editor_interface = editor_plugin.get_editor_interface()
|
|
var current_scene = editor_interface.get_edited_scene_root()
|
|
|
|
if current_scene == null:
|
|
return {"error": "No scene is currently open"}
|
|
|
|
# Check if name already exists and handle replacement
|
|
if name != "":
|
|
var existing_node = current_scene.find_child(name, true, false)
|
|
if existing_node and not replace_if_exists:
|
|
return {"error": "Node with name '" + name + "' already exists. Use replace_if_exists=true to replace it."}
|
|
elif existing_node and replace_if_exists:
|
|
existing_node.queue_free()
|
|
|
|
# Create the node based on type (case-insensitive)
|
|
var node
|
|
var upper_type = type.to_upper()
|
|
|
|
# Handle common 3D node types
|
|
match upper_type:
|
|
"NODE", "EMPTY":
|
|
node = Node3D.new()
|
|
"NODE3D":
|
|
node = Node3D.new()
|
|
"SPATIAL": # For compatibility with Godot 3.x terminology
|
|
node = Node3D.new()
|
|
"MESH", "MESHINSTANCE3D":
|
|
node = MeshInstance3D.new()
|
|
"CUBE", "BOX":
|
|
node = MeshInstance3D.new()
|
|
node.mesh = BoxMesh.new()
|
|
"SPHERE":
|
|
node = MeshInstance3D.new()
|
|
node.mesh = SphereMesh.new()
|
|
"CYLINDER":
|
|
node = MeshInstance3D.new()
|
|
node.mesh = CylinderMesh.new()
|
|
"PLANE":
|
|
node = MeshInstance3D.new()
|
|
node.mesh = PlaneMesh.new()
|
|
"CAMERA", "CAMERA3D":
|
|
node = Camera3D.new()
|
|
"LIGHT", "DIRECTIONALLIGHT", "DIRECTIONALLIGHT3D":
|
|
node = DirectionalLight3D.new()
|
|
"SPOTLIGHT", "SPOTLIGHT3D":
|
|
node = SpotLight3D.new()
|
|
"OMNILIGHT", "OMNILIGHT3D":
|
|
node = OmniLight3D.new()
|
|
"RIGIDBODY", "RIGIDBODY3D":
|
|
node = RigidBody3D.new()
|
|
"STATICBODY", "STATICBODY3D":
|
|
node = StaticBody3D.new()
|
|
"CHARACTERBODY", "CHARACTERBODY3D":
|
|
node = CharacterBody3D.new()
|
|
"AREA", "AREA3D":
|
|
node = Area3D.new()
|
|
"COLLISION", "COLLISIONSHAPE3D":
|
|
node = CollisionShape3D.new()
|
|
# Add a default sphere shape
|
|
node.shape = SphereShape3D.new()
|
|
|
|
# Handle common 2D node types
|
|
"NODE2D":
|
|
node = Node2D.new()
|
|
"SPRITE", "SPRITE2D":
|
|
node = Sprite2D.new()
|
|
"CAMERA2D":
|
|
node = Camera2D.new()
|
|
"AREA2D":
|
|
node = Area2D.new()
|
|
"COLLISION2D", "COLLISIONSHAPE2D":
|
|
node = CollisionShape2D.new()
|
|
# Add a default circle shape
|
|
node.shape = CircleShape2D.new()
|
|
"RIGIDBODY2D":
|
|
node = RigidBody2D.new()
|
|
"STATICBODY2D":
|
|
node = StaticBody2D.new()
|
|
"CHARACTERBODY2D":
|
|
node = CharacterBody2D.new()
|
|
|
|
# Handle UI node types
|
|
"CONTROL":
|
|
node = Control.new()
|
|
"PANEL":
|
|
node = Panel.new()
|
|
"BUTTON":
|
|
node = Button.new()
|
|
"LABEL":
|
|
node = Label.new()
|
|
"LINEEDIT":
|
|
node = LineEdit.new()
|
|
"TEXTEDIT":
|
|
node = TextEdit.new()
|
|
"CONTAINER":
|
|
node = Container.new()
|
|
"VBOX", "VBOXCONTAINER":
|
|
node = VBoxContainer.new()
|
|
"HBOX", "HBOXCONTAINER":
|
|
node = HBoxContainer.new()
|
|
_:
|
|
# Try to create the node directly by class name using ClassDB
|
|
if ClassDB.class_exists(type) and ClassDB.can_instantiate(type):
|
|
node = ClassDB.instantiate(type)
|
|
else:
|
|
return {"error": "Unsupported object type: " + type}
|
|
|
|
# Set node name if provided
|
|
if name != "":
|
|
node.name = name
|
|
|
|
# Add to scene
|
|
current_scene.add_child(node)
|
|
node.owner = current_scene
|
|
|
|
# Set transform for Node3D or Node2D objects
|
|
if node is Node3D:
|
|
node.position = Vector3(location[0], location[1], location[2])
|
|
node.rotation_degrees = Vector3(rotation[0], rotation[1], rotation[2])
|
|
node.scale = Vector3(scale[0], scale[1], scale[2])
|
|
elif node is Node2D:
|
|
node.position = Vector2(location[0], location[1])
|
|
node.rotation_degrees = rotation[0]
|
|
node.scale = Vector2(scale[0], scale[1])
|
|
|
|
return {
|
|
"name": node.name,
|
|
"type": node.get_class(),
|
|
"path": current_scene.get_path_to(node)
|
|
}
|
|
|
|
|
|
func handle_delete_object(params):
|
|
if not params.has("name"):
|
|
return {"error": "Missing required parameter: name"}
|
|
|
|
var name_or_path = params.name
|
|
var editor_interface = editor_plugin.get_editor_interface()
|
|
var current_scene = editor_interface.get_edited_scene_root()
|
|
|
|
if current_scene == null:
|
|
return {"error": "No scene is currently open"}
|
|
|
|
# Find node by name or path
|
|
var node = null
|
|
|
|
# Check if it's a path (contains /)
|
|
if "/" in name_or_path:
|
|
# Try direct path lookup first
|
|
node = current_scene.get_node_or_null(name_or_path)
|
|
|
|
# If that fails, try with leading slash
|
|
if not node and not name_or_path.begins_with("/"):
|
|
node = current_scene.get_node_or_null("/" + name_or_path)
|
|
|
|
# If still not found, try parsing the path components
|
|
if not node:
|
|
var parts = name_or_path.split("/")
|
|
var current = current_scene
|
|
|
|
for part in parts:
|
|
if part == "":
|
|
continue
|
|
|
|
var found = false
|
|
for child in current.get_children():
|
|
if child.name == part:
|
|
current = child
|
|
found = true
|
|
break
|
|
|
|
if not found:
|
|
# Try searching recursively for this part
|
|
var found_node = current.find_child(part, true, false)
|
|
if found_node:
|
|
current = found_node
|
|
else:
|
|
# Part not found, path is invalid
|
|
return {"error": "Could not find part '" + part + "' in path: " + name_or_path}
|
|
|
|
node = current
|
|
else:
|
|
# Simple name lookup for non-path names
|
|
node = current_scene.find_child(name_or_path, true, false)
|
|
|
|
if not node:
|
|
return {"error": "Node not found: " + name_or_path}
|
|
|
|
# Store the node's path for the response before deleting
|
|
var node_path = current_scene.get_path_to(node)
|
|
var node_type = node.get_class()
|
|
|
|
# Delete the node
|
|
node.queue_free()
|
|
|
|
return {
|
|
"message": "Node deleted: " + name_or_path,
|
|
"path": node_path,
|
|
"type": node_type
|
|
}
|
|
#func handle_delete_object(params):
|
|
#if not params.has("name"):
|
|
#return {"error": "Missing required parameter: name"}
|
|
#
|
|
#var name = params.name
|
|
#var editor_interface = editor_plugin.get_editor_interface()
|
|
#var current_scene = editor_interface.get_edited_scene_root()
|
|
#
|
|
#if current_scene == null:
|
|
#return {"error": "No scene is currently open"}
|
|
#
|
|
## Find node by name
|
|
#var node = current_scene.find_child(name, true, false)
|
|
#if not node:
|
|
#return {"error": "Node not found: " + name}
|
|
#
|
|
## Delete the node
|
|
#node.queue_free()
|
|
#return {"message": "Node deleted: " + name}
|
|
|
|
func handle_find_objects_by_name(params):
|
|
if not params.has("name"):
|
|
return {"error": "Missing required parameter: name"}
|
|
|
|
var search_name = params.name
|
|
var editor_interface = editor_plugin.get_editor_interface()
|
|
var current_scene = editor_interface.get_edited_scene_root()
|
|
|
|
if current_scene == null:
|
|
return {"error": "No scene is currently open"}
|
|
|
|
var objects = []
|
|
var nodes = _find_nodes_by_name(current_scene, search_name)
|
|
|
|
for node in nodes:
|
|
objects.append({
|
|
"name": node.name,
|
|
"path": _get_node_path(node),
|
|
"type": node.get_class()
|
|
})
|
|
|
|
return {"objects": objects}
|
|
|
|
# Helper function to find nodes recursively
|
|
func _find_nodes_by_name(root, search_name):
|
|
var result = []
|
|
|
|
if search_name in root.name:
|
|
result.append(root)
|
|
|
|
for child in root.get_children():
|
|
result.append_array(_find_nodes_by_name(child, search_name))
|
|
|
|
return result
|
|
|
|
# Helper function to get a node's path relative to the scene root
|
|
func _get_node_path(node):
|
|
var current_scene = editor_plugin.get_editor_interface().get_edited_scene_root()
|
|
if current_scene:
|
|
return current_scene.get_path_to(node)
|
|
return node.get_path()
|
|
|
|
func handle_get_object_properties(params):
|
|
if not params.has("name"):
|
|
return {"error": "Missing required parameter: name"}
|
|
|
|
var name_or_path = params.name
|
|
var editor_interface = editor_plugin.get_editor_interface()
|
|
var current_scene = editor_interface.get_edited_scene_root()
|
|
|
|
if current_scene == null:
|
|
return {"error": "No scene is currently open"}
|
|
|
|
var node
|
|
|
|
# Check if we're dealing with a path or just a name
|
|
if "/" in name_or_path:
|
|
# It's a path, try to get the node using get_node
|
|
node = current_scene.get_node_or_null(name_or_path)
|
|
|
|
# If that fails, try with NodePath
|
|
if not node:
|
|
var node_path = NodePath(name_or_path)
|
|
node = current_scene.get_node_or_null(node_path)
|
|
|
|
# Try finding from root with a leading slash
|
|
if not node and not name_or_path.begins_with("/"):
|
|
node = current_scene.get_node_or_null("/" + name_or_path)
|
|
else:
|
|
# It's just a name, use find_child (which searches recursively)
|
|
node = current_scene.find_child(name_or_path, true, false)
|
|
|
|
if not node:
|
|
return {"error": "Node not found: " + name_or_path}
|
|
|
|
# Get properties
|
|
var properties = {
|
|
"name": node.name,
|
|
"type": node.get_class(),
|
|
"path": current_scene.get_path_to(node),
|
|
"visible": node.visible if "visible" in node else true,
|
|
"components": []
|
|
}
|
|
|
|
# Handle transform properties for 3D nodes
|
|
if node is Node3D:
|
|
properties["transform"] = {
|
|
"position": [node.position.x, node.position.y, node.position.z],
|
|
"rotation": [node.rotation_degrees.x, node.rotation_degrees.y, node.rotation_degrees.z],
|
|
"scale": [node.scale.x, node.scale.y, node.scale.z]
|
|
}
|
|
elif node is Node2D:
|
|
properties["transform"] = {
|
|
"position": [node.position.x, node.position.y],
|
|
"rotation": node.rotation_degrees,
|
|
"scale": [node.scale.x, node.scale.y]
|
|
}
|
|
|
|
# Get node properties and components
|
|
if node.get_script():
|
|
properties["components"].append({
|
|
"type": "Script",
|
|
"path": node.get_script().resource_path
|
|
})
|
|
|
|
# Get children information
|
|
properties["children"] = []
|
|
for child in node.get_children():
|
|
properties["children"].append({
|
|
"name": child.name,
|
|
"type": child.get_class(),
|
|
"path": current_scene.get_path_to(child)
|
|
})
|
|
|
|
# Get parent information
|
|
var parent = node.get_parent()
|
|
if parent and parent != current_scene:
|
|
properties["parent"] = {
|
|
"name": parent.name,
|
|
"type": parent.get_class(),
|
|
"path": current_scene.get_path_to(parent)
|
|
}
|
|
|
|
return properties
|
|
|
|
func handle_set_object_transform(params):
|
|
if not params.has("name"):
|
|
return {"error": "Missing required parameter: name"}
|
|
|
|
var name = params.name
|
|
var editor_interface = editor_plugin.get_editor_interface()
|
|
var current_scene = editor_interface.get_edited_scene_root()
|
|
|
|
if current_scene == null:
|
|
return {"error": "No scene is currently open"}
|
|
|
|
# Find node by name
|
|
var node = current_scene.find_child(name, true, false)
|
|
if not node:
|
|
return {"error": "Node not found: " + name}
|
|
|
|
# Check if node is 3D
|
|
if not node is Node3D:
|
|
return {"error": "Node is not a 3D node: " + name}
|
|
|
|
# Set transform properties
|
|
if params.has("location"):
|
|
var loc = params.location
|
|
node.position = Vector3(loc[0], loc[1], loc[2])
|
|
|
|
if params.has("rotation"):
|
|
var rot = params.rotation
|
|
node.rotation_degrees = Vector3(rot[0], rot[1], rot[2])
|
|
|
|
if params.has("scale"):
|
|
var scale = params.scale
|
|
node.scale = Vector3(scale[0], scale[1], scale[2])
|
|
|
|
return {"message": "Transform updated for node: " + name}
|
|
|
|
# Asset commands
|
|
func handle_get_asset_list(params):
|
|
var type = params.get("type", "")
|
|
var search_pattern = params.get("search_pattern", "*")
|
|
var folder = params.get("folder", "res://")
|
|
|
|
var assets = []
|
|
var dir = DirAccess.open(folder)
|
|
|
|
if dir == null:
|
|
return {"error": "Unable to access directory: " + folder}
|
|
|
|
dir.list_dir_begin()
|
|
var file_name = dir.get_next()
|
|
|
|
while file_name != "":
|
|
var full_path = folder.path_join(file_name)
|
|
|
|
if dir.current_is_dir():
|
|
# Handle directories if needed
|
|
pass
|
|
else:
|
|
# Check if it matches the search pattern
|
|
if search_pattern == "*" or search_pattern in file_name:
|
|
# Check type if specified
|
|
var add_file = true
|
|
if type != "":
|
|
# Determine file type based on extension
|
|
var extension = file_name.get_extension().to_lower()
|
|
match type.to_lower():
|
|
"scene":
|
|
add_file = extension == "tscn" or extension == "scn"
|
|
"script":
|
|
add_file = extension == "gd" or extension == "cs"
|
|
"texture":
|
|
add_file = extension in ["png", "jpg", "jpeg", "webp"]
|
|
"material":
|
|
add_file = extension == "material" or extension == "tres"
|
|
"prefab", "packedscene":
|
|
add_file = extension == "tscn" or extension == "scn"
|
|
|
|
if add_file:
|
|
assets.append({
|
|
"name": file_name,
|
|
"path": full_path,
|
|
"type": _get_resource_type(full_path)
|
|
})
|
|
|
|
file_name = dir.get_next()
|
|
|
|
dir.list_dir_end()
|
|
return {"assets": assets}
|
|
|
|
# Helper function to determine resource type
|
|
func _get_resource_type(path):
|
|
var extension = path.get_extension().to_lower()
|
|
match extension:
|
|
"tscn", "scn":
|
|
return "PackedScene"
|
|
"gd":
|
|
return "GDScript"
|
|
"cs":
|
|
return "CSharpScript"
|
|
"png", "jpg", "jpeg", "webp":
|
|
return "Texture"
|
|
"material", "tres":
|
|
return "Material"
|
|
"wav", "mp3", "ogg":
|
|
return "AudioStream"
|
|
_:
|
|
return "Resource"
|
|
|
|
# Script commands
|
|
func handle_view_script(params):
|
|
if not params.has("script_path"):
|
|
return {"error": "Missing required parameter: script_path"}
|
|
|
|
var script_path = params.script_path
|
|
var require_exists = params.get("require_exists", true)
|
|
|
|
if not FileAccess.file_exists(script_path):
|
|
if require_exists:
|
|
return {"error": "Script file does not exist: " + script_path}
|
|
else:
|
|
return {"exists": false, "message": "Script file does not exist: " + script_path}
|
|
|
|
var file = FileAccess.open(script_path, FileAccess.READ)
|
|
if file == null:
|
|
return {"error": "Failed to open script file: " + script_path}
|
|
|
|
var content = file.get_as_text()
|
|
return {"exists": true, "content": content}
|
|
|
|
func handle_create_script(params):
|
|
if not params.has("script_name"):
|
|
return {"error": "Missing required parameter: script_name"}
|
|
|
|
var script_name = params.script_name
|
|
var script_type = params.get("script_type", "Node")
|
|
var nam = params.get("namespace", "")
|
|
var script_folder = params.get("script_folder", "res://scripts")
|
|
var overwrite = params.get("overwrite", false)
|
|
var content = params.get("content", "")
|
|
|
|
# Ensure script has .gd extension
|
|
if not script_name.ends_with(".gd"):
|
|
script_name += ".gd"
|
|
|
|
# Create full script path
|
|
var script_path = script_folder.path_join(script_name)
|
|
|
|
# Check if directory exists, create if needed
|
|
if not DirAccess.dir_exists_absolute(script_folder):
|
|
var error = DirAccess.make_dir_recursive_absolute(script_folder)
|
|
if error != OK:
|
|
return {"error": "Failed to create script directory: " + script_folder}
|
|
|
|
# Check if script already exists
|
|
if FileAccess.file_exists(script_path) and not overwrite:
|
|
return {"error": "Script already exists. Use overwrite=true to replace it."}
|
|
|
|
# Create script content if not provided
|
|
if content == "":
|
|
content = _generate_script_template(script_type, nam)
|
|
|
|
# Write script file
|
|
var file = FileAccess.open(script_path, FileAccess.WRITE)
|
|
if file == null:
|
|
return {"error": "Failed to create script file: " + script_path}
|
|
|
|
file.store_string(content)
|
|
|
|
return {"message": "Script created successfully: " + script_path}
|
|
|
|
# Helper function to generate script template
|
|
func _generate_script_template(node_type, nam):
|
|
var template = ""
|
|
|
|
# Add class_name if namespace is provided
|
|
if nam != "":
|
|
template += "class_name " + nam + "\n\n"
|
|
|
|
# Add extends
|
|
template += "extends " + node_type + "\n\n"
|
|
|
|
# Add basic structure
|
|
template += "# Properties\n\n"
|
|
template += "# Called when the node enters the scene tree\n"
|
|
template += "func _ready():\n"
|
|
template += "\tpass\n\n"
|
|
template += "# Called every frame\n"
|
|
template += "func _process(delta):\n"
|
|
template += "\tpass\n"
|
|
|
|
return template
|
|
|
|
func handle_update_script(params):
|
|
if not params.has("script_path") or not params.has("content"):
|
|
return {"error": "Missing required parameters: script_path and content"}
|
|
|
|
var script_path = params.script_path
|
|
var content = params.content
|
|
var create_if_missing = params.get("create_if_missing", false)
|
|
var create_folder_if_missing = params.get("create_folder_if_missing", false)
|
|
|
|
# Check if script exists
|
|
if not FileAccess.file_exists(script_path):
|
|
if not create_if_missing:
|
|
return {"error": "Script file does not exist: " + script_path}
|
|
|
|
# Create directory if needed
|
|
var directory = script_path.get_base_dir()
|
|
if create_folder_if_missing and not DirAccess.dir_exists_absolute(directory):
|
|
var dir_error = DirAccess.make_dir_recursive_absolute(directory)
|
|
if dir_error != OK:
|
|
return {"error": "Failed to create directory: " + directory}
|
|
|
|
# Write script content
|
|
var file = FileAccess.open(script_path, FileAccess.WRITE)
|
|
if file == null:
|
|
return {"error": "Failed to open script file for writing: " + script_path}
|
|
|
|
file.store_string(content)
|
|
|
|
return {"message": "Script updated successfully: " + script_path}
|
|
|
|
func handle_list_scripts(params):
|
|
var folder_path = params.get("folder_path", "res://")
|
|
|
|
var scripts = []
|
|
var dir = DirAccess.open(folder_path)
|
|
|
|
if dir == null:
|
|
return {"error": "Unable to access directory: " + folder_path}
|
|
|
|
dir.list_dir_begin()
|
|
var file_name = dir.get_next()
|
|
|
|
while file_name != "":
|
|
if not dir.current_is_dir():
|
|
var extension = file_name.get_extension().to_lower()
|
|
if extension == "gd" or extension == "cs":
|
|
scripts.append(folder_path.path_join(file_name))
|
|
|
|
file_name = dir.get_next()
|
|
|
|
dir.list_dir_end()
|
|
return {"scripts": scripts}
|
|
|
|
# Editor control commands
|
|
func handle_editor_control(params):
|
|
if not params.has("command"):
|
|
return {"error": "Missing required parameter: command"}
|
|
|
|
var command = params.command
|
|
var editor_interface = editor_plugin.get_editor_interface()
|
|
|
|
match command:
|
|
"PLAY":
|
|
editor_interface.play_main_scene()
|
|
return {"message": "Started playing the main scene"}
|
|
"STOP":
|
|
editor_interface.stop_playing_scene()
|
|
return {"message": "Stopped playing the scene"}
|
|
"SAVE":
|
|
editor_interface.save_scene()
|
|
return {"message": "Scene saved"}
|
|
"READ_CONSOLE":
|
|
# Godot doesn't have a direct API for reading console output
|
|
return {"message": "Console reading not implemented in Godot"}
|
|
_:
|
|
return {"error": "Unknown editor command: " + command}
|
|
|
|
# Material commands
|
|
func handle_set_material(params):
|
|
if not params.has("object_name"):
|
|
return {"error": "Missing required parameter: object_name"}
|
|
|
|
var object_name = params.object_name
|
|
var material_name = params.get("material_name", "")
|
|
var color = params.get("color", [1.0, 1.0, 1.0, 1.0])
|
|
var create_if_missing = params.get("create_if_missing", true)
|
|
|
|
print("handle_set_material called for node: ", object_name, " color: ", color)
|
|
|
|
var editor_interface = editor_plugin.get_editor_interface()
|
|
var current_scene = editor_interface.get_edited_scene_root()
|
|
|
|
if current_scene == null:
|
|
return {"error": "No scene is currently open"}
|
|
|
|
# Find node using the improved node lookup
|
|
var node = _find_node(current_scene, object_name)
|
|
if not node:
|
|
return {"error": "Node not found: " + object_name}
|
|
|
|
print("Node found: ", node.name, " type: ", node.get_class())
|
|
|
|
# Check if node can have materials
|
|
if not (node is MeshInstance3D or node is CSGShape3D):
|
|
return {"error": "Node does not support materials: " + object_name}
|
|
|
|
var material
|
|
|
|
# Create or load material
|
|
if material_name != "":
|
|
var material_path = "res://materials/" + material_name + ".material"
|
|
|
|
if FileAccess.file_exists(material_path):
|
|
# Load existing material
|
|
material = load(material_path)
|
|
elif create_if_missing:
|
|
# Create directory if needed
|
|
if not DirAccess.dir_exists_absolute("res://materials"):
|
|
DirAccess.make_dir_recursive_absolute("res://materials")
|
|
|
|
# Create new material
|
|
material = StandardMaterial3D.new()
|
|
|
|
# Set color
|
|
if color.size() >= 3:
|
|
var albedo_color = Color(color[0], color[1], color[2])
|
|
if color.size() >= 4:
|
|
albedo_color.a = color[3]
|
|
material.albedo_color = albedo_color
|
|
|
|
# Save material
|
|
var error = ResourceSaver.save(material, material_path)
|
|
if error != OK:
|
|
return {"error": "Failed to save material: " + str(error)}
|
|
else:
|
|
return {"error": "Material not found and create_if_missing is false"}
|
|
else:
|
|
# Create instance material
|
|
material = StandardMaterial3D.new()
|
|
|
|
# Set color
|
|
if color.size() >= 3:
|
|
var albedo_color = Color(color[0], color[1], color[2])
|
|
if color.size() >= 4:
|
|
albedo_color.a = color[3]
|
|
material.albedo_color = albedo_color
|
|
|
|
# Apply material
|
|
if node is MeshInstance3D:
|
|
node.material_override = material
|
|
elif node is CSGShape3D:
|
|
node.material = material
|
|
|
|
if material_name != "":
|
|
return {
|
|
"material_name": material_name,
|
|
"path": "res://materials/" + material_name + ".material",
|
|
"message": "Applied shared material to " + object_name
|
|
}
|
|
else:
|
|
return {
|
|
"material_name": "instance_material",
|
|
"message": "Applied instance material to " + object_name
|
|
}
|
|
|
|
# Asset import
|
|
func handle_import_asset(params):
|
|
if not params.has("source_path") or not params.has("target_path"):
|
|
return {"error": "Missing required parameters: source_path and target_path"}
|
|
|
|
var source_path = params.source_path
|
|
var target_path = params.target_path
|
|
var overwrite = params.get("overwrite", false)
|
|
|
|
# Ensure target_path starts with res://
|
|
if not target_path.begins_with("res://"):
|
|
target_path = "res://" + target_path
|
|
|
|
# Check if source file exists
|
|
if not FileAccess.file_exists(source_path):
|
|
return {"error": "Source file does not exist: " + source_path}
|
|
|
|
# Check if target file exists
|
|
if FileAccess.file_exists(target_path) and not overwrite:
|
|
return {"error": "Target file already exists. Use overwrite=true to replace it."}
|
|
|
|
# Create target directory if needed
|
|
var target_dir = target_path.get_base_dir()
|
|
if not DirAccess.dir_exists_absolute(target_dir):
|
|
var dir_error = DirAccess.make_dir_recursive_absolute(target_dir)
|
|
if dir_error != OK:
|
|
return {"error": "Failed to create target directory: " + target_dir}
|
|
|
|
# Copy the file
|
|
var source_file = FileAccess.open(source_path, FileAccess.READ)
|
|
if source_file == null:
|
|
return {"error": "Failed to open source file: " + source_path}
|
|
|
|
var target_file = FileAccess.open(target_path, FileAccess.WRITE)
|
|
if target_file == null:
|
|
return {"error": "Failed to create target file: " + target_path}
|
|
|
|
target_file.store_buffer(source_file.get_buffer(source_file.get_length()))
|
|
|
|
return {
|
|
"success": true,
|
|
"message": "Asset imported successfully: " + target_path
|
|
}
|
|
|
|
func handle_create_packed_scene(params):
|
|
"""Create a packed scene (prefab) from an existing node."""
|
|
if not params.has("object_name") or not params.has("prefab_path"):
|
|
return {"error": "Missing required parameters: object_name and prefab_path"}
|
|
|
|
var object_name = params.object_name
|
|
var prefab_path = params.prefab_path
|
|
var overwrite = params.get("overwrite", false)
|
|
|
|
# Ensure prefab_path starts with res://
|
|
if not prefab_path.begins_with("res://"):
|
|
prefab_path = "res://" + prefab_path
|
|
|
|
# Ensure it has .tscn extension
|
|
if not prefab_path.ends_with(".tscn"):
|
|
prefab_path += ".tscn"
|
|
|
|
# Check if file exists and handle overwrite
|
|
if FileAccess.file_exists(prefab_path) and not overwrite:
|
|
return {"error": "Packed scene file already exists. Use overwrite=true to replace it."}
|
|
|
|
var editor_interface = editor_plugin.get_editor_interface()
|
|
var current_scene = editor_interface.get_edited_scene_root()
|
|
|
|
if current_scene == null:
|
|
return {"error": "No scene is currently open"}
|
|
|
|
# Find node by name
|
|
var node = current_scene.find_child(object_name, true, false)
|
|
if not node:
|
|
return {"error": "Node not found: " + object_name}
|
|
|
|
# Create directory if needed
|
|
var directory = prefab_path.get_base_dir()
|
|
if not DirAccess.dir_exists_absolute(directory):
|
|
var dir_error = DirAccess.make_dir_recursive_absolute(directory)
|
|
if dir_error != OK:
|
|
return {"error": "Failed to create directory: " + directory}
|
|
|
|
# Create packed scene
|
|
var packed_scene = PackedScene.new()
|
|
var result = packed_scene.pack(node)
|
|
if result != OK:
|
|
return {"error": "Failed to pack scene: " + str(result)}
|
|
|
|
# Save packed scene
|
|
result = ResourceSaver.save(packed_scene, prefab_path)
|
|
if result != OK:
|
|
return {"error": "Failed to save packed scene: " + str(result)}
|
|
|
|
return {
|
|
"success": true,
|
|
"path": prefab_path,
|
|
"message": "Packed scene created successfully from " + object_name
|
|
}
|
|
|
|
func handle_instantiate_prefab(params):
|
|
"""Instantiate a packed scene (prefab) into the current scene."""
|
|
if not params.has("prefab_path"):
|
|
return {"error": "Missing required parameter: prefab_path"}
|
|
|
|
var prefab_path = params.prefab_path
|
|
var position_x = params.get("position_x", 0.0)
|
|
var position_y = params.get("position_y", 0.0)
|
|
var position_z = params.get("position_z", 0.0)
|
|
var rotation_x = params.get("rotation_x", 0.0)
|
|
var rotation_y = params.get("rotation_y", 0.0)
|
|
var rotation_z = params.get("rotation_z", 0.0)
|
|
|
|
# Ensure prefab_path starts with res://
|
|
if not prefab_path.begins_with("res://"):
|
|
prefab_path = "res://" + prefab_path
|
|
|
|
# Check if file exists
|
|
if not FileAccess.file_exists(prefab_path):
|
|
return {"error": "Packed scene file does not exist: " + prefab_path}
|
|
|
|
var editor_interface = editor_plugin.get_editor_interface()
|
|
var current_scene = editor_interface.get_edited_scene_root()
|
|
|
|
if current_scene == null:
|
|
return {"error": "No scene is currently open"}
|
|
|
|
# Load the packed scene
|
|
var scene_resource = load(prefab_path)
|
|
if not scene_resource is PackedScene:
|
|
return {"error": "Failed to load packed scene: " + prefab_path}
|
|
|
|
# Instantiate the scene
|
|
var instance = scene_resource.instantiate()
|
|
if not instance:
|
|
return {"error": "Failed to instantiate packed scene"}
|
|
|
|
# Add to the current scene
|
|
current_scene.add_child(instance)
|
|
instance.owner = current_scene
|
|
|
|
# Set transform if it's a spatial node
|
|
if instance is Node3D:
|
|
instance.position = Vector3(position_x, position_y, position_z)
|
|
instance.rotation_degrees = Vector3(rotation_x, rotation_y, rotation_z)
|
|
|
|
return {
|
|
"success": true,
|
|
"instance_name": instance.name,
|
|
"message": "Packed scene instantiated successfully"
|
|
}
|
|
func handle_set_property(params):
|
|
if not params.has("node_name") or not params.has("property_name") or not params.has("value"):
|
|
return {"error": "Missing required parameters: node_name, property_name, and value"}
|
|
|
|
var node_name = params.node_name
|
|
var property_name = params.property_name
|
|
var value = params.value
|
|
var force_type = params.get("force_type", "")
|
|
|
|
print("handle_set_property called with node_name: ", node_name, " property_name: ", property_name, " value: ", value)
|
|
|
|
var editor_interface = editor_plugin.get_editor_interface()
|
|
var current_scene = editor_interface.get_edited_scene_root()
|
|
|
|
if current_scene == null:
|
|
return {"error": "No scene is currently open"}
|
|
|
|
# Find node by name or path
|
|
var node = null
|
|
|
|
# Check if it's a path (contains /)
|
|
if "/" in node_name:
|
|
# Try direct path lookup first
|
|
node = current_scene.get_node_or_null(node_name)
|
|
if not node:
|
|
# Try as relative path
|
|
if not node_name.begins_with("/"):
|
|
node = current_scene.get_node_or_null("/" + node_name)
|
|
else:
|
|
# Simple name lookup
|
|
node = current_scene.find_child(node_name, true, false)
|
|
|
|
if node:
|
|
print("Node found: ", node.name, " of type: ", node.get_class())
|
|
else:
|
|
print("Node not found: ", node_name)
|
|
return {"error": "Node not found: " + node_name}
|
|
|
|
# Handle special properties
|
|
if property_name == "script":
|
|
# Check if script file exists
|
|
if not FileAccess.file_exists(value):
|
|
return {"error": "Script file not found: " + value}
|
|
|
|
# Try to load the script
|
|
var script_resource = load(value)
|
|
if not script_resource:
|
|
return {"error": "Failed to load script: " + value}
|
|
|
|
if not script_resource is GDScript:
|
|
return {"error": "Resource is not a GDScript: " + value}
|
|
|
|
# Set the script
|
|
node.set_script(script_resource)
|
|
return {"message": "Script set on node '" + node_name + "': " + value}
|
|
|
|
# Handle other special properties
|
|
if property_name == "visible":
|
|
node.visible = bool(value)
|
|
return {"message": "Property set: visible = " + str(bool(value))}
|
|
|
|
if property_name == "name":
|
|
# Check if name is already taken
|
|
var existing = current_scene.find_child(str(value), true, false)
|
|
if existing != null and existing != node:
|
|
return {"error": "Name already in use by another node"}
|
|
node.name = str(value)
|
|
return {"message": "Node renamed to: " + str(value)}
|
|
|
|
# Handle property paths (e.g., "position:x")
|
|
var parts = property_name.split(":")
|
|
if parts.size() == 2:
|
|
var base_property = parts[0]
|
|
var sub_property = parts[1]
|
|
|
|
if base_property in node:
|
|
var base_value = node.get(base_property)
|
|
|
|
# Handle different vector types
|
|
if base_value is Vector2 or base_value is Vector3 or base_value is Color:
|
|
if sub_property in ["x", "y", "z", "r", "g", "b", "a"]:
|
|
# Safety check for Vector2 which doesn't have z
|
|
if base_value is Vector2 and sub_property == "z":
|
|
return {"error": "Vector2 doesn't have a z component"}
|
|
|
|
# Safety check for components that don't exist
|
|
if sub_property == "z" and not (base_value is Vector3):
|
|
return {"error": "Property doesn't have a z component"}
|
|
|
|
if sub_property in ["r", "g", "b", "a"] and not (base_value is Color):
|
|
return {"error": "Only Color has r, g, b, a components"}
|
|
|
|
# Convert value to float for vector components
|
|
var float_value
|
|
if typeof(value) == TYPE_STRING:
|
|
float_value = float(value)
|
|
else:
|
|
float_value = float(value)
|
|
|
|
# Set the appropriate component
|
|
match sub_property:
|
|
"x":
|
|
base_value.x = float_value
|
|
"y":
|
|
base_value.y = float_value
|
|
"z":
|
|
base_value.z = float_value
|
|
"r":
|
|
base_value.r = float_value
|
|
"g":
|
|
base_value.g = float_value
|
|
"b":
|
|
base_value.b = float_value
|
|
"a":
|
|
base_value.a = float_value
|
|
|
|
# Set the modified vector back to the node
|
|
node.set(base_property, base_value)
|
|
return {"message": "Property set: " + property_name + " = " + str(float_value)}
|
|
|
|
return {"error": "Unable to set sub-property: " + property_name}
|
|
|
|
# Handle regular properties
|
|
if not property_name in node:
|
|
return {"error": "Property not found on node: " + property_name}
|
|
|
|
# Get the current property value to determine its type
|
|
var current_value = node.get(property_name)
|
|
print("Current value type: ", typeof(current_value))
|
|
|
|
var converted_value = value
|
|
|
|
# If force_type is specified, use that for conversion
|
|
if force_type != "":
|
|
match force_type:
|
|
"bool":
|
|
if typeof(value) == TYPE_STRING:
|
|
converted_value = (value.to_lower() == "true")
|
|
else:
|
|
converted_value = bool(value)
|
|
"int":
|
|
converted_value = int(value)
|
|
"float":
|
|
converted_value = float(value)
|
|
"string":
|
|
converted_value = str(value)
|
|
"Vector2":
|
|
if typeof(value) == TYPE_ARRAY and value.size() >= 2:
|
|
converted_value = Vector2(float(value[0]), float(value[1]))
|
|
"Vector3":
|
|
if typeof(value) == TYPE_ARRAY and value.size() >= 3:
|
|
converted_value = Vector3(float(value[0]), float(value[1]), float(value[2]))
|
|
"Color":
|
|
if typeof(value) == TYPE_ARRAY:
|
|
if value.size() >= 4:
|
|
converted_value = Color(float(value[0]), float(value[1]), float(value[2]), float(value[3]))
|
|
elif value.size() >= 3:
|
|
converted_value = Color(float(value[0]), float(value[1]), float(value[2]))
|
|
else:
|
|
# Try to convert the value to match the current property type
|
|
match typeof(current_value):
|
|
TYPE_BOOL:
|
|
if typeof(value) == TYPE_STRING:
|
|
converted_value = (value.to_lower() == "true")
|
|
else:
|
|
converted_value = bool(value)
|
|
TYPE_INT:
|
|
converted_value = int(value)
|
|
TYPE_FLOAT:
|
|
converted_value = float(value)
|
|
TYPE_STRING:
|
|
converted_value = str(value)
|
|
TYPE_VECTOR2:
|
|
if typeof(value) == TYPE_ARRAY and value.size() >= 2:
|
|
converted_value = Vector2(float(value[0]), float(value[1]))
|
|
TYPE_VECTOR3:
|
|
if typeof(value) == TYPE_ARRAY and value.size() >= 3:
|
|
converted_value = Vector3(float(value[0]), float(value[1]), float(value[2]))
|
|
TYPE_COLOR:
|
|
if typeof(value) == TYPE_ARRAY:
|
|
if value.size() >= 4:
|
|
converted_value = Color(float(value[0]), float(value[1]), float(value[2]), float(value[3]))
|
|
elif value.size() >= 3:
|
|
converted_value = Color(float(value[0]), float(value[1]), float(value[2]))
|
|
|
|
print("Converted value: ", converted_value, " of type: ", typeof(converted_value))
|
|
|
|
# Try to set the property
|
|
var previous_value = node.get(property_name)
|
|
|
|
# Use set to safely call the setter
|
|
var error = null
|
|
node.set(property_name, converted_value)
|
|
|
|
# Check if the property actually changed
|
|
var new_value = node.get(property_name)
|
|
# Handle type-safe comparison - don't compare different types directly
|
|
var values_different = false
|
|
if typeof(new_value) == typeof(converted_value):
|
|
values_different = (new_value != converted_value)
|
|
else:
|
|
# Different types means they're different values
|
|
values_different = true
|
|
|
|
if new_value == previous_value and values_different:
|
|
return {"warning": "Property might be read-only or conversion failed: " + property_name, "previous_value": previous_value, "attempted_value": converted_value}
|
|
|
|
return {"message": "Property set: " + property_name + " = " + str(converted_value)}
|
|
|
|
|
|
#func handle_set_property(params):
|
|
#if not params.has("node_name") or not params.has("property_name") or not params.has("value"):
|
|
#return {"error": "Missing required parameters: node_name, property_name, and value"}
|
|
#
|
|
#var node_name = params.node_name
|
|
#var property_name = params.property_name
|
|
#var value = params.value
|
|
#var force_type = params.get("force_type", "")
|
|
#
|
|
#var editor_interface = editor_plugin.get_editor_interface()
|
|
#var current_scene = editor_interface.get_edited_scene_root()
|
|
#
|
|
#if current_scene == null:
|
|
#return {"error": "No scene is currently open"}
|
|
#
|
|
## Find node by name
|
|
#var node = current_scene.find_child(node_name, true, false)
|
|
#if not node:
|
|
#return {"error": "Node not found: " + node_name}
|
|
#
|
|
## Handle special properties
|
|
#if property_name == "script":
|
|
## Check if script file exists
|
|
#if not FileAccess.file_exists(value):
|
|
#return {"error": "Script file not found: " + value}
|
|
#
|
|
## Try to load the script
|
|
#var script_resource = load(value)
|
|
#if not script_resource:
|
|
#return {"error": "Failed to load script: " + value}
|
|
#
|
|
#if not script_resource is GDScript:
|
|
#return {"error": "Resource is not a GDScript: " + value}
|
|
#
|
|
## Set the script
|
|
#node.set_script(script_resource)
|
|
#return {"message": "Script set on node '" + node_name + "': " + value}
|
|
#
|
|
## Handle other special properties
|
|
#if property_name == "visible":
|
|
#node.visible = bool(value)
|
|
#return {"message": "Property set: visible = " + str(bool(value))}
|
|
#
|
|
#if property_name == "name":
|
|
## Check if name is already taken
|
|
#var existing = current_scene.find_child(str(value), true, false)
|
|
#if existing != null and existing != node:
|
|
#return {"error": "Name already in use by another node"}
|
|
#node.name = str(value)
|
|
#return {"message": "Node renamed to: " + str(value)}
|
|
#
|
|
## Handle property paths (e.g., "position:x")
|
|
#var parts = property_name.split(":")
|
|
#if parts.size() == 2:
|
|
#var base_property = parts[0]
|
|
#var sub_property = parts[1]
|
|
#
|
|
#if base_property in node:
|
|
#var base_value = node.get(base_property)
|
|
#
|
|
## Handle different vector types
|
|
#if base_value is Vector2 or base_value is Vector3 or base_value is Color:
|
|
#if sub_property in ["x", "y", "z", "r", "g", "b", "a"]:
|
|
## Safety check for Vector2 which doesn't have z
|
|
#if base_value is Vector2 and sub_property == "z":
|
|
#return {"error": "Vector2 doesn't have a z component"}
|
|
#
|
|
## Safety check for components that don't exist
|
|
#if sub_property == "z" and not (base_value is Vector3):
|
|
#return {"error": "Property doesn't have a z component"}
|
|
#
|
|
#if sub_property in ["r", "g", "b", "a"] and not (base_value is Color):
|
|
#return {"error": "Only Color has r, g, b, a components"}
|
|
#
|
|
## Convert value to float for vector components
|
|
#var float_value
|
|
#if typeof(value) == TYPE_STRING:
|
|
#float_value = float(value)
|
|
#else:
|
|
#float_value = float(value)
|
|
#
|
|
## Set the appropriate component
|
|
#match sub_property:
|
|
#"x": base_value.x = float_value
|
|
#"y": base_value.y = float_value
|
|
#"z": base_value.z = float_value
|
|
#"r": base_value.r = float_value
|
|
#"g": base_value.g = float_value
|
|
#"b": base_value.b = float_value
|
|
#"a": base_value.a = float_value
|
|
#
|
|
## Set the modified vector back to the node
|
|
#node.set(base_property, base_value)
|
|
#return {"message": "Property set: " + property_name + " = " + str(float_value)}
|
|
#
|
|
#return {"error": "Unable to set sub-property: " + property_name}
|
|
#
|
|
## Handle regular properties
|
|
#if not property_name in node:
|
|
#return {"error": "Property not found on node: " + property_name}
|
|
#
|
|
## Get the current property value to determine its type
|
|
#var current_value = node.get(property_name)
|
|
#var converted_value = value
|
|
#
|
|
## If force_type is specified, use that for conversion
|
|
#if force_type != "":
|
|
#match force_type:
|
|
#"bool":
|
|
#if typeof(value) == TYPE_STRING:
|
|
#converted_value = (value.to_lower() == "true")
|
|
#else:
|
|
#converted_value = bool(value)
|
|
#"int":
|
|
#converted_value = int(value)
|
|
#"float":
|
|
#converted_value = float(value)
|
|
#"string":
|
|
#converted_value = str(value)
|
|
#"Vector2":
|
|
#if typeof(value) == TYPE_ARRAY and value.size() >= 2:
|
|
#converted_value = Vector2(float(value[0]), float(value[1]))
|
|
#"Vector3":
|
|
#if typeof(value) == TYPE_ARRAY and value.size() >= 3:
|
|
#converted_value = Vector3(float(value[0]), float(value[1]), float(value[2]))
|
|
#"Color":
|
|
#if typeof(value) == TYPE_ARRAY:
|
|
#if value.size() >= 4:
|
|
#converted_value = Color(float(value[0]), float(value[1]), float(value[2]), float(value[3]))
|
|
#elif value.size() >= 3:
|
|
#converted_value = Color(float(value[0]), float(value[1]), float(value[2]))
|
|
#else:
|
|
## Try to convert the value to match the current property type
|
|
#match typeof(current_value):
|
|
#TYPE_BOOL:
|
|
#if typeof(value) == TYPE_STRING:
|
|
#converted_value = (value.to_lower() == "true")
|
|
#else:
|
|
#converted_value = bool(value)
|
|
#TYPE_INT:
|
|
#converted_value = int(value)
|
|
#TYPE_FLOAT:
|
|
#converted_value = float(value)
|
|
#TYPE_STRING:
|
|
#converted_value = str(value)
|
|
#TYPE_VECTOR2:
|
|
#if typeof(value) == TYPE_ARRAY and value.size() >= 2:
|
|
#converted_value = Vector2(float(value[0]), float(value[1]))
|
|
#TYPE_VECTOR3:
|
|
#if typeof(value) == TYPE_ARRAY and value.size() >= 3:
|
|
#converted_value = Vector3(float(value[0]), float(value[1]), float(value[2]))
|
|
#TYPE_COLOR:
|
|
#if typeof(value) == TYPE_ARRAY:
|
|
#if value.size() >= 4:
|
|
#converted_value = Color(float(value[0]), float(value[1]), float(value[2]), float(value[3]))
|
|
#elif value.size() >= 3:
|
|
#converted_value = Color(float(value[0]), float(value[1]), float(value[2]))
|
|
#
|
|
## Try to set the property
|
|
#var previous_value = node.get(property_name)
|
|
#
|
|
## Use callv to safely call the setter
|
|
#node.set(property_name, converted_value)
|
|
#
|
|
## Check if the property actually changed
|
|
#var new_value = node.get(property_name)
|
|
#if new_value == previous_value and new_value != converted_value:
|
|
#return {"warning": "Property might be read-only or conversion failed: " + property_name, "previous_value": previous_value, "attempted_value": converted_value}
|
|
#
|
|
#return {"message": "Property set: " + property_name + " = " + str(converted_value)}
|
|
|
|
func handle_set_parent(params):
|
|
if not params.has("child_name") or not params.has("parent_name"):
|
|
return {"error": "Missing required parameters: child_name and parent_name"}
|
|
|
|
var child_name = params.child_name
|
|
var parent_name = params.parent_name
|
|
var keep_global_transform = params.get("keep_global_transform", true)
|
|
|
|
var editor_interface = editor_plugin.get_editor_interface()
|
|
var current_scene = editor_interface.get_edited_scene_root()
|
|
|
|
if current_scene == null:
|
|
return {"error": "No scene is currently open"}
|
|
|
|
# Find child node (search the entire scene)
|
|
var child_node = current_scene.find_child(child_name, true, false)
|
|
if not child_node:
|
|
return {"error": "Child node not found: " + child_name}
|
|
|
|
# Find parent node (search the entire scene)
|
|
var parent_node
|
|
if parent_name == "root":
|
|
parent_node = current_scene
|
|
else:
|
|
parent_node = current_scene.find_child(parent_name, true, false)
|
|
|
|
if not parent_node:
|
|
return {"error": "Parent node not found: " + parent_name}
|
|
|
|
# Store the global transform if needed
|
|
var global_transform = null
|
|
if child_node is Node3D and keep_global_transform:
|
|
global_transform = child_node.global_transform
|
|
elif child_node is Node2D and keep_global_transform:
|
|
global_transform = child_node.global_transform
|
|
|
|
# Get the original parent
|
|
var original_parent = child_node.get_parent()
|
|
if original_parent:
|
|
# Remove from original parent
|
|
original_parent.remove_child(child_node)
|
|
|
|
# Add to new parent
|
|
parent_node.add_child(child_node)
|
|
|
|
# Ensure ownership is set correctly
|
|
child_node.owner = current_scene
|
|
|
|
# Set all children's owner recursively
|
|
_set_owner_recursive(child_node, current_scene)
|
|
|
|
# Restore global transform if needed
|
|
if child_node is Node3D and global_transform and keep_global_transform:
|
|
child_node.global_transform = global_transform
|
|
elif child_node is Node2D and global_transform and keep_global_transform:
|
|
child_node.global_transform = global_transform
|
|
|
|
return {"message": "Set parent of '" + child_name + "' to '" + parent_name + "'"}
|
|
|
|
# Helper function to set owner recursively
|
|
func _set_owner_recursive(node, owner):
|
|
for child in node.get_children():
|
|
child.owner = owner
|
|
_set_owner_recursive(child, owner)
|
|
|
|
|
|
|
|
|
|
func handle_create_child_object(params):
|
|
"""Create a new object as a child of an existing node."""
|
|
if not params.has("parent_name"):
|
|
return {"error": "Missing required parameter: parent_name"}
|
|
|
|
var parent_name = params.parent_name
|
|
var type = params.get("type", "EMPTY")
|
|
var name = params.get("name", "")
|
|
var location = params.get("location", [0, 0, 0])
|
|
var rotation = params.get("rotation", [0, 0, 0])
|
|
var scale = params.get("scale", [1, 1, 1])
|
|
var replace_if_exists = params.get("replace_if_exists", false)
|
|
|
|
var editor_interface = editor_plugin.get_editor_interface()
|
|
var current_scene = editor_interface.get_edited_scene_root()
|
|
|
|
if current_scene == null:
|
|
return {"error": "No scene is currently open"}
|
|
|
|
# Find parent node with proper path handling
|
|
var parent_node = null
|
|
|
|
if parent_name == "root":
|
|
parent_node = current_scene
|
|
else:
|
|
# Check if it's a path (contains /)
|
|
if "/" in parent_name:
|
|
# Try direct path lookup first
|
|
parent_node = current_scene.get_node_or_null(parent_name)
|
|
|
|
# If that fails, try with leading slash
|
|
if not parent_node and not parent_name.begins_with("/"):
|
|
parent_node = current_scene.get_node_or_null("/" + parent_name)
|
|
|
|
# If still not found, try parsing the path components
|
|
if not parent_node:
|
|
var parts = parent_name.split("/")
|
|
var current = current_scene
|
|
|
|
for part in parts:
|
|
if part == "":
|
|
continue
|
|
|
|
var found = false
|
|
for child in current.get_children():
|
|
if child.name == part:
|
|
current = child
|
|
found = true
|
|
break
|
|
|
|
if not found:
|
|
# Try searching recursively for this part
|
|
var found_node = current.find_child(part, true, false)
|
|
if found_node:
|
|
current = found_node
|
|
else:
|
|
# Part not found, path is invalid
|
|
return {"error": "Could not find part '" + part + "' in path: " + parent_name}
|
|
|
|
parent_node = current
|
|
else:
|
|
# Simple name lookup for non-path names
|
|
parent_node = current_scene.find_child(parent_name, true, false)
|
|
|
|
if not parent_node:
|
|
return {"error": "Parent node not found: " + parent_name}
|
|
|
|
# Check if name already exists and handle replacement
|
|
if name != "":
|
|
var existing_node = current_scene.find_child(name, true, false)
|
|
if existing_node and not replace_if_exists:
|
|
return {"error": "Node with name '" + name + "' already exists. Use replace_if_exists=true to replace it."}
|
|
elif existing_node and replace_if_exists:
|
|
existing_node.queue_free()
|
|
|
|
# Create the node based on type
|
|
var node
|
|
var upper_type = type.to_upper()
|
|
|
|
# Handle common 3D node types
|
|
match upper_type:
|
|
"NODE", "EMPTY":
|
|
node = Node3D.new()
|
|
"NODE3D":
|
|
node = Node3D.new()
|
|
"SPATIAL": # For compatibility with Godot 3.x terminology
|
|
node = Node3D.new()
|
|
"MESH", "MESHINSTANCE3D":
|
|
node = MeshInstance3D.new()
|
|
"CUBE", "BOX":
|
|
node = MeshInstance3D.new()
|
|
node.mesh = BoxMesh.new()
|
|
"SPHERE":
|
|
node = MeshInstance3D.new()
|
|
node.mesh = SphereMesh.new()
|
|
"CYLINDER":
|
|
node = MeshInstance3D.new()
|
|
node.mesh = CylinderMesh.new()
|
|
"PLANE":
|
|
node = MeshInstance3D.new()
|
|
node.mesh = PlaneMesh.new()
|
|
"CAMERA", "CAMERA3D":
|
|
node = Camera3D.new()
|
|
"LIGHT", "DIRECTIONALLIGHT", "DIRECTIONALLIGHT3D":
|
|
node = DirectionalLight3D.new()
|
|
"SPOTLIGHT", "SPOTLIGHT3D":
|
|
node = SpotLight3D.new()
|
|
"OMNILIGHT", "OMNILIGHT3D":
|
|
node = OmniLight3D.new()
|
|
"RIGIDBODY", "RIGIDBODY3D":
|
|
node = RigidBody3D.new()
|
|
"STATICBODY", "STATICBODY3D":
|
|
node = StaticBody3D.new()
|
|
"CHARACTERBODY", "CHARACTERBODY3D":
|
|
node = CharacterBody3D.new()
|
|
"AREA", "AREA3D":
|
|
node = Area3D.new()
|
|
"COLLISION", "COLLISIONSHAPE3D":
|
|
node = CollisionShape3D.new()
|
|
# Add a default sphere shape
|
|
node.shape = SphereShape3D.new()
|
|
|
|
# Handle common 2D node types
|
|
"NODE2D":
|
|
node = Node2D.new()
|
|
"SPRITE", "SPRITE2D":
|
|
node = Sprite2D.new()
|
|
"CAMERA2D":
|
|
node = Camera2D.new()
|
|
"AREA2D":
|
|
node = Area2D.new()
|
|
"COLLISION2D", "COLLISIONSHAPE2D":
|
|
node = CollisionShape2D.new()
|
|
# Add a default circle shape
|
|
node.shape = CircleShape2D.new()
|
|
"RIGIDBODY2D":
|
|
node = RigidBody2D.new()
|
|
"STATICBODY2D":
|
|
node = StaticBody2D.new()
|
|
"CHARACTERBODY2D":
|
|
node = CharacterBody2D.new()
|
|
|
|
# Handle UI node types
|
|
"CONTROL":
|
|
node = Control.new()
|
|
"PANEL":
|
|
node = Panel.new()
|
|
"BUTTON":
|
|
node = Button.new()
|
|
"LABEL":
|
|
node = Label.new()
|
|
"LINEEDIT":
|
|
node = LineEdit.new()
|
|
"TEXTEDIT":
|
|
node = TextEdit.new()
|
|
"CONTAINER":
|
|
node = Container.new()
|
|
"VBOX", "VBOXCONTAINER":
|
|
node = VBoxContainer.new()
|
|
"HBOX", "HBOXCONTAINER":
|
|
node = HBoxContainer.new()
|
|
_:
|
|
# Try to create the node directly by class name using ClassDB
|
|
if ClassDB.class_exists(type) and ClassDB.can_instantiate(type):
|
|
node = ClassDB.instantiate(type)
|
|
else:
|
|
return {"error": "Unsupported object type: " + type}
|
|
|
|
# Set node name if provided
|
|
if name != "":
|
|
node.name = name
|
|
|
|
# Add to parent node directly
|
|
parent_node.add_child(node)
|
|
node.owner = current_scene
|
|
|
|
# Recursively set ownership for all children
|
|
_set_owner_recursive(node, current_scene)
|
|
|
|
# Set transform for Node3D or Node2D objects
|
|
if node is Node3D:
|
|
node.position = Vector3(location[0], location[1], location[2])
|
|
node.rotation_degrees = Vector3(rotation[0], rotation[1], rotation[2])
|
|
node.scale = Vector3(scale[0], scale[1], scale[2])
|
|
elif node is Node2D:
|
|
node.position = Vector2(location[0], location[1])
|
|
node.rotation_degrees = rotation[0]
|
|
node.scale = Vector2(scale[0], scale[1])
|
|
|
|
# In the JSON output, include the full path to the parent to help with debugging
|
|
var parent_path = current_scene.get_path_to(parent_node)
|
|
|
|
return {
|
|
"name": node.name,
|
|
"type": node.get_class(),
|
|
"path": current_scene.get_path_to(node),
|
|
"parent": parent_node.name,
|
|
"parent_path": parent_path
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#func handle_create_child_object(params):
|
|
#"""Create a new object as a child of an existing node."""
|
|
#if not params.has("parent_name"):
|
|
#return {"error": "Missing required parameter: parent_name"}
|
|
#
|
|
#var parent_name = params.parent_name
|
|
#var type = params.get("type", "EMPTY")
|
|
#var name = params.get("name", "")
|
|
#var location = params.get("location", [0, 0, 0])
|
|
#var rotation = params.get("rotation", [0, 0, 0])
|
|
#var scale = params.get("scale", [1, 1, 1])
|
|
#var replace_if_exists = params.get("replace_if_exists", false)
|
|
#
|
|
#var editor_interface = editor_plugin.get_editor_interface()
|
|
#var current_scene = editor_interface.get_edited_scene_root()
|
|
#
|
|
#if current_scene == null:
|
|
#return {"error": "No scene is currently open"}
|
|
#
|
|
## Find parent node
|
|
#var parent_node
|
|
#if parent_name == "root":
|
|
#parent_node = current_scene
|
|
#else:
|
|
#parent_node = current_scene.find_child(parent_name, true, false)
|
|
#
|
|
#if not parent_node:
|
|
#return {"error": "Parent node not found: " + parent_name}
|
|
#
|
|
## Check if name already exists and handle replacement
|
|
#if name != "":
|
|
#var existing_node = current_scene.find_child(name, true, false)
|
|
#if existing_node and not replace_if_exists:
|
|
#return {"error": "Node with name '" + name + "' already exists. Use replace_if_exists=true to replace it."}
|
|
#elif existing_node and replace_if_exists:
|
|
#existing_node.queue_free()
|
|
#
|
|
## Create the node based on type (same logic as handle_create_object)
|
|
#var node
|
|
#var upper_type = type.to_upper()
|
|
#
|
|
## Handle common 3D node types
|
|
#match upper_type:
|
|
#"NODE", "EMPTY":
|
|
#node = Node3D.new()
|
|
## (rest of the type matching code from handle_create_object)
|
|
## ...
|
|
#_:
|
|
## Try to create the node directly by class name using ClassDB
|
|
#if ClassDB.class_exists(type) and ClassDB.can_instantiate(type):
|
|
#node = ClassDB.instantiate(type)
|
|
#else:
|
|
#return {"error": "Unsupported object type: " + type}
|
|
#
|
|
## Set node name if provided
|
|
#if name != "":
|
|
#node.name = name
|
|
#
|
|
## Add to parent node directly
|
|
#parent_node.add_child(node)
|
|
#node.owner = current_scene
|
|
#
|
|
## Set transform for Node3D or Node2D objects
|
|
#if node is Node3D:
|
|
#node.position = Vector3(location[0], location[1], location[2])
|
|
#node.rotation_degrees = Vector3(rotation[0], rotation[1], rotation[2])
|
|
#node.scale = Vector3(scale[0], scale[1], scale[2])
|
|
#elif node is Node2D:
|
|
#node.position = Vector2(location[0], location[1])
|
|
#node.rotation_degrees = rotation[0]
|
|
#node.scale = Vector2(scale[0], scale[1])
|
|
#
|
|
#return {
|
|
#"name": node.name,
|
|
#"type": node.get_class(),
|
|
#"path": current_scene.get_path_to(node),
|
|
#"parent": parent_name
|
|
#}
|
|
|
|
func handle_set_mesh(params):
|
|
"""Create and set a mesh on a MeshInstance3D node."""
|
|
if not params.has("node_name") or not params.has("mesh_type"):
|
|
return {"error": "Missing required parameters: node_name and mesh_type"}
|
|
|
|
var node_name = params.node_name
|
|
var mesh_type = params.mesh_type
|
|
|
|
print("handle_set_mesh called for node: ", node_name, " mesh_type: ", mesh_type)
|
|
|
|
var editor_interface = editor_plugin.get_editor_interface()
|
|
var current_scene = editor_interface.get_edited_scene_root()
|
|
|
|
if current_scene == null:
|
|
return {"error": "No scene is currently open"}
|
|
|
|
# Find node using the improved node lookup
|
|
var node = _find_node(current_scene, node_name)
|
|
if not node:
|
|
return {"error": "Node not found: " + node_name}
|
|
|
|
print("Node found: ", node.name, " type: ", node.get_class())
|
|
|
|
# Check if the node can have a mesh property
|
|
if not node is MeshInstance3D:
|
|
return {"error": "Node is not a MeshInstance3D: " + node_name}
|
|
|
|
# Create mesh based on type
|
|
var mesh
|
|
match mesh_type.to_upper():
|
|
"CAPSULEMESH":
|
|
mesh = CapsuleMesh.new()
|
|
if params.has("radius"):
|
|
mesh.radius = float(params.radius)
|
|
if params.has("height"):
|
|
mesh.height = float(params.height)
|
|
"BOXMESH":
|
|
mesh = BoxMesh.new()
|
|
if params.has("size"):
|
|
var size = params.size
|
|
mesh.size = Vector3(float(size[0]), float(size[1]), float(size[2]))
|
|
"SPHEREMESH":
|
|
mesh = SphereMesh.new()
|
|
if params.has("radius"):
|
|
mesh.radius = float(params.radius)
|
|
"CYLINDERMESH":
|
|
mesh = CylinderMesh.new()
|
|
if params.has("radius"):
|
|
mesh.radius = float(params.radius)
|
|
if params.has("height"):
|
|
mesh.height = float(params.height)
|
|
"PLANEMESH":
|
|
mesh = PlaneMesh.new()
|
|
if params.has("size"):
|
|
var size = params.size
|
|
mesh.size = Vector2(float(size[0]), float(size[1]))
|
|
_:
|
|
return {"error": "Unsupported mesh type: " + mesh_type}
|
|
|
|
# Set the mesh
|
|
node.mesh = mesh
|
|
|
|
return {"message": "Set " + mesh_type + " on " + node_name}
|
|
|
|
|
|
func _find_node(root, name_or_path):
|
|
# Check if it's a path (contains /)
|
|
if "/" in name_or_path:
|
|
# Try direct path lookup first
|
|
var node = root.get_node_or_null(name_or_path)
|
|
if node:
|
|
return node
|
|
|
|
# Try as relative path
|
|
if not name_or_path.begins_with("/"):
|
|
node = root.get_node_or_null("/" + name_or_path)
|
|
if node:
|
|
return node
|
|
|
|
# Try all combinations of path separators
|
|
var parts = name_or_path.split("/")
|
|
var current = root
|
|
|
|
for part in parts:
|
|
# Look for the next part in current node's children
|
|
var found = false
|
|
for child in current.get_children():
|
|
if child.name == part:
|
|
current = child
|
|
found = true
|
|
break
|
|
|
|
if not found:
|
|
# Try searching recursively
|
|
var found_node = current.find_child(part, true, false)
|
|
if found_node:
|
|
current = found_node
|
|
else:
|
|
return null
|
|
|
|
return current
|
|
else:
|
|
# Simple name lookup
|
|
return root.find_child(name_or_path, true, false)
|
|
|
|
|
|
func handle_set_collision_shape(params):
|
|
"""Create and set a collision shape on a CollisionShape3D or CollisionShape2D node."""
|
|
if not params.has("node_name") or not params.has("shape_type"):
|
|
return {"error": "Missing required parameters: node_name and shape_type"}
|
|
|
|
var node_name = params.node_name
|
|
var shape_type = params.shape_type
|
|
var shape_params = params.get("shape_params", {})
|
|
|
|
var editor_interface = editor_plugin.get_editor_interface()
|
|
var current_scene = editor_interface.get_edited_scene_root()
|
|
|
|
if current_scene == null:
|
|
return {"error": "No scene is currently open"}
|
|
|
|
# Find node by name or path
|
|
var node = _find_node(current_scene, node_name)
|
|
if not node:
|
|
return {"error": "Node not found: " + node_name}
|
|
|
|
# Check if the node can have a shape property
|
|
if not (node is CollisionShape3D or node is CollisionShape2D):
|
|
return {"error": "Node is not a CollisionShape: " + node_name}
|
|
|
|
# Create shape based on type
|
|
var shape
|
|
match shape_type.to_upper():
|
|
"CAPSULESHAPE3D":
|
|
shape = CapsuleShape3D.new()
|
|
if shape_params.has("radius"):
|
|
shape.radius = float(shape_params.radius)
|
|
if shape_params.has("height"):
|
|
shape.height = float(shape_params.height)
|
|
"BOXSHAPE3D":
|
|
shape = BoxShape3D.new()
|
|
if shape_params.has("size"):
|
|
var size = shape_params.size
|
|
shape.size = Vector3(float(size[0]), float(size[1]), float(size[2]))
|
|
"SPHERESHAPE3D":
|
|
shape = SphereShape3D.new()
|
|
if shape_params.has("radius"):
|
|
shape.radius = float(shape_params.radius)
|
|
"CYLINDERSHAPE3D":
|
|
shape = CylinderShape3D.new()
|
|
if shape_params.has("radius"):
|
|
shape.radius = float(shape_params.radius)
|
|
if shape_params.has("height"):
|
|
shape.height = float(shape_params.height)
|
|
"WORLDBOUNDARYSHAPE3D":
|
|
shape = WorldBoundaryShape3D.new()
|
|
# 2D Shapes
|
|
"CIRCLESHAPE2D":
|
|
shape = CircleShape2D.new()
|
|
if shape_params.has("radius"):
|
|
shape.radius = float(shape_params.radius)
|
|
"RECTANGLESHAPE2D":
|
|
shape = RectangleShape2D.new()
|
|
if shape_params.has("size"):
|
|
var size = shape_params.size
|
|
shape.size = Vector2(float(size[0]), float(size[1]))
|
|
"CAPSULESHAPE2D":
|
|
shape = CapsuleShape2D.new()
|
|
if shape_params.has("radius"):
|
|
shape.radius = float(shape_params.radius)
|
|
if shape_params.has("height"):
|
|
shape.height = float(shape_params.height)
|
|
_:
|
|
return {"error": "Unsupported shape type: " + shape_type}
|
|
|
|
# Set the shape
|
|
node.shape = shape
|
|
|
|
return {"message": "Set " + shape_type + " on " + node_name}
|
|
|
|
|
|
#func handle_set_nested_property(params):
|
|
#"""Set a nested property like environment/sky/sky_material on a node."""
|
|
#if not params.has("node_name") or not params.has("property_name") or not params.has("value"):
|
|
#return {"error": "Missing required parameters: node_name, property_name, and value"}
|
|
#
|
|
#var node_name = params.node_name
|
|
#var property_path = params.property_name
|
|
#var value = params.value
|
|
#var value_type = params.get("value_type", "")
|
|
#
|
|
#var editor_interface = editor_plugin.get_editor_interface()
|
|
#var current_scene = editor_interface.get_edited_scene_root()
|
|
#
|
|
#if current_scene == null:
|
|
#return {"error": "No scene is currently open"}
|
|
#
|
|
## Find node by name or path
|
|
#var node = null
|
|
#
|
|
## Check if it's a path (contains /)
|
|
#if "/" in node_name:
|
|
## Try direct path lookup first
|
|
#node = current_scene.get_node_or_null(node_name)
|
|
#
|
|
## If that fails, try with leading slash
|
|
#if not node and not node_name.begins_with("/"):
|
|
#node = current_scene.get_node_or_null("/" + node_name)
|
|
#
|
|
## Try other lookup methods if needed...
|
|
#else:
|
|
## Simple name lookup for non-path names
|
|
#node = current_scene.find_child(node_name, true, false)
|
|
#
|
|
#if not node:
|
|
#return {"error": "Node not found: " + node_name}
|
|
#
|
|
## Split the property path
|
|
#var property_parts = property_path.split("/")
|
|
#
|
|
## Handle special cases for common node types
|
|
#if node is WorldEnvironment:
|
|
## Make sure the environment exists
|
|
#if not node.environment:
|
|
#node.environment = Environment.new()
|
|
#
|
|
## Handle environment properties
|
|
#if property_parts.size() >= 1 and property_parts[0] == "environment":
|
|
#var env = node.environment
|
|
#
|
|
## Handle special case for sky material
|
|
#if property_parts.size() >= 3 and property_parts[1] == "sky" and property_parts[2] == "sky_material":
|
|
## Make sure sky exists
|
|
#if not env.sky:
|
|
#env.sky = Sky.new()
|
|
#
|
|
#if property_parts.size() == 3:
|
|
## Create the material based on type
|
|
#var material = null
|
|
#if value == "ProceduralSkyMaterial":
|
|
#material = ProceduralSkyMaterial.new()
|
|
#elif value == "PanoramaSkyMaterial":
|
|
#material = PanoramaSkyMaterial.new()
|
|
#elif value == "PhysicalSkyMaterial":
|
|
#material = PhysicalSkyMaterial.new()
|
|
#else:
|
|
#return {"error": "Unknown sky material type: " + str(value)}
|
|
#
|
|
## Set the sky material
|
|
#env.sky.sky_material = material
|
|
#return {"message": "Set sky material to " + str(value)}
|
|
#
|
|
## Handle sky material properties (e.g., environment/sky/sky_material/sky_top_color)
|
|
#elif property_parts.size() >= 4 and env.sky.sky_material:
|
|
#var material = env.sky.sky_material
|
|
#var mat_prop = property_parts[3]
|
|
#
|
|
## Verify property exists on the material
|
|
#if not mat_prop in material:
|
|
#return {"error": "Property not found on sky material: " + mat_prop}
|
|
#
|
|
## Handle color properties
|
|
#if typeof(material.get(mat_prop)) == TYPE_COLOR:
|
|
#if typeof(value) == TYPE_ARRAY and value.size() >= 3:
|
|
#if value.size() >= 4:
|
|
#material.set(mat_prop, Color(value[0], value[1], value[2], value[3]))
|
|
#else:
|
|
#material.set(mat_prop, Color(value[0], value[1], value[2]))
|
|
#return {"message": "Set sky material property " + mat_prop + " to " + str(value)}
|
|
#else:
|
|
## Set other property types
|
|
#material.set(mat_prop, value)
|
|
#return {"message": "Set sky material property " + mat_prop + " to " + str(value)}
|
|
#
|
|
## Handle direct environment properties
|
|
#elif property_parts.size() == 2:
|
|
#var prop = property_parts[1]
|
|
#
|
|
## Map common property names to actual Godot property names
|
|
#match prop:
|
|
#"background_color":
|
|
#prop = "background_color"
|
|
#"ambient_light_color":
|
|
#prop = "ambient_light_color"
|
|
#"fog_color":
|
|
#prop = "fog_color"
|
|
#"background_mode":
|
|
#prop = "background_mode"
|
|
## Add more mappings as needed
|
|
#
|
|
## Verify property exists
|
|
#if not prop in env:
|
|
#var available_props = []
|
|
#for p in ["background_color", "ambient_light_color", "fog_color", "background_mode",
|
|
#"fog_enabled", "fog_density", "glow_enabled", "glow_intensity",
|
|
#"adjustment_enabled", "tonemap_mode"]:
|
|
#if p in env:
|
|
#available_props.append(p)
|
|
#return {"error": "Property not found on environment: " + prop +
|
|
#". Available properties include: " + str(available_props)}
|
|
#
|
|
## Convert value based on property type
|
|
#var converted_value = value
|
|
#var current_value = env.get(prop)
|
|
#
|
|
## Handle different types
|
|
#if typeof(current_value) == TYPE_COLOR:
|
|
#if typeof(value) == TYPE_ARRAY and value.size() >= 3:
|
|
#if value.size() >= 4:
|
|
#converted_value = Color(value[0], value[1], value[2], value[3])
|
|
#else:
|
|
#converted_value = Color(value[0], value[1], value[2])
|
|
#elif typeof(current_value) == TYPE_BOOL:
|
|
#if typeof(value) == TYPE_STRING:
|
|
#converted_value = (value.to_lower() == "true")
|
|
#else:
|
|
#converted_value = bool(value)
|
|
#elif typeof(current_value) == TYPE_INT:
|
|
#converted_value = int(value)
|
|
#elif typeof(current_value) == TYPE_FLOAT:
|
|
#converted_value = float(value)
|
|
#
|
|
## Set the property
|
|
#env.set(prop, converted_value)
|
|
#return {"message": "Set environment property " + prop + " to " + str(converted_value)}
|
|
#
|
|
## Handle environment sub-objects (e.g., environment/fog/enabled)
|
|
#elif property_parts.size() == 3:
|
|
#var sub_obj = property_parts[1]
|
|
#var sub_prop = property_parts[2]
|
|
#
|
|
## Map common property paths
|
|
#if sub_obj == "fog" and sub_prop == "enabled":
|
|
#env.fog_enabled = bool(value)
|
|
#return {"message": "Set fog_enabled to " + str(bool(value))}
|
|
#elif sub_obj == "glow" and sub_prop == "enabled":
|
|
#env.glow_enabled = bool(value)
|
|
#return {"message": "Set glow_enabled to " + str(bool(value))}
|
|
#elif sub_obj == "ambient_light" and sub_prop == "color":
|
|
#if typeof(value) == TYPE_ARRAY and value.size() >= 3:
|
|
#env.ambient_light_color = Color(value[0], value[1], value[2])
|
|
#return {"message": "Set ambient_light_color to " + str(value)}
|
|
#
|
|
## Generic property mapping attempt
|
|
#var full_prop = sub_obj + "_" + sub_prop
|
|
#if full_prop in env:
|
|
#env.set(full_prop, value)
|
|
#return {"message": "Set " + full_prop + " to " + str(value)}
|
|
#
|
|
#return {"error": "Unsupported environment property path: " + property_path}
|
|
#
|
|
## Handle other node types here
|
|
## ...
|
|
#
|
|
#return {"error": "Unsupported nested property path: " + property_path}
|
|
|
|
|
|
func handle_set_nested_property(params):
|
|
"""Set a nested property like environment/sky/sky_material on a node."""
|
|
if not params.has("node_name") or not params.has("property_name") or not params.has("value"):
|
|
return {"error": "Missing required parameters: node_name, property_name, and value"}
|
|
|
|
var node_name = params.node_name
|
|
var property_path = params.property_name
|
|
var value = params.value
|
|
var value_type = params.get("value_type", "")
|
|
|
|
print("Setting nested property: ", property_path, " to ", value, " on ", node_name)
|
|
|
|
var editor_interface = editor_plugin.get_editor_interface()
|
|
var current_scene = editor_interface.get_edited_scene_root()
|
|
|
|
if current_scene == null:
|
|
return {"error": "No scene is currently open"}
|
|
|
|
# Find node by name or path
|
|
var node = null
|
|
|
|
# Check if it's a path (contains /)
|
|
if "/" in node_name:
|
|
# Try direct path lookup first
|
|
node = current_scene.get_node_or_null(node_name)
|
|
|
|
# If that fails, try with leading slash
|
|
if not node and not node_name.begins_with("/"):
|
|
node = current_scene.get_node_or_null("/" + node_name)
|
|
|
|
# Try parsing the path manually if needed
|
|
if not node:
|
|
# Try other path resolution methods...
|
|
pass
|
|
else:
|
|
# Simple name lookup for non-path names
|
|
node = current_scene.find_child(node_name, true, false)
|
|
|
|
if not node:
|
|
return {"error": "Node not found: " + node_name}
|
|
|
|
# Split the property path
|
|
var property_parts = property_path.split("/")
|
|
|
|
# Handle WorldEnvironment properties
|
|
if node is WorldEnvironment:
|
|
return _handle_worldenvironment_properties(node, property_parts, value)
|
|
|
|
# Add other node type handlers here
|
|
|
|
# Default case for simple properties
|
|
return {"error": "Unsupported node type for nested properties: " + node.get_class()}
|
|
|
|
func _handle_worldenvironment_properties(node, property_parts, value):
|
|
"""Handle nested properties for WorldEnvironment nodes."""
|
|
# Make sure the environment exists
|
|
if not node.environment:
|
|
node.environment = Environment.new()
|
|
|
|
# Get environment resource for easier access
|
|
var env = node.environment
|
|
|
|
# First level should be "environment"
|
|
if property_parts.size() < 1 or property_parts[0] != "environment":
|
|
return {"error": "WorldEnvironment properties must start with 'environment/'"}
|
|
|
|
# Handle sky material properties (4-part paths)
|
|
if property_parts.size() == 4 and property_parts[1] == "sky" and property_parts[2] == "sky_material":
|
|
# Make sure sky exists
|
|
if not env.sky:
|
|
env.sky = Sky.new()
|
|
|
|
# Make sure sky material exists
|
|
if not env.sky.sky_material:
|
|
env.sky.sky_material = ProceduralSkyMaterial.new()
|
|
|
|
var material = env.sky.sky_material
|
|
var property_name = property_parts[3]
|
|
|
|
# Debug info
|
|
print("Trying to set sky material property: ", property_name)
|
|
print("Material class: ", material.get_class())
|
|
|
|
# Direct property handling for cloud properties
|
|
if property_name == "use_clouds" or property_name == "clouds_enabled":
|
|
# Enable/disable clouds directly
|
|
material.use_clouds = bool(value)
|
|
return {"message": "Set use_clouds to " + str(bool(value))}
|
|
|
|
elif property_name == "cloud_color":
|
|
# Set cloud color directly
|
|
if typeof(value) == TYPE_ARRAY and value.size() >= 3:
|
|
material.cloud_color = Color(float(value[0]), float(value[1]), float(value[2]))
|
|
return {"message": "Set cloud_color to " + str(material.cloud_color)}
|
|
|
|
elif property_name == "cloud_coverage":
|
|
# Set cloud coverage directly
|
|
material.cloud_coverage = float(value)
|
|
return {"message": "Set cloud_coverage to " + str(float(value))}
|
|
|
|
elif property_name == "cloud_size":
|
|
# Set cloud size directly
|
|
material.cloud_size = float(value)
|
|
return {"message": "Set cloud_size to " + str(float(value))}
|
|
|
|
# Handle other properties with the property map
|
|
var property_map = {
|
|
# Sky colors
|
|
"sky_top_color": "sky_top_color",
|
|
"sky_horizon_color": "sky_horizon_color",
|
|
"sky_curve": "sky_curve",
|
|
"ground_horizon_color": "ground_horizon_color",
|
|
"ground_bottom_color": "ground_bottom_color",
|
|
"ground_curve": "ground_curve",
|
|
|
|
# Sun properties
|
|
"sun_angle_max": "sun_angle_max",
|
|
"sun_curve": "sun_curve"
|
|
}
|
|
|
|
# Check if property name needs to be mapped
|
|
if property_name in property_map:
|
|
property_name = property_map[property_name]
|
|
|
|
# Try to set the property with property-specific type conversion
|
|
var current_value = material.get(property_name)
|
|
var converted_value = value
|
|
|
|
# Handle different property types
|
|
match typeof(current_value):
|
|
TYPE_BOOL:
|
|
converted_value = bool(value)
|
|
TYPE_INT:
|
|
converted_value = int(value)
|
|
TYPE_FLOAT:
|
|
converted_value = float(value)
|
|
TYPE_COLOR:
|
|
if typeof(value) == TYPE_ARRAY and value.size() >= 3:
|
|
if value.size() >= 4:
|
|
converted_value = Color(float(value[0]), float(value[1]), float(value[2]), float(value[3]))
|
|
else:
|
|
converted_value = Color(float(value[0]), float(value[1]), float(value[2]))
|
|
|
|
# Set the property
|
|
material.set(property_name, converted_value)
|
|
return {"message": "Set sky material property " + property_name + " to " + str(converted_value)}
|
|
|
|
return {"error": "Unknown sky material property: " + property_name + ". Available cloud properties: use_clouds, cloud_color, cloud_coverage, cloud_size"}
|
|
|
|
# Handle sky material type (3-part paths)
|
|
elif property_parts.size() == 3 and property_parts[1] == "sky" and property_parts[2] == "sky_material":
|
|
# Make sure sky exists
|
|
if not env.sky:
|
|
env.sky = Sky.new()
|
|
|
|
# Create the material based on type
|
|
var material = null
|
|
|
|
if value == "ProceduralSkyMaterial":
|
|
material = ProceduralSkyMaterial.new()
|
|
elif value == "PanoramaSkyMaterial":
|
|
material = PanoramaSkyMaterial.new()
|
|
elif value == "PhysicalSkyMaterial":
|
|
material = PhysicalSkyMaterial.new()
|
|
else:
|
|
return {"error": "Unknown sky material type: " + str(value)}
|
|
|
|
# Set the sky material
|
|
env.sky.sky_material = material
|
|
return {"message": "Set sky material to " + str(value)}
|
|
|
|
# Handle direct environment properties (2-part paths)
|
|
elif property_parts.size() == 2:
|
|
var property_name = property_parts[1]
|
|
|
|
# Map common property name variations to actual property names
|
|
var property_map = {
|
|
"background": "background_mode",
|
|
"ambient_light_color": "ambient_light_color",
|
|
"background_color": "background_color",
|
|
"fog_enabled": "fog_enabled",
|
|
"glow_enabled": "glow_enabled",
|
|
"glow_intensity": "glow_intensity",
|
|
"fog_density": "fog_density",
|
|
"fog_color": "fog_color",
|
|
"volumetric_fog_enabled": "volumetric_fog_enabled",
|
|
"volumetric_fog_density": "volumetric_fog_density",
|
|
"volumetric_fog_albedo": "volumetric_fog_albedo"
|
|
}
|
|
|
|
# Check if we need to map the property name
|
|
if property_name in property_map:
|
|
property_name = property_map[property_name]
|
|
|
|
# Verify property exists
|
|
if not property_name in env:
|
|
return {"error": "Property not found on environment: " + property_name}
|
|
|
|
# Convert value based on property type
|
|
var converted_value = value
|
|
var current_value = env.get(property_name)
|
|
|
|
# Handle different property types
|
|
match typeof(current_value):
|
|
TYPE_BOOL:
|
|
converted_value = bool(value)
|
|
TYPE_INT:
|
|
converted_value = int(value)
|
|
TYPE_FLOAT:
|
|
converted_value = float(value)
|
|
TYPE_COLOR:
|
|
if typeof(value) == TYPE_ARRAY and value.size() >= 3:
|
|
if value.size() >= 4:
|
|
converted_value = Color(float(value[0]), float(value[1]), float(value[2]), float(value[3]))
|
|
else:
|
|
converted_value = Color(float(value[0]), float(value[1]), float(value[2]))
|
|
|
|
# Set the property
|
|
env.set(property_name, converted_value)
|
|
return {"message": "Set environment property " + property_name + " to " + str(converted_value)}
|
|
|
|
# Handle fog properties (3-part paths)
|
|
elif property_parts.size() == 3 and property_parts[1] == "fog":
|
|
var prop = property_parts[2]
|
|
|
|
if prop == "enabled":
|
|
env.fog_enabled = bool(value)
|
|
return {"message": "Set fog_enabled to " + str(bool(value))}
|
|
elif prop == "density":
|
|
env.fog_density = float(value)
|
|
return {"message": "Set fog_density to " + str(float(value))}
|
|
elif prop == "color":
|
|
if typeof(value) == TYPE_ARRAY and value.size() >= 3:
|
|
env.fog_color = Color(float(value[0]), float(value[1]), float(value[2]))
|
|
return {"message": "Set fog_color to " + str(env.fog_color)}
|
|
else:
|
|
# Try a direct combined property name
|
|
var combined_prop = "fog_" + prop
|
|
if combined_prop in env:
|
|
env.set(combined_prop, value)
|
|
return {"message": "Set " + combined_prop + " to " + str(value)}
|
|
return {"error": "Unknown fog property: " + prop}
|
|
|
|
# Handle volumetric fog properties (3-part paths)
|
|
elif property_parts.size() == 3 and property_parts[1] == "volumetric_fog":
|
|
var prop = property_parts[2]
|
|
|
|
if prop == "enabled":
|
|
env.volumetric_fog_enabled = bool(value)
|
|
return {"message": "Set volumetric_fog_enabled to " + str(bool(value))}
|
|
elif prop == "density":
|
|
env.volumetric_fog_density = float(value)
|
|
return {"message": "Set volumetric_fog_density to " + str(float(value))}
|
|
elif prop == "albedo":
|
|
if typeof(value) == TYPE_ARRAY and value.size() >= 3:
|
|
env.volumetric_fog_albedo = Color(float(value[0]), float(value[1]), float(value[2]))
|
|
return {"message": "Set volumetric_fog_albedo to " + str(env.volumetric_fog_albedo)}
|
|
else:
|
|
# Try a direct combined property name
|
|
var combined_prop = "volumetric_fog_" + prop
|
|
if combined_prop in env:
|
|
env.set(combined_prop, value)
|
|
return {"message": "Set " + combined_prop + " to " + str(value)}
|
|
return {"error": "Unknown volumetric fog property: " + prop}
|
|
|
|
# Other environment properties can be added here
|
|
|
|
return {"error": "Unsupported property path: " + property_parts.join("/")}
|
|
|
|
# Add the following function:
|
|
func handle_delete_script(params):
|
|
if not params.has("script_path"):
|
|
return {"error": "Missing required parameter: script_path"}
|
|
|
|
var script_path = params.script_path
|
|
|
|
# Check if file exists
|
|
if not FileAccess.file_exists(script_path):
|
|
return {"error": "Script file does not exist: " + script_path}
|
|
|
|
# Delete the file
|
|
var error = DirAccess.remove_absolute(script_path)
|
|
if error != OK:
|
|
return {"error": "Failed to delete script file: " + script_path + " (Error code: " + str(error) + ")"}
|
|
|
|
return {
|
|
"message": "Script deleted successfully: " + script_path
|
|
}
|
|
|
|
# Add the following function near the handle_delete_script function:
|
|
func handle_delete_file(params):
|
|
if not params.has("file_path"):
|
|
return {"error": "Missing required parameter: file_path"}
|
|
|
|
var file_path = params.file_path
|
|
|
|
# Check if file exists
|
|
if not FileAccess.file_exists(file_path):
|
|
return {"error": "File does not exist: " + file_path}
|
|
|
|
# Delete the file
|
|
var error = DirAccess.remove_absolute(file_path)
|
|
if error != OK:
|
|
return {"error": "Failed to delete file: " + file_path + " (Error code: " + str(error) + ")"}
|
|
|
|
return {
|
|
"message": "File deleted successfully: " + file_path
|
|
}
|
|
|
|
func handle_show_message(params):
|
|
if not params.has("message"):
|
|
return {"error": "Missing required parameter: message"}
|
|
|
|
var message = params.message
|
|
|
|
# Show a message dialog
|
|
editor_plugin.get_editor_interface().show_message_notification(message)
|
|
|
|
return {"message": "Message shown successfully"}
|
|
|
|
func handle_reimport_asset(params):
|
|
if not params.has("asset_path"):
|
|
return {"error": "Missing required parameter: asset_path"}
|
|
|
|
var asset_path = params.asset_path
|
|
|
|
# Force a filesystem scan to detect the new file
|
|
var editor_filesystem = editor_plugin.get_editor_interface().get_resource_filesystem()
|
|
if editor_filesystem:
|
|
editor_filesystem.scan()
|
|
# Also scan the specific directory
|
|
var dir_path = asset_path.get_base_dir()
|
|
editor_filesystem.scan_sources()
|
|
|
|
return {
|
|
"message": "Triggered filesystem scan for: " + asset_path
|
|
}
|
|
else:
|
|
return {"error": "Could not access editor filesystem"}
|
|
|
|
func handle_import_glb_scene(params):
|
|
if not params.has("glb_path"):
|
|
return {"error": "Missing required parameter: glb_path"}
|
|
|
|
var glb_path = params.glb_path
|
|
var name = params.get("name", "")
|
|
var position = params.get("position", [0, 0, 0])
|
|
var rotation = params.get("rotation", [0, 0, 0])
|
|
var scale = params.get("scale", [1, 1, 1])
|
|
|
|
var editor_interface = editor_plugin.get_editor_interface()
|
|
var current_scene = editor_interface.get_edited_scene_root()
|
|
|
|
if current_scene == null:
|
|
return {"error": "No scene is currently open"}
|
|
|
|
# Check if the GLB file exists
|
|
if not FileAccess.file_exists(glb_path):
|
|
return {"error": "GLB file not found: " + glb_path}
|
|
|
|
# Try to load the GLB as a PackedScene
|
|
var glb_scene = load(glb_path)
|
|
if not glb_scene:
|
|
return {"error": "Failed to load GLB file: " + glb_path}
|
|
|
|
# Check if it's a PackedScene
|
|
if glb_scene is PackedScene:
|
|
# Instantiate the packed scene
|
|
var instance = glb_scene.instantiate()
|
|
if not instance:
|
|
return {"error": "Failed to instantiate GLB scene"}
|
|
|
|
# Set the name
|
|
if name != "":
|
|
instance.name = name
|
|
else:
|
|
# Use filename without extension
|
|
var filename = glb_path.get_file().get_basename()
|
|
instance.name = filename
|
|
|
|
# Add to current scene
|
|
current_scene.add_child(instance)
|
|
instance.owner = current_scene
|
|
|
|
# Set ownership recursively for all children
|
|
_set_owner_recursive(instance, current_scene)
|
|
|
|
# Set transform if it's a 3D node
|
|
if instance is Node3D:
|
|
instance.position = Vector3(position[0], position[1], position[2])
|
|
instance.rotation_degrees = Vector3(rotation[0], rotation[1], rotation[2])
|
|
instance.scale = Vector3(scale[0], scale[1], scale[2])
|
|
|
|
return {
|
|
"success": true,
|
|
"instance_name": instance.name,
|
|
"message": "GLB scene imported successfully as: " + instance.name
|
|
}
|
|
else:
|
|
# If it's not a PackedScene, try to create a MeshInstance3D
|
|
var node_name = name if name != "" else glb_path.get_file().get_basename()
|
|
|
|
# Create a MeshInstance3D
|
|
var mesh_instance = MeshInstance3D.new()
|
|
mesh_instance.name = node_name
|
|
|
|
# Try to set the mesh
|
|
if glb_scene is Mesh:
|
|
mesh_instance.mesh = glb_scene
|
|
else:
|
|
# Try loading it differently
|
|
return {"error": "GLB file is not a PackedScene or Mesh resource"}
|
|
|
|
# Add to scene
|
|
current_scene.add_child(mesh_instance)
|
|
mesh_instance.owner = current_scene
|
|
|
|
# Set transform
|
|
mesh_instance.position = Vector3(position[0], position[1], position[2])
|
|
mesh_instance.rotation_degrees = Vector3(rotation[0], rotation[1], rotation[2])
|
|
mesh_instance.scale = Vector3(scale[0], scale[1], scale[2])
|
|
|
|
return {
|
|
"success": true,
|
|
"instance_name": mesh_instance.name,
|
|
"message": "GLB imported as MeshInstance3D: " + mesh_instance.name
|
|
}
|