Coming soon: RPC API

Started by Matt, October 26, 2022, 02:13:19 PM

Previous topic - Next topic

Matt

Coming soon: Terragen RPC - a multi-layered API for remote, scriptable interaction with Terragen.

We're working on an RPC (remote procedure call) system for Terragen, so you can query a running instance of Terragen for information about the project, make changes to nodes, create nodes etc. It talks to Terragen over TCP (JSON-RPC 2.0 over TCP, to be exact). There is also a Python module which is a wrapper for this, with high-level calls such as root(), children(), delete(), save_project() and so on. Standalone Python programs, as well as scripts running in other applications, can import this module to interact with an instance of Terragen.

To quote the docs:

QuoteTerragen RPC is a remote procedure call system for certain versions of Terragen. A running instance of Terragen can act as a server, allowing other programs (or scripts within other programs) to make remote procedure calls (RPCs) to query the open project and modify its state.

A Terragen RPC system consists of:

  • Server – a running instance of Terragen
  • Client process(es) using any of the following APIs:
       
    • High Level Python API
    • Low Level Python API
    • JSON-RPC 2.0 messages over TCP

Beta documentation is here:

https://planetside.co.uk/docs/terragen-rpc/

You can even download beta versions of the terragen_rpc Python module if you like, but it won't be much use until we release a version of Terragen that supports it. You can expect that to happen pretty soon though. It has already been used in production for a film project, and we're ready to open it up to a wider audience as "beta". 8)

I'd love to know what you all think of this idea.
Just because milk is white doesn't mean that clouds are made of milk.

WAS

I'm still not sure if I can budget in a maintenance upgrade, but if I did, I would definitely like to use this in either one of my packages or pitch it at the startup I am in for use with our AI system. With my planned texture model I can see this being a great way to easy generate textures and send them to Terragen for use.

Perhaps you can incorporate a method to take incoming base64 encoded assets to store them to a cache (for things like images, if it's a remote call, though could obviously be local too). I could see that being useful. There might be better ways to send that, but for JSON it's the first thing that popped into my head. We're actually trying to figure out a more efficient way to get around this ourselves the JSON aspect of our API.

This looks very cool, and a great idea. I hope I'll get it try it someday soon. I could port my tools over to python GUI apps (like the planetary crater generator, light populator, etc)

aokcub

Quote from: WAS on October 27, 2022, 02:32:43 AMThis looks very cool, and a great idea. I hope I'll get it try it someday soon. I could port my tools over to python GUI apps (like the planetary crater generator, light populator, etc)
I'll try to incorporate this into my PBR node generator.

digitalguru

#3
Getting to grips with the RPC and it's working well so far.

Have a question though - is it possible to position nodes in the top level node network?

I can set the position of a node (even a group) using 'gui_node_pos' so long as it is a child of another group:

root = tg.root()
group_node_class_name = 'group'

# create a group node
group_nodeA = tg.create_child(root, group_node_class_name)
# create two more groups
group_nodeB = tg.create_child(group_nodeA, group_node_class_name)
group_nodeC = tg.create_child(group_nodeA, group_node_class_name)

# position one of the groups
group_nodeC.set_param('gui_node_pos', '720 200 0')

But not if the groups are created at the root level.

Is there a way round this? I'd like to organise the nodes as they get created - for instance, relative to an existing group in the default node network.

Also, what do these parameters in the group node do?:
gui_group = ""
special_group = "8"
global_bookmark = "1"

aokcub

I made a similar question here.
https://planetside.co.uk/forums/index.php/topic,30319.msg295895.html

It seems that gui_pos_node does not work until the project is reopened.

digitalguru

#5
Interesting to see you had the same issue.

It's quite important I think to be able to position these RPC created nodes precisely in the GUI (without reloading the scene)

If you can package up your nodes up in a group, that will work, but not always desirable.

Matt

Quote from: digitalguru on November 07, 2022, 04:28:30 PMHave a question though - is it possible to position nodes in the top level node network? I can set the position of a node (even a group) using 'gui_node_pos' so long as it is a child of another group. But not if the groups are created at the root level.

I will get that working. I might need to add a dedicated function to move nodes, or a function to update the view after changing these parameters, but I'm not sure yet. I want to refactor some of our internal code related to this.

Quote from: digitalguru on November 07, 2022, 04:28:30 PMAlso, what do these parameters in the group node do?:
gui_group = ""
special_group = "8"
global_bookmark = "1"

'gui_group' is an attribute of all nodes (and groups, which are a kind of node). It is how a node becomes part of a group. It is the name (or full path) of the group that the node is a member of. If you look at the default project you'll see that the terrain nodes use this to denote that they are part of the Terrain group.

'special_group' is an attribute of group nodes. It corresponds to the "Special group" dropdown you see in the group parameters in the UI. The Special Group parameter should always be set to "No" (0) for user defined groups, because Terragen uses this parameter internally to identify the default groups of a scene.

'global_bookmark' in an attribute of group nodes. It corresponds to the "Global bookmark" checkbox in the group parameters in the UI. When global bookmark is checked the bookmark will show up on the left in all Node Network views, but when unchecked will only show up in the local Node Network view that it resides in. For example, adding a group within an object's internal shader network and leaving the global bookmark parameter unchecked will only allow the bookmark to show up in the node's internal shader network, but when checked will show the bookmark in all the Node Network views.

https://planetside.co.uk/wiki/index.php?title=Organizing_Your_Terragen_Project
Just because milk is white doesn't mean that clouds are made of milk.

Matt

Which syntax do you prefer?

Using Node class member functions:
    root.create_child(type)

Using free functions (module-member functions):
    tg.create_child(root, type)

The free function method is deprecated, but I can keep it around if you prefer it.
Just because milk is white doesn't mean that clouds are made of milk.

digitalguru

Quote from: Matt on November 09, 2022, 09:18:39 PMI will get that working. I might need to add a dedicated function to move nodes, or a function to update the view after changing these parameters, but I'm not sure yet. I want to refactor some of our internal code related to this.
Both? Refreshing the node network would be a good idea.

Quote from: Matt on November 09, 2022, 09:18:39 PM'gui_group' is an attribute of all nodes (and groups, which are a kind of node). It is how a node becomes part of a group. It is the name (or full path) of the group that the node is a member of. If you look at the default project you'll see that the terrain nodes use this to denote that they are part of the Terrain group.
Ah, so this is the same as "Group: Capture Nodes" in the Node network?, was wondering how to set that up to create Object groups in the render layers :)

Quote from: Matt on November 09, 2022, 09:18:48 PMWhich syntax do you prefer?

Using Node class member functions:
    root.create_child(type)

Using free functions (module-member functions):
    tg.create_child(root, type)

The free function method is deprecated, but I can keep it around if you prefer it.

I prefer the free function - a bit easier to read when scanning the code.

Just noticed when creating a Surface layer with RPC it creates the default Fractal Breakup node also. So in my script where I don't need it I had to then delete it.
Would it be posssible when creating nodes like this it only creates the node you specify? You create a fractal breakup node and connect it if you need it.

aokcub

#9
Quote from: Matt on November 09, 2022, 09:18:39 PMI will get that working. I might need to add a dedicated function to move nodes, or a function to update the view after changing these parameters, but I'm not sure yet. I want to refactor some of our internal code related to this.

Thank you, Matt.

I was going to request it eventually, but I would like to have convenience methods.
I guess it would be a difficult question that how many methods to implement......


Quote from: Matt on November 09, 2022, 09:18:48 PMWhich syntax do you prefer?

Using Node class member functions:
    root.create_child(type)

Using free functions (module-member functions):
    tg.create_child(root, type)

The free function method is deprecated, but I can keep it around if you prefer it.



I prefer the former. It is consistent with other methods such as node.set_param()

digitalguru

Is there a way to get the class of a node selected in Terragen?
I'd like to test if a node is "legal" to connect to with RPC - for example a heightfield shader should only connect to Compute terrain, warp shaders etc

Matt

Quote from: digitalguru on November 15, 2022, 06:10:12 PMIs there a way to get the class of a node selected in Terragen?
I'd like to test if a node is "legal" to connect to with RPC - for example a heightfield shader should only connect to Compute terrain, warp shaders etc

It sounds like you need to ask a node for not only the name of its class (e.g. "heightfield_shader") but also the name of its superclass ("shader", or "shader/surface"). I'll add those to the TODO list.
Just because milk is white doesn't mean that clouds are made of milk.

digitalguru

#12
Quote from: Matt on November 15, 2022, 08:11:13 PMIt sounds like you need to ask a node for not only the name of its class (e.g. "heightfield_shader") but also the name of its superclass ("shader", or "shader/surface"). I'll add those to the TODO list.
That's great and will be useful. But I meant how to to query the class of a single selected node, can't see a way to do that unless I've missed something :)

I can test a node if I know what class I expect the node to be:

def tt_check_selected_is_class(self, tg_class, tg_selection):

    check_node = 0
    root = tg.root()
    if len(tg_selection) == 1:
       tg_sel_path = (tg_selection.path())
  •        tg_class_filter = tg.children_filtered_by_class(root, tg_class)
       test_for_class = [i for i in tg_class_filter if i.path() == tg_sel_path]
       check_node = len(test_for_class)
 return check_node[/font]

But not for any selection a use might make.




Matt

Quote from: digitalguru on November 17, 2022, 02:49:22 PM
Quote from: Matt on November 15, 2022, 08:11:13 PMIt sounds like you need to ask a node for not only the name of its class (e.g. "heightfield_shader") but also the name of its superclass ("shader", or "shader/surface"). I'll add those to the TODO list.
That's great and will be useful. But I meant how to to query the class of a single selected node, can't see a way to do that unless I've missed something :)

That's what I understood you to mean, and you're right, there isn't a function for that yet. It's on the TODO list.
Just because milk is white doesn't mean that clouds are made of milk.

digitalguru

Gotcha.
I've got the RPC working to assemble a group of tiled heightfields and plug them into the scene. It's hard coded to plug into the Compute Terrain for now.
Works really well.