Skip to content

lib_renderproducts

AOV

Dataclass for AOVs

This should hold all the data to be able to define the resolved path.

Source code in client/ayon_cinema4d/api/lib_renderproducts.py
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
@attr.s
class AOV:
    """Dataclass for AOVs

    This should hold all the data to be able to define the resolved path.
    """
    item: Any = attr.ib()  # The Redshift AOV object
    enabled: bool = attr.ib()
    name: str = attr.ib()
    effective_name: str = attr.ib()
    aov_type: int = attr.ib()
    multipass_enabled: bool = attr.ib()    # Multi-pass Output enabled
    direct_enabled: bool = attr.ib()       # Direct Output enabled
    filepath: str = attr.ib()              # Direct Output path
    file_effective_path: str = attr.ib()   # Effective path for direct output

    @property
    def layer_name(self) -> str:
        return self.effective_name

    @property
    def layer_type_name(self) -> str:
        return self.name

LayerMetadata

Bases: object

Data class for Render Layer metadata.

Source code in client/ayon_cinema4d/api/lib_renderproducts.py
341
342
343
344
345
346
@attr.s
class LayerMetadata(object):
    """Data class for Render Layer metadata."""
    frameStart = attr.ib()
    frameEnd = attr.ib()
    products: list[RenderProduct] = attr.ib(factory=list)

RenderProduct

Bases: object

Getting Colorspace as Specific Render Product Parameter for submitting publish job.

Source code in client/ayon_cinema4d/api/lib_renderproducts.py
349
350
351
352
353
354
355
356
@attr.s
class RenderProduct(object):
    """
    Getting Colorspace as Specific Render Product Parameter for submitting
    publish job.
    """
    productName: str = attr.ib()   # AOV name or "" for Beauty
    colorspace: str = attr.ib()  # Render Colorspace

apply_name_format(path, name_format, file_format, frame=0)

Apply the C4D render data name format to the given filepath.

Reference

RDATA_NAMEFORMAT_0 = Name0000.TIF RDATA_NAMEFORMAT_1 = Name0000 RDATA_NAMEFORMAT_2 = Name.0000 RDATA_NAMEFORMAT_3 = Name000.TIF RDATA_NAMEFORMAT_4 = Name000 RDATA_NAMEFORMAT_5 = Name.000 RDATA_NAMEFORMAT_6 = Name.0000.TIF

Parameters:

Name Type Description Default
path str
required
name_format int
required
file_format int
required
frame int
0

Returns:

Source code in client/ayon_cinema4d/api/lib_renderproducts.py
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
def apply_name_format(
    path: str,
    name_format: int,
    file_format: int,
    frame: int = 0
) -> str:
    """Apply the C4D render data name format to the given filepath.

    Reference:
        RDATA_NAMEFORMAT_0 = Name0000.TIF
        RDATA_NAMEFORMAT_1 = Name0000
        RDATA_NAMEFORMAT_2 = Name.0000
        RDATA_NAMEFORMAT_3 = Name000.TIF
        RDATA_NAMEFORMAT_4 = Name000
        RDATA_NAMEFORMAT_5 = Name.000
        RDATA_NAMEFORMAT_6 = Name.0000.TIF

    Args:
        path:
        name_format:
        file_format:
        frame:

    Returns:

    """
    head, _ = os.path.splitext(path)
    try:
        padding: int = {
            c4d.RDATA_NAMEFORMAT_0: 4,
            c4d.RDATA_NAMEFORMAT_1: 4,
            c4d.RDATA_NAMEFORMAT_2: 4,
            c4d.RDATA_NAMEFORMAT_3: 3,
            c4d.RDATA_NAMEFORMAT_4: 3,
            c4d.RDATA_NAMEFORMAT_5: 3,
            c4d.RDATA_NAMEFORMAT_6: 4,
        }[name_format]
    except KeyError as exc:
        raise ValueError(f"Unsupported name format: {name_format}") from exc
    frame_str = str(frame).zfill(padding)

    # Prefix frame number with a dot for specific name formats
    if name_format in {
        c4d.RDATA_NAMEFORMAT_2,
        c4d.RDATA_NAMEFORMAT_5,
        c4d.RDATA_NAMEFORMAT_6,
    }:
        frame_str = "." + frame_str
    # Whenever the frame number directly follows the name and the name ends
    # with a digit then C4D adds an underscore before the frame number.
    elif head and head[-1].isdigit():
        frame_str = "_" + frame_str

    # Add file format extension if name format includes it
    if name_format in {
        c4d.RDATA_NAMEFORMAT_0,
        c4d.RDATA_NAMEFORMAT_3,
        c4d.RDATA_NAMEFORMAT_6,
    }:
        extension: str = get_renderdata_file_format_extension(file_format)
    else:
        # No extension
        extension: str = ""

    return f"{head}{frame_str}{extension}"

find_video_post(render_data, plugin_id)

Find first video post with plugin_id in render data.

Source code in client/ayon_cinema4d/api/lib_renderproducts.py
21
22
23
24
25
26
27
28
29
30
def find_video_post(
        render_data: c4d.documents.RenderData, plugin_id: int
) -> Optional[c4d.documents.BaseVideoPost]:
    """Find first video post with plugin_id in render data."""
    vp = render_data.GetFirstVideoPost()
    while vp is not None:
        if vp.IsInstanceOf(plugin_id):
            return vp
        vp = vp.GetNext()
    return None

get_default_ocio_resource()

Return default OCIO config path for Cinema4D.

Source code in client/ayon_cinema4d/api/lib_renderproducts.py
374
375
376
377
def get_default_ocio_resource() -> str:
    """Return default OCIO config path for Cinema4D."""
    resources = c4d.storage.GeGetC4DPath(c4d.C4D_PATH_RESOURCE)
    return os.path.join(resources, "ocio", "config.ocio")

get_multipasses(render_data)

Return all multipasses in render data.

Source code in client/ayon_cinema4d/api/lib_renderproducts.py
45
46
47
48
49
50
51
52
53
54
def get_multipasses(
        render_data: c4d.documents.RenderData,
) -> list[c4d.BaseList2D]:
    """Return all multipasses in render data."""
    multipasses = []
    multipass = render_data.GetFirstMultipass()
    while multipass is not None:
        multipasses.append(multipass)
        multipass = multipass.GetNext()
    return multipasses

get_renderdata_file_format_extension(file_format)

Get the file extension for a given render data file format.

The file format is e.g. render data like: - c4d.RDATA_FORMAT - c4d.RDATA_MULTIPASS_SAVEFORMAT

Parameters:

Name Type Description Default
file_format int

The C4D render data file format constant.

required

Returns:

Name Type Description
str str

A file extension.

Source code in client/ayon_cinema4d/api/lib_renderproducts.py
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
def get_renderdata_file_format_extension(file_format: int) -> str:
    """Get the file extension for a given render data file format.

    The file format is e.g. render data like:
        - c4d.RDATA_FORMAT
        - c4d.RDATA_MULTIPASS_SAVEFORMAT

    Args:
        file_format: The C4D render data file format constant.

    Returns:
        str: A file extension.
    """
    try:
        return {
            c4d.FILTER_AVI: ".avi",
            c4d.FILTER_B3D: ".b3d",
            c4d.FILTER_B3DNET: ".b3d",
            c4d.FILTER_BMP: ".bmp",
            c4d.FILTER_DDS: ".dds",
            c4d.FILTER_DPX: ".dpx",
            c4d.FILTER_EXR: ".exr",
            c4d.FILTER_HDR: ".hdr",
            c4d.FILTER_IES: ".ies",
            c4d.FILTER_IFF: ".iff",
            c4d.FILTER_JPG: ".jpg",
            c4d.FILTER_PICT: ".pict",
            c4d.FILTER_PNG: ".png",
            c4d.FILTER_PSB: ".psb",
            c4d.FILTER_PSD: ".psd",
            c4d.FILTER_RLA: ".rla",
            c4d.FILTER_RPF: ".rpf",
            c4d.FILTER_TGA: ".tga",
            c4d.FILTER_TIF: ".tif",
            c4d.FILTER_TIF_B3D: ".tif",
        }[file_format]
    except KeyError as exc:
        raise ValueError(f"Unsupported file format: {file_format}") from exc

iter_redshift_aovs(video_post)

Using a Video Post from Redshift render yield all Redshift AOVs.

This may separate light-groups into separate AOVs.

Source code in client/ayon_cinema4d/api/lib_renderproducts.py
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
def iter_redshift_aovs(video_post: c4d.documents.BaseVideoPost) -> Generator[AOV, None, None]:
    """Using a Video Post from Redshift render yield all Redshift AOVs.

    This may separate light-groups into separate AOVs.
    """
    aovs = redshift.RendererGetAOVs(video_post)
    scene_light_groups = get_redshift_light_groups(video_post.GetDocument())

    for aov in aovs:
        # Redshift Cryptomatte is always separate
        global_aov = AOV(
            item=aov,
            name=aov.GetParameter(c4d.REDSHIFT_AOV_NAME),
            effective_name=aov.GetParameter(c4d.REDSHIFT_AOV_EFFECTIVE_NAME),
            aov_type=aov.GetParameter(c4d.REDSHIFT_AOV_TYPE),
            enabled=bool(aov.GetParameter(c4d.REDSHIFT_AOV_ENABLED)),
            multipass_enabled=bool(
                aov.GetParameter(c4d.REDSHIFT_AOV_MULTIPASS_ENABLED)),
            direct_enabled=bool(aov.GetParameter(c4d.REDSHIFT_AOV_FILE_ENABLED)),
            filepath=aov.GetParameter(c4d.REDSHIFT_AOV_FILE_PATH),
            file_effective_path=aov.GetParameter(
                c4d.REDSHIFT_AOV_FILE_EFFECTIVE_PATH)
        )

        if global_aov.effective_name == "Z":
            # Z AOV gets merged into main layer?
            continue

        # The list of returned light group names may contain 'unused' entries
        # that do not exist (anymore?) so we must filter the list against the
        # scene light groups.
        light_groups: list[str] = [
            lg.strip() for lg in
            aov.GetParameter(c4d.REDSHIFT_AOV_LIGHTGROUP_NAMES).split("\n")
        ]
        light_groups = [
            lg for lg in light_groups if lg and lg in scene_light_groups
        ]
        all_light_groups: bool = aov.GetParameter(c4d.REDSHIFT_AOV_LIGHTGROUP_ALL)
        if all_light_groups:
            light_groups = list(scene_light_groups)

        light_group_mode: int = aov.GetParameter(c4d.REDSHIFT_AOV_LIGHTGROUP_GLOBALAOV)  # noqa

        # Global AOV (Main output)
        if not light_groups or light_group_mode == c4d.REDSHIFT_AOV_LIGHTGROUP_GLOBALAOV_ALL:
            yield global_aov

        # Global Remainder AOV
        if light_groups and light_group_mode == c4d.REDSHIFT_AOV_LIGHTGROUP_GLOBALAOV_REMAINDER:
            remainder_aov = copy.copy(global_aov)
            # Only specify name if already set
            if remainder_aov.name:
                remainder_aov.name += "_other"
            remainder_aov.effective_name += "_other"
            yield remainder_aov

        # AOV output per light group
        for light_group in light_groups:
            light_aov = copy.copy(global_aov)
            if light_aov.name:
                light_aov.name += f"_{light_group}"
            light_aov.effective_name += f"_{light_group}"
            yield light_aov

resolve_filepath(token_path, doc=None, render_data=None, render_settings=None, frame=None, take=None, layer_name=None, layer_type_name=None, layer_type=None)

Resolve a path with tokens to a resolved path.

See: https://developers.maxon.net/docs/py/2024_4_0a/modules/c4d.modules/tokensystem/index.html # noqa

Constructs the rpData (RenderPathData) dictionary: _doc: BaseDocument -> $prj _rData: RenderData -> $res, $height, $rs, $renderer _rBc: BaseContainer _take: BaseTake -> $take _frame: int -> $frame _layerName: str -> $userpass _layerTypeName: str -> $pass _layerType: int _isLight: bool _lightNumber: int _isMaterial: bool _nodeName: str _checkUnresolved: bool

Source code in client/ayon_cinema4d/api/lib_renderproducts.py
 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
def resolve_filepath(
    token_path: str,
    doc: Optional[c4d.documents.BaseDocument] = None,
    render_data: Optional[c4d.documents.RenderData] = None,
    render_settings: Optional[c4d.BaseContainer] = None,
    frame: Optional[int] = None,
    take: Optional[c4d.modules.takesystem.BaseTake] = None,
    layer_name: Optional[str] = None,
    layer_type_name: Optional[str] = None,
    layer_type: Optional[int] = None,
) -> str:
    """Resolve a path with tokens to a resolved path.

    See: https://developers.maxon.net/docs/py/2024_4_0a/modules/c4d.modules/tokensystem/index.html  # noqa

    Constructs the `rpData (RenderPathData)` dictionary:
        _doc: BaseDocument     -> $prj
        _rData: RenderData     -> $res, $height, $rs, $renderer
        _rBc: BaseContainer
        _take: BaseTake        -> $take
        _frame: int            -> $frame
        _layerName: str        -> $userpass
        _layerTypeName: str    -> $pass
        _layerType: int
        _isLight: bool
        _lightNumber: int
        _isMaterial: bool
        _nodeName: str
        _checkUnresolved: bool
    """
    if doc is None:
        doc = c4d.documents.GetActiveDocument()
    if render_data is None:
        render_data = doc.GetActiveRenderData()
    if render_settings is None:
        render_settings = render_data.GetDataInstance()
    if frame is None:
        frame = doc.GetTime().GetFrame(doc.GetFps())
    if take is None:
        take = doc.GetTakeData().GetCurrentTake()

    rpd = {
        "_doc": doc,
        "_rData": render_data,
        "_rBc": render_settings,
        "_frame": frame,
    }
    optionals = {
        "_take": take,
        "_layerName": layer_name,
        "_layerTypeName": layer_type_name,
        "_layerType": layer_type,
    }
    for key, value in optionals.items():
        if value is not None:
            rpd[key] = value

    # When passing the token itself as the value for a token, e.g. $pass=$pass
    # it may hang Cinema4D. So, we swap those out to placeholders to replace
    # after resolving.
    placeholders = {}
    for key, value in rpd.items():
        if isinstance(value, str) and "$" in value:
            placeholder = f"____{key.upper()}__PLACEHOLDER____"
            placeholders[value] = placeholder
            rpd[key] = placeholder

    resolved = c4d.modules.tokensystem.StringConvertTokens(token_path, rpd)
    for value, placeholder in placeholders.items():
        resolved = resolved.replace(placeholder, value)
    return resolved