DefTree: A python module for editing Defold files

I have a python module for Defold that I use quite frequently when I do batch changes. Removing all layouts, scaling all particle effects, cleaning up gui scenes, generally finding out information and such. Available here if anyone want to check it out https://deftree.readthedocs.io

It works but consider it a beta, as I will highly likely make breaking change to the API in the future (but will keep it versioned properly of course).

You can find the source on

Examples

Here are some examples, updated for 1.0.2

You have removed a lot of images from disk that you know are obsolete and now you need to clean up all atlases.

Remove unused images in atlases
import os
import deftree


def get_files(project_root, ending):
    # Generator to get files
    for root, folders, files in os.walk(project_root):
        for f in files:
            if f.endswith(ending):
                yield os.path.join(root, f)


def get_images(df_object):
    """get all images paths in a given DefTree Element instance"""
    for x in df_object.elements():
        if deftree._is_element(x):
            get_images(x)
        else:
            yield x


def missing_images_in_atlas(project_root, atlas):
    """get images in an atlas that does not exist on disk"""
    tree = deftree.DefTree()
    root = tree.parse(atlas)
    for image in get_images(root):
        if not os.path.exists(os.path.join(project_root, image[1:])):
            yield image


def clean_up_atlas(project_root, atlas):
    """Remove all missing image in entries in the atlas"""
    for missing in missing_images_in_atlas(project_root, atlas):
        tree = deftree.parse(atlas)
        root = tree.get_root()
        for img in get_images(root):
            if img == missing:
                root.remove(img.get_parent())
        tree.write(atlas)


def verify(project_folder):
    """Print images that are missing on disk"""
    for atlas in get_files(project_folder, ".atlas"):
        for missing in missing_images_in_atlas(project_folder, atlas):
            print("{}{}{}".format(atlas.replace(project_folder, ""),
                                  (50 - len(atlas.replace(project_folder, ""))) * " ",
                                  missing))


def clean_up_all_atlases(project_folder):
    """Remove all missing image in entries in the project"""
    for atlas in get_files(project_folder, ".atlas"):
        clean_up_atlas(project_folder, atlas)

You thought you wanted layouts when you started but now you have changed your mind.

Remove Layouts
import deftree
import os


def get_files(project_root, ending):
    # Generator to get files
    for root, folders, files in os.walk(project_root):
        for f in files:
            if f.endswith(ending):
                yield os.path.join(root, f)


def main(project):
    errors = list()
    for gui in get_files(project, ".gui"):
        # Create our tree
        tree = deftree.parse(gui)
        root = tree.get_root()
        rem = False

        # Find all layouts
        for layout in root.iter_elements("layouts"):
            # For error reporting
            errors.append("{}".format(gui))

            rem = True

            # Remove the child layout from the root
            root.remove(layout)

        # If we removed a layout rewrite the file
        if rem:
            tree.write(gui)

    # Report our errors
    if errors:
        [print(x) for x in errors]

You for some reason want to sort your atlases

Sort Atlases
import os
import deftree

def sort_atlas(atlas):
    tree = deftree.parse(atlas)
    root = tree.get_root()
    org = deftree.to_string(root)
    new_tree = deftree.DefTree()
    new_root = new_tree.get_root()
    _atlas = []
    _animations = []

    for at in root.iter_elements("images"):
        _atlas.append(at.get_attribute("image").value)

    for ani in root.iter_elements("animations"):
        _animations.append(ani)

    # Sort the images
    for a in sorted(_atlas, key=lambda x: x.split("/")[-1]):
        ele = new_root.add_element("images")
        ele.add_attribute("image", a)

    # Add the animations
    for animation in _animations:
        new_root.append(animation)

    # Add the atlas attributes. Margin, extrude
    for at in root.attributes():
        if not at.name == "image":
            new_root.append(at)

    new_tree.write(atlas)
    if not deftree.validate(org, deftree.to_string(new_root)):
        print("Sorted: {}".format(atlas))


You noticed that without extrude 2 on your altases you get bleeding

Batch Edit Atlas Property
import os
import deftree


def get_files(project_root, ending):
    # Generator to get files
    for root, folders, files in os.walk(project_root):
        for f in files:
            if f.endswith(ending):
                yield os.path.join(root, f)


def set_atlas_extrude(project_folder, value):
    for atlas in get_files(project_folder, ".atlas"):
        tree = deftree.parse(atlas)
        root = tree.get_root()
        root.set_attribute("extrude_borders", value)

        tree.write(atlas)


It is made for python 3.3 or newer and is easiest installed with pip install deftree

15 Likes

mmmm… @Axel may want to at some point help with the graphics so this tool end up nicely on the Asset Portal :wink:

2 Likes

DefTree is awesome! Thank for the lib and your help!
I made few scripts for my projects.

One of them, for example, removing all unused PNG files from the project.
I am find all links to PNG in every atlas, tilesource, cubemap and game.project (maybe I forgot something?) and then checking every png in project with this list I am searching unused PNGs and remove them. After that I removes all empty folders.

Maybe somebody it will be useful:

Dependencies: deftree and configparser

7 Likes

Thanks @AGulev happy it was useful!

It is a good idea to share small scripts here so everyone can benefit :slight_smile:
Here is one that I made, it removes all resources in a gui file that are not used in it. If you don’t change resources with gui_script (i.e. do a lot of gui.set_texture()) this can save memory for you!

4 Likes

:star_struck:

Sometimes flipbook animations has the same frames in different png files. It makes atlases bigger than they should be, because Defold allows to use one png in few different animation(in one atlas) or few times in one animation.

This script replace duplicates in all project’s flipbook animations:

4 Likes

few more scripts and we’re ready for a magic button named “make my game much smaller and faster before I ship”.

4 Likes

My (not quite) first python script (Before first experements with DefTree) =)
I found a python script for searching duplicates and made few modernisations:

  • sorting by file sizes;
  • calculation of total duplicates size.

This script don’t use DefTree, But I think this forum thread is suitable here.

Original script is here: https://www.pythoncentral.io/finding-duplicate-files-with-python/

My version: https://gist.github.com/AGulev/d5dc12127e0fbe1cd4f239effb76cd81

1 Like

I updated prev. my scripts (some bug fixes and improvements)

New script for removing multi-adding same sprites to the non animation atlas.
User can add one sprite few times, but it has no sense without animation.I think it do not affect to the bundle, but it makes the search in the atlas more difficult.


1 Like