Skip to content

Instantly share code, notes, and snippets.

@zvodd
Created March 1, 2026 15:28
Show Gist options
  • Select an option

  • Save zvodd/df23f3851685db42630229a96d3ab63e to your computer and use it in GitHub Desktop.

Select an option

Save zvodd/df23f3851685db42630229a96d3ab63e to your computer and use it in GitHub Desktop.
Godot Addon :: Export Node Tree Text - Show AI scene structure + scripts
@tool
extends EditorPlugin
const PanelScript = preload("res://addons/exportnodetree/tree_exporter_panel.gd")
var dock_panel
func _enter_tree():
# Instantiate the panel script
dock_panel = PanelScript.new()
dock_panel.name = "Node Tree Export"
# Pass the editor interface reference to the panel so it can access selection/root
dock_panel.set_editor_interface(get_editor_interface())
# Add the control to the bottom dock
add_control_to_bottom_panel(dock_panel, "Node Export")
func _exit_tree():
if dock_panel:
remove_control_from_bottom_panel(dock_panel)
dock_panel.queue_free()
[plugin]
name="ExportNodeTree"
description=""
author=""
version=""
script="export_node_tree.gd"
@tool
extends VBoxContainer
const TreeGenerator = preload("res://addons/exportnodetree/tree_generator.gd")
var editor_interface: EditorInterface
# UI Elements
var _text_edit: TextEdit
var _btn_print: Button
var _chk_scripts: CheckBox
var _chk_paths: CheckBox
var _chk_groups: CheckBox
func _ready():
# Layout Setup
_setup_ui()
func set_editor_interface(ei: EditorInterface):
editor_interface = ei
func _setup_ui():
# Toolbar HBox
var toolbar = HBoxContainer.new()
add_child(toolbar)
_btn_print = Button.new()
_btn_print.text = "Generate Tree"
_btn_print.pressed.connect(_on_print_pressed)
toolbar.add_child(_btn_print)
var separator = VSeparator.new()
toolbar.add_child(separator)
_chk_scripts = CheckBox.new()
_chk_scripts.text = "Include Script Code"
toolbar.add_child(_chk_scripts)
_chk_paths = CheckBox.new()
_chk_paths.text = "Full Paths"
toolbar.add_child(_chk_paths)
_chk_groups = CheckBox.new()
_chk_groups.text = "Show Groups"
toolbar.add_child(_chk_groups)
# Output Buffer
_text_edit = TextEdit.new()
_text_edit.size_flags_vertical = Control.SIZE_EXPAND_FILL
_text_edit.editable = false # Read only
_text_edit.placeholder_text = "Tree output will appear here..."
# Use code font for alignment
_text_edit.add_theme_font_override("font", get_theme_font("source", "EditorFonts"))
add_child(_text_edit)
func _on_print_pressed():
if not editor_interface:
return
var root = editor_interface.get_edited_scene_root()
if not root:
_text_edit.text = "Error: No edited scene root found."
return
var selection = editor_interface.get_selection()
var selected_nodes = selection.get_selected_nodes()
var config = {
"include_scripts_content": _chk_scripts.button_pressed,
"show_full_paths": _chk_paths.button_pressed,
"show_groups": _chk_groups.button_pressed
}
var result_text = TreeGenerator.generate_output(root, selected_nodes, config)
_text_edit.text = result_text
# RuneRealm/addons/exportnodetree/tree_generator.gd
@tool
extends RefCounted
# Generates the complete output string based on the configuration
static func generate_output(root_node: Node, selected_nodes: Array[Node], config: Dictionary) -> String:
if not is_instance_valid(root_node):
return "Error: No Valid Root Node found."
var output: String = ""
var script_list: Array[Script] = []
var included_nodes: Dictionary = {}
# --- 1. Determine Scope ---
if selected_nodes.size() > 0:
output += "--- Printing Selection Tree ---\n"
for node in selected_nodes:
_add_ancestors(node, included_nodes)
else:
output += "--- Printing Full Scene Tree (No selection made) ---\n"
_mark_all_children(root_node, included_nodes)
# --- 2. Build Tree String ---
output += _build_tree_string(root_node, "", included_nodes, true, config, script_list)
# --- 3. Append Scripts (if requested) ---
if config.get("include_scripts_content", false) and script_list.size() > 0:
output += "\n\n--- Attached Scripts Source Code ---\n"
for script in script_list:
output += "##########################\n"
output += "# %s\n" % script.resource_path.get_file()
output += "##########################\n\n"
output += script.source_code + "\n\n"
return output
# --- Recursive Tree Builder ---
static func _build_tree_string(node: Node, current_indent: String, included_nodes: Dictionary, is_root: bool, config: Dictionary, script_collector: Array[Script]) -> String:
var result = ""
# Check visibility
if not included_nodes.is_empty() and not node in included_nodes:
return result
# Construct line text
var line_text = "%s [%s]" % [node.name, node.get_class()]
var script = node.get_script()
if script:
var script_path = script.resource_path
# When show_full_paths is true, print the full resource path (res://...)
# Otherwise print only the filename.
if not config.get("show_full_paths", false):
script_path = script_path.get_file()
line_text += " (script: %s)" % script_path
# Collect script for later printing if needed
if not script in script_collector:
script_collector.append(script)
if config.get("show_groups", false):
var groups = node.get_groups()
if groups.size() > 0:
line_text += " {groups: %s}" % str(groups)
# Add to result
if is_root:
result += line_text + "\n"
else:
result += current_indent + line_text + "\n"
# Calculate Children Logic
var children = node.get_children()
var visible_children = []
for child in children:
if included_nodes.is_empty() or child in included_nodes:
visible_children.append(child)
var next_level_indent = ""
if is_root:
next_level_indent = ""
else:
if current_indent.ends_with("└── "):
next_level_indent = current_indent.replace("└── ", " ")
elif current_indent.ends_with("├── "):
next_level_indent = current_indent.replace("├── ", "│ ")
else:
next_level_indent = current_indent
for i in range(visible_children.size()):
var child = visible_children[i]
var is_last_child = (i == visible_children.size() - 1)
var connector = "└── " if is_last_child else "├── "
result += _build_tree_string(child, next_level_indent + connector, included_nodes, false, config, script_collector)
return result
# --- Helpers ---
static func _mark_all_children(node: Node, dict: Dictionary):
dict[node] = true
for child in node.get_children():
_mark_all_children(child, dict)
static func _add_ancestors(node: Node, included_nodes: Dictionary):
var current = node
while current != null:
included_nodes[current] = true
current = current.get_parent()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment