Skip to content

Commit 3406168

Browse files
committed
fix: Fix the color tone for tile rendering.
Signed-off-by: Karthik Bekal Pattathana <133984042+karthikbekalp@users.noreply.github.com>
1 parent 0364064 commit 3406168

50 files changed

Lines changed: 916 additions & 62 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/deadline/cinema4d_adaptor/Cinema4DClient/tile_rendering.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -228,13 +228,16 @@ def finalize_tile_render(
228228
render_data: The render data object (to restore output paths).
229229
frame: The current frame number.
230230
"""
231-
# Restore the OCIO bake flag to its original value
232-
if ctx.orig_bake_flag is not None:
233-
rd[c4d.RDATA_BAKE_OCIO_VIEW_TRANSFORM_RENDER] = ctx.orig_bake_flag
234-
235-
if ctx.requires_baking:
236-
baked = c4d.documents.BakeOcioViewToBitmap(bm, rd, c4d.SAVEBIT_NONE)
237-
bm = baked or bm
231+
try:
232+
if ctx.requires_baking:
233+
# setup_tile_render disabled the render-time bake. It must remain
234+
# disabled here or BakeOcioViewToBitmap assumes the bitmap was
235+
# already baked and returns None.
236+
baked = c4d.documents.BakeOcioViewToBitmap(bm, rd, c4d.SAVEBIT_NONE)
237+
bm = baked or bm
238+
finally:
239+
if ctx.orig_bake_flag is not None:
240+
rd[c4d.RDATA_BAKE_OCIO_VIEW_TRANSFORM_RENDER] = ctx.orig_bake_flag
238241

239242
if c4d.GetC4DVersion() >= C4D_VERSION_2025_2:
240243
bm.SetColorProfile(c4d.bitmaps.ColorProfile(), c4d.COLORPROFILE_INDEX_DISPLAYSPACE)

test/integ/fixtures/auto_open_submitter/AutoOpenSubmitter.pyp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ Where the submitter's AWS calls go depends on the test mode:
3232
3333
The test then drives the resulting Qt dialog with xa11y.
3434
"""
35+
3536
import os
3637
import traceback
3738

@@ -201,6 +202,11 @@ def _load_active_scene(scene_path):
201202
c4d.documents.InsertBaseDocument(doc)
202203
c4d.documents.SetActiveDocument(doc)
203204
c4d.EventAdd()
205+
if doc.GetChanged():
206+
# The fixture was just generated and saved. Older C4D versions
207+
# can mark Redshift node materials changed while loading them.
208+
doc[c4d.DOCUMENT_USERCHANGE] = False
209+
_diag("cleared generated scene changed state after load")
204210
_diag("scene loaded and set as active")
205211
else:
206212
_diag(f"LoadDocument returned None for {scene_path}")

test/integ/ocio_scene.py

Lines changed: 354 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,354 @@
1+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
"""Shared Physical OCIO studio scene for Cinema 4D integration tests."""
3+
4+
from __future__ import annotations
5+
6+
import math
7+
import os
8+
from typing import Any
9+
10+
import c4d
11+
12+
WIDTH = 384
13+
HEIGHT = 216
14+
ACES_DEFAULT_VIEW_TRANSFORM = 0
15+
ACES_UNTONE_MAPPED_VIEW_TRANSFORM = 3
16+
PHYSICAL_RENDERER_ID = 1023342
17+
18+
19+
def _configure_physical_renderer(render_data: Any) -> None:
20+
"""Use fixed sampling so repeated renders produce stable image baselines."""
21+
render_data[c4d.RDATA_RENDERENGINE] = PHYSICAL_RENDERER_ID
22+
23+
physical_settings = render_data.GetFirstVideoPost()
24+
while physical_settings is not None:
25+
if physical_settings.GetType() == PHYSICAL_RENDERER_ID:
26+
break
27+
physical_settings = physical_settings.GetNext()
28+
29+
if physical_settings is None:
30+
physical_settings = c4d.documents.BaseVideoPost(PHYSICAL_RENDERER_ID)
31+
if physical_settings is None:
32+
raise RuntimeError("Failed to create Physical renderer settings")
33+
render_data.InsertVideoPostLast(physical_settings)
34+
35+
physical_settings[c4d.VP_XMB_RAYTRACING_SAMPLER] = c4d.VP_XMB_RAYTRACING_SAMPLER_FIXED
36+
physical_settings[c4d.VP_XMB_RAYTRACING_QUALITY] = c4d.VP_XMB_RAYTRACING_QUALITY_CUSTOM
37+
physical_settings[c4d.VP_XMB_RAYTRACING_SAMPLES] = 4.0
38+
39+
40+
def _physical_material(
41+
doc: Any,
42+
name: str,
43+
color: tuple[float, float, float],
44+
*,
45+
roughness: float = 0.35,
46+
metalness: float = 0.0,
47+
emission: tuple[float, float, float] | None = None,
48+
) -> Any:
49+
material = c4d.BaseMaterial(c4d.Mmaterial)
50+
material.SetName(name)
51+
52+
material[c4d.MATERIAL_USE_COLOR] = emission is None
53+
material[c4d.MATERIAL_USE_LUMINANCE] = emission is not None
54+
material[c4d.MATERIAL_USE_REFLECTION] = False
55+
56+
if emission is not None:
57+
peak = max(emission)
58+
if peak <= 0.0:
59+
raise ValueError("emission must contain at least one positive channel")
60+
material[c4d.MATERIAL_LUMINANCE_COLOR] = c4d.Vector(
61+
*(channel / peak for channel in emission)
62+
)
63+
material[c4d.MATERIAL_LUMINANCE_BRIGHTNESS] = peak
64+
material[c4d.MATERIAL_USE_SPECULAR] = False
65+
else:
66+
material[c4d.MATERIAL_COLOR_COLOR] = c4d.Vector(*color)
67+
material[c4d.MATERIAL_COLOR_BRIGHTNESS] = 1.0
68+
material[c4d.MATERIAL_COLOR_MODEL] = c4d.MATERIAL_COLOR_MODEL_ORENNAYAR
69+
material[c4d.MATERIAL_COLOR_ROUGHNESS] = roughness
70+
material[c4d.MATERIAL_USE_SPECULAR] = True
71+
material[c4d.MATERIAL_SPECULAR_MODE] = (
72+
c4d.MATERIAL_SPECULAR_MODE_METAL
73+
if metalness >= 0.5
74+
else c4d.MATERIAL_SPECULAR_MODE_PLASTIC
75+
)
76+
material[c4d.MATERIAL_SPECULAR_COLOR] = (
77+
c4d.Vector(*color) if metalness >= 0.5 else c4d.Vector(1.0)
78+
)
79+
material[c4d.MATERIAL_SPECULAR_BRIGHTNESS] = 0.2 + (0.6 * metalness)
80+
material[c4d.MATERIAL_SPECULAR_WIDTH] = max(0.05, min(1.0, roughness))
81+
82+
doc.InsertMaterial(material)
83+
return material
84+
85+
86+
def _attach_material(obj: Any, material: Any) -> None:
87+
tag = c4d.TextureTag()
88+
tag.SetMaterial(material)
89+
obj.InsertTag(tag)
90+
91+
92+
def _add_cube(
93+
doc: Any,
94+
name: str,
95+
size: tuple[float, float, float],
96+
position: tuple[float, float, float],
97+
material: Any,
98+
*,
99+
rotation: tuple[float, float, float] = (0.0, 0.0, 0.0),
100+
fillet: float = 0.0,
101+
) -> Any:
102+
cube = c4d.BaseObject(c4d.Ocube)
103+
cube.SetName(name)
104+
cube[c4d.PRIM_CUBE_LEN] = c4d.Vector(*size)
105+
if fillet > 0.0:
106+
cube[c4d.PRIM_CUBE_DOFILLET] = True
107+
cube[c4d.PRIM_CUBE_FRAD] = fillet
108+
cube[c4d.PRIM_CUBE_SUBF] = 4
109+
cube.SetAbsPos(c4d.Vector(*position))
110+
cube.SetAbsRot(c4d.Vector(*(math.radians(value) for value in rotation)))
111+
_attach_material(cube, material)
112+
doc.InsertObject(cube)
113+
return cube
114+
115+
116+
def _point_at(obj: Any, target: tuple[float, float, float]) -> None:
117+
direction = c4d.Vector(*target) - obj.GetAbsPos()
118+
obj.SetAbsRot(c4d.utils.VectorToHPB(direction))
119+
120+
121+
def _add_area_light(
122+
doc: Any,
123+
name: str,
124+
position: tuple[float, float, float],
125+
target: tuple[float, float, float],
126+
color: tuple[float, float, float],
127+
intensity: float,
128+
size: tuple[float, float],
129+
) -> None:
130+
light = c4d.BaseObject(c4d.Olight)
131+
light.SetName(name)
132+
light[c4d.LIGHT_TYPE] = c4d.LIGHT_TYPE_AREA
133+
light[c4d.LIGHT_COLOR] = c4d.Vector(*color)
134+
light[c4d.LIGHT_BRIGHTNESS] = intensity
135+
light[c4d.LIGHT_SHADOWTYPE] = c4d.LIGHT_SHADOWTYPE_AREA
136+
light[c4d.LIGHT_AREADETAILS_SHAPE] = c4d.LIGHT_AREADETAILS_SHAPE_RECTANGLE
137+
light[c4d.LIGHT_AREADETAILS_SIZEX] = size[0]
138+
light[c4d.LIGHT_AREADETAILS_SIZEY] = size[1]
139+
light[c4d.LIGHT_AREADETAILS_SHOWINRENDER] = False
140+
light[c4d.LIGHT_AREADETAILS_SHOWINREFLECTION] = False
141+
light.SetAbsPos(c4d.Vector(*position))
142+
_point_at(light, target)
143+
doc.InsertObject(light)
144+
145+
146+
def _build_geometry(doc: Any) -> None:
147+
backdrop = _physical_material(
148+
doc,
149+
"Charcoal backdrop",
150+
(0.028, 0.035, 0.05),
151+
roughness=0.62,
152+
)
153+
floor = _physical_material(
154+
doc,
155+
"Neutral floor",
156+
(0.12, 0.13, 0.15),
157+
roughness=0.28,
158+
metalness=0.15,
159+
)
160+
orange = _physical_material(
161+
doc,
162+
"Burnished orange",
163+
(0.95, 0.09, 0.015),
164+
roughness=0.2,
165+
metalness=0.25,
166+
)
167+
cyan = _physical_material(
168+
doc,
169+
"Glossy cyan",
170+
(0.015, 0.42, 0.72),
171+
roughness=0.14,
172+
metalness=0.05,
173+
)
174+
magenta = _physical_material(
175+
doc,
176+
"Matte magenta",
177+
(0.72, 0.025, 0.2),
178+
roughness=0.48,
179+
)
180+
green = _physical_material(
181+
doc,
182+
"Metallic green",
183+
(0.025, 0.52, 0.13),
184+
roughness=0.24,
185+
metalness=0.7,
186+
)
187+
blue_emission = _physical_material(
188+
doc,
189+
"HDR blue strip",
190+
(0.0, 0.0, 0.0),
191+
emission=(0.02, 0.65, 6.0),
192+
)
193+
194+
_add_cube(
195+
doc,
196+
"Backdrop",
197+
(1300, 760, 30),
198+
(0, 90, 430),
199+
backdrop,
200+
)
201+
_add_cube(
202+
doc,
203+
"Floor",
204+
(1300, 30, 1050),
205+
(0, -210, 50),
206+
floor,
207+
fillet=8,
208+
)
209+
_add_cube(
210+
doc,
211+
"Diagonal HDR strip",
212+
(980, 24, 18),
213+
(0, 115, 395),
214+
blue_emission,
215+
rotation=(0, 0, -8),
216+
fillet=6,
217+
)
218+
219+
sphere = c4d.BaseObject(c4d.Osphere)
220+
sphere.SetName("Orange sphere")
221+
sphere[c4d.PRIM_SPHERE_RAD] = 150
222+
sphere[c4d.PRIM_SPHERE_SUB] = 48
223+
sphere.SetAbsPos(c4d.Vector(-285, -55, 45))
224+
_attach_material(sphere, orange)
225+
doc.InsertObject(sphere)
226+
227+
_add_cube(
228+
doc,
229+
"Cyan beveled cube",
230+
(225, 225, 225),
231+
(-35, -50, 60),
232+
cyan,
233+
rotation=(12, -24, 8),
234+
fillet=24,
235+
)
236+
237+
torus = c4d.BaseObject(c4d.Otorus)
238+
torus.SetName("Green torus")
239+
torus[c4d.PRIM_TORUS_OUTERRAD] = 150
240+
torus[c4d.PRIM_TORUS_INNERRAD] = 48
241+
torus[c4d.PRIM_TORUS_SEG] = 64
242+
torus[c4d.PRIM_TORUS_CSUB] = 24
243+
torus.SetAbsPos(c4d.Vector(285, -45, 75))
244+
torus.SetAbsRot(c4d.Vector(math.radians(78), math.radians(-10), math.radians(16)))
245+
_attach_material(torus, green)
246+
doc.InsertObject(torus)
247+
248+
pyramid = c4d.BaseObject(c4d.Opyramid)
249+
pyramid.SetName("Magenta pyramid")
250+
pyramid[c4d.PRIM_PYRAMID_LEN] = c4d.Vector(230, 285, 230)
251+
pyramid.SetAbsPos(c4d.Vector(115, 85, 205))
252+
pyramid.SetAbsRot(c4d.Vector(0, math.radians(24), math.radians(-5)))
253+
_attach_material(pyramid, magenta)
254+
doc.InsertObject(pyramid)
255+
256+
exposure_values = (0.18, 1.0, 4.0, 16.0)
257+
exposure_positions = (-300, -100, 100, 300)
258+
for value, x_position in zip(exposure_values, exposure_positions):
259+
swatch_material = _physical_material(
260+
doc,
261+
f"Exposure {value:g}",
262+
(0.0, 0.0, 0.0),
263+
emission=(value, value, value),
264+
)
265+
_add_cube(
266+
doc,
267+
f"Exposure swatch {value:g}",
268+
(118, 42, 18),
269+
(x_position, 285, 395),
270+
swatch_material,
271+
fillet=5,
272+
)
273+
274+
target = (0, 10, 80)
275+
_add_area_light(
276+
doc,
277+
"Warm key",
278+
(-430, 430, -360),
279+
target,
280+
(1.0, 0.68, 0.48),
281+
1.35,
282+
(430, 430),
283+
)
284+
_add_area_light(
285+
doc,
286+
"Cool fill",
287+
(470, 160, -220),
288+
target,
289+
(0.3, 0.62, 1.0),
290+
0.8,
291+
(340, 340),
292+
)
293+
_add_area_light(
294+
doc,
295+
"Magenta rim",
296+
(60, 440, 360),
297+
target,
298+
(1.0, 0.18, 0.45),
299+
0.6,
300+
(280, 280),
301+
)
302+
303+
camera = c4d.BaseObject(c4d.Ocamera)
304+
camera.SetName("OCIO studio camera")
305+
camera.SetAbsPos(c4d.Vector(0, 70, -1320))
306+
_point_at(camera, (0, 35, 90))
307+
camera[c4d.CAMERA_FOCUS] = 52.0
308+
doc.InsertObject(camera)
309+
doc.GetRenderBaseDraw().SetSceneCamera(camera)
310+
311+
312+
def build_ocio_scene(
313+
output_dir: str,
314+
scene_name: str,
315+
view_transform: int,
316+
) -> None:
317+
"""Build and save the shared Physical OCIO scene."""
318+
output_dir = os.path.abspath(output_dir)
319+
os.makedirs(output_dir, exist_ok=True)
320+
321+
doc = c4d.documents.GetActiveDocument()
322+
doc.Flush()
323+
doc[c4d.DOCUMENT_COLOR_MANAGEMENT] = c4d.DOCUMENT_COLOR_MANAGEMENT_OCIO
324+
doc[c4d.DOCUMENT_OCIO_PRESET] = c4d.DOCUMENT_OCIO_PRESET_ACES
325+
doc[c4d.DOCUMENT_OCIO_VIEW_TRANSFORM] = view_transform
326+
327+
_build_geometry(doc)
328+
329+
render_data = doc.GetActiveRenderData()
330+
_configure_physical_renderer(render_data)
331+
render_data[c4d.RDATA_XRES] = WIDTH
332+
render_data[c4d.RDATA_YRES] = HEIGHT
333+
render_data[c4d.RDATA_FRAMEFROM] = c4d.BaseTime(1, doc.GetFps())
334+
render_data[c4d.RDATA_FRAMETO] = c4d.BaseTime(1, doc.GetFps())
335+
render_data[c4d.RDATA_FORMAT] = c4d.FILTER_PNG
336+
render_data[c4d.RDATA_FORMATDEPTH] = c4d.RDATA_FORMATDEPTH_8
337+
render_data[c4d.RDATA_ALPHACHANNEL] = False
338+
render_data[c4d.RDATA_MULTIPASS_SAVEIMAGE] = False
339+
if hasattr(c4d, "RDATA_BAKE_OCIO_VIEW_TRANSFORM_RENDER"):
340+
render_data.GetDataInstance()[c4d.RDATA_BAKE_OCIO_VIEW_TRANSFORM_RENDER] = True
341+
342+
render_data[c4d.RDATA_PATH] = "renders/$prj"
343+
344+
scene_path = os.path.join(output_dir, scene_name)
345+
doc.SetDocumentPath(output_dir)
346+
doc.SetDocumentName(scene_name)
347+
if not c4d.documents.SaveDocument(
348+
doc,
349+
scene_path,
350+
c4d.SAVEDOCUMENTFLAGS_0,
351+
c4d.FORMAT_C4DEXPORT,
352+
):
353+
raise RuntimeError(f"Failed to save integration scene: {scene_path}")
354+
c4d.EventAdd()
21.6 KB
Loading
960 Bytes
Loading
3.05 KB
Loading
4.64 KB
Loading
-341 Bytes
Loading
2.4 KB
Loading
3.97 KB
Loading

0 commit comments

Comments
 (0)