Skip to main content

Basics and Filtering

The host globals​

When a script runs inside RPTS (RapidPipeline Twin Studio), a few names are already available without any import:

  • rpts - the RPTS scripting module, with helpers for selection, visibility, materials and more.
  • rpd - the RapidPipeline data module, which provides the scene classes (Node, Mesh, Material, ...).
  • get_asset() - returns the currently loaded asset, or None if nothing is loaded.

import rpts and import rpd are harmless if you prefer to be explicit, and rpts.get_asset() is the same call as get_asset(). The examples on this page use the explicit form.

note

Always check for None before using the asset, since a script can run while no model is loaded.

Getting the current asset​

import rpts

asset = rpts.get_asset()
if asset is None:
print("No model currently loaded.")
else:
print(f"Asset has {asset.num_meshes} mesh(es) and {asset.num_materials} material(s).")

Keep in mind that an asset object is tied to the model that was loaded when you fetched it. Operations that replace the model (such as processing or undo) invalidate it, so fetch the asset again with rpts.get_asset() after such a change.

Walking the scene hierarchy​

Every scene is a tree of Node objects. The top of the tree is asset.root_node, and each node can be used like a read-only list of its children:

  • node[i] returns the i-th direct child (a Node).
  • len(node) returns the number of direct children.
  • node.parent returns the parent node, or None for the root.
  • node.name is the node's name, and node.path is its full hierarchical path, which is unique within the scene.

A small recursive helper collects a node and all of its descendants:

def walk(node):
# yields the node itself, then every node beneath it (depth-first)
yield node
for i in range(len(node)):
yield from walk(node[i])

To look up a node directly when you already know its path, use asset.node_at_path(path), which returns the node or None.

Nodes with and without a mesh​

Not every node carries geometry. A node that holds a mesh has node.mesh_index set to the index of that mesh. node.mesh_index == -1 only means that the node has no mesh of its own; it does not tell you what kind of node it is:

for node in walk(asset.root_node):
if node.mesh_index != -1:
mesh = asset.mesh(node.mesh_index)
print(node.path, "has", mesh.num_triangles, "triangles")

If a node references a mesh, a relationship can be established between the node and the mesh’s material. This can be useful when assigning materials.

Filtering nodes​

Instead of walking the hierarchy by hand and checking a condition yourself, you can describe what you're looking for with an rpd.NodeFilter and pass it to asset.query_nodes.

A NodeFilter always takes the same four keyword arguments:

  • target - a rpd.NodeFilterTarget value saying what to match against: NODE_NAME, MATERIAL_NAME, MESH_NAME, or METADATA_SUB_ATTRIB_TITLE.
  • match_type - a rpd.FilterMatchType value saying how to match: REGEX, EXACT, CONTAINS, STARTS_WITH, or ENDS_WITH.
  • pattern - the string (or regular expression, when match_type is REGEX) to match with.
  • case_sensitive - whether the comparison is case-sensitive.

asset.query_nodes(filters, query_mode) takes a list of NodeFilter objects and a rpd.QueryMode value, and returns the matching nodes (a sequence supporting len(), with .path/.name on each node):

  • rpd.QueryMode.MATCH_ANY - a node matches if any filter in the list matches (OR).
  • rpd.QueryMode.MATCH_ALL - a node matches only if every filter in the list matches (AND).

Here is a single filter with MATCH_ANY, finding nodes whose name follows a prefix naming convention:

# this script is written for RPTS 0.9.8

import rpts
import rpd

asset = rpts.get_asset()

name_filter = rpd.NodeFilter(
target=rpd.NodeFilterTarget.NODE_NAME,
match_type=rpd.FilterMatchType.REGEX,
pattern=r"^(Proxy|Collision)_",
case_sensitive=False,
)

matches = asset.query_nodes(
[name_filter], rpd.QueryMode.MATCH_ANY
)

for node in matches:
print(node.path)

Passing more than one filter combines them according to query_mode. With MATCH_ALL, every filter must match the same node, which is how you express an AND condition - for example, nodes whose name contains "Handle" and that use a material whose name contains "Chrome":

# this script is written for RPTS 0.9.8

import rpts
import rpd

asset = rpts.get_asset()

filters = [
rpd.NodeFilter(
target=rpd.NodeFilterTarget.NODE_NAME,
match_type=rpd.FilterMatchType.CONTAINS,
pattern="Handle",
case_sensitive=False,
),
rpd.NodeFilter(
target=rpd.NodeFilterTarget.MATERIAL_NAME,
match_type=rpd.FilterMatchType.CONTAINS,
pattern="Chrome",
case_sensitive=False,
),
]

matches = asset.query_nodes(
filters, rpd.QueryMode.MATCH_ALL
)

for node in matches:
print(node.path)

Example: finding nodes by metadata attribute​

The following script finds every node that carries a metadata sub-attribute with a given title, and prints their paths. It only reads the scene: nothing is selected, hidden, renamed or deleted, so it is safe to run on any model.

Replace "<metadata-key>" with the title of the metadata attribute you are interested in.

# this script is written for RPTS 0.9.8
# read-only: this script only reads the scene and never changes it

import rpts
import rpd

METADATA_KEY = "<metadata-key>"


def report_nodes_with_metadata(key):
asset = rpts.get_asset()
if asset is None:
print("No model currently loaded.")
return

metadata_filter = rpd.NodeFilter(
target=rpd.NodeFilterTarget.METADATA_SUB_ATTRIB_TITLE,
match_type=rpd.FilterMatchType.EXACT,
pattern=key,
case_sensitive=False,
)

matches = asset.query_nodes(
[metadata_filter], rpd.QueryMode.MATCH_ANY
)

for node in matches:
print(node.path)

print(f"Found {len(matches)} node(s). The scene was not changed.")


report_nodes_with_metadata(METADATA_KEY)
tip

Metadata filters search metadata paths or attribute titles, not the stored attribute values. To filter by a metadata value, first query the relevant nodes and then inspect their metadata separately.

Next steps​

Once you can find the nodes you care about, you can act on them. Selection and visibility shows how to select, hide and delete nodes. For the basics of materials, see Working with Materials.