Skip to content

Commit 247689e

Browse files
committed
Validate for relative render paths to avoid issues
1 parent d8ebf03 commit 247689e

File tree

1 file changed

+122
-0
lines changed

1 file changed

+122
-0
lines changed
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import os
2+
import inspect
3+
4+
import pyblish.api
5+
import c4d
6+
7+
from ayon_core.pipeline.publish import (
8+
OptionalPyblishPluginMixin,
9+
PublishValidationError,
10+
RepairAction,
11+
)
12+
13+
RENDER_DATA_KEYS: dict[int, str] = {
14+
c4d.RDATA_PATH: "Regular Image path",
15+
c4d.RDATA_MULTIPASS_FILENAME: "Multi-Pass Image path",
16+
}
17+
18+
19+
class ValidateRenderRelativePaths(
20+
pyblish.api.InstancePlugin, OptionalPyblishPluginMixin
21+
):
22+
"""Validates render scene does not use relative paths.
23+
24+
Because the scene will be rendered from the published folder if it renders
25+
to a relative path then the output will end up in the publish folder
26+
instead of the expected location.
27+
28+
"""
29+
label = "Validate Render Relative Paths"
30+
order = pyblish.api.ValidatorOrder
31+
families = ["render"]
32+
optional = False
33+
actions = [RepairAction]
34+
35+
settings_category = "cinema4d"
36+
37+
def process(self, instance: pyblish.api.Instance):
38+
if not self.is_active(instance.data):
39+
return
40+
41+
doc: c4d.documents.BaseDocument = instance.context.data["doc"]
42+
take_data = doc.GetTakeData()
43+
take: c4d.modules.takesystem.BaseTake = (
44+
instance.data["transientData"]["take"]
45+
)
46+
render_data, base_take = take.GetEffectiveRenderData(take_data)
47+
invalid: bool = False
48+
49+
# Regular image
50+
save_image: bool = render_data[c4d.RDATA_SAVEIMAGE]
51+
if save_image:
52+
token_path: str = render_data[c4d.RDATA_PATH]
53+
if not os.path.isabs(token_path):
54+
self.log.error(
55+
"Regular image Render output path is relative:\n"
56+
f"{token_path}"
57+
)
58+
invalid = True
59+
60+
# Multi-Pass image
61+
save_multipass_image: bool = render_data[c4d.RDATA_MULTIPASS_SAVEIMAGE]
62+
if save_multipass_image:
63+
token_path: str = render_data[c4d.RDATA_MULTIPASS_FILENAME]
64+
if not os.path.isabs(token_path):
65+
self.log.error(
66+
"Multi-Pass Image render output path is relative:\n"
67+
f"{token_path}",
68+
)
69+
invalid = True
70+
71+
if invalid:
72+
raise PublishValidationError(
73+
"Please use an absolute path for render output.",
74+
description=self.get_description()
75+
)
76+
77+
78+
@classmethod
79+
def repair(cls, instance: pyblish.api.Instance):
80+
81+
current_file = instance.context.data["currentFile"]
82+
if not current_file:
83+
raise RuntimeError(
84+
"Cannot repair relative paths because the current "
85+
"file path is unknown. Please save the Cinema 4D "
86+
"project and try again."
87+
)
88+
current_folder = os.path.dirname(current_file)
89+
90+
doc: c4d.documents.BaseDocument = instance.context.data["doc"]
91+
take_data = doc.GetTakeData()
92+
take: c4d.modules.takesystem.BaseTake = (
93+
instance.data["transientData"]["take"]
94+
)
95+
render_data, base_take = take.GetEffectiveRenderData(take_data)
96+
97+
for key, label in RENDER_DATA_KEYS.items():
98+
token_path: str = render_data[key]
99+
100+
# Strip leading dot from ./ or .\ start if present
101+
if token_path.startswith(("./", ".\\")):
102+
token_path = token_path[2:]
103+
104+
if not os.path.isabs(token_path):
105+
render_data[key] = os.path.join(current_folder, token_path)
106+
107+
c4d.EventAdd()
108+
109+
@classmethod
110+
def get_description(cls) -> str:
111+
return inspect.cleandoc(
112+
"""### Render paths are relative
113+
114+
The render output paths must be absolute paths.
115+
116+
Relative paths can lead to renders being saved in unexpected
117+
locations due to the render possibly occurring from a published
118+
workfile.
119+
120+
Use the 'Repair' action to convert relative paths to
121+
absolute paths based on the current Cinema4D project folder.
122+
""")

0 commit comments

Comments
 (0)