Skip to content

create_saver

CreateSaver

Bases: GenericCreateSaver

Fusion Saver to generate image sequence of 'render' product type.

Original Saver creator targeted for 'render' product type. It uses original not to descriptive name because of values in Settings.

Source code in client/ayon_fusion/plugins/create/create_saver.py
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 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
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
class CreateSaver(GenericCreateSaver):
    """Fusion Saver to generate image sequence of 'render' product type.

     Original Saver creator targeted for 'render' product type. It uses
     original not to descriptive name because of values in Settings.
    """
    identifier = "io.openpype.creators.fusion.saver"
    label = "Render (saver)"
    name = "render"
    product_base_type = "render"
    product_type = product_base_type
    description = "Fusion Saver to generate image sequence"

    default_frame_range_option = "current_context"

    def get_detail_description(self):
        return inspect.cleandoc(
            """Fusion Saver to generate image sequence.

            This creator is expected for publishing of image sequences for 
            'render' product type. (But can publish even single frame 
            'render'.)

            Select what should be source of render range:
            - "Current Folder context" - values set on folder on AYON server
            - "From render in/out" - from node itself
            - "From composition timeline" - from timeline

            Supports local and farm rendering.

            Supports selection from predefined set of output file extensions:
            - exr
            - tga
            - png
            - tif
            - jpg
            """
        )

    def register_callbacks(self):
        self.create_context.add_value_changed_callback(self.on_values_changed)

    def on_values_changed(self, event):
        """Update instance attribute definitions on attribute changes."""

        for instance_change in event["changes"]:
            # First check if there's a change we want to respond to
            instance = instance_change["instance"]
            if instance is None:
                # Change is on context
                continue

            if instance["creator_identifier"] != self.identifier:
                continue

            value_changes = instance_change["changes"]
            if (
                "frame_range_source"
                not in value_changes.get("creator_attributes", {})
            ):
                continue

            # Update the attribute definitions
            new_attrs = self.get_attr_defs_for_instance(instance)
            instance.set_create_attr_defs(new_attrs)

    def get_managed_tool_data(self, tool):
        data = super().get_managed_tool_data(tool)
        if not data:
            return data

        # Backwards compatibility: The 'current_context' frame range source
        # was stored as 'current_task' before. Convert it explicitly because
        # otherwise the unknown value would fall back to the default frame
        # range source of the settings, which may be a different source.
        creator_attributes = data.get("creator_attributes")
        if (
            isinstance(creator_attributes, dict)
            and creator_attributes.get("frame_range_source") == "current_task"
        ):
            creator_attributes["frame_range_source"] = "current_context"

        return data

    def get_pre_create_attr_defs(self):
        """Settings for create page"""
        attr_defs = [
            self._get_render_target_enum(),
            self._get_reviewable_bool(),
            self._get_frame_range_enum(),
            self._get_image_format_enum(),
            *self._get_custom_frame_range_attribute_defs()
        ]
        return attr_defs

    def get_attr_defs_for_instance(self, instance):
        return [
            self._get_render_target_enum(),
            self._get_reviewable_bool(),
            self._get_frame_range_enum(),
            self._get_image_format_enum(),
            *self._get_custom_frame_range_attribute_defs(instance)
        ]

    def _get_frame_range_enum(self):
        frame_range_options = {
            "current_context": "Current context",
            "render_range": "From render in/out",
            "comp_range": "From composition timeline",
            "custom_range": "Custom frame range",
        }

        return EnumDef(
            "frame_range_source",
            items=frame_range_options,
            label="Frame range source",
            default=self.default_frame_range_option
        )

    @staticmethod
    def _get_custom_frame_range_attribute_defs(instance=None) -> list:

        # If an instance is provided and 'custom_range' is not the frame
        # range source, then we will disable the custom frame range attributes
        custom_enabled = True
        if instance is not None:
            frame_range_source = instance.get(
                "creator_attributes", {}).get("frame_range_source")
            custom_enabled = frame_range_source == "custom_range"

        # Define custom frame range defaults based on current comp
        # timeline settings (if a comp is currently open)
        comp = get_current_comp()
        if comp is not None:
            attrs = comp.GetAttrs()
            global_start = attrs["COMPN_GlobalStart"]
            global_end = attrs["COMPN_GlobalEnd"]
            render_start = attrs["COMPN_RenderStart"]
            render_end = attrs["COMPN_RenderEnd"]

            # Whenever render ranges are undefined fall back
            # to the comp's global start and end
            if render_start == -1000000000:
                render_start = global_start
            if render_end == -1000000000:
                render_end = global_end

            # The render range is the frame range and the remainder up to
            # the global range are the handles, so that the range including
            # handles matches the comp's global range.
            frame_defaults = {
                "frameStart": int(render_start),
                "frameEnd": int(render_end),
                "handleStart": int(render_start - global_start),
                "handleEnd": int(global_end - render_end),
            }
        else:
            frame_defaults = {
                "frameStart": 1001,
                "frameEnd": 1100,
                "handleStart": 0,
                "handleEnd": 0
            }

        attr_defs = []
        if custom_enabled:
            # UILabelDef does not support `hidden` argument so we exclude it
            # manually
            attr_defs.append(
                UILabelDef(
                    label="<br><b>Custom Frame Range</b>",
                ),
            )

        attr_defs.extend([
            NumberDef(
                "custom_frameStart",
                label="Frame Start",
                default=frame_defaults["frameStart"],
                minimum=0,
                decimals=0,
                tooltip=(
                    "Set the start frame for the export.\n"
                    "Only used if frame range source is 'Custom frame range'."
                ),
                visible=custom_enabled
            ),
            NumberDef(
                "custom_frameEnd",
                label="Frame End",
                default=frame_defaults["frameEnd"],
                minimum=0,
                decimals=0,
                tooltip=(
                    "Set the end frame for the export.\n"
                    "Only used if frame range source is 'Custom frame range'."
                ),
                visible=custom_enabled
            ),
            NumberDef(
                "custom_handleStart",
                label="Handle Start",
                default=frame_defaults["handleStart"],
                minimum=0,
                decimals=0,
                tooltip=(
                    "Set the start handles for the export, this will be "
                    "added before the start frame.\n"
                    "Only used if frame range source is 'Custom frame range'."
                ),
                visible=custom_enabled
            ),
            NumberDef(
                "custom_handleEnd",
                label="Handle End",
                default=frame_defaults["handleEnd"],
                minimum=0,
                decimals=0,
                tooltip=(
                    "Set the end handles for the export, this will be added "
                    "after the end frame.\n"
                    "Only used if frame range source is 'Custom frame range'."
                ),
                visible=custom_enabled
            )
        ])
        return attr_defs

get_pre_create_attr_defs()

Settings for create page

Source code in client/ayon_fusion/plugins/create/create_saver.py
 97
 98
 99
100
101
102
103
104
105
106
def get_pre_create_attr_defs(self):
    """Settings for create page"""
    attr_defs = [
        self._get_render_target_enum(),
        self._get_reviewable_bool(),
        self._get_frame_range_enum(),
        self._get_image_format_enum(),
        *self._get_custom_frame_range_attribute_defs()
    ]
    return attr_defs

on_values_changed(event)

Update instance attribute definitions on attribute changes.

Source code in client/ayon_fusion/plugins/create/create_saver.py
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
def on_values_changed(self, event):
    """Update instance attribute definitions on attribute changes."""

    for instance_change in event["changes"]:
        # First check if there's a change we want to respond to
        instance = instance_change["instance"]
        if instance is None:
            # Change is on context
            continue

        if instance["creator_identifier"] != self.identifier:
            continue

        value_changes = instance_change["changes"]
        if (
            "frame_range_source"
            not in value_changes.get("creator_attributes", {})
        ):
            continue

        # Update the attribute definitions
        new_attrs = self.get_attr_defs_for_instance(instance)
        instance.set_create_attr_defs(new_attrs)