gopeak 2.1.0 → 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +224 -75
- package/build/addon/godot_mcp_editor/mcp_client.gd +178 -0
- package/build/addon/godot_mcp_editor/plugin.cfg +6 -0
- package/build/addon/godot_mcp_editor/plugin.gd +84 -0
- package/build/addon/godot_mcp_editor/tool_executor.gd +114 -0
- package/build/addon/godot_mcp_editor/tools/animation_tools.gd +502 -0
- package/build/addon/godot_mcp_editor/tools/resource_tools.gd +425 -0
- package/build/addon/godot_mcp_editor/tools/scene_tools.gd +710 -0
- package/build/cli/check.js +77 -0
- package/build/cli/notify.js +88 -0
- package/build/cli/setup.js +115 -0
- package/build/cli/star.js +51 -0
- package/build/cli/uninstall.js +26 -0
- package/build/cli/utils.js +149 -0
- package/build/cli.js +91 -0
- package/build/gdscript_parser.js +828 -0
- package/build/godot-bridge.js +556 -0
- package/build/index.js +2761 -2064
- package/build/prompts.js +163 -0
- package/build/visualizer/canvas.js +832 -0
- package/build/visualizer/events.js +814 -0
- package/build/visualizer/layout.js +304 -0
- package/build/visualizer/main.js +245 -0
- package/build/visualizer/modals.js +239 -0
- package/build/visualizer/panel.js +1091 -0
- package/build/visualizer/state.js +210 -0
- package/build/visualizer/syntax.js +106 -0
- package/build/visualizer/usages.js +352 -0
- package/build/visualizer/websocket.js +85 -0
- package/build/visualizer-server.js +375 -0
- package/build/visualizer.html +6395 -0
- package/package.json +15 -6
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
@tool
|
|
2
|
+
extends Node
|
|
3
|
+
class_name MCPResourceTools
|
|
4
|
+
|
|
5
|
+
var _editor_plugin: EditorPlugin = null
|
|
6
|
+
|
|
7
|
+
func set_editor_plugin(plugin: EditorPlugin) -> void:
|
|
8
|
+
_editor_plugin = plugin
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
func _ensure_res_path(path: String) -> String:
|
|
12
|
+
if path.begins_with("res://"):
|
|
13
|
+
return path
|
|
14
|
+
if path.begins_with("/"):
|
|
15
|
+
var project_abs := ProjectSettings.globalize_path("res://")
|
|
16
|
+
if path.begins_with(project_abs):
|
|
17
|
+
var rel := path.substr(project_abs.length())
|
|
18
|
+
return "res://" + rel
|
|
19
|
+
return "res://" + path
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
func _refresh_filesystem() -> void:
|
|
23
|
+
if _editor_plugin:
|
|
24
|
+
EditorInterface.get_resource_filesystem().scan()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
func _parse_value(value):
|
|
28
|
+
if typeof(value) == TYPE_DICTIONARY:
|
|
29
|
+
if value.has("type") or value.has("_type"):
|
|
30
|
+
var t = value.get("type", value.get("_type", ""))
|
|
31
|
+
match t:
|
|
32
|
+
"Vector2":
|
|
33
|
+
return Vector2(value.get("x", 0), value.get("y", 0))
|
|
34
|
+
"Vector3":
|
|
35
|
+
return Vector3(value.get("x", 0), value.get("y", 0), value.get("z", 0))
|
|
36
|
+
"Color":
|
|
37
|
+
return Color(value.get("r", 1), value.get("g", 1), value.get("b", 1), value.get("a", 1))
|
|
38
|
+
"Vector2i":
|
|
39
|
+
return Vector2i(value.get("x", 0), value.get("y", 0))
|
|
40
|
+
"Vector3i":
|
|
41
|
+
return Vector3i(value.get("x", 0), value.get("y", 0), value.get("z", 0))
|
|
42
|
+
"Rect2":
|
|
43
|
+
return Rect2(value.get("x", 0), value.get("y", 0), value.get("width", 0), value.get("height", 0))
|
|
44
|
+
"NodePath":
|
|
45
|
+
return NodePath(value.get("path", ""))
|
|
46
|
+
if typeof(value) == TYPE_ARRAY:
|
|
47
|
+
var result := []
|
|
48
|
+
for item in value:
|
|
49
|
+
result.append(_parse_value(item))
|
|
50
|
+
return result
|
|
51
|
+
return value
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
func _set_resource_properties(resource: Resource, properties) -> void:
|
|
55
|
+
var props = properties
|
|
56
|
+
if typeof(properties) == TYPE_STRING:
|
|
57
|
+
var json := JSON.new()
|
|
58
|
+
if json.parse(properties) == OK:
|
|
59
|
+
props = json.data
|
|
60
|
+
else:
|
|
61
|
+
return
|
|
62
|
+
if typeof(props) != TYPE_DICTIONARY:
|
|
63
|
+
return
|
|
64
|
+
for key in props:
|
|
65
|
+
var val = _parse_value(props[key])
|
|
66
|
+
resource.set(key, val)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
func _parse_properties_dict(raw) -> Dictionary:
|
|
70
|
+
if typeof(raw) == TYPE_DICTIONARY:
|
|
71
|
+
return raw
|
|
72
|
+
if typeof(raw) == TYPE_STRING and raw != "":
|
|
73
|
+
var json := JSON.new()
|
|
74
|
+
if json.parse(raw) == OK and typeof(json.data) == TYPE_DICTIONARY:
|
|
75
|
+
return json.data
|
|
76
|
+
return {}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
func _load_theme(theme_path: String) -> Theme:
|
|
80
|
+
var loaded = load(theme_path)
|
|
81
|
+
if loaded is Theme:
|
|
82
|
+
return loaded
|
|
83
|
+
return Theme.new()
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
func _save_scene_root(root: Node, scene_path: String) -> int:
|
|
87
|
+
var packed := PackedScene.new()
|
|
88
|
+
var pack_result := packed.pack(root)
|
|
89
|
+
if pack_result != OK:
|
|
90
|
+
return pack_result
|
|
91
|
+
return ResourceSaver.save(packed, scene_path)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
func create_resource(args: Dictionary) -> Dictionary:
|
|
95
|
+
var res_path := _ensure_res_path(str(args.get("resourcePath", "")))
|
|
96
|
+
var resource_type := str(args.get("resourceType", "Resource"))
|
|
97
|
+
if res_path == "res://":
|
|
98
|
+
return {"ok": false, "error": "resourcePath is required"}
|
|
99
|
+
|
|
100
|
+
var resource = ClassDB.instantiate(resource_type)
|
|
101
|
+
if resource == null or not (resource is Resource):
|
|
102
|
+
return {"ok": false, "error": "Failed to instantiate resource type", "resourceType": resource_type}
|
|
103
|
+
|
|
104
|
+
var script_path := str(args.get("script", ""))
|
|
105
|
+
if script_path != "":
|
|
106
|
+
var script_obj = load(_ensure_res_path(script_path))
|
|
107
|
+
if script_obj:
|
|
108
|
+
resource.set_script(script_obj)
|
|
109
|
+
|
|
110
|
+
if args.has("properties"):
|
|
111
|
+
_set_resource_properties(resource, args.get("properties"))
|
|
112
|
+
|
|
113
|
+
var save_result := ResourceSaver.save(resource, res_path)
|
|
114
|
+
if save_result != OK:
|
|
115
|
+
return {"ok": false, "error": "Failed to save resource", "code": save_result}
|
|
116
|
+
|
|
117
|
+
_refresh_filesystem()
|
|
118
|
+
return {
|
|
119
|
+
"ok": true,
|
|
120
|
+
"resourcePath": res_path,
|
|
121
|
+
"resourceType": args.get("resourceType")
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
func modify_resource(args: Dictionary) -> Dictionary:
|
|
126
|
+
var res_path := _ensure_res_path(str(args.get("resourcePath", "")))
|
|
127
|
+
if res_path == "res://":
|
|
128
|
+
return {"ok": false, "error": "resourcePath is required"}
|
|
129
|
+
|
|
130
|
+
var resource = load(res_path)
|
|
131
|
+
if resource == null or not (resource is Resource):
|
|
132
|
+
return {"ok": false, "error": "Resource not found", "resourcePath": res_path}
|
|
133
|
+
|
|
134
|
+
_set_resource_properties(resource, args.get("properties", ""))
|
|
135
|
+
var save_result := ResourceSaver.save(resource, res_path)
|
|
136
|
+
if save_result != OK:
|
|
137
|
+
return {"ok": false, "error": "Failed to save resource", "code": save_result}
|
|
138
|
+
|
|
139
|
+
_refresh_filesystem()
|
|
140
|
+
return {"ok": true, "resourcePath": res_path}
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
func create_material(args: Dictionary) -> Dictionary:
|
|
144
|
+
var mat_path := _ensure_res_path(str(args.get("materialPath", "")))
|
|
145
|
+
var material_type := str(args.get("materialType", "StandardMaterial3D"))
|
|
146
|
+
if mat_path == "res://":
|
|
147
|
+
return {"ok": false, "error": "materialPath is required"}
|
|
148
|
+
|
|
149
|
+
var material = ClassDB.instantiate(material_type)
|
|
150
|
+
if material == null or not (material is Material):
|
|
151
|
+
return {"ok": false, "error": "Failed to instantiate material type", "materialType": material_type}
|
|
152
|
+
|
|
153
|
+
if material is ShaderMaterial:
|
|
154
|
+
var shader_path := str(args.get("shader", ""))
|
|
155
|
+
if shader_path != "":
|
|
156
|
+
var shader_res = load(_ensure_res_path(shader_path))
|
|
157
|
+
if shader_res is Shader:
|
|
158
|
+
(material as ShaderMaterial).shader = shader_res
|
|
159
|
+
|
|
160
|
+
if args.has("properties"):
|
|
161
|
+
_set_resource_properties(material, args.get("properties"))
|
|
162
|
+
|
|
163
|
+
var save_result := ResourceSaver.save(material, mat_path)
|
|
164
|
+
if save_result != OK:
|
|
165
|
+
return {"ok": false, "error": "Failed to save material", "code": save_result}
|
|
166
|
+
|
|
167
|
+
_refresh_filesystem()
|
|
168
|
+
return {
|
|
169
|
+
"ok": true,
|
|
170
|
+
"materialPath": mat_path,
|
|
171
|
+
"materialType": material_type
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
func create_shader(args: Dictionary) -> Dictionary:
|
|
176
|
+
var shader_path := _ensure_res_path(str(args.get("shaderPath", "")))
|
|
177
|
+
var shader_type := str(args.get("shaderType", "canvas_item"))
|
|
178
|
+
if shader_path == "res://":
|
|
179
|
+
return {"ok": false, "error": "shaderPath is required"}
|
|
180
|
+
|
|
181
|
+
var code := str(args.get("code", ""))
|
|
182
|
+
var template := str(args.get("template", ""))
|
|
183
|
+
|
|
184
|
+
if code == "":
|
|
185
|
+
if template != "":
|
|
186
|
+
match template:
|
|
187
|
+
"basic":
|
|
188
|
+
code = "shader_type %s;\n\nvoid fragment() {\n\tCOLOR = vec4(1.0);\n}\n" % shader_type
|
|
189
|
+
"color_shift":
|
|
190
|
+
code = "shader_type %s;\n\nuniform vec4 color_shift : source_color = vec4(0.1, 0.0, 0.2, 0.0);\n\nvoid fragment() {\n\tvec4 base = texture(TEXTURE, UV);\n\tCOLOR = vec4(clamp(base.rgb + color_shift.rgb, 0.0, 1.0), base.a);\n}\n" % shader_type
|
|
191
|
+
"outline":
|
|
192
|
+
code = "shader_type %s;\n\nuniform vec4 outline_color : source_color = vec4(0.0, 0.0, 0.0, 1.0);\nuniform float outline_width : hint_range(0.0, 8.0) = 1.0;\n\nvoid fragment() {\n\tvec2 px = TEXTURE_PIXEL_SIZE * outline_width;\n\tfloat a = texture(TEXTURE, UV).a;\n\tfloat edge = max(max(texture(TEXTURE, UV + vec2(px.x, 0.0)).a, texture(TEXTURE, UV - vec2(px.x, 0.0)).a), max(texture(TEXTURE, UV + vec2(0.0, px.y)).a, texture(TEXTURE, UV - vec2(0.0, px.y)).a));\n\tvec4 base = texture(TEXTURE, UV);\n\tCOLOR = mix(outline_color * edge, base, a);\n}\n" % shader_type
|
|
193
|
+
_:
|
|
194
|
+
code = "shader_type %s;\n\nvoid fragment() {\n}\n" % shader_type
|
|
195
|
+
else:
|
|
196
|
+
code = "shader_type %s;\n\nvoid fragment() {\n}\n" % shader_type
|
|
197
|
+
|
|
198
|
+
var file := FileAccess.open(shader_path, FileAccess.WRITE)
|
|
199
|
+
if file == null:
|
|
200
|
+
return {"ok": false, "error": "Failed to open shader file for writing", "shaderPath": shader_path}
|
|
201
|
+
file.store_string(code)
|
|
202
|
+
file.close()
|
|
203
|
+
|
|
204
|
+
_refresh_filesystem()
|
|
205
|
+
return {"ok": true, "shaderPath": shader_path, "shaderType": shader_type}
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
func create_tileset(args: Dictionary) -> Dictionary:
|
|
209
|
+
var tileset_path := _ensure_res_path(str(args.get("tilesetPath", "")))
|
|
210
|
+
if tileset_path == "res://":
|
|
211
|
+
return {"ok": false, "error": "tilesetPath is required"}
|
|
212
|
+
|
|
213
|
+
var tileset := TileSet.new()
|
|
214
|
+
var sources = args.get("sources", [])
|
|
215
|
+
if typeof(sources) != TYPE_ARRAY:
|
|
216
|
+
sources = []
|
|
217
|
+
|
|
218
|
+
for source in sources:
|
|
219
|
+
if typeof(source) != TYPE_DICTIONARY:
|
|
220
|
+
continue
|
|
221
|
+
var atlas := TileSetAtlasSource.new()
|
|
222
|
+
var tex_path := _ensure_res_path(str(source.get("texture", "")))
|
|
223
|
+
var tex = load(tex_path)
|
|
224
|
+
if tex == null:
|
|
225
|
+
continue
|
|
226
|
+
atlas.texture = tex
|
|
227
|
+
|
|
228
|
+
var tile_size: Dictionary = source.get("tileSize", {})
|
|
229
|
+
atlas.texture_region_size = Vector2i(int(tile_size.get("x", 0)), int(tile_size.get("y", 0)))
|
|
230
|
+
|
|
231
|
+
if source.has("separation"):
|
|
232
|
+
var sep: Dictionary = source.get("separation", {})
|
|
233
|
+
atlas.separation = Vector2i(int(sep.get("x", 0)), int(sep.get("y", 0)))
|
|
234
|
+
|
|
235
|
+
if source.has("offset"):
|
|
236
|
+
var off: Dictionary = source.get("offset", {})
|
|
237
|
+
atlas.margins = Vector2i(int(off.get("x", 0)), int(off.get("y", 0)))
|
|
238
|
+
|
|
239
|
+
tileset.add_source(atlas)
|
|
240
|
+
|
|
241
|
+
var save_result := ResourceSaver.save(tileset, tileset_path)
|
|
242
|
+
if save_result != OK:
|
|
243
|
+
return {"ok": false, "error": "Failed to save TileSet", "code": save_result}
|
|
244
|
+
|
|
245
|
+
_refresh_filesystem()
|
|
246
|
+
return {"ok": true, "tilesetPath": tileset_path}
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
func set_tilemap_cells(args: Dictionary) -> Dictionary:
|
|
250
|
+
var scene_path := _ensure_res_path(str(args.get("scenePath", "")))
|
|
251
|
+
var node_path := str(args.get("tilemapNodePath", ""))
|
|
252
|
+
if scene_path == "res://":
|
|
253
|
+
return {"ok": false, "error": "scenePath is required"}
|
|
254
|
+
|
|
255
|
+
var scene_res = load(scene_path)
|
|
256
|
+
if scene_res == null or not (scene_res is PackedScene):
|
|
257
|
+
return {"ok": false, "error": "Scene not found", "scenePath": scene_path}
|
|
258
|
+
|
|
259
|
+
var root := (scene_res as PackedScene).instantiate()
|
|
260
|
+
if root == null:
|
|
261
|
+
return {"ok": false, "error": "Failed to instantiate scene"}
|
|
262
|
+
|
|
263
|
+
var tilemap: TileMap = null
|
|
264
|
+
if node_path == "." or node_path == "":
|
|
265
|
+
tilemap = root as TileMap
|
|
266
|
+
else:
|
|
267
|
+
tilemap = root.get_node_or_null(node_path) as TileMap
|
|
268
|
+
|
|
269
|
+
if tilemap == null:
|
|
270
|
+
root.queue_free()
|
|
271
|
+
return {"ok": false, "error": "TileMap node not found", "tilemapNodePath": node_path}
|
|
272
|
+
|
|
273
|
+
var layer := int(args.get("layer", 0))
|
|
274
|
+
var cells = args.get("cells", [])
|
|
275
|
+
if typeof(cells) != TYPE_ARRAY:
|
|
276
|
+
cells = []
|
|
277
|
+
|
|
278
|
+
for cell in cells:
|
|
279
|
+
if typeof(cell) != TYPE_DICTIONARY:
|
|
280
|
+
continue
|
|
281
|
+
var coords: Dictionary = cell.get("coords", {})
|
|
282
|
+
var atlas_coords: Dictionary = cell.get("atlasCoords", {})
|
|
283
|
+
tilemap.set_cell(
|
|
284
|
+
layer,
|
|
285
|
+
Vector2i(int(coords.get("x", 0)), int(coords.get("y", 0))),
|
|
286
|
+
int(cell.get("sourceId", -1)),
|
|
287
|
+
Vector2i(int(atlas_coords.get("x", 0)), int(atlas_coords.get("y", 0))),
|
|
288
|
+
int(cell.get("alternativeTile", 0))
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
var save_result := _save_scene_root(root, scene_path)
|
|
292
|
+
root.queue_free()
|
|
293
|
+
if save_result != OK:
|
|
294
|
+
return {"ok": false, "error": "Failed to save scene", "code": save_result}
|
|
295
|
+
|
|
296
|
+
_refresh_filesystem()
|
|
297
|
+
return {"ok": true, "cellCount": cells.size()}
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
func set_theme_color(args: Dictionary) -> Dictionary:
|
|
301
|
+
var theme_path := _ensure_res_path(str(args.get("themePath", "")))
|
|
302
|
+
if theme_path == "res://":
|
|
303
|
+
return {"ok": false, "error": "themePath is required"}
|
|
304
|
+
|
|
305
|
+
var theme := _load_theme(theme_path)
|
|
306
|
+
var c: Dictionary = args.get("color", {})
|
|
307
|
+
var color := Color(float(c.get("r", 1.0)), float(c.get("g", 1.0)), float(c.get("b", 1.0)), float(c.get("a", 1.0)))
|
|
308
|
+
theme.set_color(str(args.get("colorName", "")), str(args.get("controlType", "")), color)
|
|
309
|
+
|
|
310
|
+
var save_result := ResourceSaver.save(theme, theme_path)
|
|
311
|
+
if save_result != OK:
|
|
312
|
+
return {"ok": false, "error": "Failed to save theme", "code": save_result}
|
|
313
|
+
|
|
314
|
+
_refresh_filesystem()
|
|
315
|
+
return {"ok": true}
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
func set_theme_font_size(args: Dictionary) -> Dictionary:
|
|
319
|
+
var theme_path := _ensure_res_path(str(args.get("themePath", "")))
|
|
320
|
+
if theme_path == "res://":
|
|
321
|
+
return {"ok": false, "error": "themePath is required"}
|
|
322
|
+
|
|
323
|
+
var theme := _load_theme(theme_path)
|
|
324
|
+
theme.set_font_size(str(args.get("fontSizeName", "")), str(args.get("controlType", "")), int(args.get("size", 0)))
|
|
325
|
+
|
|
326
|
+
var save_result := ResourceSaver.save(theme, theme_path)
|
|
327
|
+
if save_result != OK:
|
|
328
|
+
return {"ok": false, "error": "Failed to save theme", "code": save_result}
|
|
329
|
+
|
|
330
|
+
_refresh_filesystem()
|
|
331
|
+
return {"ok": true}
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
func _get_theme_shader_code(theme: String, effect: String) -> String:
|
|
335
|
+
var base_color := "vec3(0.8, 0.8, 0.8)"
|
|
336
|
+
match theme:
|
|
337
|
+
"medieval":
|
|
338
|
+
base_color = "vec3(0.52, 0.42, 0.30)"
|
|
339
|
+
"cyberpunk":
|
|
340
|
+
base_color = "vec3(0.08, 0.12, 0.22)"
|
|
341
|
+
"nature":
|
|
342
|
+
base_color = "vec3(0.20, 0.44, 0.24)"
|
|
343
|
+
"scifi":
|
|
344
|
+
base_color = "vec3(0.35, 0.55, 0.72)"
|
|
345
|
+
"horror":
|
|
346
|
+
base_color = "vec3(0.12, 0.05, 0.08)"
|
|
347
|
+
"cartoon":
|
|
348
|
+
base_color = "vec3(0.95, 0.65, 0.20)"
|
|
349
|
+
|
|
350
|
+
var effect_block := "ALBEDO = base_col;"
|
|
351
|
+
match effect:
|
|
352
|
+
"glow":
|
|
353
|
+
effect_block = "ALBEDO = base_col; EMISSION = base_col * (0.5 + 0.5 * sin(TIME * 2.0));"
|
|
354
|
+
"hologram":
|
|
355
|
+
effect_block = "float scan = sin(UV.y * 120.0 + TIME * 6.0) * 0.5 + 0.5; ALBEDO = base_col * 0.5; EMISSION = base_col * (0.8 + scan); ALPHA = 0.65;"
|
|
356
|
+
"wind_sway":
|
|
357
|
+
effect_block = "ALBEDO = base_col + vec3(0.05 * sin(TIME + UV.x * 10.0));"
|
|
358
|
+
"torch_fire":
|
|
359
|
+
effect_block = "float flicker = 0.8 + 0.2 * sin(TIME * 17.0 + UV.y * 13.0); ALBEDO = base_col * flicker; EMISSION = vec3(1.0, 0.5, 0.1) * (flicker - 0.6);"
|
|
360
|
+
"dissolve":
|
|
361
|
+
effect_block = "float n = fract(sin(dot(UV * 123.4, vec2(12.9898, 78.233))) * 43758.5453); float cut = 0.45 + 0.25 * sin(TIME); ALBEDO = base_col; ALPHA = step(cut, n); EMISSION = vec3(1.0, 0.4, 0.1) * step(cut - 0.03, n) * (1.0 - step(cut + 0.03, n));"
|
|
362
|
+
"outline":
|
|
363
|
+
effect_block = "float e = abs(sin(UV.x * 80.0)) * abs(sin(UV.y * 80.0)); ALBEDO = mix(base_col, vec3(0.0), step(0.85, e));"
|
|
364
|
+
_:
|
|
365
|
+
effect_block = "ALBEDO = base_col;"
|
|
366
|
+
|
|
367
|
+
return "shader_type spatial;\nrender_mode cull_back, depth_draw_opaque;\n\nvoid fragment() {\n\tvec3 base_col = %s;\n\t%s\n}\n" % [base_color, effect_block]
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
func apply_theme_shader(args: Dictionary) -> Dictionary:
|
|
371
|
+
var scene_path := _ensure_res_path(str(args.get("scenePath", "")))
|
|
372
|
+
var node_path := str(args.get("nodePath", ""))
|
|
373
|
+
var theme := str(args.get("theme", "nature"))
|
|
374
|
+
var effect := str(args.get("effect", "none"))
|
|
375
|
+
if scene_path == "res://":
|
|
376
|
+
return {"ok": false, "error": "scenePath is required"}
|
|
377
|
+
|
|
378
|
+
var scene_res = load(scene_path)
|
|
379
|
+
if scene_res == null or not (scene_res is PackedScene):
|
|
380
|
+
return {"ok": false, "error": "Scene not found", "scenePath": scene_path}
|
|
381
|
+
|
|
382
|
+
var root := (scene_res as PackedScene).instantiate()
|
|
383
|
+
if root == null:
|
|
384
|
+
return {"ok": false, "error": "Failed to instantiate scene"}
|
|
385
|
+
|
|
386
|
+
var target: Node = null
|
|
387
|
+
if node_path == "." or node_path == "":
|
|
388
|
+
target = root
|
|
389
|
+
else:
|
|
390
|
+
target = root.get_node_or_null(node_path)
|
|
391
|
+
|
|
392
|
+
if target == null:
|
|
393
|
+
root.queue_free()
|
|
394
|
+
return {"ok": false, "error": "Target node not found", "nodePath": node_path}
|
|
395
|
+
|
|
396
|
+
var shader := Shader.new()
|
|
397
|
+
shader.code = _get_theme_shader_code(theme, effect)
|
|
398
|
+
var material := ShaderMaterial.new()
|
|
399
|
+
material.shader = shader
|
|
400
|
+
|
|
401
|
+
var params := _parse_properties_dict(args.get("shaderParams", ""))
|
|
402
|
+
for key in params:
|
|
403
|
+
material.set_shader_parameter(str(key), _parse_value(params[key]))
|
|
404
|
+
|
|
405
|
+
if target is MeshInstance3D:
|
|
406
|
+
(target as MeshInstance3D).material_override = material
|
|
407
|
+
elif target is Sprite2D:
|
|
408
|
+
(target as Sprite2D).material = material
|
|
409
|
+
elif target is Sprite3D:
|
|
410
|
+
(target as Sprite3D).material_override = material
|
|
411
|
+
elif target is CanvasItem:
|
|
412
|
+
(target as CanvasItem).material = material
|
|
413
|
+
elif target is GeometryInstance3D:
|
|
414
|
+
(target as GeometryInstance3D).material_override = material
|
|
415
|
+
else:
|
|
416
|
+
root.queue_free()
|
|
417
|
+
return {"ok": false, "error": "Unsupported node type for material application", "nodePath": node_path}
|
|
418
|
+
|
|
419
|
+
var save_result := _save_scene_root(root, scene_path)
|
|
420
|
+
root.queue_free()
|
|
421
|
+
if save_result != OK:
|
|
422
|
+
return {"ok": false, "error": "Failed to save scene", "code": save_result}
|
|
423
|
+
|
|
424
|
+
_refresh_filesystem()
|
|
425
|
+
return {"ok": true, "theme": theme, "effect": effect}
|