Task 737: .TSCN File Format
Task 737: .TSCN File Format
1. List of Properties Intrinsic to the .TSCN File Format
The .TSCN file format is a text-based representation of a scene tree in the Godot Engine, designed for human readability and version control compatibility. It does not possess binary headers or magic numbers typical of some file formats, as it is entirely text-based. Instead, its structure revolves around sections denoted by square brackets, followed by key-value pairs. Properties are intrinsic to the format's logical structure rather than low-level file system attributes (such as file size or permissions, which are not format-specific). Based on the official Godot Engine specification for format version 3 (used in Godot 4.x), the key sections and their intrinsic properties are as follows:
File Descriptor Section ([gd_scene] or [gd_resource]): Represents the header of the scene or resource file.
load_steps: Integer indicating the total number of resources (external + internal) plus one for the file itself; optional if no resources are present.format: Integer specifying the file format version (e.g., 3 for Godot 4.x).uid: String providing a unique identifier for the file (e.g., "uid://cecaux1sm7mo0"), used for path-independent referencing.
External Resources Section ([ext_resource]): References resources external to the file.
type: String denoting the resource type (e.g., "Texture2D", "PackedScene").uid: String unique identifier for the external resource.path: String path to the resource (e.g., "res://gradient.tres").id: String unique identifier for referencing within the file.
Internal Resources Section ([sub_resource]): Defines resources embedded within the file.
type: String denoting the resource type (e.g., "CapsuleShape3D", "ArrayMesh", "Animation").id: String unique identifier for referencing within the file.- Additional type-specific properties (examples):
- For shapes (e.g., CapsuleShape3D):
radius,height. - For meshes (e.g., ArrayMesh):
resource_name,_surfaces(an array of dictionaries with keys likeaabb,attribute_data,format,index_count,index_data,material,primitive,vertex_count,vertex_data). - For animations:
resource_name,length,loop_mode,step,tracks/<index>/<attribute>(e.g.,tracks/0/type,tracks/0/path,tracks/0/keyswith sub-keys liketimes,transitions,values).
Nodes Section ([node]): Defines the hierarchical scene tree.
name: String name of the node.type: String class type of the node (e.g., "Node3D", "RigidBody3D").parent: String path to the parent node (omitted for the root; "." for direct children of root).instance: Reference to a packed scene (e.g., ExtResource("id")).instance_placeholder: Reference if the node is a placeholder.owner: NodePath to the owning node.index: Integer specifying the order among siblings.groups: Array of strings for node groups.- Additional node-specific properties (e.g.,
transformas Transform3D,shapeas SubResource,meshas SubResource).
Connections Section ([connection]): Defines signal connections between nodes (not always present).
signal: String name of the signal (e.g., "pressed").from: NodePath to the emitting node.to: NodePath to the receiving node.method: String name of the method to call.flags: Integer flags for connection behavior.binds: Array of bound arguments.
Editable Instances Section ([editable]): Specifies editable paths in inherited scenes (not always present).
path: String path to the editable node or child.
General parsing conventions include: lines are key-value pairs; values support complex types (e.g., Vector3(1, 2, 3), arrays [], dictionaries {}); comments start with ";"; whitespace is ignored except in strings; sections must appear in order (descriptor, external, internal, nodes, connections); exactly one root node; resource references use ExtResource("id") or SubResource("id").
2. Two Direct Download Links for .TSCN Files
- https://raw.githubusercontent.com/study-game-engines/grass-godot/master/Main.tscn
- https://raw.githubusercontent.com/mfdeveloper/godot-isometric-example/master/scenes/Building.tscn
These links provide direct access to example .TSCN files from public Godot-related repositories on GitHub.
3. Ghost Blog Embedded HTML JavaScript for Drag-and-Drop .TSCN File Dumping
The following is a self-contained HTML snippet with embedded JavaScript that can be inserted into a Ghost blog post. It creates a drag-and-drop area where a user can drop a .TSCN file. The script reads the file as text, parses its sections and properties according to the format specification, and displays them in a structured, readable format on the screen (as a JSON-like output for clarity).
This script performs basic parsing: it identifies sections, extracts header properties, and collects key-value pairs. For production use, enhance the value parser to handle nested structures (e.g., via a recursive function for arrays and dictionaries).
4. Python Class for .TSCN File Handling
The following Python class can open, decode (parse), read, write, and print all properties from a .TSCN file based on the listed format.
import json
import re
class TSCNHandler:
def __init__(self):
self.sections = []
def open_and_decode(self, filepath):
with open(filepath, 'r') as f:
content = f.read()
self.sections = self._parse(content)
def _parse(self, content):
sections = []
lines = [line.strip() for line in content.split('\n') if line.strip() and not line.strip().startswith(';')]
current_section = None
for line in lines:
if line.startswith('['):
if current_section:
sections.append(current_section)
match = re.match(r'\[(\w+)(.*)\]', line)
section_type = match.group(1)
props_str = match.group(2).strip()
properties = {}
for kv in re.findall(r'(\w+)=([^ ]+)', props_str):
key, value = kv
properties[key] = value.strip('"')
current_section = {'type': section_type, 'properties': properties}
elif '=' in line and current_section:
key, value = line.split('=', 1)
current_section['properties'][key.strip()] = value.strip()
if current_section:
sections.append(current_section)
return sections
def print_properties(self):
print(json.dumps(self.sections, indent=2))
def write(self, filepath):
with open(filepath, 'w') as f:
for section in self.sections:
header = f"[{section['type']}"
for key, value in section['properties'].items():
header += f' {key}="{value}"'
header += "]"
f.write(header + '\n')
f.write('\n') # Basic write; enhance for complex values
# Example usage:
# handler = TSCNHandler()
# handler.open_and_decode('example.tscn')
# handler.print_properties()
# handler.write('output.tscn')
This class parses the file into a list of section dictionaries, prints them in JSON format, and supports basic writing (assuming simple values; extend for complex types like vectors).
5. Java Class for .TSCN File Handling
The following Java class provides equivalent functionality.
import java.io.*;
import java.util.*;
import java.util.regex.*;
public class TSCNHandler {
private List<Map<String, Object>> sections = new ArrayList<>();
public void openAndDecode(String filepath) throws IOException {
StringBuilder content = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new FileReader(filepath))) {
String line;
while ((line = reader.readLine()) != null) {
content.append(line).append("\n");
}
}
sections = parse(content.toString());
}
private List<Map<String, Object>> parse(String content) {
List<Map<String, Object>> sectionsList = new ArrayList<>();
String[] lines = content.split("\n");
Map<String, Object> currentSection = null;
Pattern sectionPattern = Pattern.compile("\\[(\\w+)(.*)\\]");
Pattern kvPattern = Pattern.compile("(\\w+)=([^ ]+)");
for (String line : lines) {
line = line.trim();
if (line.isEmpty() || line.startsWith(";")) continue;
Matcher sectionMatcher = sectionPattern.matcher(line);
if (sectionMatcher.matches()) {
if (currentSection != null) sectionsList.add(currentSection);
String type = sectionMatcher.group(1);
String propsStr = sectionMatcher.group(2).trim();
Map<String, Object> properties = new HashMap<>();
Matcher kvMatcher = kvPattern.matcher(propsStr);
while (kvMatcher.find()) {
properties.put(kvMatcher.group(1), kvMatcher.group(2).replace("\"", ""));
}
currentSection = new HashMap<>();
currentSection.put("type", type);
currentSection.put("properties", properties);
} else if (currentSection != null && line.contains("=")) {
String[] kv = line.split("=", 2);
((Map<String, Object>) currentSection.get("properties")).put(kv[0].trim(), kv[1].trim());
}
}
if (currentSection != null) sectionsList.add(currentSection);
return sectionsList;
}
public void printProperties() {
System.out.println(sections); // Prints as list of maps; use JSON library for formatted output if needed
}
public void write(String filepath) throws IOException {
try (BufferedWriter writer = new BufferedWriter(new FileWriter(filepath))) {
for (Map<String, Object> section : sections) {
writer.write("[" + section.get("type"));
@SuppressWarnings("unchecked")
Map<String, Object> props = (Map<String, Object>) section.get("properties");
for (Map.Entry<String, Object> entry : props.entrySet()) {
writer.write(" " + entry.getKey() + "=\"" + entry.getValue() + "\"");
}
writer.write("]\n");
}
}
}
// Example usage:
// public static void main(String[] args) throws IOException {
// TSCNHandler handler = new TSCNHandler();
// handler.openAndDecode("example.tscn");
// handler.printProperties();
// handler.write("output.tscn");
// }
}
This implementation uses regex for parsing and supports basic writing.
6. JavaScript Class for .TSCN File Handling
The following JavaScript class (ES6) can be used in a Node.js environment (requires 'fs' module).
const fs = require('fs');
class TSCNHandler {
constructor() {
this.sections = [];
}
openAndDecode(filepath) {
const content = fs.readFileSync(filepath, 'utf8');
this.sections = this.parse(content);
}
parse(content) {
const sections = [];
const lines = content.split('\n').map(line => line.trim()).filter(line => line && !line.startsWith(';'));
let currentSection = null;
lines.forEach(line => {
if (line.startsWith('[')) {
if (currentSection) sections.push(currentSection);
const match = line.match(/\[(\w+)(.*)\]/);
const type = match[1];
const propsStr = match[2].trim();
const properties = {};
const kvMatches = propsStr.matchAll(/(\w+)=([^ ]+)/g);
for (const kv of kvMatches) {
properties[kv[1]] = kv[2].replace(/"/g, '');
}
currentSection = { type, properties };
} else if (currentSection && line.includes('=')) {
const [key, value] = line.split('=');
currentSection.properties[key.trim()] = value.trim();
}
});
if (currentSection) sections.push(currentSection);
return sections;
}
printProperties() {
console.log(JSON.stringify(this.sections, null, 2));
}
write(filepath) {
let output = '';
this.sections.forEach(section => {
output += `[${section.type}`;
for (const [key, value] of Object.entries(section.properties)) {
output += ` ${key}="${value}"`;
}
output += ']\n';
});
fs.writeFileSync(filepath, output);
}
}
// Example usage:
// const handler = new TSCNHandler();
// handler.openAndDecode('example.tscn');
// handler.printProperties();
// handler.write('output.tscn');
This class mirrors the others, with parsing via regex and string methods.
7. C++ Class for .TSCN File Handling
The following C++ class provides the functionality, using standard libraries for file I/O and string manipulation.
#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <map>
#include <regex>
#include <string>
class TSCNHandler {
private:
std::vector<std::map<std::string, std::string>> sections;
public:
void openAndDecode(const std::string& filepath) {
std::ifstream file(filepath);
std::stringstream content;
content << file.rdbuf();
sections = parse(content.str());
}
std::vector<std::map<std::string, std::string>> parse(const std::string& content) {
std::vector<std::map<std::string, std::string>> sectionsList;
std::istringstream iss(content);
std::string line;
std::map<std::string, std::string> currentSection;
std::regex sectionRegex(R"(\[(\w+)(.*)\])");
std::regex kvRegex(R"((\w+)=([^ ]+))");
while (std::getline(iss, line)) {
line.erase(0, line.find_first_not_of(" \t"));
line.erase(line.find_last_not_of(" \t") + 1);
if (line.empty() || line[0] == ';') continue;
std::smatch sectionMatch;
if (std::regex_match(line, sectionMatch, sectionRegex)) {
if (!currentSection.empty()) sectionsList.push_back(currentSection);
currentSection.clear();
currentSection["type"] = sectionMatch[1].str();
std::string propsStr = sectionMatch[2].str();
std::sregex_iterator iter(propsStr.begin(), propsStr.end(), kvRegex);
for (; iter != std::sregex_iterator(); ++iter) {
currentSection[(*iter)[1].str()] = (*iter)[2].str();
}
} else if (!currentSection.empty()) {
size_t eqPos = line.find('=');
if (eqPos != std::string::npos) {
std::string key = line.substr(0, eqPos);
key.erase(key.find_last_not_of(" \t") + 1);
std::string value = line.substr(eqPos + 1);
value.erase(0, value.find_first_not_of(" \t"));
currentSection[key] = value;
}
}
}
if (!currentSection.empty()) sectionsList.push_back(currentSection);
return sectionsList;
}
void printProperties() {
for (const auto& section : sections) {
std::cout << "Section type: " << section.at("type") << std::endl;
for (const auto& kv : section) {
if (kv.first != "type") {
std::cout << " " << kv.first << ": " << kv.second << std::endl;
}
}
std::cout << std::endl;
}
}
void write(const std::string& filepath) {
std::ofstream file(filepath);
for (const auto& section : sections) {
file << "[" << section.at("type");
for (const auto& kv : section) {
if (kv.first != "type") {
file << " " << kv.first << "=\"" << kv.second << "\"";
}
}
file << "]\n";
}
}
};
// Example usage:
// int main() {
// TSCNHandler handler;
// handler.openAndDecode("example.tscn");
// handler.printProperties();
// handler.write("output.tscn");
// return 0;
// }
This class uses regex for parsing and supports basic output, aligned with the format's requirements.