Code Monkey home page Code Monkey logo

godot_parser's Introduction

Godot Parser

Build Status Coverage Status Downloads

This is a python library for parsing Godot scene (.tscn) and resource (.tres) files. It's intended to make it easier to automate certain aspects of editing scene files or resources in Godot.

High-level API

godot_parser has roughly two levels of API. The low-level API has no Godot-specific logic and is just a dumb wrapper for the file format.

The high-level API has a bit of application logic on top to mirror Godot functionality and make it easier to perform certain tasks. Let's look at an example by creating a new scene file for a Player:

  from godot_parser import GDScene, Node

  scene = GDScene()
  res = scene.add_ext_resource("res://PlayerSprite.png", "PackedScene")
  with scene.use_tree() as tree:
      tree.root = Node("Player", type="KinematicBody2D")
      tree.root.add_child(
          Node(
              "Sprite",
              type="Sprite",
              properties={"texture": res.reference},
          )
      )
  scene.write("Player.tscn")

It's much easier to use the high-level API when it's available, but it doesn't cover everything. Note that use_tree() does support inherited scenes, and will generally function as expected (e.g. nodes on the parent scene will be available, and making edits will properly override fields in the child scene). There is no support yet for changing the inheritence of a scene.

Low-level API

Let's look at creating that same Player scene with the low-level API:

  from godot_parser import GDFile, ExtResource, GDSection, GDSectionHeader

  scene = GDFile(
      GDSection(GDSectionHeader("gd_scene", load_steps=2, format=2))
  )
  scene.add_section(
      GDSection(GDSectionHeader("ext_resource", path="res://PlayerSprite.png", type="PackedScene", id=1))
  )
  scene.add_section(
      GDSection(GDSectionHeader("node", name="Player", type="KinematicBody2D"))
  )
  scene.add_section(
      GDSection(
          GDSectionHeader("node", name="Sprite", type="Sprite", parent="."),
          texture=ExtResource(1)
      )
  )
  scene.write("Player.tscn")

You can see that this requires you to manage more of the application logic yourself, such as resource IDs and node structure, but it can be used to create any kind of TSCN file.

More Examples

Here are some more examples of how you can use this library.

Find all scenes in your project with a "Sensor" node and change the collision_layer:

  import os
  import sys
  from godot_parser import load

  def main(project):
      for root, _dirs, files in os.walk(project):
          for file in files:
              if os.path.splitext(file)[1] == '.tscn':
                  update_collision_layer(os.path.join(root, file))

  def update_collision_layer(filepath):
      scene = load(filepath)
      updated = False
      with scene.use_tree() as tree:
          sensor = tree.get_node('Sensor')
          if sensor is not None:
              sensor['collision_layer'] = 5
              updated = True

      if updated:
          scene.write(filepath)

  main(sys.argv[1])

Caveats

This was written with the help of the Godot TSCN docs, but it's still mostly based on visual inspection of the Godot files I'm working on. If you find a situation godot_parser doesn't handle or a feature it doesn't support, file an issue with the scene file and an explanation of the desired behavior. If you want to dig in and submit a pull request, so much the better!

If you want to run a quick sanity check for this tool, you can use the test_parse_files.py script. Pass in your root Godot directory and it will verify that it can correctly parse and re-serialize all scene and resource files in your project.

godot_parser's People

Contributors

excaliburzero avatar jjmontesl avatar micimize avatar richard-gebbia avatar stevearc avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

godot_parser's Issues

ParseException: Expected end of text, found 'tracks' on tscn file

Hi,

Thank you very much for this project ! I found it very useful.

I've encountered an issue when trying to parse a tscn file.
The scene contains an AnimationPlayer which seems to confuse the parser.

I get a ParseException with the following message :
pyparsing.exceptions.ParseException: Expected end of text, found 'tracks' (at char 1796), (line:65, col:1)

I'm using Godot version 4.2.1

Here is the tscn file I'm trying to parse :

player.tscn

[gd_scene load_steps=23 format=3 uid="uid://d4karstyb4ncb"]

[ext_resource type="Script" path="res://scenes/player/player.gd" id="1_oy55l"]
[ext_resource type="Texture2D" uid="uid://kuwx1qhyq2py" path="res://sprites/spaceguy/spaceguy.png" id="2_u40j3"]

[sub_resource type="RectangleShape2D" id="RectangleShape2D_olhyg"]
size = Vector2(128, 248)

[sub_resource type="RectangleShape2D" id="RectangleShape2D_qwa4x"]
size = Vector2(192, 192)

[sub_resource type="RectangleShape2D" id="RectangleShape2D_klhvu"]
size = Vector2(176, 192)

[sub_resource type="Animation" id="Animation_5jvi1"]
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Sprite/Sprite2D:frame")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [1]
}

[sub_resource type="Animation" id="Animation_mm4ub"]
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Sprite/Sprite2D:frame")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [1]
}

[sub_resource type="Animation" id="Animation_2u8pe"]
resource_name = "hammer"
length = 0.3
step = 0.02
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Sprite/Sprite2D:frame")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.06, 0.12, 0.18),
"transitions": PackedFloat32Array(1, 1, 1, 1),
"update": 1,
"values": [11, 19, 20, 21]
}
tracks/1/type = "method"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath(".")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/keys = {
"times": PackedFloat32Array(0.06, 0.3),
"transitions": PackedFloat32Array(1, 1),
"values": [{
"args": [],
"method": &"hammer_attack"
}, {
"args": [],
"method": &"on_attack_end"
}]
}

[sub_resource type="Animation" id="Animation_ngtps"]
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Sprite/Sprite2D:frame")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [11]
}

[sub_resource type="Animation" id="Animation_0kt8b"]
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Sprite/Sprite2D:frame")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0.1),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [11]
}

[sub_resource type="Animation" id="Animation_uvgue"]
resource_name = "hold_idle"
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Sprite/Sprite2D:frame")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [8]
}

[sub_resource type="Animation" id="Animation_dmd0o"]
resource_name = "hold_jump"
length = 0.3
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Sprite/Sprite2D:frame")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.1, 0.2),
"transitions": PackedFloat32Array(1, 1, 1),
"update": 1,
"values": [13, 14, 15]
}

[sub_resource type="Animation" id="Animation_4diky"]
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Sprite/Sprite2D:frame")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0.2),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [13]
}

[sub_resource type="Animation" id="Animation_kxrlm"]
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Sprite/Sprite2D:frame")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0.1),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [13]
}

[sub_resource type="Animation" id="Animation_7ghh1"]
resource_name = "hold_run"
length = 0.4
loop_mode = 1
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Sprite/Sprite2D:frame")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.1, 0.2, 0.3),
"transitions": PackedFloat32Array(1, 1, 1, 1),
"update": 1,
"values": [9, 10, 11, 12]
}

[sub_resource type="Animation" id="Animation_0la8n"]
resource_name = "idle"
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Sprite/Sprite2D:frame")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.1, 0.2, 0.3, 0.4),
"transitions": PackedFloat32Array(1, 1, 1, 1, 1),
"update": 1,
"values": [0, 0, 0, 0, 0]
}

[sub_resource type="Animation" id="Animation_46fb1"]
resource_name = "jump"
length = 0.3
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Sprite/Sprite2D:frame")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.1, 0.2),
"transitions": PackedFloat32Array(1, 1, 1),
"update": 1,
"values": [5, 6, 7]
}

[sub_resource type="Animation" id="Animation_y38yn"]
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Sprite/Sprite2D:frame")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0.2),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [5]
}

[sub_resource type="Animation" id="Animation_i1kx2"]
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Sprite/Sprite2D:frame")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0.3),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [5]
}

[sub_resource type="Animation" id="Animation_qr7es"]
resource_name = "throw"
length = 0.3
step = 0.02
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Sprite/Sprite2D:frame")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.06, 0.12),
"transitions": PackedFloat32Array(1, 1, 1),
"update": 1,
"values": [16, 17, 18]
}
tracks/1/type = "method"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath(".")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/keys = {
"times": PackedFloat32Array(0.3),
"transitions": PackedFloat32Array(1),
"values": [{
"args": [],
"method": &"on_throw_end"
}]
}

[sub_resource type="Animation" id="Animation_52hkb"]
resource_name = "run"
length = 0.4
loop_mode = 1
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Sprite/Sprite2D:frame")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.1, 0.2, 0.3),
"transitions": PackedFloat32Array(1, 1, 1, 1),
"update": 1,
"values": [1, 2, 3, 4]
}

[sub_resource type="AnimationLibrary" id="AnimationLibrary_cv44o"]
_data = {
"die": SubResource("Animation_5jvi1"),
"fall": SubResource("Animation_mm4ub"),
"hammer": SubResource("Animation_2u8pe"),
"hammer_air": SubResource("Animation_ngtps"),
"hold_fall": SubResource("Animation_0kt8b"),
"hold_idle": SubResource("Animation_uvgue"),
"hold_jump": SubResource("Animation_dmd0o"),
"hold_land": SubResource("Animation_4diky"),
"hold_land_walk": SubResource("Animation_kxrlm"),
"hold_walk": SubResource("Animation_7ghh1"),
"idle": SubResource("Animation_0la8n"),
"jump": SubResource("Animation_46fb1"),
"land": SubResource("Animation_y38yn"),
"land_walk": SubResource("Animation_i1kx2"),
"throw": SubResource("Animation_qr7es"),
"walk": SubResource("Animation_52hkb")
}

[node name="PhPlayer" type="CharacterBody2D" groups=["players"]]
collision_mask = 128
script = ExtResource("1_oy55l")

[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
position = Vector2(0, 4)
shape = SubResource("RectangleShape2D_olhyg")
one_way_collision_margin = 16.0

[node name="GrabArea" type="Area2D" parent="."]
collision_layer = 0
collision_mask = 256

[node name="CollisionShape2D" type="CollisionShape2D" parent="GrabArea"]
shape = SubResource("RectangleShape2D_qwa4x")

[node name="LeftFloorRayCast" type="RayCast2D" parent="."]
position = Vector2(-80, 80)
target_position = Vector2(0, 32)

[node name="RightFloorRayCast" type="RayCast2D" parent="."]
position = Vector2(80, 80)
target_position = Vector2(0, 32)

[node name="Sprite" type="Node2D" parent="."]

[node name="HammerArea" type="Area2D" parent="Sprite"]
collision_layer = 2
collision_mask = 256

[node name="CollisionShape2D" type="CollisionShape2D" parent="Sprite/HammerArea"]
position = Vector2(136, 0)
shape = SubResource("RectangleShape2D_klhvu")
disabled = true

[node name="HoldPosition" type="Marker2D" parent="Sprite"]
position = Vector2(88, -72)

[node name="ThrowPosition" type="Marker2D" parent="Sprite"]
position = Vector2(96, 0)

[node name="AnimationPlayer" type="AnimationPlayer" parent="Sprite"]
root_node = NodePath("../..")
libraries = {
"": SubResource("AnimationLibrary_cv44o")
}
autoplay = "idle"

[node name="Sprite2D" type="Sprite2D" parent="Sprite"]
texture = ExtResource("2_u40j3")
hframes = 10
vframes = 10
frame = 1

[connection signal="body_entered" from="Sprite/HammerArea" to="." method="_on_hammer_area_body_entered"]

Binary parent scenes cause exception

This one is pretty obvious, but perhaps something to wrap in a friendly "I can't do that Dave" style exception.

I have a .tscn file (text-based) which inherits from a .scn file (binary).

When I open the .tscn file in tree mode, I get an exception.

I suggest that until you start supporting .scn files you catch and re-raise this exception as something akin to "Binary files are not supported yet"

Thanks

c:\users\tom\appdata\local\programs\python\python39\lib\site-packages\godot_parser\tree.py in _load_parent_scene(root, file)
    327 
    328 def _load_parent_scene(root: Node, file: GDFile):
--> 329     parent_file: GDFile = file.load_parent_scene()
    330     parent_tree = Tree.build(parent_file)
    331     # Transfer parent scene's children to this scene

c:\users\tom\appdata\local\programs\python\python39\lib\site-packages\godot_parser\files.py in load_parent_scene(self)
    256                 "Could not find parent scene resource id(%d)" % root.instance
    257             )
--> 258         return GDScene.load(gdpath_to_filepath(self.project_root, parent_res.path))
    259 
    260     @contextmanager

c:\users\tom\appdata\local\programs\python\python39\lib\site-packages\godot_parser\files.py in load(cls, filepath)
    306     def load(cls: Type[GDFileType], filepath: str) -> GDFileType:
    307         with open(filepath, "r") as ifile:
--> 308             file = cls.parse(ifile.read())
    309         file.project_root = find_project_root(filepath)
    310         return file

c:\users\tom\appdata\local\programs\python\python39\lib\encodings\cp1252.py in decode(self, input, final)
     21 class IncrementalDecoder(codecs.IncrementalDecoder):
     22     def decode(self, input, final=False):
---> 23         return codecs.charmap_decode(input,self.errors,decoding_table)[0]
     24 
     25 class StreamWriter(Codec,codecs.StreamWriter):

UnicodeDecodeError: 'charmap' codec can't decode byte 0x81 in position 1848: character maps to <undefined>

Parsing large scene is slow.

Parsing small scenes works really well.

And, I have this 50k lines, 67mb scene file converted from an .escn file imported from blender. I wanted to shrink its size from the source. I wrote some regex-based scripts and it was neither elegant nor convinent, then I found this parser.

However, It took 11 minutes to parse this 67mb scene. RAM usage increased very slowly and the parsing was using only 6% CPU usage of a Ryzen 2700X.

67mb lines is definitely large, but I think 11 minutes is a bit much. Is it a normal speed with pyparsing or does the script need deeper optimization idk.

City.zip

Missing: Does not handle escaped strings in .tscn files

I have a pseudo-language that I use in some props of my scene objects, and godot-parser doesn't like escaped strings.

Example from test_parse_files.py on my project:
condition = "has_ability(\"djump\")" <x> condition = "has_ability("djump")"

Note that I didn't add the escaping. I just typed has_ability("djump") in the editor, and apparently on save it escapes it.

Are there any plans to port it to 4.0?

I was trying to create a tool for helping me in development of my game but realized that this hasn't been updated yet (and trying to parse a 4.0 scene just throws a ParseException), and considering this may be a big rewrite, I just wanted to know if there were any plans for it.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.