Editor Scripts šŸ”„: Alpha Release

Is there a way to download this version other than through the in-editor updater? My internet is pretty bad, so updating with the editor rarely works.

Played around with it a bit, and made one that just deletes the contents of the selected text file(s) for no reason.

Code
local M = {}

function M.get_commands()
	return {
		{
			label = "Delete",
			locations = {"Edit", "Assets", "Outline"},
			query = {
				selection = {type = "resource", cardinality = "many"}
			},
			active = function(opts)
				return true
			end,
			run = function(opts)
				local actions = {}
				for _,id in ipairs(opts.selection) do
					table.insert(actions, {action = "set", node_id = id, property = "text", value = ""})
				end
				return actions
			end
		}
	}
end

return M

Splendid!
Pretty sad we cannot yet modify collection files. I tried to make an editor script that automatizes creating collection of levels, and catched this error:

ERROR:EXT: Pack levels's "run" in /main/collection_packer.editor_script failed:
ERROR:EXT: No method in multimethod 'transaction-action->txs' for dispatch value: ["set" :editor.collection/CollectionNode "text"]

The code:

local M = {}

M.target_file = nil

function M.get_commands()
    return {
        {
            label = 'Pack levels',
            locations = {'Assets'},
            query = {
                selection = { type = 'resource', cardinality = 'many' }
            },
            active = function(opts)
                for _, node_id in ipairs(opts.selection) do
                    if not M.is_collection(node_id) then
                        return false
                    end
                end
                return true
            end,
            run = M.pack_collections,
        },
        {
            label = 'Set as levels collection',
            locations = {'Assets'},
            query = {
                selection = { type = 'resource', cardinality = 'one' }
            },
            active = function(opts)
                return M.is_collection(opts.selection)
            end,
            run = function(opts)
                local path = editor.get(opts.selection, 'path')
                print('Pack target is set to '..path)
                M.target_file = opts.selection
            end,
        }
    }
end

function M.is_collection(node_id)
    local path = editor.get(node_id, 'path')
    return path:find('.collection', nil, true)
end

local function make_embedded_factory(path, exclude)
    return [[embedded_instances {
    id: "]]..path..[["
    data: "embedded_components {\n"
    "  id: \"collectionproxy\"\n"
    "  type: \"collectionproxy\"\n"
    "  data: \"collection: \\\"]]..path..[[\\\"\\n"
    "exclude: ]]..tostring(exclude)..[[\\n"
    "\"\n"
    "  position {\n"
    "    x: 0.0\n"
    "    y: 0.0\n"
    "    z: 0.0\n"
    "  }\n"
    "  rotation {\n"
    "    x: 0.0\n"
    "    y: 0.0\n"
    "    z: 0.0\n"
    "    w: 1.0\n"
    "  }\n"
    "}\n"
    ""
    position {
        x: 0.0
        y: 0.0
        z: 0.0
    }
    rotation {
        x: 0.0
        y: 0.0
        z: 0.0
        w: 1.0
    }
    scale3 {
        x: 1.0
        y: 1.0
        z: 1.0
    }
}
]]
end

function M.pack_collections(opts)
    assert(M.target_file, 'No target file specified. Use "Set as levels collection" first')
    local content = [[name: "pack"
scale_along_z: 0
]]
    for _, node_id in ipairs(opts.selection) do
        filename = editor.get(node_id, 'path')
        content = content .. make_embedded_factory(filename, false)
    end

    return {
        {
            action = 'set',
            node_id = M.target_file,
            property = 'text',
            value = content,
        },
    }
end

return M
4 Likes

Oooh, Iā€™m excited to see that your guys are starting to use it already. @vlaaad will take a look at your specific issue @azotkirill

2 Likes

Hi! Error happens because from the point of view of an editor, collections are not text files: they are structured data structures. This allows editor to automatically fix references to collections from other collections while renaming them. We want to expand over time on what properties are available for different nodes, but for now you can just write to a collection file with io.open(M.target_file, "w"):write(...).

1 Like

What are the chance of us getting our hands on the protobuf definitions? That could make creating files a bit neater :smiley:

4 Likes

Thanks for explanations!
Itā€™s cool that different file types have different scripting interfaces, shooting yourself in the foot became less handy (sigh). Editor scripting feels easy and handy unless you hit limitations. I already can make most things I planned, though it can take much more effort than I planned.

Some issues I faced this far:

  • editor.get(nil, 'text') throws java.NullPointerException.
  • Is there any way to reload editor scripts without reloading editor?
  • Filtering resources of specific type is cumbersome, and it will be used in most editor scripts.
2 Likes

Iā€™ll improve error message for invalid input to editor.get.

Project ā†’ Reload Editor Scripts (itā€™s mentioned in Editor Script Runtime section of a manual).

You can just write a function that receives node id and expected file extension and checks if path of this node has this extension, is it not enough? What else is cumbersome here?

3 Likes

Yes, thatā€™s what I did, however almost every editor script I can imagine will contain this function, so maybe it could be part of editor module or query.selection?

2 Likes

Just pushed a couple of improvements (and updated manual to reflect the changes):

  • better error reporting when invalid arguments are passed to editor.get()
  • added os.remove
  • allow resource paths as a node_id argument to editor.get(), like that: editor.get("/main/game.script", "text")
5 Likes

OK, Iā€™m just messing around learning how this works, so Iā€™m probably doing something wrong, but it seems like a command may not be run depending on what you have selected?

My Test Code...
local M = {}

local runIdx = 1

local commands = {
    {
        label = "Test Command",
        locations = {"Edit", "Assets", "Outline"},
        query = {
            selection = { type = "resource", cardinality = "many" }
        },
        run = function(opts)
            print("action run", runIdx)
            runIdx = runIdx + 1
            return {}
        end
    },
}

function M.get_commands()
    print("Test Editor Extension Loaded.")
    return commands
end

return M

With that, if I have an embedded (not from another file) game object or component selected, the run function doesnā€™t get called. If I have the root of the .go or .collection file selected, or anything instanced from another file, it does run.

Add new collection --> right-click base node and click custom command --> it runs.
ā€¦in new collection --> press A to add new game object --> right click that go and click custom command --> nothing.

2 Likes

@ross.grams Thatā€™s because currently selection query supports only resources, which are files on a file system. Root of a .go or .collection file has corresponding file, but embedded components do not have their own files, thatā€™s why you canā€™t target them.

3 Likes

Got it, thanks. Makes sense. And uhh, I guess I should have read the manual more closely!

ā€¦currently only "resource" is allowed. In Assets and Outline, resource is selected item. In menu bar (Edit or View), resource is a currently open file;

But now Iā€™ve discovered if I add an ā€œactiveā€ callback and always return true, then it does run on embedded game objects and I get an ID back (but of course thereā€™s not much I can do with it yet).

I guess thatā€™s my first feature request then: access to embedded game object and component properties. My main use for editor extensions would be to speed up and expand on the workflow for editing scenes. I guess you could maaaybe do this right now by parsing the collection file and editing it, but Iā€™m not sure how you could specify which things to edit. I think it would be quite hard to make something convenient to use (which is the whole point).
For example consider a simple ā€œAlign Verticallyā€ command. You select multiple objects, right click in the Outline, click ā€œAlign Verticallyā€, and boom, the selected objects have their positions changed so they line up. Being able to do things like that with editor extensions would be pretty amazing.

Some things I wish for down the roadā€¦

  • Input - Thereā€™s only so much you can do with menu items. If extensions could work with keyboard input then you could really do cool stuff (at the press of a buttonā€¦).
    • ā€¦and probably make a big mess with multiple extensions and conflicting, hard-coded keybindings, but thatā€™s ok. :slight_smile:
  • Drawing stuff in the viewport - Lower priority, but if you really want to make custom editing tools I think you need some way to visually indicate whatā€™s going on. Just something like the render script ā€œdraw_lineā€ message, would do it.
  • Viewport camera transforms - OK now this is really wishful thinking, but now that I am thinking about it, to make a custom editing gizmo you would need 1) The viewport camera matrices for collision checking, and 2) Be able to hijack mouse input before the editor uses it.

Anyway! The fact that editor extensions already work, are doable very easily with lua, can be reloaded instantly, and are so far bug-free, is really awesome!!!

3 Likes

Thank you very much for feedback! I agree, getting and setting transforms looks like a good next step for editor scripts, as well as keyboard shortcuts.

3 Likes

Also I want to point out that active callback is optional, and it will be more performant if you omit it.

2 Likes

Another question: How do I require lua modules from an editor_script? The usual require path doesnā€™t seem to work. The manual specifically mentions that itā€™s possible.

1 Like

This all seems like a great start from quickly playing with it. It feels powerful yet minimalist as well.

My personal requests:

  • viewport interaction (manipulators custom drawing in the viewport). Want to draw splines directly in it.
  • custom property panels.
2 Likes

I managed to get it to work, but I have no idea what I did :expressionless: . Going to play around with it some more.

1 Like

Huh, used to work for me, although Iā€™m not on Windows and we probably expect forward slashes, while lua on Windows asks backward slashesā€¦

1 Like

Ah, darn I thought I tried that. Sure enough, a forward slash instead of a dot works.