Skip to content

pipeline

Pipeline tools for AYON 3ds max integration.

MaxHost

Bases: HostBase, IWorkfileHost, ILoadHost, IPublishHost

Source code in client/ayon_max/api/pipeline.py
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 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
class MaxHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost):

    name = "max"
    menu = None

    def __init__(self):
        super(MaxHost, self).__init__()
        self._op_events = {}
        self._has_been_setup = False

    def install(self):
        pyblish.api.register_host("max")

        pyblish.api.register_plugin_path(PUBLISH_PATH)
        register_loader_plugin_path(LOAD_PATH)
        register_creator_plugin_path(CREATE_PATH)

        _set_project()
        _set_autobackup_dir()
        lib.set_context_setting()

        self.menu = AYONMenu()

        register_event_callback("workfile.open.before", on_before_open)
        register_event_callback("workfile.open.after", on_after_open)
        self._has_been_setup = True
        rt.callbacks.addScript(rt.Name('systemPostNew'), on_new)

        rt.callbacks.addScript(rt.Name('filePostOpen'),
                               lib.check_colorspace)

        rt.callbacks.addScript(rt.Name('postWorkspaceChange'),
                               self._deferred_menu_creation)
        rt.NodeEventCallback(
            nameChanged=lib.update_modifier_node_names)

    def workfile_has_unsaved_changes(self):
        return rt.getSaveRequired()

    def get_workfile_extensions(self):
        return [".max"]

    def save_workfile(self, dst_path=None):
        rt.saveMaxFile(dst_path)
        return dst_path

    def open_workfile(self, filepath):
        rt.checkForSave()
        rt.loadMaxFile(filepath)
        return filepath

    def get_current_workfile(self):
        return os.path.join(rt.maxFilePath, rt.maxFileName)

    def get_containers(self):
        return ls()

    def _register_callbacks(self):
        rt.callbacks.removeScripts(id=rt.name("OpenPypeCallbacks"))

        rt.callbacks.addScript(
            rt.Name("postLoadingMenus"),
            self._deferred_menu_creation, id=rt.Name('OpenPypeCallbacks'))

    def _deferred_menu_creation(self):
        self.log.info("Building menu ...")
        self.menu = AYONMenu()

    @staticmethod
    def create_context_node():
        """Helper for creating context holding node."""

        root_scene = rt.rootScene

        create_attr_script = ("""
attributes "OpenPypeContext"
(
    parameters main rollout:params
    (
        context type: #string
    )

    rollout params "OpenPype Parameters"
    (
        editText editTextContext "Context" type: #string
    )
)
        """)

        attr = rt.execute(create_attr_script)
        rt.custAttributes.add(root_scene, attr)

        return root_scene.OpenPypeContext.context

    def update_context_data(self, data, changes):
        try:
            _ = rt.rootScene.OpenPypeContext.context
        except AttributeError:
            # context node doesn't exists
            self.create_context_node()

        rt.rootScene.OpenPypeContext.context = json.dumps(data)

    def get_context_data(self):
        try:
            context = rt.rootScene.OpenPypeContext.context
        except AttributeError:
            # context node doesn't exists
            context = self.create_context_node()
        if not context:
            context = "{}"
        return json.loads(context)

    def save_file(self, dst_path=None):
        # Force forwards slashes to avoid segfault
        dst_path = dst_path.replace("\\", "/")
        rt.saveMaxFile(dst_path)

create_context_node() staticmethod

Helper for creating context holding node.

Source code in client/ayon_max/api/pipeline.py
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
    @staticmethod
    def create_context_node():
        """Helper for creating context holding node."""

        root_scene = rt.rootScene

        create_attr_script = ("""
attributes "OpenPypeContext"
(
    parameters main rollout:params
    (
        context type: #string
    )

    rollout params "OpenPype Parameters"
    (
        editText editTextContext "Context" type: #string
    )
)
        """)

        attr = rt.execute(create_attr_script)
        rt.custAttributes.add(root_scene, attr)

        return root_scene.OpenPypeContext.context

get_previous_loaded_object(container)

Get previous loaded_object through the OP data

Parameters:

Name Type Description Default
container str

the container which stores the OP data

required

Returns:

Name Type Description
node_list list

list of nodes which are previously loaded

Source code in client/ayon_max/api/pipeline.py
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
def get_previous_loaded_object(container: str):
    """Get previous loaded_object through the OP data

    Args:
        container (str): the container which stores the OP data

    Returns:
        node_list(list): list of nodes which are previously loaded
    """
    node_list = []
    node_transform_monitor_list = rt.getProperty(
        container.modifiers[0].openPypeData, "all_handles")
    for node_transform_monitor in node_transform_monitor_list:
        node_list.append(node_transform_monitor.node)
    return node_list

import_custom_attribute_data(container, selections)

Importing the Openpype/AYON custom parameter built by the creator

Parameters:

Name Type Description Default
container str

target container which adds custom attributes

required
selections list

nodes to be added into

required
Source code in client/ayon_max/api/pipeline.py
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
def import_custom_attribute_data(container: str, selections: list):
    """Importing the Openpype/AYON custom parameter built by the creator

    Args:
        container (str): target container which adds custom attributes
        selections (list): nodes to be added into
        group in custom attributes
    """
    attrs = load_custom_attribute_data()
    modifier = rt.EmptyModifier()
    rt.addModifier(container, modifier)
    container.modifiers[0].name = "OP Data"
    rt.custAttributes.add(container.modifiers[0], attrs)
    node_list = []
    sel_list = []
    for i in selections:
        node_ref = rt.NodeTransformMonitor(node=i)
        node_list.append(node_ref)
        sel_list.append(str(i))

    # Setting the property
    rt.setProperty(
        container.modifiers[0].openPypeData,
        "all_handles", node_list)
    rt.setProperty(
        container.modifiers[0].openPypeData,
        "sel_list", sel_list)

load_custom_attribute_data()

Re-loading the AYON custom parameter built by the creator

Returns:

Name Type Description
attribute

re-loading the custom OP attributes set in Maxscript

Source code in client/ayon_max/api/pipeline.py
261
262
263
264
265
266
267
def load_custom_attribute_data():
    """Re-loading the AYON custom parameter built by the creator

    Returns:
        attribute: re-loading the custom OP attributes set in Maxscript
    """
    return rt.Execute(MS_CUSTOM_ATTRIB)

ls()

Get all AYON containers.

Source code in client/ayon_max/api/pipeline.py
176
177
178
179
180
181
182
183
184
185
186
187
def ls():
    """Get all AYON containers."""
    objs = rt.objects
    containers = [
        obj for obj in objs
        if rt.getUserProp(obj, "id") in {
            AYON_CONTAINER_ID, AVALON_CONTAINER_ID
        }
    ]

    for container in sorted(containers, key=attrgetter("name")):
        yield parse_container(container)

on_after_open()

Check and set up unit scale after opening workfile if user enabled.

Source code in client/ayon_max/api/pipeline.py
255
256
257
258
def on_after_open():
    """Check and set up unit scale after opening workfile if user enabled.
    """
    lib.validate_unit_scale()

on_before_open()

Check and set up project before opening workfile

Source code in client/ayon_max/api/pipeline.py
249
250
251
252
def on_before_open():
    """Check and set up project before opening workfile
    """
    _set_project()

parse_container(container)

Return the container node's full container data.

Parameters:

Name Type Description Default
container str

A container node name.

required

Returns:

Name Type Description
dict

The container schema data for this container node.

Source code in client/ayon_max/api/pipeline.py
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
def parse_container(container):
    """Return the container node's full container data.

    Args:
        container (str): A container node name.

    Returns:
        dict: The container schema data for this container node.

    """
    data = lib.read(container)

    # Backwards compatibility pre-schemas for containers
    data["schema"] = data.get("schema", "openpype:container-3.0")

    # Append transient data
    data["objectName"] = container.Name
    return data

remove_container_data(container_node)

Function to remove container data after updating, switching or deleting it.

Parameters:

Name Type Description Default
container_node str

container node

required
Source code in client/ayon_max/api/pipeline.py
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
def remove_container_data(container_node: str):
    """Function to remove container data after updating, switching or deleting it.

    Args:
        container_node (str): container node
    """
    if container_node.modifiers[0].name == "OP Data":
        all_set_members_names = [
            member.node for member
            in container_node.modifiers[0].openPypeData.all_handles]
        # clean up the children of alembic dummy objects
        for current_set_member in all_set_members_names:
            shape_list = [members for members in current_set_member.Children
                          if rt.ClassOf(members) == rt.AlembicObject
                          or rt.isValidNode(members)]
            if shape_list:  # noqa
                rt.Delete(shape_list)
            rt.Delete(current_set_member)
        rt.deleteModifier(container_node, container_node.modifiers[0])

    rt.Delete(container_node)
    rt.redrawViews()

update_custom_attribute_data(container, selections)

Updating the AYON custom parameter built by the creator

Parameters:

Name Type Description Default
container str

target container which adds custom attributes

required
selections list

nodes to be added into

required
Source code in client/ayon_max/api/pipeline.py
299
300
301
302
303
304
305
306
307
308
309
def update_custom_attribute_data(container: str, selections: list):
    """Updating the AYON custom parameter built by the creator

    Args:
        container (str): target container which adds custom attributes
        selections (list): nodes to be added into
        group in custom attributes
    """
    if container.modifiers[0].name == "OP Data":
        rt.deleteModifier(container, container.modifiers[0])
    import_custom_attribute_data(container, selections)