Skip to content

api

API for the Equalizer plugin.

Container dataclass

Container data class.

Source code in client/ayon_equalizer/api/pipeline.py
 9
10
11
12
13
14
15
16
17
18
19
20
@dataclass
class Container:
    """Container data class."""

    name: str = None
    id: str = AYON_CONTAINER_ID
    namespace: str = ""
    loader: str = None
    representation: str = None
    objectName: str = None  # noqa: N815
    timestamp: int = 0
    version: str = None

EqualizerCreator

Bases: Creator

Base class for creating instances in 3DEqualizer.

Source code in client/ayon_equalizer/api/plugin.py
 23
 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
 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
class EqualizerCreator(Creator):
    """Base class for creating instances in 3DEqualizer."""
    skip_discovery = True
    settings_category = "equalizer"

    def create(
        self,
        product_name: str,
        instance_data: dict,
        pre_create_data: dict,
    ) -> CreatedInstance:
        """Create a subset in the host application.

        Args:
            product_name (str): Name of the subset to create.
            instance_data (dict): Data of the instance to create.
            pre_create_data (dict): Data from the pre-create step.

        Returns:
            ayon_core.pipeline.CreatedInstance: Created instance.

        """
        self.log.debug("EqualizerCreator.create")
        product_type = instance_data.get("productType")
        if not product_type:
            product_type = self.product_base_type
        instance = CreatedInstance(
            product_base_type=self.product_base_type,
            product_type=product_type,
            product_name=product_name,
            data=instance_data,
            creator=self,
        )
        self._add_instance_to_context(instance)

        host: EqualizerHost = self.host
        host.add_publish_instance(instance.data_to_store())

        return instance

    def collect_instances(self) -> None:
        """Collect instances from the host application.

        Returns:
            list[openpype.pipeline.CreatedInstance]: List of instances.

        """
        host: EqualizerHost = self.host
        for instance_data in host.get_publish_instances():
            if instance_data["creator_identifier"] != self.identifier:
                continue
            created_instance = CreatedInstance.from_existing(
                instance_data, self
            )
            self._add_instance_to_context(created_instance)

    def update_instances(self, update_list: list[dict]) -> None:
        """Update instances in the host application."""
        host: EqualizerHost = self.host

        current_instances = host.get_publish_instances()
        cur_instances_by_id = {}
        for instance_data in current_instances:
            # sourcery skip: use-named-expression
            instance_id = instance_data.get("instance_id")
            if instance_id:
                cur_instances_by_id[instance_id] = instance_data

        for instance, changes in update_list:
            instance_data = changes.new_value
            cur_instance_data = cur_instances_by_id.get(instance.id)
            if cur_instance_data is None:
                current_instances.append(instance_data)
                continue
            for key in set(cur_instance_data) - set(instance_data):
                cur_instance_data.pop(key)
            cur_instance_data.update(instance_data)
        host.write_create_instances(current_instances)

    def remove_instances(self, instances: list[CreatedInstance]) -> None:
        """Remove instances from the host application."""
        host: EqualizerHost = self.host
        for instance in instances:
            self._remove_instance_from_context(instance)
            host.remove_create_instance(instance.id)

collect_instances()

Collect instances from the host application.

Returns:

Type Description
None

list[openpype.pipeline.CreatedInstance]: List of instances.

Source code in client/ayon_equalizer/api/plugin.py
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
def collect_instances(self) -> None:
    """Collect instances from the host application.

    Returns:
        list[openpype.pipeline.CreatedInstance]: List of instances.

    """
    host: EqualizerHost = self.host
    for instance_data in host.get_publish_instances():
        if instance_data["creator_identifier"] != self.identifier:
            continue
        created_instance = CreatedInstance.from_existing(
            instance_data, self
        )
        self._add_instance_to_context(created_instance)

create(product_name, instance_data, pre_create_data)

Create a subset in the host application.

Parameters:

Name Type Description Default
product_name str

Name of the subset to create.

required
instance_data dict

Data of the instance to create.

required
pre_create_data dict

Data from the pre-create step.

required

Returns:

Type Description
CreatedInstance

ayon_core.pipeline.CreatedInstance: Created instance.

Source code in client/ayon_equalizer/api/plugin.py
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
61
def create(
    self,
    product_name: str,
    instance_data: dict,
    pre_create_data: dict,
) -> CreatedInstance:
    """Create a subset in the host application.

    Args:
        product_name (str): Name of the subset to create.
        instance_data (dict): Data of the instance to create.
        pre_create_data (dict): Data from the pre-create step.

    Returns:
        ayon_core.pipeline.CreatedInstance: Created instance.

    """
    self.log.debug("EqualizerCreator.create")
    product_type = instance_data.get("productType")
    if not product_type:
        product_type = self.product_base_type
    instance = CreatedInstance(
        product_base_type=self.product_base_type,
        product_type=product_type,
        product_name=product_name,
        data=instance_data,
        creator=self,
    )
    self._add_instance_to_context(instance)

    host: EqualizerHost = self.host
    host.add_publish_instance(instance.data_to_store())

    return instance

remove_instances(instances)

Remove instances from the host application.

Source code in client/ayon_equalizer/api/plugin.py
102
103
104
105
106
107
def remove_instances(self, instances: list[CreatedInstance]) -> None:
    """Remove instances from the host application."""
    host: EqualizerHost = self.host
    for instance in instances:
        self._remove_instance_from_context(instance)
        host.remove_create_instance(instance.id)

update_instances(update_list)

Update instances in the host application.

Source code in client/ayon_equalizer/api/plugin.py
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
def update_instances(self, update_list: list[dict]) -> None:
    """Update instances in the host application."""
    host: EqualizerHost = self.host

    current_instances = host.get_publish_instances()
    cur_instances_by_id = {}
    for instance_data in current_instances:
        # sourcery skip: use-named-expression
        instance_id = instance_data.get("instance_id")
        if instance_id:
            cur_instances_by_id[instance_id] = instance_data

    for instance, changes in update_list:
        instance_data = changes.new_value
        cur_instance_data = cur_instances_by_id.get(instance.id)
        if cur_instance_data is None:
            current_instances.append(instance_data)
            continue
        for key in set(cur_instance_data) - set(instance_data):
            cur_instance_data.pop(key)
        cur_instance_data.update(instance_data)
    host.write_create_instances(current_instances)

EqualizerHost

Bases: HostBase, IWorkfileHost, ILoadHost, IPublishHost

3DEqualizer host implementation.

Source code in client/ayon_equalizer/api/host.py
 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
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
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
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
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
class EqualizerHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost):
    """3DEqualizer host implementation."""

    name = "equalizer"
    _instance = None

    def __new__(cls):
        """Singleton implementation."""
        # singleton - ensure only one instance of the host is created.
        # This is necessary because 3DEqualizer doesn't have a way to
        # store custom data, so we need to store it in the project notes.
        if not hasattr(cls, "_instance") or not cls._instance:
            cls._instance = super().__new__(cls)
        return cls._instance

    def __init__(self):
        """Initialize the host."""
        self._qapp = None
        super().__init__()

    def workfile_has_unsaved_changes(self) -> bool:
        """Return the state of the current workfile.

        3DEqualizer returns state as 1 or zero, so we need to invert it.

        Returns:
            bool: True if the current workfile has unsaved changes.

        """
        return not bool(tde4.isProjectUpToDate())

    def get_workfile_extensions(self) -> list[str]:
        """Return the workfile extensions for 3DEqualizer."""
        return [".3de"]

    def save_workfile(self, dst_path: Optional[str]=None) -> str:
        """Save the current workfile.

        Arguments:
            dst_path (str): Destination path to save the workfile.

        """
        if not dst_path:
            dst_path = tde4.getProjectPath()
        result = tde4.saveProject(dst_path, True)  # noqa: FBT003
        if not bool(result):
            err_msg = f"Failed to save workfile {dst_path}."
            raise RuntimeError(err_msg)

        return dst_path

    def open_workfile(self, filepath: str) -> str:
        """Open a workfile in 3DEqualizer."""
        result = tde4.loadProject(filepath, True)  # noqa: FBT003
        if not bool(result):
            err_msg = f"Failed to open workfile {filepath}."
            raise RuntimeError(err_msg)

        return filepath

    def get_current_workfile(self) -> str:
        """Return the current workfile path."""
        return tde4.getProjectPath()

    def get_containers(self) -> Generator[Container, Any, Optional[list]]:
        """Get containers from the current workfile."""
        # sourcery skip: use-named-expression
        data = self.get_ayon_data() or {}
        for container in data.get(EQUALIZER_CONTAINERS_KEY, []):
            # convert dict to dataclass
            _container = Container(**container)
            # check if the container is valid
            if _container.name and _container.namespace:
                yield dataclasses.asdict(_container)

    def add_container(self, container: Container) -> None:
        """Add a container to the current workfile.

        Args:
            container (Container): Container to add.

        """
        data = self.get_ayon_data()

        containers = [
            _container for _container in self.get_containers()
            # Remove existing container with the same name and namespace to
            # avoid duplicates.
            if not (
                container.name == _container["name"]
                and container.namespace == _container["namespace"]
            )
        ]
        data[EQUALIZER_CONTAINERS_KEY] = [
            *containers, dataclasses.asdict(container)
        ]

        self.update_ayon_data(data)

    def _create_ayon_data(self) -> None:
        """Create AYON data in the current project."""
        tde4.setProjectNotes(
            f"{tde4.getProjectNotes()}\n"
            f"{AYON_METADATA_GUARD}\n")
        # this is really necessary otherwise the data is not saved
        tde4.updateGUI()

    def get_ayon_data(self) -> dict:
        """Get AYON context data from the current project.

        3Dequalizer doesn't have any custom node or other
        place to store metadata, so we store context data in
        the project notes encoded as JSON and wrapped in a
        special guard string `AYON_CONTEXT::...::AYON_CONTEXT_END`.

        Returns:
            dict: Context data.

        """
        # sourcery skip: use-named-expression
        m = re.search(AYON_METADATA_REGEX, tde4.getProjectNotes())
        if not m:
            self._create_ayon_data()
            return {}
        try:
            context = json.loads(m["context"]) if m else {}
        except ValueError:
            self.log.debug("AYON data is not valid json")
            # AYON data not found or invalid, create empty placeholder
            self._create_ayon_data()
            return {}

        return context

    def update_ayon_data(self, data: dict) -> None:
        """Update AYON context data in the current project.

        Serialize context data as json and store it in the
        project notes. If the context data is not found, create
        a placeholder there. See `get_context_data` for more info.

        Args:
            data (dict): Context data.

        """
        original_data = self.get_ayon_data()

        updated_data = original_data.copy()
        updated_data.update(data)
        update_str = json.dumps(
            updated_data or {}, indent=4, cls=AYONJSONEncoder)

        tde4.setProjectNotes(
            re.sub(
                AYON_METADATA_REGEX,
                AYON_METADATA_GUARD.format(update_str),
                tde4.getProjectNotes(),
            )
        )
        tde4.updateGUI()

    def get_context_data(self) -> dict:
        """Get context data from the current project."""
        data = self.get_ayon_data()

        return data.get(EQUALIZER_CONTEXT_KEY, {})

    def update_context_data(self, data: dict, changes: dict) -> None:
        """Update context data in the current project.

        Args:
            data (dict): Context data.
            changes (dict): Changes to the context data.

        Raises:
            RuntimeError: If the context data is not found.

        """
        if not data:
            return
        ayon_data = self.get_ayon_data()
        ayon_data[EQUALIZER_CONTEXT_KEY] = data
        self.update_ayon_data(ayon_data)


    def get_publish_instances(self) -> list[dict]:
        """Get publish instances from the current project."""
        data = self.get_ayon_data()
        return data.get(EQUALIZER_INSTANCES_KEY, [])

    def add_publish_instance(self, instance_data: dict) -> None:
        """Add a publish instance to the current project.

        Args:
            instance_data (dict): Publish instance to add.

        """
        data = self.get_ayon_data()
        publish_instances = self.get_publish_instances()
        publish_instances.append(instance_data)
        data[EQUALIZER_INSTANCES_KEY] = publish_instances

        self.update_ayon_data(data)

    def update_publish_instance(
            self,
            instance_id: str,
            data: dict,
    ) -> None:
        """Update a publish instance in the current project.

        Args:
            instance_id (str): Publish instance id to update.
            data (dict): Data to update.

        """
        ayon_data = self.get_ayon_data()
        publish_instances = self.get_publish_instances()
        for idx, publish_instance in enumerate(publish_instances):
            if publish_instance["instance_id"] == instance_id:
                publish_instances[idx] = data
                break
        ayon_data[EQUALIZER_INSTANCES_KEY] = publish_instances

        self.update_ayon_data(ayon_data)

    def write_create_instances(
            self, instances: list[dict]) -> None:
        """Write publish instances to the current project."""
        ayon_data = self.get_ayon_data()
        ayon_data[EQUALIZER_INSTANCES_KEY] = instances
        self.update_ayon_data(ayon_data)

    def remove_create_instance(self, instance_id: str) -> None:
        """Remove a publish instance from the current project.

        Args:
            instance_id (str): Publish instance id to remove.

        """
        data = self.get_ayon_data()
        publish_instances = self.get_publish_instances()
        publish_instances = [
            publish_instance
            for publish_instance in publish_instances
            if publish_instance["instance_id"] != instance_id
        ]
        data[EQUALIZER_INSTANCES_KEY] = publish_instances

        self.update_ayon_data(data)


    def install(self) -> None:
        """Install the host."""
        if not QtCore.QCoreApplication.instance():
            app = QtWidgets.QApplication([])
            self._qapp = app
            self._qapp.setQuitOnLastWindowClosed(False)

        pyblish.api.register_host("equalizer")

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

        try:
            heartbeat_interval = int(
                os.getenv("AYON_TDE4_HEARTBEAT_INTERVAL")) or 100
        except (ValueError, TypeError):
            self.log.warning(
                "AYON_TDE4_HEARTBEAT_INTERVAL is not a valid integer")
            heartbeat_interval = 100

        tde4.setTimerCallbackFunction(
            "EqualizerHost._timer", heartbeat_interval)

    @staticmethod
    def _timer() -> None:
        """Timer callback function."""
        QtWidgets.QApplication.instance().processEvents(
            QtCore.QEventLoop.AllEvents)

    @classmethod
    def get_host(cls) -> EqualizerHost:
        """Get the host instance."""
        return cls._instance

    def get_main_window(self) -> QtWidgets.QWidget:
        """Get the main window of the host application."""
        return self._qapp.activeWindow()

__init__()

Initialize the host.

Source code in client/ayon_equalizer/api/host.py
77
78
79
80
def __init__(self):
    """Initialize the host."""
    self._qapp = None
    super().__init__()

__new__()

Singleton implementation.

Source code in client/ayon_equalizer/api/host.py
68
69
70
71
72
73
74
75
def __new__(cls):
    """Singleton implementation."""
    # singleton - ensure only one instance of the host is created.
    # This is necessary because 3DEqualizer doesn't have a way to
    # store custom data, so we need to store it in the project notes.
    if not hasattr(cls, "_instance") or not cls._instance:
        cls._instance = super().__new__(cls)
    return cls._instance

add_container(container)

Add a container to the current workfile.

Parameters:

Name Type Description Default
container Container

Container to add.

required
Source code in client/ayon_equalizer/api/host.py
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
def add_container(self, container: Container) -> None:
    """Add a container to the current workfile.

    Args:
        container (Container): Container to add.

    """
    data = self.get_ayon_data()

    containers = [
        _container for _container in self.get_containers()
        # Remove existing container with the same name and namespace to
        # avoid duplicates.
        if not (
            container.name == _container["name"]
            and container.namespace == _container["namespace"]
        )
    ]
    data[EQUALIZER_CONTAINERS_KEY] = [
        *containers, dataclasses.asdict(container)
    ]

    self.update_ayon_data(data)

add_publish_instance(instance_data)

Add a publish instance to the current project.

Parameters:

Name Type Description Default
instance_data dict

Publish instance to add.

required
Source code in client/ayon_equalizer/api/host.py
252
253
254
255
256
257
258
259
260
261
262
263
264
def add_publish_instance(self, instance_data: dict) -> None:
    """Add a publish instance to the current project.

    Args:
        instance_data (dict): Publish instance to add.

    """
    data = self.get_ayon_data()
    publish_instances = self.get_publish_instances()
    publish_instances.append(instance_data)
    data[EQUALIZER_INSTANCES_KEY] = publish_instances

    self.update_ayon_data(data)

get_ayon_data()

Get AYON context data from the current project.

3Dequalizer doesn't have any custom node or other place to store metadata, so we store context data in the project notes encoded as JSON and wrapped in a special guard string AYON_CONTEXT::...::AYON_CONTEXT_END.

Returns:

Name Type Description
dict dict

Context data.

Source code in client/ayon_equalizer/api/host.py
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
def get_ayon_data(self) -> dict:
    """Get AYON context data from the current project.

    3Dequalizer doesn't have any custom node or other
    place to store metadata, so we store context data in
    the project notes encoded as JSON and wrapped in a
    special guard string `AYON_CONTEXT::...::AYON_CONTEXT_END`.

    Returns:
        dict: Context data.

    """
    # sourcery skip: use-named-expression
    m = re.search(AYON_METADATA_REGEX, tde4.getProjectNotes())
    if not m:
        self._create_ayon_data()
        return {}
    try:
        context = json.loads(m["context"]) if m else {}
    except ValueError:
        self.log.debug("AYON data is not valid json")
        # AYON data not found or invalid, create empty placeholder
        self._create_ayon_data()
        return {}

    return context

get_containers()

Get containers from the current workfile.

Source code in client/ayon_equalizer/api/host.py
126
127
128
129
130
131
132
133
134
135
def get_containers(self) -> Generator[Container, Any, Optional[list]]:
    """Get containers from the current workfile."""
    # sourcery skip: use-named-expression
    data = self.get_ayon_data() or {}
    for container in data.get(EQUALIZER_CONTAINERS_KEY, []):
        # convert dict to dataclass
        _container = Container(**container)
        # check if the container is valid
        if _container.name and _container.namespace:
            yield dataclasses.asdict(_container)

get_context_data()

Get context data from the current project.

Source code in client/ayon_equalizer/api/host.py
223
224
225
226
227
def get_context_data(self) -> dict:
    """Get context data from the current project."""
    data = self.get_ayon_data()

    return data.get(EQUALIZER_CONTEXT_KEY, {})

get_current_workfile()

Return the current workfile path.

Source code in client/ayon_equalizer/api/host.py
122
123
124
def get_current_workfile(self) -> str:
    """Return the current workfile path."""
    return tde4.getProjectPath()

get_host() classmethod

Get the host instance.

Source code in client/ayon_equalizer/api/host.py
344
345
346
347
@classmethod
def get_host(cls) -> EqualizerHost:
    """Get the host instance."""
    return cls._instance

get_main_window()

Get the main window of the host application.

Source code in client/ayon_equalizer/api/host.py
349
350
351
def get_main_window(self) -> QtWidgets.QWidget:
    """Get the main window of the host application."""
    return self._qapp.activeWindow()

get_publish_instances()

Get publish instances from the current project.

Source code in client/ayon_equalizer/api/host.py
247
248
249
250
def get_publish_instances(self) -> list[dict]:
    """Get publish instances from the current project."""
    data = self.get_ayon_data()
    return data.get(EQUALIZER_INSTANCES_KEY, [])

get_workfile_extensions()

Return the workfile extensions for 3DEqualizer.

Source code in client/ayon_equalizer/api/host.py
93
94
95
def get_workfile_extensions(self) -> list[str]:
    """Return the workfile extensions for 3DEqualizer."""
    return [".3de"]

install()

Install the host.

Source code in client/ayon_equalizer/api/host.py
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
def install(self) -> None:
    """Install the host."""
    if not QtCore.QCoreApplication.instance():
        app = QtWidgets.QApplication([])
        self._qapp = app
        self._qapp.setQuitOnLastWindowClosed(False)

    pyblish.api.register_host("equalizer")

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

    try:
        heartbeat_interval = int(
            os.getenv("AYON_TDE4_HEARTBEAT_INTERVAL")) or 100
    except (ValueError, TypeError):
        self.log.warning(
            "AYON_TDE4_HEARTBEAT_INTERVAL is not a valid integer")
        heartbeat_interval = 100

    tde4.setTimerCallbackFunction(
        "EqualizerHost._timer", heartbeat_interval)

open_workfile(filepath)

Open a workfile in 3DEqualizer.

Source code in client/ayon_equalizer/api/host.py
113
114
115
116
117
118
119
120
def open_workfile(self, filepath: str) -> str:
    """Open a workfile in 3DEqualizer."""
    result = tde4.loadProject(filepath, True)  # noqa: FBT003
    if not bool(result):
        err_msg = f"Failed to open workfile {filepath}."
        raise RuntimeError(err_msg)

    return filepath

remove_create_instance(instance_id)

Remove a publish instance from the current project.

Parameters:

Name Type Description Default
instance_id str

Publish instance id to remove.

required
Source code in client/ayon_equalizer/api/host.py
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
def remove_create_instance(self, instance_id: str) -> None:
    """Remove a publish instance from the current project.

    Args:
        instance_id (str): Publish instance id to remove.

    """
    data = self.get_ayon_data()
    publish_instances = self.get_publish_instances()
    publish_instances = [
        publish_instance
        for publish_instance in publish_instances
        if publish_instance["instance_id"] != instance_id
    ]
    data[EQUALIZER_INSTANCES_KEY] = publish_instances

    self.update_ayon_data(data)

save_workfile(dst_path=None)

Save the current workfile.

Parameters:

Name Type Description Default
dst_path str

Destination path to save the workfile.

None
Source code in client/ayon_equalizer/api/host.py
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
def save_workfile(self, dst_path: Optional[str]=None) -> str:
    """Save the current workfile.

    Arguments:
        dst_path (str): Destination path to save the workfile.

    """
    if not dst_path:
        dst_path = tde4.getProjectPath()
    result = tde4.saveProject(dst_path, True)  # noqa: FBT003
    if not bool(result):
        err_msg = f"Failed to save workfile {dst_path}."
        raise RuntimeError(err_msg)

    return dst_path

update_ayon_data(data)

Update AYON context data in the current project.

Serialize context data as json and store it in the project notes. If the context data is not found, create a placeholder there. See get_context_data for more info.

Parameters:

Name Type Description Default
data dict

Context data.

required
Source code in client/ayon_equalizer/api/host.py
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
def update_ayon_data(self, data: dict) -> None:
    """Update AYON context data in the current project.

    Serialize context data as json and store it in the
    project notes. If the context data is not found, create
    a placeholder there. See `get_context_data` for more info.

    Args:
        data (dict): Context data.

    """
    original_data = self.get_ayon_data()

    updated_data = original_data.copy()
    updated_data.update(data)
    update_str = json.dumps(
        updated_data or {}, indent=4, cls=AYONJSONEncoder)

    tde4.setProjectNotes(
        re.sub(
            AYON_METADATA_REGEX,
            AYON_METADATA_GUARD.format(update_str),
            tde4.getProjectNotes(),
        )
    )
    tde4.updateGUI()

update_context_data(data, changes)

Update context data in the current project.

Parameters:

Name Type Description Default
data dict

Context data.

required
changes dict

Changes to the context data.

required

Raises:

Type Description
RuntimeError

If the context data is not found.

Source code in client/ayon_equalizer/api/host.py
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
def update_context_data(self, data: dict, changes: dict) -> None:
    """Update context data in the current project.

    Args:
        data (dict): Context data.
        changes (dict): Changes to the context data.

    Raises:
        RuntimeError: If the context data is not found.

    """
    if not data:
        return
    ayon_data = self.get_ayon_data()
    ayon_data[EQUALIZER_CONTEXT_KEY] = data
    self.update_ayon_data(ayon_data)

update_publish_instance(instance_id, data)

Update a publish instance in the current project.

Parameters:

Name Type Description Default
instance_id str

Publish instance id to update.

required
data dict

Data to update.

required
Source code in client/ayon_equalizer/api/host.py
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
def update_publish_instance(
        self,
        instance_id: str,
        data: dict,
) -> None:
    """Update a publish instance in the current project.

    Args:
        instance_id (str): Publish instance id to update.
        data (dict): Data to update.

    """
    ayon_data = self.get_ayon_data()
    publish_instances = self.get_publish_instances()
    for idx, publish_instance in enumerate(publish_instances):
        if publish_instance["instance_id"] == instance_id:
            publish_instances[idx] = data
            break
    ayon_data[EQUALIZER_INSTANCES_KEY] = publish_instances

    self.update_ayon_data(ayon_data)

workfile_has_unsaved_changes()

Return the state of the current workfile.

3DEqualizer returns state as 1 or zero, so we need to invert it.

Returns:

Name Type Description
bool bool

True if the current workfile has unsaved changes.

Source code in client/ayon_equalizer/api/host.py
82
83
84
85
86
87
88
89
90
91
def workfile_has_unsaved_changes(self) -> bool:
    """Return the state of the current workfile.

    3DEqualizer returns state as 1 or zero, so we need to invert it.

    Returns:
        bool: True if the current workfile has unsaved changes.

    """
    return not bool(tde4.isProjectUpToDate())

write_create_instances(instances)

Write publish instances to the current project.

Source code in client/ayon_equalizer/api/host.py
288
289
290
291
292
293
def write_create_instances(
        self, instances: list[dict]) -> None:
    """Write publish instances to the current project."""
    ayon_data = self.get_ayon_data()
    ayon_data[EQUALIZER_INSTANCES_KEY] = instances
    self.update_ayon_data(ayon_data)

ExtractScriptBase

Bases: OptionalPyblishPluginMixin

Base class for extract script plugins.

Source code in client/ayon_equalizer/api/plugin.py
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
168
169
170
171
172
class ExtractScriptBase(OptionalPyblishPluginMixin):
    """Base class for extract script plugins."""

    hide_reference_frame = False
    export_uv_textures = False
    overscan_percent_width = 100
    overscan_percent_height = 100
    units = "mm"

    @classmethod
    def apply_settings(
            cls, project_settings: dict,
            system_settings: dict) -> None:  # noqa: ARG003
        """Apply settings from the configuration."""
        settings = project_settings["equalizer"]["publish"][
            "ExtractMatchmoveScriptMaya"]

        cls.hide_reference_frame = settings.get(
            "hide_reference_frame", cls.hide_reference_frame)
        cls.export_uv_textures = settings.get(
            "export_uv_textures", cls.export_uv_textures)
        cls.overscan_percent_width = settings.get(
            "overscan_percent_width", cls.overscan_percent_width)
        cls.overscan_percent_height = settings.get(
            "overscan_percent_height", cls.overscan_percent_height)
        cls.units = settings.get("units", cls.units)

    @classmethod
    def get_attribute_defs(cls) -> list:
        """Get attribute definitions for the plugin."""
        defs = super().get_attribute_defs()

        defs.extend([
            BoolDef("hide_reference_frame",
                    label="Hide Reference Frame",
                    default=cls.hide_reference_frame),
            BoolDef("export_uv_textures",
                    label="Export UV Textures",
                    default=cls.export_uv_textures),
            NumberDef("overscan_percent_width",
                      label="Overscan Width %",
                      default=cls.overscan_percent_width,
                      decimals=0,
                      minimum=1,
                      maximum=1000),
            NumberDef("overscan_percent_height",
                      label="Overscan Height %",
                      default=cls.overscan_percent_height,
                      decimals=0,
                      minimum=1,
                      maximum=1000),
            EnumDef("units",
                    ["mm", "cm", "m", "in", "ft", "yd"],
                    default=cls.units,
                    label="Units"),
            BoolDef("point_sets",
                    label="Export Point Sets",
                    default=True),
            BoolDef("export_2p5d",
                    label="Export 2.5D Points",
                    default=True),
        ])
        return defs

apply_settings(project_settings, system_settings) classmethod

Apply settings from the configuration.

Source code in client/ayon_equalizer/api/plugin.py
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
@classmethod
def apply_settings(
        cls, project_settings: dict,
        system_settings: dict) -> None:  # noqa: ARG003
    """Apply settings from the configuration."""
    settings = project_settings["equalizer"]["publish"][
        "ExtractMatchmoveScriptMaya"]

    cls.hide_reference_frame = settings.get(
        "hide_reference_frame", cls.hide_reference_frame)
    cls.export_uv_textures = settings.get(
        "export_uv_textures", cls.export_uv_textures)
    cls.overscan_percent_width = settings.get(
        "overscan_percent_width", cls.overscan_percent_width)
    cls.overscan_percent_height = settings.get(
        "overscan_percent_height", cls.overscan_percent_height)
    cls.units = settings.get("units", cls.units)

get_attribute_defs() classmethod

Get attribute definitions for the plugin.

Source code in client/ayon_equalizer/api/plugin.py
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
168
169
170
171
172
@classmethod
def get_attribute_defs(cls) -> list:
    """Get attribute definitions for the plugin."""
    defs = super().get_attribute_defs()

    defs.extend([
        BoolDef("hide_reference_frame",
                label="Hide Reference Frame",
                default=cls.hide_reference_frame),
        BoolDef("export_uv_textures",
                label="Export UV Textures",
                default=cls.export_uv_textures),
        NumberDef("overscan_percent_width",
                  label="Overscan Width %",
                  default=cls.overscan_percent_width,
                  decimals=0,
                  minimum=1,
                  maximum=1000),
        NumberDef("overscan_percent_height",
                  label="Overscan Height %",
                  default=cls.overscan_percent_height,
                  decimals=0,
                  minimum=1,
                  maximum=1000),
        EnumDef("units",
                ["mm", "cm", "m", "in", "ft", "yd"],
                default=cls.units,
                label="Units"),
        BoolDef("point_sets",
                label="Export Point Sets",
                default=True),
        BoolDef("export_2p5d",
                label="Export 2.5D Points",
                default=True),
    ])
    return defs

maintained_model_selection()

Maintain model selection during context.

Source code in client/ayon_equalizer/api/pipeline.py
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
@contextlib.contextmanager
def maintained_model_selection() -> None:
    """Maintain model selection during context."""
    point_groups = tde4.getPGroupList()
    point_group = next(
        (
            pg for pg in point_groups
            if tde4.getPGroupType(pg) == "CAMERA"
        ), None,
    )
    selected_models = tde4.get3DModelList(point_group, 1) \
        if point_group else []
    try:
        yield
    finally:
        if point_group:
            # 3 restore model selection
            for model in tde4.get3DModelList(point_group, 0):
                if model in selected_models:
                    tde4.set3DModelSelectionFlag(point_group, model, 1)
                else:
                    tde4.set3DModelSelectionFlag(point_group, model, 0)