Skip to content

pipeline

Pipeline tools for Ayon Substance Designer integration.

MarvelousDesignerHost

Bases: HostBase, IWorkfileHost, ILoadHost, IPublishHost

Host class for Marvelous Designer integration with AYON pipeline.

This class provides the main interface between AYON and Marvelous Designer, implementing workfile operations, loading, and publishing functionality.

Attributes:

Name Type Description
name str

The host name identifier.

_has_been_setup bool

Flag indicating if the host has been

callbacks list

List of registered callbacks.

shelves list

List of UI shelves.

Source code in client/ayon_marvelousdesigner/api/pipeline.py
 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
class MarvelousDesignerHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost):
    """Host class for Marvelous Designer integration with AYON pipeline.

    This class provides the main interface between AYON and Marvelous Designer,
    implementing workfile operations, loading, and publishing functionality.

    Attributes:
        name (str): The host name identifier.
        _has_been_setup (bool): Flag indicating if the host has been
        initialized.
        callbacks (list): List of registered callbacks.
        shelves (list): List of UI shelves.
    """
    name = "marvelousdesigner"

    def __init__(self):
        """Initialize the Marvelous Designer host with default settings."""
        super().__init__()
        self._has_been_setup = False
        self.callbacks = []
        self.shelves = []

    @staticmethod
    def show_tools_dialog() -> None:
        """Show tools dialog with actions leading to show other tools."""
        show_tools_dialog()

    def install(self) -> None:
        """Install and register the MD host with Ayon pipeline."""
        pyblish.api.register_host("marvelousdesigner")

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

        self._has_been_setup = True

    def workfile_has_unsaved_changes(self) -> bool:  # noqa: PLR6301
        """Check if the current workfile has unsaved changes.

        Returns:
            bool: True if there are unsaved changes, False otherwise.
        """
        # API not supported for the check
        return utility_api.CheckZPRJForUnsavedChanges()

    def get_workfile_extensions(self) -> list[str]:  # noqa: PLR6301
        """Get the list of supported workfile extensions.

        Returns:
            list[str]: List of supported workfile extensions.
        """
        return [".zprj"]

    def save_workfile(self, dst_path: str | None = None) -> str | None:  # noqa: PLR6301
        """Save the current workfile to the specified destination path.

        Args:
            dst_path (str | None, optional): Destination path to save
                the workfile. Defaults to None.

        Returns:
            str | None: The path where the workfile was saved, or None
            if saving failed.
        """
        save_workfile(dst_path)
        return dst_path

    def open_workfile(self, filepath: str) -> None:  # noqa: PLR6301
        """Open a workfile from the specified file path."""
        open_workfile(filepath)

    def get_current_workfile(self) -> str:  # noqa: PLR6301
        """Get the current workfile path from the host.

        Returns:
            str: The current workfile path.
        """
        return utility_api.GetProjectFilePath()

    def get_containers(self) -> list:  # noqa: PLR6301
        """Get the list of containers in the current scene.

        Returns:
            list: List of container metadata dictionaries.
        """
        return ls()

    def update_context_data(self, data: dict, changes: dict) -> None:  # noqa : PLR6301
        """Update the context data in the current file metadata."""
        # Note: 'changes' parameter is part of the interface but not used in MD
        _ = changes  # Explicitly ignore the unused parameter
        set_metadata(AYON_CONTEXT_DATA, data)

    def get_context_data(self) -> dict:  # noqa: PLR6301
        """Get the context data from the current file metadata.

        Returns:
            dict: Context data dictionary.
        """
        metadata = get_ayon_metadata() or {}
        return metadata.get(AYON_CONTEXT_DATA, {})

__init__()

Initialize the Marvelous Designer host with default settings.

Source code in client/ayon_marvelousdesigner/api/pipeline.py
58
59
60
61
62
63
def __init__(self):
    """Initialize the Marvelous Designer host with default settings."""
    super().__init__()
    self._has_been_setup = False
    self.callbacks = []
    self.shelves = []

get_containers()

Get the list of containers in the current scene.

Returns:

Name Type Description
list list

List of container metadata dictionaries.

Source code in client/ayon_marvelousdesigner/api/pipeline.py
123
124
125
126
127
128
129
def get_containers(self) -> list:  # noqa: PLR6301
    """Get the list of containers in the current scene.

    Returns:
        list: List of container metadata dictionaries.
    """
    return ls()

get_context_data()

Get the context data from the current file metadata.

Returns:

Name Type Description
dict dict

Context data dictionary.

Source code in client/ayon_marvelousdesigner/api/pipeline.py
137
138
139
140
141
142
143
144
def get_context_data(self) -> dict:  # noqa: PLR6301
    """Get the context data from the current file metadata.

    Returns:
        dict: Context data dictionary.
    """
    metadata = get_ayon_metadata() or {}
    return metadata.get(AYON_CONTEXT_DATA, {})

get_current_workfile()

Get the current workfile path from the host.

Returns:

Name Type Description
str str

The current workfile path.

Source code in client/ayon_marvelousdesigner/api/pipeline.py
115
116
117
118
119
120
121
def get_current_workfile(self) -> str:  # noqa: PLR6301
    """Get the current workfile path from the host.

    Returns:
        str: The current workfile path.
    """
    return utility_api.GetProjectFilePath()

get_workfile_extensions()

Get the list of supported workfile extensions.

Returns:

Type Description
list[str]

list[str]: List of supported workfile extensions.

Source code in client/ayon_marvelousdesigner/api/pipeline.py
89
90
91
92
93
94
95
def get_workfile_extensions(self) -> list[str]:  # noqa: PLR6301
    """Get the list of supported workfile extensions.

    Returns:
        list[str]: List of supported workfile extensions.
    """
    return [".zprj"]

install()

Install and register the MD host with Ayon pipeline.

Source code in client/ayon_marvelousdesigner/api/pipeline.py
70
71
72
73
74
75
76
77
78
def install(self) -> None:
    """Install and register the MD host with Ayon pipeline."""
    pyblish.api.register_host("marvelousdesigner")

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

    self._has_been_setup = True

open_workfile(filepath)

Open a workfile from the specified file path.

Source code in client/ayon_marvelousdesigner/api/pipeline.py
111
112
113
def open_workfile(self, filepath: str) -> None:  # noqa: PLR6301
    """Open a workfile from the specified file path."""
    open_workfile(filepath)

save_workfile(dst_path=None)

Save the current workfile to the specified destination path.

Parameters:

Name Type Description Default
dst_path str | None

Destination path to save the workfile. Defaults to None.

None

Returns:

Type Description
str | None

str | None: The path where the workfile was saved, or None

str | None

if saving failed.

Source code in client/ayon_marvelousdesigner/api/pipeline.py
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
def save_workfile(self, dst_path: str | None = None) -> str | None:  # noqa: PLR6301
    """Save the current workfile to the specified destination path.

    Args:
        dst_path (str | None, optional): Destination path to save
            the workfile. Defaults to None.

    Returns:
        str | None: The path where the workfile was saved, or None
        if saving failed.
    """
    save_workfile(dst_path)
    return dst_path

show_tools_dialog() staticmethod

Show tools dialog with actions leading to show other tools.

Source code in client/ayon_marvelousdesigner/api/pipeline.py
65
66
67
68
@staticmethod
def show_tools_dialog() -> None:
    """Show tools dialog with actions leading to show other tools."""
    show_tools_dialog()

update_context_data(data, changes)

Update the context data in the current file metadata.

Source code in client/ayon_marvelousdesigner/api/pipeline.py
131
132
133
134
135
def update_context_data(self, data: dict, changes: dict) -> None:  # noqa : PLR6301
    """Update the context data in the current file metadata."""
    # Note: 'changes' parameter is part of the interface but not used in MD
    _ = changes  # Explicitly ignore the unused parameter
    set_metadata(AYON_CONTEXT_DATA, data)

workfile_has_unsaved_changes()

Check if the current workfile has unsaved changes.

Returns:

Name Type Description
bool bool

True if there are unsaved changes, False otherwise.

Source code in client/ayon_marvelousdesigner/api/pipeline.py
80
81
82
83
84
85
86
87
def workfile_has_unsaved_changes(self) -> bool:  # noqa: PLR6301
    """Check if the current workfile has unsaved changes.

    Returns:
        bool: True if there are unsaved changes, False otherwise.
    """
    # API not supported for the check
    return utility_api.CheckZPRJForUnsavedChanges()

containerise(name, namespace, context, loader, options=None)

Imprint a loaded container with metadata.

Containerisation enables a tracking of version, author and origin for loaded assets.

Parameters:

Name Type Description Default
name str

Name of resulting assembly

required
namespace str

Namespace under which to host container

required
context dict

Asset information

required
loader LoaderPlugin

loader instance used to produce container.

required
options dict

options

None
Source code in client/ayon_marvelousdesigner/api/pipeline.py
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
def containerise(
        name: str, namespace: str,
        context: dict, loader: object,
        options: dict | None = None) -> None:
    """Imprint a loaded container with metadata.

    Containerisation enables a tracking of version, author and origin
    for loaded assets.

    Arguments:
        name (str): Name of resulting assembly
        namespace (str): Namespace under which to host container
        context (dict): Asset information
        loader (load.LoaderPlugin): loader instance used to produce container.
        options (dict): options

    """
    data = {
        "schema": "ayon:container-3.0",
        "id": AYON_CONTAINER_ID,
        "name": str(name),
        "namespace": str(namespace) if namespace else None,
        "loader": str(loader.__class__.__name__),
        "representation": context["representation"]["id"],
        "project_name": context["project"]["name"],
        "objectName": name,
    }
    if options:
        fabric_index = options.get("fabricIndex")
        data["fabricIndex"] = fabric_index
        data["objectName"] = f"{name}_fabric_{fabric_index}"
    else:
        data["objectName"] = name
    # save the main_data in a temp folder
    container_data = ls() or []
    container_data.append(data)
    set_metadata(AYON_CONTAINERS, container_data)

get_ayon_metadata()

Get AYON relevant metadata from current file.

Returns:

Name Type Description
dict dict

AYON metadata as a dictionary.

Source code in client/ayon_marvelousdesigner/api/pipeline.py
236
237
238
239
240
241
242
243
244
def get_ayon_metadata() -> dict:
    """Get AYON relevant metadata from current file.

    Returns:
        dict: AYON metadata as a dictionary.
    """
    # need to convert string to dict
    metadata_str = utility_api.GetMetaDataForCurrentGarment()
    return json.loads(metadata_str)

get_current_workfile()

Get the current file path from the host.

Returns:

Name Type Description
str str

The current workfile path.

Source code in client/ayon_marvelousdesigner/api/pipeline.py
226
227
228
229
230
231
232
233
def get_current_workfile() -> str:
    """Get the current file path from the host.

    Returns:
        str: The current workfile path.
    """
    host = registered_host()
    return host.get_current_workfile()

get_instances()

Retrieve all stored instances from the project settings.

Returns:

Name Type Description
dict dict

Dictionary of stored instances from the project settings.

Source code in client/ayon_marvelousdesigner/api/pipeline.py
247
248
249
250
251
252
253
254
def get_instances() -> dict:
    """Retrieve all stored instances from the project settings.

    Returns:
        dict: Dictionary of stored instances from the project settings.
    """
    ayon_metadata = get_ayon_metadata()
    return ayon_metadata.get(AYON_INSTANCES, {})

get_instances_values()

Retrieve all stored instances from the project settings.

Returns:

Name Type Description
list list

List of all instance values from the project settings.

Source code in client/ayon_marvelousdesigner/api/pipeline.py
257
258
259
260
261
262
263
264
def get_instances_values() -> list:
    """Retrieve all stored instances from the project settings.

    Returns:
        list: List of all instance values from the project settings.
    """
    ayon_instances = get_instances()
    return list(ayon_instances.values())

imprint(object_name, data)

Imprint metadata onto an object in the scene.

Parameters:

Name Type Description Default
object_name str

Name of the object to imprint metadata on.

required
data dict

Metadata to imprint.

required
Source code in client/ayon_marvelousdesigner/api/pipeline.py
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
def imprint(object_name: str, data: dict) -> None:
    """Imprint metadata onto an object in the scene.

    Args:
        object_name (str): Name of the object to imprint metadata on.
        data (dict): Metadata to imprint.
    """
    # Retrieve existing containers
    container_data = ls()
    # Find the container for the specified object
    for container in container_data:
        if container.get("objectName") == object_name:
            container.update(data)
            break
    else:
        log.warning(
            "No container found for object %s to imprint data.", object_name
        )
        return
    # Update the metadata
    set_metadata(AYON_CONTAINERS, container_data)

ls()

List all AYON containers in the current file metadata.

Returns:

Name Type Description
list list

List of AYON container metadata dictionaries.

Source code in client/ayon_marvelousdesigner/api/pipeline.py
267
268
269
270
271
272
273
274
def ls() -> list:
    """List all AYON containers in the current file metadata.

    Returns:
        list: List of AYON container metadata dictionaries.
    """
    ayon_metadata = get_ayon_metadata() or {}
    return ayon_metadata.get(AYON_CONTAINERS, [])

open_workfile(filepath)

Open a workfile from the specified file path.

Source code in client/ayon_marvelousdesigner/api/pipeline.py
324
325
326
327
def open_workfile(filepath: str) -> None:
    """Open a workfile from the specified file path."""
    import_options = ApiTypes.ImportZPRJOption()
    import_api.ImportZprj(filepath, import_options)

remove_container_data(object_name)

Remove container data for a specific object in the scene.

Parameters:

Name Type Description Default
object_name str

Name of the object whose container data is to be removed.

required
Source code in client/ayon_marvelousdesigner/api/pipeline.py
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
def remove_container_data(object_name: str) -> None:
    """Remove container data for a specific object in the scene.

    Args:
        object_name (str): Name of the object whose container data is to
            be removed.
    """
    container_data = ls()
    # Filter out the container for the specified object
    updated_containers = [
        container for container in container_data
        if container.get("objectName") != object_name
    ]
    # Update the metadata
    set_metadata(AYON_CONTAINERS, updated_containers)

remove_instance(instance_id)

Helper method to remove the data for a specific container.

Source code in client/ayon_marvelousdesigner/api/pipeline.py
311
312
313
314
315
def remove_instance(instance_id: str) -> None:
    """Helper method to remove the data for a specific container."""
    instances = get_instances()
    instances.pop(instance_id, None)
    set_metadata(AYON_INSTANCES, instances)

save_workfile(filepath)

Save the current workfile to the specified file path.

Source code in client/ayon_marvelousdesigner/api/pipeline.py
318
319
320
321
def save_workfile(filepath: str) -> None:
    """Save the current workfile to the specified file path."""
    export_api.ExportZPrj(filepath)
    open_workfile(filepath)

set_instance(instance_id, instance_data, *, update=False)

Set a single instance into the current file metadata.

Source code in client/ayon_marvelousdesigner/api/pipeline.py
286
287
288
289
def set_instance(
    instance_id: str, instance_data: dict, *, update: bool = False) -> None:
    """Set a single instance into the current file metadata."""
    set_instances({instance_id: instance_data}, update=update)

set_instances(instance_data_by_id, *, update=False)

Set multiple instances into the current file metadata.

Parameters:

Name Type Description Default
instance_data_by_id dict

instance data mapped by their IDs

required
update bool

Whether to update existing instances.

False
Source code in client/ayon_marvelousdesigner/api/pipeline.py
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
def set_instances(instance_data_by_id: dict, *, update: bool = False) -> None:
    """Set multiple instances into the current file metadata.

    Args:
        instance_data_by_id (dict): instance data mapped by their IDs
        update (bool, optional): Whether to update existing instances.
        Defaults to False.
    """
    instances = get_instances()
    for instance_id, instance_data in instance_data_by_id.items():
        if update:
            existing_data = instances.get(instance_id, {})
            existing_data.update(instance_data)
        else:
            instances[instance_id] = instance_data

    set_metadata(AYON_INSTANCES, instances)

set_metadata(data_type, data)

Set instance data into the current file metadata.

Source code in client/ayon_marvelousdesigner/api/pipeline.py
277
278
279
280
281
282
283
def set_metadata(data_type: str, data: Union[dict, list]) -> None:
    """Set instance data into the current file metadata."""
    ayon_metadata = get_ayon_metadata()
    ayon_metadata[data_type] = data
    # Serialize with optional formatting
    json_to_str_data = f"{json.dumps(ayon_metadata)}"
    utility_api.SetMetaDataForCurrentGarment(json_to_str_data)