Skip to content

workfile_template_builder

HoudiniPlaceholderPlugin

Bases: PlaceholderPlugin

Base Placeholder Plugin for Houdini with one unified cache.

Inherited classes must still implement populate_placeholder

Source code in client/ayon_houdini/api/workfile_template_builder.py
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
class HoudiniPlaceholderPlugin(PlaceholderPlugin):
    """Base Placeholder Plugin for Houdini with one unified cache.

    Inherited classes must still implement `populate_placeholder`
    """

    attr_prefix: str = "AYON_placeholder_"

    def get_placeholder_node_name(self, placeholder_data):
        return self.identifier.replace(".", "_")

    def create_placeholder_node(self, node_name=None):
        """Create node to be used as placeholder.

        By default, it creates a null node in '/out'.
        Feel free to override it in different workfile build plugins.
        """

        node = hou.node("/out").createNode(
            "null", node_name, force_valid_node_name=True)
        node.moveToGoodPosition()
        parms = node.parmTemplateGroup()
        for parm in {"execute", "renderdialog"}:
            p = parms.find(parm)
            p.hide(True)
            parms.replace(parm, p)
        node.setParmTemplateGroup(parms)
        return node

    def create_placeholder(self, placeholder_data):
        node_name = self.get_placeholder_node_name(placeholder_data)

        placeholder_node = self.create_placeholder_node(node_name)
        HoudiniCreator.customize_node_look(placeholder_node)

        placeholder_data["plugin_identifier"] = self.identifier
        self._imprint(placeholder_node, placeholder_data)

    def collect_scene_placeholders(self):
        # Read the cache by identifier
        placeholder_nodes = self.builder.get_shared_populate_data(
            self.identifier
        )
        if placeholder_nodes is None:

            placeholder_nodes = lsattr(self.attr_prefix + "plugin_identifier",
                                       self.identifier)
            if self.attr_prefix:
                # Backwards compatibility: support cases without attr prefix
                placeholder_nodes.extend(
                    lsattr("plugin_identifier", self.identifier)
                )

            # Set the cache by identifier
            self.builder.set_shared_populate_data(
                    self.identifier, placeholder_nodes
                )

        return placeholder_nodes

    def update_placeholder(self, placeholder_item, placeholder_data):
        placeholder_node = hou.node(placeholder_item.scene_identifier)
        self._imprint(placeholder_node, placeholder_data, update=True)

        # Update node name
        node_name = self.get_placeholder_node_name(placeholder_data)
        node_name = hou.text.variableName(node_name)
        placeholder_node.setName(node_name, unique_name=True)

    def delete_placeholder(self, placeholder):
        placeholder_node = hou.node(placeholder.scene_identifier)

        # Connections should have been transferred but may also have been
        # inserted to output nodes with the placeholder node, in that case
        # the connection with placeholder node still exists, but we don't want
        # Houdini to delete the node and then 'keep' the connection passed
        # through from input node to output node.
        disconnect_node(placeholder_node)

        placeholder_node.destroy()

    def _imprint(self, placeholder_node, placeholder_data, update=False):
        imprint(
            placeholder_node,
            placeholder_data,
            update=update,
            folder="AYON Placeholder",
            prefix=self.attr_prefix
        )

    def _read(self, placeholder_node: hou.Node) -> dict[str, Any]:
        """Read attributes from the node, removing the placeholder prefix"""
        data = read(placeholder_node)

        # Convert prefixed attributes to the regular data keys overriding
        # any backwards compatible keys that pre-existed from before we
        # started adding prefixes.
        for key in list(data):
            if key.startswith(self.attr_prefix):
                # Remove prefix and re-assign it
                value = data.pop(key)
                key = key[len(self.attr_prefix):]
                data[key] = value

        return data

create_placeholder_node(node_name=None)

Create node to be used as placeholder.

By default, it creates a null node in '/out'. Feel free to override it in different workfile build plugins.

Source code in client/ayon_houdini/api/workfile_template_builder.py
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
def create_placeholder_node(self, node_name=None):
    """Create node to be used as placeholder.

    By default, it creates a null node in '/out'.
    Feel free to override it in different workfile build plugins.
    """

    node = hou.node("/out").createNode(
        "null", node_name, force_valid_node_name=True)
    node.moveToGoodPosition()
    parms = node.parmTemplateGroup()
    for parm in {"execute", "renderdialog"}:
        p = parms.find(parm)
        p.hide(True)
        parms.replace(parm, p)
    node.setParmTemplateGroup(parms)
    return node

HoudiniTemplateBuilder

Bases: AbstractTemplateBuilder

Concrete implementation of AbstractTemplateBuilder for Houdini

Source code in client/ayon_houdini/api/workfile_template_builder.py
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
class HoudiniTemplateBuilder(AbstractTemplateBuilder):
    """Concrete implementation of AbstractTemplateBuilder for Houdini"""

    def resolve_template_path(self, path, fill_data=None):
        """Allows additional resolving over the template path using custom
        integration methods, like Houdini's expand string functionality.

        This only works with ayon-core 0.4.5+
        """
        # use default template data formatting
        path = super().resolve_template_path(path, fill_data)

        # escape backslashes for `expandString` and expand houdini vars
        path = path.replace("\\", "\\\\")
        path = hou.text.expandString(path)
        return path

    def import_template(self, path):
        """Import template into current scene.
        Block if a template is already loaded.

        Args:
            path (str): A path to current template (usually given by
            get_template_preset implementation)

        Returns:
            bool: Whether the template was successfully imported or not
        """

        # TODO Check if template is already imported

        # Merge (Load) template workfile in the current scene.
        try:
            hou.hipFile.merge(path, ignore_load_warnings=True)
            return True
        except hou.OperationFailed:
            return False

import_template(path)

Import template into current scene. Block if a template is already loaded.

Parameters:

Name Type Description Default
path str

A path to current template (usually given by

required

Returns:

Name Type Description
bool

Whether the template was successfully imported or not

Source code in client/ayon_houdini/api/workfile_template_builder.py
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
def import_template(self, path):
    """Import template into current scene.
    Block if a template is already loaded.

    Args:
        path (str): A path to current template (usually given by
        get_template_preset implementation)

    Returns:
        bool: Whether the template was successfully imported or not
    """

    # TODO Check if template is already imported

    # Merge (Load) template workfile in the current scene.
    try:
        hou.hipFile.merge(path, ignore_load_warnings=True)
        return True
    except hou.OperationFailed:
        return False

resolve_template_path(path, fill_data=None)

Allows additional resolving over the template path using custom integration methods, like Houdini's expand string functionality.

This only works with ayon-core 0.4.5+

Source code in client/ayon_houdini/api/workfile_template_builder.py
27
28
29
30
31
32
33
34
35
36
37
38
39
def resolve_template_path(self, path, fill_data=None):
    """Allows additional resolving over the template path using custom
    integration methods, like Houdini's expand string functionality.

    This only works with ayon-core 0.4.5+
    """
    # use default template data formatting
    path = super().resolve_template_path(path, fill_data)

    # escape backslashes for `expandString` and expand houdini vars
    path = path.replace("\\", "\\\\")
    path = hou.text.expandString(path)
    return path