Skip to content

rendering

get_render_config(project_name, project_render_settings=None)

Returns Unreal asset from render config.

Expects configured location of render config set in Settings. This path must contain stored render config in Unreal project Args: project_name (str): project_settings (dict): Project render settings from get_project_settings Returns (str, uasset): path and UAsset Raises: RuntimeError if no path to config is set

Source code in client/ayon_unreal/api/rendering.py
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
def get_render_config(project_name, project_render_settings=None):
    """Returns Unreal asset from render config.

    Expects configured location of render config set in Settings. This path
    must contain stored render config in Unreal project
    Args:
        project_name (str):
        project_settings (dict): Project render settings from get_project_settings
    Returns
        (str, uasset): path and UAsset
    Raises:
        RuntimeError if no path to config is set
    """
    if not project_render_settings:
        project_settings = get_project_settings(project_name)
        project_render_settings = project_settings["unreal"]["unreal_setup"]

    ar = unreal.AssetRegistryHelpers.get_asset_registry()
    config_path = project_render_settings["render_config_path"]

    if not config_path:
        raise RuntimeError("Please provide location for stored render "
            "config in `ayon+settings://unreal/render_setup/render_config_path`")

    unreal.log(f"Configured config path {config_path}")
    if not unreal.EditorAssetLibrary.does_asset_exist(config_path):
        raise RuntimeError(f"No config found at {config_path}")

    unreal.log("Found saved render configuration")
    config = ar.get_asset_by_object_path(config_path).get_asset()

    return config_path, config

set_output_extension_from_settings(render_format, config)

Forces output extension from Settings if available.

Clear all other extensions if there is value in Settings. Args: render_format (str): "png"|"jpg"|"exr"|"bmp" config (unreal.MoviePipelineMasterConfig) Returns (unreal.MoviePipelineMasterConfig)

Source code in client/ayon_unreal/api/rendering.py
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
def set_output_extension_from_settings(render_format, config):
    """Forces output extension from Settings if available.

    Clear all other extensions if there is value in Settings.
    Args:
        render_format (str): "png"|"jpg"|"exr"|"bmp"
        config (unreal.MoviePipelineMasterConfig)
    Returns
        (unreal.MoviePipelineMasterConfig)
    """
    if not render_format:
        return config

    cls_from_map = SUPPORTED_EXTENSION_MAP.get(render_format.lower())
    if not cls_from_map:
        return config

    for ext, cls in SUPPORTED_EXTENSION_MAP.items():
        current_sett = config.find_setting_by_class(cls)
        if current_sett and ext == render_format:
            return config
        config.remove_setting(current_sett)

    config.find_or_add_setting_by_class(cls_from_map)
    return config

start_rendering()

Start the rendering process.

Source code in client/ayon_unreal/api/rendering.py
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
def start_rendering():
    """
    Start the rendering process.
    """
    unreal.log("Starting rendering...")

    # Get selected sequences
    assets = unreal.EditorUtilityLibrary.get_selected_assets()

    if not assets:
        show_message_dialog(
            title="No assets selected",
            message="No assets selected. Select a render instance.",
            level="warning")
        raise RuntimeError(
            "No assets selected. You need to select a render instance.")

    # instances = pipeline.ls_inst()
    instances = [
        a for a in assets
        if a.get_class().get_name() == "AyonPublishInstance"]
    if not instances:
        show_message_dialog(
            title="No AyonPublishInstance selected",
            message="No AyonPublishInstance selected. Select render instance data asset.",      # noqa
            level="warning"
        )
        raise RuntimeError(
            "No AyonPublishInstance selected. Select render instance data asset.")
    inst_data = []

    for i in instances:
        data = pipeline.parse_container(i.get_path_name())
        if data["productType"] == "render":
            inst_data.append(data)

    try:
        project_name = os.environ.get("AYON_PROJECT_NAME")
        anatomy = Anatomy(project_name)
        root = anatomy.roots['renders']
    except Exception as e:
        raise Exception(
            "Could not find render root in anatomy settings.") from e

    render_dir = f"{root}/{project_name}"

    # subsystem = unreal.get_editor_subsystem(
    #     unreal.MoviePipelineQueueSubsystem)
    # queue = subsystem.get_queue()
    global queue
    queue = unreal.MoviePipelineQueue()

    ar = unreal.AssetRegistryHelpers.get_asset_registry()

    project_settings = get_project_settings(project_name)
    render_settings = project_settings["unreal"]["render_setup"]
    _, config = get_render_config(project_name, render_settings)

    les = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem)
    current_level = les.get_current_level()
    current_level_name = current_level.get_outer().get_path_name()

    for i in inst_data:
        sequence = ar.get_asset_by_object_path(i["sequence"]).get_asset()

        sequences = [{
            "sequence": sequence,
            "output": f"{i['output']}",
            "frame_range": (
                int(float(i["frameStart"])),
                int(float(i["frameEnd"])) + 1)
        }]
        render_list = []

        # Get all the sequences to render. If there are subsequences,
        # add them and their frame ranges to the render list. We also
        # use the names for the output paths.
        for seq in sequences:
            subscenes = pipeline.get_subsequences(seq.get('sequence'))

            if subscenes:
                for sub_seq in subscenes:
                    sequences.append({
                        "sequence": sub_seq.get_sequence(),
                        "output": (f"{seq.get('output')}/"
                                   f"{sub_seq.get_sequence().get_name()}"),
                        "frame_range": (
                            sub_seq.get_start_frame(), sub_seq.get_end_frame())
                    })
            else:
                # Avoid rendering camera sequences
                if "_camera" not in seq.get('sequence').get_name():
                    render_list.append(seq)

        if i["master_level"] != current_level_name:
            unreal.log_warning(
                "{} is not the persistent level, use {} for rendering".format(
                i["master_level"], current_level_name)
            )
            i["master_level"] = current_level_name

        # Create the rendering jobs and add them to the queue.
        for render_setting in render_list:
            job = queue.allocate_new_job(unreal.MoviePipelineExecutorJob)
            job.sequence = unreal.SoftObjectPath(i["master_sequence"])
            job.map = unreal.SoftObjectPath(i["master_level"])
            job.author = "Ayon"

            # If we have a saved configuration, copy it to the job.
            if config:
                job.get_configuration().copy_from(config)

            job_config = job.get_configuration()
            # User data could be used to pass data to the job, that can be
            # read in the job's OnJobFinished callback. We could,
            # for instance, pass the AyonPublishInstance's path to the job.
            # job.user_data = ""

            output_dir = render_setting.get('output')
            shot_name = render_setting.get('sequence').get_name()

            settings = job_config.find_or_add_setting_by_class(
                unreal.MoviePipelineOutputSetting)
            settings.output_resolution = unreal.IntPoint(1920, 1080)
            settings.custom_start_frame = render_setting.get("frame_range")[0]
            settings.custom_end_frame = render_setting.get("frame_range")[1]
            settings.use_custom_playback_range = True
            settings.file_name_format = f"{shot_name}" + ".{frame_number}"
            settings.output_directory.path = f"{render_dir}/{output_dir}"

            job_config.find_or_add_setting_by_class(
                unreal.MoviePipelineDeferredPassBase)

            render_format = render_settings.get("render_format",
                                                "png")

            set_output_extension_from_settings(render_format,
                                               job_config)

    # If there are jobs in the queue, start the rendering process.
    if queue.get_jobs():
        global executor
        executor = unreal.MoviePipelinePIEExecutor()
        preroll_frames = render_settings.get("preroll_frames", 0)

        settings = unreal.MoviePipelinePIEExecutorSettings()
        settings.set_editor_property(
            "initial_delay_frame_count", preroll_frames)

        executor.on_executor_finished_delegate.add_callable_unique(
            _queue_finish_callback)
        executor.on_individual_job_finished_delegate.add_callable_unique(
            _job_finish_callback)  # Only available on PIE Executor
        executor.execute(queue)