-
Notifications
You must be signed in to change notification settings - Fork 28
draft: fix: Ensure filenames are encoded in UTF-8. #130
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| assetReferences: | ||
| inputs: | ||
| directories: [] | ||
| filenames: | ||
| - C:\normalized\job\bundle\dir\redshift_textured-_\u20BF_\u0119_\xF1_\u03B2_\u0411\ | ||
| _\u062A.c4d | ||
| - C:\normalized\job\bundle\dir\tex\checkerboard-_\u20BF_\u0119_\xF1_\u03B2_\u0411\ | ||
| _\u062A.bmp | ||
| outputs: | ||
| directories: | ||
| - C:\normalized\job\bundle\dir\renders | ||
| referencedPaths: [] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| parameterValues: | ||
| - name: Cinema4DFile | ||
| value: C:\normalized\job\bundle\dir\redshift_textured-_\u20BF_\u0119_\xF1_\u03B2_\u0411\ | ||
| _\u062A.c4d | ||
| - name: OutputPath | ||
| value: C:\normalized\job\bundle\dir\renders\redshift_textured | ||
| - name: MultiPassPath | ||
| value: '' | ||
| - name: Frames | ||
| value: '1' | ||
| - name: deadline:targetTaskRunStatus | ||
| value: READY | ||
| - name: deadline:maxFailedTasksCount | ||
| value: 20 | ||
| - name: deadline:maxRetriesPerTask | ||
| value: 5 | ||
| - name: deadline:priority | ||
| value: 50 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,103 @@ | ||
| specificationVersion: jobtemplate-2023-09 | ||
| name: physical-_₿_ę_ñ_β_Б_ت.c4d | ||
| parameterDefinitions: | ||
| - name: Cinema4DFile | ||
| type: PATH | ||
| objectType: FILE | ||
| dataFlow: IN | ||
| userInterface: | ||
| control: CHOOSE_INPUT_FILE | ||
| label: Cinema4D Document File | ||
| groupLabel: Cinema4D Settings | ||
| fileFilters: | ||
| - label: Cinema4D document files | ||
| patterns: | ||
| - '*.c4d' | ||
| - label: All Files | ||
| patterns: | ||
| - '*' | ||
| description: The Cinema4D document file to render. | ||
| - name: Frames | ||
| type: STRING | ||
| userInterface: | ||
| control: LINE_EDIT | ||
| label: Frames | ||
| groupLabel: Cinema4D Settings | ||
| description: The frames to render. E.g. 1-3,8,11-15 | ||
| minLength: 1 | ||
| - name: OutputPath | ||
| type: STRING | ||
| userInterface: | ||
| control: LINE_EDIT | ||
| label: Default image output | ||
| groupLabel: Cinema4D Settings | ||
| description: Image output path | ||
| - name: MultiPassPath | ||
| type: STRING | ||
| userInterface: | ||
| control: LINE_EDIT | ||
| label: Multi-pass output path | ||
| groupLabel: Cinema4D Settings | ||
| description: Multi-pass image output | ||
| steps: | ||
| - name: Main | ||
| parameterSpace: | ||
| taskParameterDefinitions: | ||
| - name: Frame | ||
| type: INT | ||
| range: '{{Param.Frames}}' | ||
| stepEnvironments: | ||
| - name: Cinema4D | ||
| description: Runs Cinema4D in the background. | ||
| script: | ||
| embeddedFiles: | ||
| - name: initData | ||
| filename: init-data.yaml | ||
| type: TEXT | ||
| data: |- | ||
| scene_file: '{{Param.Cinema4DFile}}' | ||
| take: 'Main' | ||
| output_path: '{{Param.OutputPath}}' | ||
| multi_pass_path: '{{Param.MultiPassPath}}' | ||
| actions: | ||
| onEnter: | ||
| command: cinema4d-openjd | ||
| args: | ||
| - daemon | ||
| - start | ||
| - --path-mapping-rules | ||
| - file://{{Session.PathMappingRulesFile}} | ||
| - --connection-file | ||
| - '{{Session.WorkingDirectory}}/connection.json' | ||
| - --init-data | ||
| - file://{{Env.File.initData}} | ||
| cancelation: | ||
| mode: NOTIFY_THEN_TERMINATE | ||
| onExit: | ||
| command: cinema4d-openjd | ||
| args: | ||
| - daemon | ||
| - stop | ||
| - --connection-file | ||
| - '{{ Session.WorkingDirectory }}/connection.json' | ||
| cancelation: | ||
| mode: NOTIFY_THEN_TERMINATE | ||
| script: | ||
| embeddedFiles: | ||
| - name: runData | ||
| filename: run-data.yaml | ||
| type: TEXT | ||
| data: | | ||
| frame: {{Task.Param.Frame}} | ||
| actions: | ||
| onRun: | ||
| command: cinema4d-openjd | ||
| args: | ||
| - daemon | ||
| - run | ||
| - --connection-file | ||
| - '{{ Session.WorkingDirectory }}/connection.json' | ||
| - --run-data | ||
| - file://{{ Task.File.runData }} | ||
| cancelation: | ||
| mode: NOTIFY_THEN_TERMINATE |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| import os | ||
| import struct | ||
| import c4d | ||
|
|
||
|
|
||
| def _checkerboard_bmp(filename): | ||
| width = 128 | ||
| height = 128 | ||
| color1 = (255, 255, 255) | ||
| color2 = (0, 0, 0) | ||
| # BMP file header | ||
| file_size = 14 + 40 + (width * height * 3) | ||
| file_header = struct.pack("<2sIHHI", b"BM", file_size, 0, 0, 54) | ||
| # DIB header | ||
| dib_header = struct.pack( | ||
| "<IiiHHIIiiII", 40, width, height, 1, 24, 0, width * height * 3, 2835, 2835, 0, 0 | ||
| ) | ||
| pixel_data = [] | ||
| for y in range(height): | ||
| row_data = [] | ||
| for x in range(width): | ||
| if (x // 8 + y // 8) % 2 == 0: | ||
| row_data.extend(color1) | ||
| else: | ||
| row_data.extend(color2) | ||
| padding = (4 - (width * 3) % 4) % 4 | ||
| row_data.extend([0] * padding) | ||
| pixel_data.extend(row_data) | ||
| with open(filename, "wb") as bmp_file: | ||
| bmp_file.write(file_header) | ||
| bmp_file.write(dib_header) | ||
| bmp_file.write(bytearray(pixel_data)) | ||
|
|
||
|
|
||
| def main(): | ||
| doc = c4d.documents.GetActiveDocument() | ||
| doc.Flush() | ||
| cube = c4d.BaseObject(c4d.Ocube) | ||
| cube[c4d.PRIM_CUBE_LEN] = c4d.Vector(400, 400, 400) | ||
| cube.SetAbsPos(c4d.Vector(0, 50, -50)) | ||
| doc.InsertObject(cube) | ||
| mat = c4d.BaseList2D(c4d.Mmaterial) | ||
| doc.InsertMaterial(mat) | ||
| mat[c4d.MATERIAL_USE_REFLECTION] = False | ||
| bitmap_shader = c4d.BaseShader(c4d.Xbitmap) | ||
| tex_dir = os.path.join(os.path.dirname(__file__), "tex") | ||
| os.makedirs(tex_dir, exist_ok=True) | ||
| _checkerboard_bmp(os.path.join(tex_dir, "checkerboard-_₿_ę_ñ_β_Б_ت.bmp")) | ||
| bitmap_shader[c4d.BITMAPSHADER_FILENAME] = "tex/checkerboard-_₿_ę_ñ_β_Б_ت.bmp" | ||
| mat[c4d.MATERIAL_COLOR_SHADER] = bitmap_shader | ||
| mat.InsertShader(bitmap_shader) | ||
| texture_tag = c4d.TextureTag() | ||
| texture_tag.SetMaterial(mat) | ||
| cube.InsertTag(texture_tag) | ||
| render_data = doc.GetActiveRenderData() | ||
| render_data[c4d.RDATA_PATH] = "renders/$prj" | ||
| frame_start = c4d.BaseTime(1, doc.GetFps()) | ||
| frame_end = c4d.BaseTime(1, doc.GetFps()) | ||
| render_data[c4d.RDATA_FRAMEFROM] = frame_start | ||
| render_data[c4d.RDATA_FRAMETO] = frame_end | ||
| render_data[c4d.RDATA_RENDERENGINE] = 1036219 # redshift | ||
|
|
||
| save_dir = os.path.dirname(__file__) | ||
| save_name = "redshift_textured-_₿_ę_ñ_β_Б_ت.c4d" | ||
| save_file = os.path.join(save_dir, save_name) | ||
| doc.SetDocumentPath(save_dir) | ||
| doc.SetDocumentName(save_name) | ||
| c4d.documents.SaveDocument(doc, save_file, c4d.SAVEDOCUMENTFLAGS_0, c4d.FORMAT_C4DEXPORT) | ||
|
|
||
| c4d.documents.InsertBaseDocument(doc) | ||
| c4d.EventAdd() | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,6 +3,7 @@ | |
|
|
||
| import os | ||
| from typing import Any, Callable, Dict | ||
| import locale | ||
|
|
||
| try: | ||
| import c4d # type: ignore | ||
|
|
@@ -38,6 +39,7 @@ def __init__(self, map_path: Callable[[str], str]) -> None: | |
| """ | ||
| Constructor for the c4dpy handler. Initializes action_dict and render variables | ||
| """ | ||
|
|
||
| self.action_dict = { | ||
| "scene_file": self.set_scene_file, | ||
| "take": self.set_take, | ||
|
|
@@ -164,6 +166,16 @@ def set_frame(self, data: dict) -> None: | |
| """ | ||
| self.render_kwargs["frame"] = int(data.get("frame", "")) | ||
|
|
||
| def _convert_filename_to_utf8_encoding(self, scene_file: str) -> str: | ||
| try: | ||
| return scene_file.encode(locale.getpreferredencoding()).decode("utf-8") | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This doesn't look right to me, because the Python 3
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yup, that tripped me as well when I was trying to root cause this issue. Turns out python uses Unicode but it does not use UTF-8 encoding by default. The default encoding is platform dependent and in Windows it is CP-1252. This is what I found out while investigating this issue in the official docs. (Its for the
I traced the issue back to adaptor.py while investigating, which showed the problem even before Cinema 4D started running. Patching it in the adaptor unfortunately causes the adaptor to freeze while running. It does not happen often but only at times and I tried root causing the issue but couldn't find it. I patched it in handler as it was the most straightforward fix and quick fix to unblock customers. Initially, this seemed to be a Cinema 4D-specific issue, not occurring in other DCCs like Unreal and Keyshot on Windows. Hence, I ruled out checking the dependencies of Cinema 4D. Since Cinema 4D customers were currently impacted and are blocked for working, I prioritized fixing it for Cinema 4D first. After that, I plan to investigate the issue further. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Have you looked into creating a minimal reproducer example as a small job template? That could help isolate the issue from the environment and provide faster iteration to debug it. |
||
| except UnicodeError as ue: | ||
| print(f"Error: Encoding to UTF-8 failed due to unicode error. Exception: {ue}") | ||
| except Exception as e: | ||
| print(f"Error: Encoding to UTF-8 failed due to unexpected error. Exception: {e}") | ||
| print("Returning scene file string as is as encoding/decoding failed.") | ||
| return scene_file | ||
|
|
||
| def set_scene_file(self, data: dict) -> None: | ||
| """ | ||
| Opens the scene file in Cinema4D. | ||
|
|
@@ -175,6 +187,9 @@ def set_scene_file(self, data: dict) -> None: | |
| FileNotFoundError: If path to the scene file does not yield a file | ||
| """ | ||
| scene_file = data.get("scene_file", "") | ||
|
|
||
| scene_file = self._convert_filename_to_utf8_encoding(scene_file) | ||
|
|
||
| if not os.path.isfile(scene_file): | ||
| raise FileNotFoundError(f"The scene file '{scene_file}' does not exist") | ||
| doc = c4d.documents.LoadDocument( | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: can you run
hatch fmton this? I would expect the imports to be in a different order.